Solid Design Principle Revisit

Solid Design Principle Revisit

This post is dedicated to those who are trying to learn the SOLID design principle. Some of my quick cases will give you easy ideas, and follow the reference links to understand more. Five principles of S.O.L.I.D by Robert C. Martin is core and fundamental to any Agile Development or Adaptive software development. Let’s get started.

Five principles of S.O.L.I.D by Robert C. Martin is core and fundamental to any Agile Development or Adaptive software development. Let’s get started.

Single-Responsibility Principle

Keep it simple, only one object is responsible for a feature in your entire application.

Try this: Can you think of objects where it is similar to a Notepad and methods(like saving in pdf/json/etc)? There are several ways we can design it, I have added some best practices below, let me know your thoughts.

Best practice

The below code snippet is having two classes, one to hold Notepad content and another generate Report in PDF.

public class Doc {
    private String content;
   // ...
    Doc(String content) {
        this.content = content;
    }
   // ...
}

// Report in seperate class
public class Report {
    public PDF output;
    ...
    Report(String doc) { ... }
    private String pdf() { ... }
    ...
}

Not recommended way

Below Doc class is having content and report generation code, so the Doc has two responsibilities, one is handling content and another is generating the report.

public class Doc {
    String content;
    String reportJson;
    String reportPdf;

    Doc(String content) {
        this.content = content;
        this.reportJson = json();
        this.reportPdf = pdf();
    }

    private String json() { ... }
    private String pdf() { ... }
}

Why the above single class is not recommended?

  • Since generating a report in a different format will change from time to time, keeping it in Doc class would not be a good idea(changing existing source code involves in lots of testing — so maintenance cost increases), instead, keep Doc class just for storing the content of the Doc.

Notes

  • Repeat: Keep it simple, if it is complex to your intuition, then definitely it will be more complex to other developers.
  • Caution: The above example is just to illustrate the Principle of Single Responsibility. Not to show how to design actual notepad.

Open-closed Principle

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

Try this: Imagine you are building an MS Office word 2020. The above code is very old version, has just two types of export options(pdf, json), but new MSWord2020 has some new features: — such as text to speech. Also, you will be adding new features after few years. How will do you design ?

There are several ways we can design it, I have added some Do’s/Don’t below, check across your solution.

Best practice

interface doc { void display(String content); }

interface doc_feature1 { void feature1(String content); }

class MSWord2020 implements doc, doc_feature1
{
    public void display(String content) {  ... }
    public void feature1(String content) {  ... }
}

Add new feature in 2030

interface doc_feature2 { void feature2(String content); }

class MSWord2030 extends MSWord2020 implements doc_feature2 {
    public void feature2(String content)  {  ... }
}

Not recommended way

Alternatively, you can modify the original class to add new feature 2, like below — Below code does not obey Rule 1, i.e, single responsibility principle

class MSWord2030 {
    void display() { ... }
    void feature1() { ... }
    void feature2() { ... }
    void feature3() { ... }
}

Notes

  • Its always better to program your code in Interfaces and apply inheritance where possible.

Liskov substitution principle

This is the most important and tricky rule to understand, I read several blogs and guides. Try to read this one at least twice.

Quick Test: Are you overriding a method or implementing an interface? If Yes, you must test this rule thoroughly.

S is Child or Extends B, Can you make B = S in your code? Full-width image

Can you succeed by Human = HumanoidRobot? Of course not! Full-width image

public interface Human {
    public void eat();
}
public class HumanoidRobot extends Human {
     // can robot eat ?
}

Human obj1 = new Human()
Human obj2 = new HumanoidRobot()

Repeat: If a class, S, is a subtype of a class, B, then S can be used to replace all instances of B without changing the behaviors of a program. The logic behind this is straightforward. If S is a subtype of B, then it can be expected that S will have the same behaviors as B. Therefore, S can be used in place of B and it would not affect the software. This means that inheritance can be tested by applying substitution.

Full-width image

