Why S.O.L.I.D
SOLID principles are design principles that enable us manage most of the software design problems. Objective of SOLID is to make software design more understandable, flexible and maintainable.Why SOLID was promoted
- Rigidity - Every change affects many other parts
- Fragility - Things breaks in unrelated places
- Immobility - Cannot reuse the code outside of its origin context
Because of these we ended up with these
- End up with tight or strong coupling of the code with many other modules.
- Tight coupling causes time to implement any new requirements, bug fixes and some times creates unknown issues
- End up with code which is not testable
- End up with duplication of code
- End up creating new bugs when changing the code
If followed SOLID
- Achieve reduction in complexity of the code
- Increase readability, extensible and maintainability
- Reduce errors and implement re-usability
- Reduce tight coupling
S.O.L.I.D Principles
Single Responsibility Principle (SRP)
"A Class should have only one reason to change"If a class have many reasons to be changed then it does not satisfy the SRP
public class User {
Boolean login(String username, String password){}
Boolean register(String username, String password, String email) {}
void logError(String error) {}
}
Boolean login(String username, String password){}
Boolean register(String username, String password, String email) {}
void logError(String error) {}
}
if the above example considered both login and register methods are associated with user class and logError method does not have direct relations to user class. if we want to change user object we will modify the User class and if we want to modify the error logging then also we have to change the User class. So this basically violate the SRP. where we have multiple reasons to change the User class.
if we follow SRP it will be like this
public class User {
Boolean login(String username, String password){}
Boolean register(String username, String password, String email) {}
}
public class Logger {
void logError(String error) {}
}
Boolean login(String username, String password){}
Boolean register(String username, String password, String email) {}
}
public class Logger {
void logError(String error) {}
}
Open/Closed Principle (OCP)
"Software entities should be open for extension. but closed for modification"Any new functionality should be implemented by adding new classes, attributes and methods instead of changing the current ones or existing ones.
public class Shape {
public double claculateArea(Object shapeObject){
if(shapeObject is Rectangle) {
Rectangle rectangle = (Rectangle)shapeObject;
return rectangle.getWitdh() * rectangle.getHeight();
}else if(shapeObject is Circle) {
Circle circle = (Circle)shapeObject;
return circle.getRadius() * circle.getRadius() * MAth.PI;
}
}
}
public double claculateArea(Object shapeObject){
if(shapeObject is Rectangle) {
Rectangle rectangle = (Rectangle)shapeObject;
return rectangle.getWitdh() * rectangle.getHeight();
}else if(shapeObject is Circle) {
Circle circle = (Circle)shapeObject;
return circle.getRadius() * circle.getRadius() * MAth.PI;
}
}
}
By having a code like this when every time we create a new shape like square or a triangle we have to change the shape class calculateArea method violating OCP by doing so we are affecting all the functionality that are related to Shape class.
if we follow OCP it will be like this
public abstract class Shape {
public abstract double claculateArea();
}
public class Rectangle extends Shape { ...
public double claculateArea() {
return width * height;
}
}
public abstract double claculateArea();
}
public class Rectangle extends Shape { ...
public double claculateArea() {
return width * height;
}
}
By doing this we can add any number of classes like Triangle or Square without modifying the origin Shape class. and doing so we are not affecting the already implemented functionality.
Liskov Substitution Principle (LSP)
"Objects in a program should be replaced with instances of their sub types without altering the correctness of that program"Sub-types should be substituteble for their base type. That means you should use inheritance when your super-class can replace by subclass in all instances.
class Rectangle {
...
void Rectangle(double h, double w){
this.height = h;
this.width = w;
}
void setHeight(double h){}
double getHeight(){}
void setWidth(double w){}
double getHeight(){}
}
class Square extends Rectangle {
void Square(double h) {
super(h,h);
}
}
...
void Rectangle(double h, double w){
this.height = h;
this.width = w;
}
void setHeight(double h){}
double getHeight(){}
void setWidth(double w){}
double getHeight(){}
}
class Square extends Rectangle {
void Square(double h) {
super(h,h);
}
}
by using this example imagine down the line we want to create a different rectangle and since square is extended from Rectangle if we use square, will it work? no. so this example actually violate LSP. Where in LSP we have to create two class for Rectangle and Square without extending. that way it will not violate the priniciple
Interface Segregation Principle (ISP)
"Many client-specific interface are better than one general-purpose interface"One big interface needed to split in to many smaller interface so that client knows about the interface that are relevant to them
public interface Animal {
void feed();
void groom();
}
public class Dog implements Animal {
void feed();
void groom();
}
public class Tiger implements Animal {
void feed();
void groom();
}
void feed();
void groom();
}
public class Dog implements Animal {
void feed();
void groom();
}
public class Tiger implements Animal {
void feed();
void groom();
}
Now as per the example we have a interface called animal and it have 2 method definitions. Dog and Tiger class both implement that interface as for they both have to have implementation written for both methods. In the Tiger class we have to have a dummy implementation for groom method, because normally only domestic pets are been groomed not wild animals. But because we are implementing a fat interface we have to have those two methods. And it make no sense.
if we follow ISP it will be like this
public interface Animal {
void feed();
}
public interface Pet extends Animal {
void groom();
}
public class Dog implements Pet {
void feed();
void groom();
}
public class Tiger implements Animal {
void feed();
}
void feed();
}
public interface Pet extends Animal {
void groom();
}
public class Dog implements Pet {
void feed();
void groom();
}
public class Tiger implements Animal {
void feed();
}
By doing like this we do not have to have unwanted methods implemented and make our code more maintainable
Dependency Inversion Principle (DIP)
"One should depend upon abstraction , not concretions"The interaction between high level and low level modules should be thought of as an abstract interaction between them. Means High level modules should not depend on low level modules. Both should depend on abstraction.
void copy(OutPutDevice dev,String content){
if(dev is Printer){
writePrinter(content);
} else if(dev is Disk){
writeDisk(content);
}
}
if(dev is Printer){
writePrinter(content);
} else if(dev is Disk){
writeDisk(content);
}
}
In this example the higher level module is depended on the lower level modules ( both Printer and Disk) that will violate DIP and make reuse of modules harder because they are tightly coupled.
interface Writer {
void write(String content);
}
public class Printer implements Writer {
void writer(String content) {
...
}
}
public class Disk implements Writer {
void writer(String content) {
...
}
}
void copy(Writer w,String content){
w.write(content)
}
void write(String content);
}
public class Printer implements Writer {
void writer(String content) {
...
}
}
public class Disk implements Writer {
void writer(String content) {
...
}
}
void copy(Writer w,String content){
w.write(content)
}
By using this the higher module Copy is not depended on lower level modules that way it will be more easier to reuse.
Another Example :
public class BusinessLogicLayer {
private DataAccessLayer dal;
BusinessLogicLayer() {
dal = new DataAccessLayer();
}
public void save(Object details) {
dal.save(details)
}
}
public class DataAccessLayer {
public void save(Object details) {
...
}
}
private DataAccessLayer dal;
BusinessLogicLayer() {
dal = new DataAccessLayer();
}
public void save(Object details) {
dal.save(details)
}
}
public class DataAccessLayer {
public void save(Object details) {
...
}
}
In this example also the business layer is tightly coupled with data access layer, and we can't change any this in data access layer (DB change, File format change) without affecting the business layer.
But if follow DIP
public class BusinessLogicLayer {
private IRepositoryLayer rl;
BusinessLogicLayer(IRepositoryLayer repository) {
rl = repository;
}
public void save(Object details) {
rl.save(details)
}
}
public interface IRepositoryLayer {
public void save(Object details) ;
}
public class DataAccessLayer implements IRepositoryLayer{
public void save(Object details) {
...
}
}
private IRepositoryLayer rl;
BusinessLogicLayer(IRepositoryLayer repository) {
rl = repository;
}
public void save(Object details) {
rl.save(details)
}
}
public interface IRepositoryLayer {
public void save(Object details) ;
}
public class DataAccessLayer implements IRepositoryLayer{
public void save(Object details) {
...
}
}
Reference : https://www.youtube.com/watch?v=yxf2spbpTSw
Comments
Post a Comment