Skip to main content

Solid Principles for Software Design - Part 2


Interface Segregation Principle

The Interface Segregation Principle (ISP) is one of the five SOLID principles of object-oriented design, which recommends that "Clients should not be forced to depend on interfaces they do not use". This means we should avoid implementing an interface that has unnecessary methods and therefore not going to be implemented. 

Some signs of violating ISP are:

  • Having a "fat" interface, which means having a high number of methods in one interface that are not so related to each other or low cohesion.
  • Empty implementation of methods, certain methods of interface are not needed for implementation.

Considering the following example, we violate the principle because CannonPrinter is designed only with the functionality to print, leaving the scan and fax method unimplemented.


interface IMultiFunction {
    void print();
    void scan();
    void fax();
}

public class HPPrinterNScanner implements ImultiFunction {
	@Override
    public void print() {
    	//Implementation
    }
	@Override
    public void scan() {
    	//Implementation
    } 
	@Override
    public void fax() {
    	//Implementation
    }
}
public class CannonPrinter implements ImultiFunction {
	@Override
    public void print() {
    	//Implementation
    }
	@Override
    public void scan() {
    	//No Implementation
    }
	@Override
    public void fax() {
    	//No Implementation
    }
}
 

To solve this problem, we can follow the ISP and refactor the code by splitting the IMultiFuntioin interface like below:


interface IPrint {
    void print();
}
interface IScan {
    void scan();
}
interface IFax {
    void fax();
}
public class HPPrinterNScanner implements IPrint, IScan, IFax {
	@Override
    public void print() {
    	//Implementation
    }
	@Override
    public void scan() {
    	//Implementation
    } 
	@Override
    public void fax() {
    	//Implementation
    }
}
public class CannonPrinter implements IPrint{
	@Override
    public void print() {
    	//Implementation
    }
}
 

By applying the ISP, we also indirectly apply Single responsibility and Liskov substitution principle since each interface now is only responsible for its specific purpose, and the class that implements for example IPrint can play a role as IPrint's subtitution.

Dependency Inversion Principle

The idea of this principle is to ensure that high level module/class doesn't depend on low level mdule class, instead both should depend on abstrations for example an interface. And abstractions should not depend on details. Details should depend on abstraction.

Let’s consider an example below. Suppose you have a Book class and a Library class, and the Library class uses the Book class:


class Book {
    String getTitle() {
        return "Some Title";
    }
}

class Library {
    private Book book;

    Library(Book book) {
        this.book = book;
    }

    void displayBookTitle() {
        System.out.println(book.getTitle());
    }
}
 

In this scenario, it is evident that the Library class, a higher-level module, relies on the Book class, a lower-level module. This reliance contradicts the Dependency Inversion Principle, which states that high-level modules should not depend on low-level modules; instead, both should depend on abstractions. Changes in the lower-level module can significantly affect the higher-level module, resulting in a system that is less flexible and maintainable. To address this issue, we can use an abstraction, such as an interface, between the Library class and the Book class as follows:


class Book implements IBook {
    String getTitle() {
        return "Some Title";
    }
}

interface IBook {
	String getTitle();
}

class Library {
    private IBook book;

    Library(IBook book) {
        this.book = book;
    }

    String displayBookTitle() {
        System.out.println(book.getTitle());
    }
}
 

Comments

Popular posts from this blog

Declarative Programming in Angular with Async Pipe and shareReplay

A declarative approach is a way that focuses on writing code that specifies what the application should do, rather than detailing how it should be done. For example, with the async pipe in Angular, we don’t need to write code to manually subscribe to an Observable, handle its data, and update the view. Instead, we simply specify in the template that we want the data from the Observable using the async pipe. Angular handles all the underlying processes to retrieve and display the data It's often used in reactive programming with RxJS and Angular's built-in features, such as the async pipe. export class ProductComponent { product$ = this.productService.getProduct(); constructor(private productService: ProductService) {} } The product observable will hold the product data and the async pipe in the template will automatically subscribe and unsubscribe observable <div *ngIf="product$ | async as product"> <h1>{{ product.name }}</h1> <p>{{...

The Developer’s Guide to Clean Code: Tips and Techniques

What is clean code? Clean code is a term used to describe code that is easy to read, understand, and maintain. It is written in a way that makes it simple, concise, and expressive. Clean code follows a set of conventions, standards, and practices that make it easy to read and follow. Here are some signs indicating that the code is not clean: 1. Poor names The name is not clear to understand, meaningless, or misleading . It doesn't reveal the intention of what it want to achieve. Consider the following examples: SqlDataReader drl; int od; void Button1_Click(); Class Pages1 In the examples above, it’s challenging to get the purpose of drl, od, or what Button1_Click() does. To enhance clarity, we can rename these identifiers as follows: SqlDataReader dataReader/reader; int overdueDays; void CheckAvailability_Click(); Class ViewCustomerPage {} Ambiguous names int? incidentNameId for instance. incidentNameId lacks clarity because if it represents the ID of an incident, then the inclu...

Date and Time in .NET: DateTime, DateTimeOffset, TimeZoneInfo, DateOnly, TimeOnly, and TimeSpan

What is UTC UTC (Coordinated Universal Time) is the world’s primary time standard used to regulate clocks and time zones. It serves as the reference point for civil time worldwide, ensuring that all local times are defined by their offset from UTC. DateTime The DateTime class provides a way to work with dates and times without including an offset. This approach can reduce a certain level of accuracy when dealing with time zones. For example, calling DateTime.Now returns the current date and time based on your computer’s local time zone. DateTime.Now gives you the local time, while DateTime.UtcNow returns the universal coordinated time (UTC). You typically use DateTime when you only need to track the date and time itself, without worrying about time zones. This is suitable for scenarios such as birthdays, deadlines, or local schedules, especially when your application is used primarily within a single time zone. The DateTime class also includes a Kind property, which provides limited in...