Pages

Saturday, November 27, 2021

SOLID - Principles of Object Oriented Design

SOLID were introduced by Robert C. Martin, with intention that
When these principles are applied together , a programmer will create  a system which is easy to maintain and extend over time.
Can avoid:
Rigidity, Fragility, Immobility, Viscosity

Single Responsibility Principle
Open Closed Principle
Liskov Substitution Principle
Interface Segregation Principle (separation)
Dependency Inversion Principle

Single Responsibility:

A class should have one and only one responsibility. There cannot be more than one reason to change for a class. A class should have only one job to do. It should be cohesive (integrated) i.e. everything in class should be related to one purpose. 

Employee.java
public class Employee{
  private String employeeId;
  private String name;
  private string address;
  private Date dateOfJoining;
  public boolean isPromotionDueThisYear(){
    //promotion logic implementation
  }
  public Double calcIncomeTaxForCurrentYear(){
    //income tax logic implementation
  }
  //Getters & Setters for all the private attributes
}
Lets move the promotion determination logic from Employee class to the HRPromotions class like this


HRPromotions.java
public class HRPromotions{
  public boolean isPromotionDueThisYear(Employee emp){
    //promotion logic implementation using the employee information passed
  }
}
Similarly, lets move the income tax calculation logic from Employee class to FinITCalculations class –
FinITCalculations.java
public class FinITCalculations{
  public Double calcIncomeTaxForCurrentYear(Employee emp){
    //income tax logic implementation using the employee information passed
  }
}
Our Employee class now remains with a single responsibility of maintaining core employee attributes –
Employee.java adhering to Single Responsibility Principle
public class Employee{
  private String employeeId;
  private String name;
  private string address;
  private Date dateOfJoining;
  //Getters & Setters for all the private attributes
}




Open Closed Principle by Bertrand Meyer


Software entities should be open for extension but closed for modification.

It can be achieved by interfaces or abstract classes, rather than using concrete classes. Functionality can be added by creating new classes that implement the interfaces.

Your classes should be designed such a way that whenever fellow developers wants to change the flow of control in specific conditions in application, all they need to extend your class and override some functions and that’s it.

public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }
}public class AreaCalculator
{
    public double Area(Rectangle[] shapes)
    {
        double area = 0;
        foreach (var shape in shapes)
        {
            area += shape.Width*shape.Height;
        }

        return area;
    }
}public double Area(object[] shapes)
{
    double area = 0;
    foreach (var shape in shapes)
    {
        if (shape is Rectangle)
        {
            Rectangle rectangle = (Rectangle) shape;
            area += rectangle.Width*rectangle.Height;
        }
        else
        {
            Circle circle = (Circle)shape;
            area += circle.Radius * circle.Radius * Math.PI;
        }
    }

    return area;
}



“extending the AreaCalculator class to also calculate the area of triangles isn’t very hard, is it?”. Of course in this very basic scenario it isn’t but it does require us to modify the code. That is, AreaCalculator isn’t closed for modification as we need to change it in order to extend it. Or in other words: it isn’t open for extension.

A solution that abides by the Open/Closed Principle

public abstract class Shape
{
    public abstract double Area();
}public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    public override double Area()
    {
        return Width*Height;
    }
}public class Circle : Shape
{
    public double Radius { get; set; }
    public override double Area()
    {
        return Radius*Radius*Math.PI;
    }
}public double Area(Shape[] shapes)
{
    double area = 0;
    foreach (var shape in shapes)
    {
        area += shape.Area();
    }

    return area;
}

Liskov Substitution Principle 

objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

If the subtype is not replaceable for the supertype reference, then in order to support the subtype instances as well we go ahead and make changes to the existing code and add the support. This is a clear violation of OCP.



class Bird {
  public void fly(){}
  public void eat(){}
}
class Crow extends Bird {}
class Ostrich extends Bird{
  fly(){
    throw new UnsupportedOperationException();
  }
}
 
public BirdTest{
  public static void main(String[] args){
    List<Bird> birdList = new ArrayList<Bird>();
    birdList.add(new Bird());
    birdList.add(new Crow());
    birdList.add(new Ostrich());
    letTheBirdsFly ( birdList );
  }
  static void letTheBirdsFly ( List<Bird> birdList ){
    for ( Bird b : birdList ) {
      b.fly();
    }
  }
}

As soon as an Ostrich instance is passed, it blows up!!! Here the sub type is not replaceable for the super type.


In the above scenario we can factor out the fly feature into- Flight and NonFlight birds.
class Bird{
  public void eat(){}
}
class FlightBird extends Bird{
  public void fly()()
}
class NonFlight extends Bird{}

















Integration Segregation Principle 

Many client specific interfaces are better than one general purpose interface



No comments:

Post a Comment