Skip to content

The SOLID Design Principles πŸ‘¨β€πŸŽ“

Lyes Sefiane edited this page Jan 18, 2023 · 1 revision

Table Of Contents

Ref: Martin, Robert C. (2000). "Design Principles and Design Patterns"

Single Responsibility Principle

  • Each class should be responsible for a single part or functionality of the system.
public class Entity {
    public void printTooMuchDetails() {}
    public void calculateEstimate() {}
    public void addEntityToDatabase() {}
}

Problem

  • The Entity class has three separate responsibilities: reporting, calculation, and database. This violate the SRP principle.

Solution

  • Separate the above class into three classes with separate responsibilities.

Open-Closed Design Principle

  • A module should be open for extension but closed for modification
public class Pizza {
    public double getPrice (Pizza pizza) {
        if (pizza instanceof VegetarianPizza) {
            return pizza.getPrice() * 0.9;
        }
        if (pizza instanceof MeatFeastPizza) {
           return pizza.getPrice() * 0.5;
        }
    }
}

Problem

  • Adding another subclass implies the modification of Pizza class by adding another if statement to getPrice method. This violate the Open-Closed principle.

Solution

  • Override getPrice() in each subclass that extends Pizza.

Liskov Substitution Principle

  • Subclasses should be substitutable for their base classes.
public class Bird {
  void fly();
}

public class Eagle extends Bird {
    @Override
    void fly() {
       // TODO
    }
}

public class Ostrich extends Bird {
    @Override
    void fly() {
       throw new UnsupportedOperationException("Ostrich can't fly.");
    }
}

Problem

  • Ostrich class can't implement fly method since Ostrich doesn't fly. This violate Liskov Substitution principle.

Solution

  • Introduce an intermediate layer class 'FlyingBird' then :

    • FlyingBird class extends Bird class
    • Move fly method from Bird class to FlyingBird class
    • Eagle extends FlyingBird class
    • Ostrich extends Bird class

Interface Segregation Principle

  • Many client specific interfaces are better than one general purpose interface.
interface OrderService {
    void orderBurger(int quantity);
    void orderFries(int fries);
}

class BurgerOrderService implements OrderService {

    private static Logger logger = LoggerFactory.getLogger(BurgerOrderService.class);

    @Override
    public void orderBurger(int quantity) {
        logger.info("Received order of {} burgers", quantity);
    }

    @Override
    public void orderFries(int fries) {
        throw new UnsupportedOperationException("No fries in burger order");
    }
}

class FriesOrderService implements OrderService {

    private static Logger logger = LoggerFactory.getLogger(FriesOrderService.class);

    @Override
    public void orderBurger(int quantity) {
        throw new UnsupportedOperationException("No burger in fries order");
    }

    @Override
    public void orderFries(int fries) {
       logger.info("Received order of {} fries", fries);
    }
}

Problem

  • It does not make sense for a BurgerOrderService class to implement the orderFries method as a BurgerOrderService does not have any fries. This violate the Interface Segregation and the Single Responsibility principles.

Solution

  • Segregate OrderService interface into multiple small cohesive interfaces so that no class is forced to implement any interface.

Dependency Inversion Principle

  • Depend upon Abstractions. Do not depend upon concretions.
public class File {

  private static Logger logger = LoggerFactory.getLogger(File.class);

  private String fileName;

  public File(String fileName) {
       this.fileName = fileName;
  }

  public void encrypt(AESEncrypter aesEncrypter) {
	logger.info("{} to {}", aesEncrypter.encryptFile(), fileName);
  }

}

public class AESEncrypter {

	@Override
	public String encryptFile() {
		return "Applying AES Encryption";
	}

}

Problem

  • Unable to use another encrypter type such as 'RSAEncrypter' without refactoring the class File.

Solution

  • Create an interface 'Encrypter' which will be implemented by 'AESEncrypter' and 'RSAEncrypter' and make sure that the class 'File' will depend on 'Encrypter' as abstraction instead of implementations (AESEncrypter and RSAEncrypter).
Clone this wiki locally