SOLID Principle - MacKittipat/note-design-pattern GitHub Wiki
Single-responsibility principle
- A class should have one and only one reason to change
- Meaning that a class should only have one job.
- Solution : Aim for high cohesion and low coupling
- High cohesion : The degree to which the elements inside a module belong together
- Low coupling : The degree of interdependence between software modules
 
Benefit :
- Class can be reusable.
- Easy to find where the problem come from
Open-Closed Principle
- Classes should be open for extension but closed for modification.
- Closed for modification means that once you have developed a class you should never modify it, except to correct bugs.
public class Warrior {
    public void attack(String weapon) {
        if("sword".equals(weapon)) {
            System.out.println("Slash !");
        } else if ("bow".equals(weapon)) {
            System.out.println("Shoot !");
        } else if ("knife".equals(weapon)) {
            System.out.println("Stab !");
        }
        // More weapon will be add here. This is not good.
    }
}
// TO BE 
public class Warrior {
    public void attack(Weapon weapon) {
        weapon.use();
    }
}
interface Weapon {
    void use();
}
class Sword implements Weapon {
    public void use() {
        System.out.println("Slash !");
    }
}
class Bow implements Weapon {
    public void use() {
        System.out.println("Shoot !");
    }
}
class Knife implements Weapon {
    public void use() {
        System.out.println("Stab !");
    }
}
Liskov substitution principle
- Ability to replace any instance of parent class with an instance of one of its child classes without negative side effects.
- Any child type of a parent type should be able to stand in for that parent without things blowing up.
- Derived class must be usable through the base class interface, without the need for the user to know the different.
- Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program
- An example of this is with a Bird base class. You might assume that it should have a fly method. But what about the birds that can’t fly? Like a Penguin. In this example, fly should not be in the base class as it does not apply to all subclasses.
 
public class App {
    public static void main(String[] args) {
        Rectangle r = new Rectangle();
        r.setHeight(10);
        r.setWidth(5);
        System.out.println(r.area()); // 50
        r = new Square();
        r.setHeight(10);
        r.setWidth(5);
        System.out.println(r.area()); // 50, Wrong because Height and width of Square must equal.
    }
}
class Rectangle {
    private int width;
    private int height;
    public void setWidth(int width) {
        this.width = width;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int area() {
        return width * height;
    }
}
class Square extends Rectangle {
}
// TO BE
public class App {
    public static void main(String[] args) {
        Rectangle r = new Rectangle();
        r.setHeight(10);
        r.setWidth(5);
        System.out.println(r.area()); // 50
        r = new Square();
        r.setHeight(10);
        r.setWidth(5);
        System.out.println(r.area()); // 25
    }
}
class Rectangle {
    private int width;
    private int height;
    public void setWidth(int width) {
        this.width = width;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int area() {
        return width * height;
    }
}
class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);
    }
    @Override
    public void setHeight(int height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}
Interface segregation principle
- Clients should not be forced to implement interface that they does not use.
- Create multiple, smaller, cohesive interfaces.
- Cohesive, meaning they have groups of operations that logically belong together.
Benefit :
- Client class don't have to override unnecessary method from Interface.
interface Cat {
    void walk();
    void eat();
}
class PersianCat implements Cat {
    public void walk() {
        System.out.println("PersianCat is walking");
    }
    public void eat() {
        System.out.println("PersianCat is eating");
    }
}
class RobotCat implements Cat {
    public void walk() {
        System.out.println("RobotCat is walking");
    }
    public void eat() {
        // Robot cat don't really need to eat.
    }
}
// TO BE 
interface Walkable {
    void walk();
}
interface Eatable {
    void eat();
}
class PersianCat implements Walkable, Eatable {
    public void walk() {
        System.out.println("PersianCat is walking");
    }
    public void eat() {
        System.out.println("PersianCat is eating");
    }
}
class RobotCat implements Walkable {
    public void walk() {
        System.out.println("RobotCat is walking");
    }
}
Dependency inversion principle
- High level modules should not depend on low level modules, both should depend on abstractions.
- High level module is a module which depends on other modules. For example, UserRestController that depends on UserService.
 
- Abstractions should not depend on details. Details should depend upon abstractions.
- Use dependency injection to reduce coupling between class.
- While dependency injection is a design pattern that allows us to separate creation from use.
Benefit :
- Easy to write unit test. We can mock low level modules
- Easy to change when high level module want to change low level module.
class StorageService {
    public void store(Object o) {
        // StorageService depend on FileStorage. 
        FileStorage fs = new FileStorage(); 
        fs.save(o);
    }
}
class FileStorage {
    public void save(Object o) {
    }
}
// TO BE
class StorageService {
    private Storage storage;
    public void setStorage(Storage storage) {
        this.storage = storage;
    }
    public void store(Object o) {
        storage.save(o);
    }
}
interface Storage {
    void save(Object o);
}
class FileStorage implements Storage {
    public void save(Object o) {
    }
}
Summary
- Single-responsibility Principle : A class should have only one job
- Open-Closed Principle : A class should open for extension and close for mofication
- Liskov Substitution Principle : Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program
- Interface segregation principle : Create small interface with minimal number of method.
- Dependency inversion : High level modules should not depend on low level modules, both should depend on abstractions to reduce coupling between module
Reference
- https://zeroturnaround.com/rebellabs/object-oriented-design-principles-and-the-5-ways-of-creating-solid-applications/
- https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design
- https://dzone.com/articles/the-solid-principles-in-real-life
- http://deviq.com/solid/
- https://medium.com/@andy.sek94/s-o-l-i-d-principles-for-software-development-611b5f7170de