S.O.L.I.D Code

S - Single Responsibility Principal (SRP)
A Class should have only one reason to change.
> Find one reason to change and take everything else out of the class
> Very precise names for many small classes> Generic Names for large classes
e.g controller should only handle the flow is, getting the input and returning the output
Example:
In the given example, we have two classes Person and Account. Both have single responsibility to store their specific information. If we want to change the state of Person then we do not need to modify the class Account and vice-versa.


Person.java
public class Person
{
    private Long personId;
    private String firstName;
    private String lastName;
    private String age;
    private List<Account> accounts;
}
Account.java
public class Account
{
    private Long guid;
    private String accountNumber;
    private String accountName;
    private String status;
    private String type;
}


O - Open Close Principle (OCP)
A class should be open to extension but close to modification
> We should use inheritance but favour composition over inheritance
> Composition is ideal where we can abstract things that vary between components of our application and compose them into separate units.
> Separate behaviours so that can be easily extended
> Extend functionality instead of adding new code


>If we see our classes needing to override several methods of the parent class, there is something wrong with the design<

> We can initialised the type in a certain class to ensure we dont need to keep adding the if else method
e.g
initialised(object){
if(type = duck){
return duckmethod();
}
else if(.....
}


Useful Design Patterns:
1. Strategy Pattern
Create multiple interfaces of behaviours
(This is also complimenting the SRP)
Example:
we can have an interface with bark and eat. The dog class can implement these methods but yet the dog class is not a concern on what the interface and the behaviours do. (SRP)
We can have a barking dog, mute dog and yelpingdog that implements bark.
The dog can implement any of these and we can change the behaviour of the dog any time we want.
> We can add on more sounds of barking by making the new behaviour implement bark. This allows us to change the behaviour without the need for changing the source code.
2. Decorator Pattern
This pattern is slightly the same as a sandwich, whereby you slowly add up the different "decoration" as you go on the line. This ensures that the class is open for the extension as you don't have to go back in line to add mayonnaise to the sandwich.
> We can have some common interface between the classes
> When adding a new responsibility, we should create another interface to get the responsibility from.
3. Observer pattern

Another example:

L - Liskov Substitution Principle (LSP)
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of the problem
> Behavior
> You are not supposed to override all the methods in the parent class.

Example:
We have a rectangle class and a transform class
When we parse in rectangle into transform, it will return a transformed rect with changes to its height and width.
But what if we have a square.
We would attempt to subclass a square under the rectangle class, however, the square is the same.
We are overriding everything because the height and width is the same.
If we parse in the square into the transformer method, the height would change which is undesirable

Design methods:
1. Design by Contracts
> Precondition and Postcondition
Favour composition over

Another Example:
Say we have a Bird class that has a flight method
We want to make a penguin class that extends bird but the problem is that penguin does not fly.
This breaks lsp
class Bird {
public:
    virtual void setLocation(double longitude, double latitude) = 0;
    virtual void setAltitude(double altitude) = 0;
    virtual void draw() = 0;
};
Solution:
-> Create a boolean in the flight method (bad)
//Solution 2: An OK way to do it
void ArrangeBirdInPattern(Bird* aBird)
{
    if(aBird->isFlightless())
        ArrangeBirdOnGround(aBird);
    else
        ArrangeBirdInSky(aBird);
}
-> Create an interface flightless and flying. Allow the bird to extend from it and remove the method in bird class. Ensure that the penguin does not inherit flying function
class Bird {
public:
    virtual void draw() = 0;
    virtual void setLocation(double longitude, double latitude) = 0;
};

class FlightfulBird : public Bird {
public:
    virtual void setAltitude(double altitude) = 0;
};

I - Interface Segregation Principal
Many clients specific interface are better than one general purpose interfaces. Code should not depend on methods that it doesn't use
Using the decorator pattern, we can have filters based on the types of interfaces being implemented. The classes have one less reason to change as its no longer concern on the implementation of the methods and behaviours.

> Create a new implementation of our interfaces
> Its all about types which are the classification of data that tells the compiler how the programmer attempts to use it in the program. (wiki)

Example:

If we have an animal interface that has the methods makeSound() and walks()
If we have a muteDog class that implement animal, this will break ISP as it wont make sound, thus we want to use interfaces ensure that classes dont have to implement methods that it doesnt use.
Say we can have muteDog implement animal but doesnt implement makeSound and we can have another class called noiseDog that implement the Noisy interface that has the method maksound();

D - Dependency Inversion Principle (DIP)
Depend upon abstraction not concretion. High level object should not perform low level methods.
Class dependencies are the things it needs to get its job done.
High-level modules should not depend on low-level modules. Both should depend on abstraction
Abstraction should not depend on details, details should depend on abstraction
There should be a model, view and controller and these are high-level models.
The view draws stuff on the screen, the controller makes logical decisions and model is in charge of doing data storage
>Abstraction is not interfaces<

Using concretion:
> Instantiating the types using "new" in a constructor
> This is bad as changing the behaviours will change the source code

DIP is not about interfaces and Objects
Example:
We are creating a project with backend developer and frontend developer


public class BackEndDeveloper {
    public void writeJava() {
    }
}
public class FrontEndDeveloper {
    public void writeJavascript() {
    }
}

Project class:
public class Project {
    private BackEndDeveloper backEndDeveloper = new BackEndDeveloper();
    private FrontEndDeveloper frontEndDeveloper = new FrontEndDeveloper();
    public void implement() {
        backEndDeveloper.writeJava();
        frontEndDeveloper.writeJavascript();
    }
}

This breaks the dip as Project class being a higher level module, it depends on lower level modules such as Back end and front end.
DIP states that the higher level modules should not need to depend on lower level modules

Solution:
Implementing a interface called developer
public interface Developer {
    void develop();
}

Backend and frontend can implement developer
public class BackEndDeveloper implements Developer {
    @Override
    public void develop() {
        writeJava();
    }
    private void writeJava() {
    }
}

public class FrontEndDeveloper implements Developer {
    @Override
    public void develop() {
        writeJavascript();
    }
    public void writeJavascript() {
    }
}

For the project class, we can have a list of developers instead of having one

public class Project {
    private List<Developer> developers;
    public Project(List<Developer> developers) {
        this.developers = developers;
    }
    public void implement() {
        developers.forEach(d->d.develop());
    }
}

Thus, the project class does not depend on lower level modules and lower level modules depend on abstraction.

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Useful Video:
Becoming a better developer by using Solid Design Principals


Referrences:
https://howtodoinjava.com/best-practices/5-class-design-principles-solid-in-java/
https://dzone.com/articles/solid-principles-dependency-inversion-principle
https://www.tomdalling.com/blog/software-design/solid-class-design-the-liskov-substitution-principle/


Next Generics >

1 comment:

  1. This help me a lot. Thank you very much!
    " onload="alert('ohoho')" foobar="

    ReplyDelete