Sitemap

SOLID Principles

4 min readOct 12, 2024

--

Source : Google

SOLID is an acronym for 5 design principles in Object Oriented Programming to create high-quality, maintainable and scalable applications. These principles were invented by Robert C. Martin (Uncle Bob).

Single Responsibility Principle

A class should have only one reason to change.

Let’s say we have a Marker entity and Invoice class for the Marker entity.

class Marker {
String name;
String color;
int year;
int price;

//constructor
public Marker(String name, String color, int year, int price){
this.name = name;
this.color = color;
this.year = year;
this.price = price;
}
}
class Invoice  {
private Marker marker;
private int quantity;

//constructor
public Invoice(Marker marker, int quantity) {
this.marker = marker;
this.quantity = quantity;
}

//calculateTotal
public int calculateTotal() {
int price = ((marker.price) + this.quantity);
return price;
}

//printInvoice
public void printInvoice() {
//do something
}

//saveToDb
public void saveToDb() {
//do something
}
}

Invoice class has 3 resposibilities to calculateTotal price , print the invoice and save the invoice to DB. It has 3 reasons to change. If either of the logic changes, classes change. We’ll have to refactor the invoice class to provide only one responsibility to each class. Invoice class below will calculateTotal, InvoiceDao will save invoice to DB and InvoicePrinter will print the invoice. Each class, one responsibility.

class Invoice  {
private Marker marker;
private int quantity;

//constructor
public Invoice(Marker marker, int quantity) {
this.marker = marker;
this.quantity = quantity;
}

//calculateTotal
public int calculateTotal() {
int price = ((marker.price) + this.quantity);
return price;
}

}

class InvoiceDao {
Invoice invoice;

//constructor

//saveToDb
public void saveToDb() {
//do something
}

}

class InvoicePrinter {
Invoice invoice;

//constructor

//printInvoice
public void printInvoice() {
//do something
}

}

Open-Closed Principle

Open for extension, closed for modification.

Let’s say our InvoiceDao is tested and live, taking traffic and another requirement comes to add a method to save invoice file to a file. Now, if we modify the same class, it violates single responsibility and is not considered a good practice.

interface InvoiceDao {
public void save(Invoice invoice);
}

class DatabaseInvoiceDao implements InvoiceDao {
@Override
public void save(Invoice invoice) {
//save to db
}
}

class FileInvoiceDao implements InvoiceDao {
@Override
public void save(Invoice invoice) {
//save to file
}
}

Liskov Substitution Principle

If class B is a sub-class of class A, then we should be able to replace object A with object B without breaking the behavior of the program. Sub class should extend the capability of the parent class and not narrow it down.

interface Bike {
void turnOnEngine();
void accelerate();
}

class Motorcycle implements Bike { //extends the capability of Bike
boolean isEngineOn;
int speed;

@Override
public void turnOnEngine(){
this.isEngienOn = true;
}

@Override
public void accelerate(){
this.speed = this.speed + 10;
}
}

class Bicycle implements Bike { //narrows the capability of Bike

@Override
public void turnOnEngine(){
throw new AssertionError("there is no engine");
}

@Override
public void accelerate(){
//do something
}
}

In the above example Bike interface is implemented by both Motorcycle and Bicycle classes, while we can easily replace Bike with Motorcycle we can not replace Bike with Bicycle because it will throw an exception. Therefore we need to design interfaces in such a way that there are no unnecessary classes.

Interface Segregation Principle

Interfaces should be such that client should not implement unnecessary functions they do not need.

interface RestaurantEmployee {
void washDishes();
void serveCustomers();
void cookFood();
}

class Waiter implements RestaurantEmployee {
@Override
public void washDishes() {
//not my job
}

@Override
public void serveCustomers() {
//is my job
}

@Override
public void cookFood() {
//not my job
}

}

Waiter class has to override methods which are irrelevant to the class. We need to build interfaces in such a way that it can be segmented well into smaller and required methods.

interface WaiterInterface {
void serveCustomers();
void takeOrder();
}

interface ChefInterface {
void cookFood();
void decideMenu();
}

Dependency Inversion Principle

Class should depend on interfaces rather than concrete classes.

Let’s say we have two interfaces

  1. KeyboardInterface is implemented by WiredKeyboard and BluetoothKeyboard classes.
  2. MouseInterface is implemented by WiredMouse and BluetoothMouse classes.
class MackBook {
private final WiredKeyboard keyboard;
private final WiredMouse mouse;

public MacBook() {
keyboard = new WiredKeyboard();
mouse = new WiredMouse();
}
}

With above implementation, we are depending on concrete classes and if we were to change the wired implementation to bluetooth in future we will have to change the MacBook class code. This is not an ideal way of implementing if interfaces are being used.

class MackBook {
private final Keyboard keyboard;
private final Mouse mouse;

public MacBook(Keyboard keyboard, Mouse mouse) {
this.keyboard = keyboard;
this.mouse = mouse;
}
}

Now, when this implementation is depending on the interface we can choose to send either a wired object or bluetooth object based on the requirement at hand without touching the MacBook class.

Reference — Concept & Coding

--

--

No responses yet