The Unwavering Guide to Solid Principles

oguzhan sarisakaloglu

talks software engineering digital transformation artificial intelligence music gaming

The Unwavering Guide to Solid Principles

22 May 2023

In the ever-evolving world of programming, it is crucial to stay ahead of the curve and adapt to new paradigms, tools, and methodologies. However, amidst this whirlwind of change, there are some principles so fundamental that they remain constant. One of these bedrocks of programming wisdom is the SOLID principles.

Coined by Robert C. Martin, also known as Uncle Bob, SOLID is an acronym that represents five principles to make software designs more understandable, flexible, and maintainable. Let’s delve into each of these principles and understand their significance in professional programming.

Single Responsibility Principle (SRP)

“The Single Responsibility Principle states that a class should have one, and only one, reason to change.”

In simpler terms, each class or module in your code should have only one job. This principle is about restraining ourselves to keep our code tidy and straightforward. When a class or module has more than one responsibility, it becomes harder to maintain and modify. If changes are required, there are likely more parts of your code that will be affected, leading to potential bugs and issues.

Let’s consider a simple example. Suppose you have a class named ‘Order’. This class handles all operations like adding items to the order, calculating the total amount, and printing the order details. But, this violates the SRP as the ‘Order’ class is responsible for more than one operation. A better design would be to split these operations into separate classes - ‘Order’, ‘OrderCalculator’, and ‘OrderPrinter’.

Instead of:

public class Order {
    public void addItem(Item item) {...}    
    public void calculateTotal() {...}
    public void printOrder() {...}
}

We would refactor this to:

public class Order {
    public void addItem(Item item) {...}
}

public class OrderCalculator {
    public void calculateTotal(Order order) {...}
}

public class OrderPrinter {
    public void printOrder(Order order) {...}
}

Open-Closed Principle (OCP)

“Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.”

The Open-Closed Principle promotes the idea that your code should allow new functionality to be added with minimum changes to the existing code. The design should be such that new functionality can be added by adding new classes, which contain the new functionality, and minimal changes to the existing classes.

Take the case of a ‘Shape’ class. If we need to calculate the area of different shapes, we might be tempted to use conditional statements within the ‘Shape’ class to handle this. However, this design violates the OCP because every time a new shape is introduced, the Shape class would need to be modified. Instead, a better design would be to have a generic ‘Shape’ class and specific classes for each shape type, like ‘Circle’, ‘Square’, etc., each having their method to calculate the area.

Instead of:

public class Shape {
    public void calculateArea(String shapeType) {
        if(shapeType.equals("Square")) {...}
        else if(shapeType.equals("Circle")) {...}
    }
}

We refactor to:

public interface Shape {
    public void calculateArea();
}

public class Square implements Shape {
    public void calculateArea() {...}
}

public class Circle implements Shape {
    public void calculateArea() {...}
}

Liskov Substitution Principle (LSP)

“Subtypes must be substitutable for their base types.”

This principle asserts that if a program is using a base class, it should be able to use any of its subclasses without the program knowing it. This principle ensures that a subclass can replace its superclass in the code without causing any issues.

Consider the example of a ‘Bird’ class. If we have a method in the ‘Bird’ class to ‘fly’, this would not be applicable to a subclass ‘Penguin’ as penguins cannot fly. This design would violate the LSP. A better design would be to have a separate ‘FlyingBird’ subclass for birds that can fly.

Instead of:

public class Bird {
    public void fly() {...}
}

public class Penguin extends Bird {
    public void fly() {
        throw new UnsupportedOperationException();
    }
}

We refactor to:

public interface Bird {
    // common bird methods
}

public interface FlyingBird extends Bird {
    public void fly();
}

public class Penguin implements Bird {
    // Penguin specific methods
}

public class Sparrow implements FlyingBird {
    public void fly() {...}
}

Interface Segregation Principle (ISP)

“Clients should not be forced to depend on interfaces they do not use.”

This principle means that a class should not be forced to implement an interface if it doesn’t use it. It’s about business logic to clients classes. It suggests that we should break our interfaces into smaller ones, so they better satisfy the needs of our clients.

Take an ‘IPrinter’ interface with methods ‘Print’, ‘Fax’, ‘Scan’. If a ‘SimplePrinter’ class implements this interface, it would be forced to implement ‘Fax’ and ‘Scan’ methods which it doesn’t use. This design violates ISP. Instead, we should have separate interfaces ‘IPrinter’, ‘IFax’, and ‘IScanner’.

Instead of:

public interface Printer {
    public void print();
    public void fax();
    public void scan();
}

public class SimplePrinter implements Printer {
    public void print() {...}
    public void fax() { throw new UnsupportedOperationException(); }
    public void scan() { throw new UnsupportedOperationException(); }
}

We refactor to:

public interface Printer {
    public void print();
}

public interface Fax {
    public void fax();
}

public interface Scanner {
    public void scan();
}

public class SimplePrinter implements Printer {
    public void print() {...}
}

Dependency Inversion Principle (DIP)

“High-level modules should not depend on low-level modules. Both should depend on abstractions.”

Dependency Inversion Principle is about the relationships between high-level and low-level classes. It specifies that high-level modules should be independent of low-level modules - instead, they should both depend on abstractions.

Consider an example where you have a high-level class ‘NotificationService’ which depends on a low-level class ‘Email’. This design would violate the DIP. If we need to send notifications via other means like ‘SMS’ or ‘PushNotification’, we would have to modify the ‘NotificationService’ class. A better design would be to have an abstraction ‘IMessage’ and have ‘Email’, ‘SMS’, and ‘PushNotification’ implement this interface. The ‘NotificationService’ class would then depend on the ‘IMessage’ abstraction and be independent of how the message is sent.

Instead of:

public class NotificationService {
    private Email email;
    public void sendNotification() {
        email.sendEmail();
    }
}

We refactor to:

public interface Message {
    public void sendMessage();
}

public class Email implements Message {
    public void sendMessage() {...}
}

public class SMS implements Message {
    public void sendMessage() {...}
}

public class NotificationService {
    private Message message;
    public void sendNotification() {
        message.sendMessage();
    }
}

In essence, SOLID principles are all about managing dependencies, increasing cohesion, and reducing the coupling of code. These principles encourage programmers to create more maintainable, flexible, and robust code. While it might seem a bit overwhelming at first, like any new skill, with practice, it will become second nature to design and organize your code following these principles.

Remember, principles are not laws, and there can be instances where you might need to prioritize one principle over another or disregard one entirely. They are guidelines meant to help, not to restrict.

Programming is an art, and like any form of art, it requires a balance. SOLID principles provide a balanced path for creating effective software designs. So, whether you are a beginner or a seasoned professional, adhering to these principles will undoubtedly enhance your abilities as a software craftsman.

Stay SOLID!

Those who love their country the most are the ones who fulfill their duty the best.