Notes Below notes are from the design pattern course in Coursera, feel free to check out.

  • The base class is the more generalized class, and therefore, its attributes and behaviors should reflect it. The names given to the attributes and methods, as well as the implementation of each method must be broad enough that all subclasses can use them.
  • If inheritance is not used correctly, it can lead to a violation of the “Liskov Substitution Principle”. This principle uses substitution to determine whether or not inheritance has been properly used.
  • These rules are not programmatically enforced by any object-oriented language. In fact, overriding a base class’s behaviors can have advantages. Subclasses can improve the performance of behaviors of its base class, without changing the expected results of said behavior.

subclass uses different sorting algo, but same behavior.

Another example: let’s take a look at a class that is an abstraction of a department store. The base class may implement a naive searching algorithm that, in the worst case, iterates through the entire list of the items that the store sells. A subclass could override this method and provide a better search algorithm. Although the approach that the subclass takes to searching is different, the expected behavior and outcome are the same. Full-width image

Interface Segrgation Principle

Suppose you have an interface for a Robot, operations include run, fight, walk, swim, shoot. Now another company wants to use your code base for Robot and extend to make a Robot which can Speak. But here is the problem, the company doesn’t want to implement swim, fight and shoot operations. So company writes something like below -

Not recommended way

The below code violates Principle 3. Never ever changes the behavior and force the client to implement the behavior. Shoot, Swim, and Fight are irrelevant to New Client.

public interface Human {
    public void eat();
}
public class HumanoidRobot extends Human {
    // Humanoid Robot  - A humanoid is something that has an appearance
    // resembling a human without actually being one.
    // can robot eat ? No
    @override
    publi void eat() {
        // do nothing
    }
}

What is the problem?

You are making client or company implement the operations forcefully. So in order avoid this we need to have interface segregation principle. It’s quite simple, that no class should be forced to depend on methods it does not use.

Best practice

Solution to the Don’t part, now read below snippet and compare with above.

public interface Everything { run() }
// similar to Object class(root class, it has most generalized methods) in Java

public class HumanoidRobot extends Everything {
    public void run() {
        // code to run using batteries.
    }
}
public class Human extends Everything {
    public void run() {
        // code to run using physical streangth.
    }
}

// Example:
    Everything ev = new HumanoidRobot()
    Everything ev = new Human()
// if you apply the substition rule it satisfies, since both does the run operation but differently.

Dependency Inversion Principle

From wikipedia:

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions

Quite easy to understand using examples First, Take a look at Java Object Class: Root class to all objects. For example, Take Number, String, Double etc Objects in java extends Object class. All above principles apply to this one. Here suppose you have Robot interface, then all objects of Robot should be referring to an abstraction rather than the concrete implementation. Referring to the above code(Principle-4: Interface Segregation):

Best practice

Robot r = new SpeakingRobot()

Implementation of SpeakingRobot Class

public interface Robot {
     void run();
     void shoot();
     void swim();
     void fight();
     void walk();
 }

// company A wants to extend the Robot to add Speaking feature


public SpeakingRobot extends Robot {
     @Overrides
     void run() {
         // .. inherits from Robot
     }

     @Overrides
     void shoot() {
         // do nothing
         // return;
     }

     @Overrides
     void swim() {
          // do nothing
         // return;
     }

     @Overrides
     void fight(){
          // do nothing
         // return;
     }

     @Overrides
     void walk() {
         // .. inherits from Robot
     }

     @Overrides
     void speak() {
        // code to speak
     }
 }

Not recommended way

SpeakingRobot r = new SpeakingRobot() // DONT Declare like this :)

SpeakingRobot class is an actual implementation(low-level class), so any object which might change in future should depend on abstractions(Robot interface is high-level abstraction here).

interface Robot {
    // very common features, every client must implement are below.
    void run();
    void walk();
}

interface ShootingRobot extends Robot { void shoot(); }
interface SwimmingRobot extends Robot { void swim(); }
interface FightingRobot extends Robot { void fight(); }

class  SpeakingRobot  implements  Robot{


    void speak() {
        // code to speak
    }


    @Override
    public void run() {
        // code to run
    }

    @Override
    public void walk() {
        // code to walk
    }
}

References


© 2021. All rights reserved.