Solid Principles: Dependency inversion principle

Up until now we had a look on the single responsibility, open/closed, liskov substitution and interface segregation principles.
Dependency Inversion is one of the last principle we are gone look at.
The principle states that

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

Let’s get started with some code that violates that principle.
As a software team and we need to implement a project. For now the software team consists of:

A BackEnd Developer

package com.gkatzioura.solid.di;

public class BackEndDeveloper {

    public void writeJava() {
    }
}

And a FrontEnd developer

package com.gkatzioura.solid.di;

public class FrontEndDeveloper {

    public void writeJavascript() {
    }

}

And our project uses both throughout the development process.

package com.gkatzioura.solid.di;

public class Project {

    private BackEndDeveloper backEndDeveloper = new BackEndDeveloper();
    private FrontEndDeveloper frontEndDeveloper = new FrontEndDeveloper();

    public void implement() {

        backEndDeveloper.writeJava();
        frontEndDeveloper.writeJavascript();
    }

}

So as we can see the Project class is a high level module and it depends on low level modules such as BackEndDeveloper and FrontEndDeveloper. We are actually violating the first part of the dependency inversion principle.

Also by the inspecting the implement function of the Project.class we realise that the methods writeJava and writeJavascript are methods bound to the corresponding classes. Regarding the project scope those are details since in both cases they are forms of development. Thus the second part of the dependency inversion principle is violated.

In order to tackle this problem we shall implement and interface called the Developer interface.

package com.gkatzioura.solid.di;

public interface Developer {

    void develop();
}

Therefore we introduce an abstraction.

The BackEndDeveloper shall be refactored to

package com.gkatzioura.solid.di;

public class BackEndDeveloper implements Developer {

    @Override 
    public void develop() {
        writeJava();
    }
    
    private void writeJava() {
    }
    
}

And the FrontEndDeveloper shall be refactored to

package com.gkatzioura.solid.di;

public class FrontEndDeveloper implements Developer {

    @Override 
    public void develop() {
        writeJavascript();
    }
    
    public void writeJavascript() {
    }
    
}

The next step in order to tackle the violation of the first part would be to refactor the Project class so that it will not depend on the FrontEndDeveloper and the BackendDeveloper class.

package com.gkatzioura.solid.di;

import java.util.List;

public class Project {

    private List<Developer> developers;
    
    public Project(List<Developer> developers) {
    
        this.developers = developers;
    }

    public void implement() {

        developers.forEach(d->d.develop());
    }

}

The outcome is that the Project class does not depend on lower level modules but abstractions. Also low-level modules and their details depend on abstractions.

You can find the source code on github.

Also I have compiled a cheat sheet containing a summary of the solid principles.
Sign up in the link to receive it.

Advertisement

Solid Principles: Interface segregation principle

Previously we examined the liskov substitution principle. Next principle is the interface-segregation. The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.

Imagine an interface with many methods in our codebase and many of our classes implement this interface although only some of its methods are implemented.

In our case the Athlete interface is an interface with some actions of an athlete.

package com.gkatzioura.solid.segragation;

public interface Athlete {

    void compete();

    void swim();

    void highJump();

    void longJump();

}

We have added the method compete but also there some extra methods like swim highJump and longJump.
Suppose that JohnDoe is a swimming athlete. By implementing the Athlete interface we have to implement methods like highJump and longJump which JohnDoe will never use.

package com.gkatzioura.solid.segragation;

public class JohnDoe implements Athlete {

    @Override
    public void compete() {
        System.out.println("John Doe started competing");
    }

    @Override
    public void swim() {
        System.out.println("John Doe started swimming");
    }

    @Override
    public void highJump() {
    }

    @Override
    public void longJump() {
    }
}

The same problem will occur for another athlete who might be a field Athlete competing on high jump and long jump.
We will follow the interface segregation principle and we will refactor the original interface

package com.gkatzioura.solid.segragation;

public interface Athlete {

void compete();
}

Then we will create two other interfaces one for Jumping athletes and one for Swimming athletes.

package com.gkatzioura.solid.segragation;

public interface SwimmingAthlete extends Athlete {

    void swim();

}
package com.gkatzioura.solid.segragation;

public interface JumpingAthlete extends Athlete {

    void highJump();

    void longJump();

}

And therefore John Doe will not have to implement actions that he is not capable of performing.

package com.gkatzioura.solid.segragation;

public class JohnDoe implements SwimmingAthlete {

    @Override
    public void compete() {
        System.out.println("John Doe started competing");
    }

    @Override
    public void swim() {
        System.out.println("John Doe started swimming");
    }

}

You can find the source code on github. The last principle is the dependency inversion principle.

Also I have compiled a cheat sheet containing a summary of the solid principles.
Sign up in the link to receive it.

Solid Principles: Liskov substitution principle

Previously we took a dive into solid principles including the single responsibility and the open/closed principle.
The Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called (strong) behavioral subtyping,

Supposing object S is a subtype of object T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of T.

Suppose we have the Employee class.

package com.gkatzioura.solid.liskov;

public class Employee {

    public void work() {

        System.out.println("Employee is working");
    }

}

Also we have another class which inherits the Employee class.

package com.gkatzioura.solid.liskov;

public class EmployeeOnVacation extends Employee {

    @Override
    public void work() {
        throw new IllegalArgumentException("Employees on vacation should not work");
    }
}

Supposing that we have a project.

package com.gkatzioura.solid.liskov;

import java.util.List;

public class Project {

    public void start(List<Employee> employees) {

        for(Employee employee:employees) {
            employee.work();
        }
    }
}

And we assign our employees to start working on it


        List<Employee> employees = new ArrayList<>();
        employees.add(new EmployeeOnVacation());
        employees.add(new Employee());

        Project project = new Project();
        project.start(employees);

The outcome would be an exception due to the employee who is on vacation and thus the project will not be completed.

The employee on vacation is an employee however he will not work. Even if the method didn’t throw an exception the method work would do nothing and this would affect delivering our project since our actucal team’s velocity is not the one we originally thought it was.

In order to avoid violating the principle we shall use a different approach and change the class hierarcy.

We will change the original employee class.

package com.gkatzioura.solid.liskov;

public class Employee {

    public String getTitle() {
        return "The employee's title";
    }

} 

And we will make make two different employee interfaces.
The WorkingEmployee interface.

 
package com.gkatzioura.solid.liskov;

public interface WorkingEmployee {

    public void work();
}

And the non working employee interface.

package com.gkatzioura.solid.liskov;

public interface NonWorkingEmployee {

    void relax();
}

Then the project will use only employees who are implementations of the WorkingEmployee interface and extend the employee class.

package com.gkatzioura.solid.liskov;

public class WorkingEmployeeImpl extends Employee implements WorkingEmployee {

    @Override
    public void work() {

    }
}
package com.gkatzioura.solid.liskov;

import java.util.List;

public class Project {

    public void start(List<WorkingEmployee> workingEmployees) {

        for(WorkingEmployee workingEmployee:workingEmployees) {
            workingEmployee.work();
        }
    }
}
List<WorkingEmployee> employees = new ArrayList<>();
employees.add(new WorkingEmployeeImpl());
Project project = new Project();
project.start(employees);

You can find the source code on github. Next principle is the interface segregation principle.

Also I have compiled a cheat sheet containing a summary of the solid principles.
Sign up in the link to receive it.

Solid Principles: Open/closed principle

Previously we talked about the single responsibility principle. The open/closed principle is the second principle in the row regarding the solid principles acronym.

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

By employing that principle the goal is to extend a module’s behaviour without modifying its source code.

Imagine a scenario of applying a discount to one of our products. A discount service will apply the discount specified and give back the discounted price.

Currently our system has only one kind of discount which applies to all adults.

package com.gkatzioura.solid.ocp;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class Discount {

    public BigDecimal apply(BigDecimal price) {

        BigDecimal percent = new BigDecimal("0.10");
        BigDecimal discount = price.multiply(percent);
        return price.subtract(discount.setScale(2, RoundingMode.HALF_UP));
    }
}

And the discount service shall apply this discount to the price given.

package com.gkatzioura.solid.ocp;

import java.math.BigDecimal;

public class DiscountService {

    public BigDecimal applyDiscounts(BigDecimal price,Discount discount) {

        return discount.apply(price);
    }
}

However our company wants to offer a discount to seniors, thus we have the senior Discount.

package com.gkatzioura.solid.ocp;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class SeniorDiscount {

    public BigDecimal apply(BigDecimal price) {

        BigDecimal percent = new BigDecimal("0.20");
        BigDecimal discount = price.multiply(percent);
        return price.subtract(discount.setScale(2, RoundingMode.HALF_UP));
    }
}

This makes things a little more complicated for the discount service since the service has to apply both the discount for adult and both the discount for seniors.

package com.gkatzioura.solid.ocp;

import java.math.BigDecimal;

public class DiscountService {

    public BigDecimal applyDiscounts(BigDecimal price,Discount discount) {

        BigDecimal discountPrice = price.add(BigDecimal.ZERO);
        discountPrice = discount.apply(discountPrice);
        return discountPrice;
    }

    public BigDecimal applySeniorDiscount(BigDecimal price,SeniorDiscount discount) {

        return discount.apply(price);
    }

}

By doing so we modified the discount service sourcecode to extend its behaviour. Also for every different discount that the sales department might come up with, the discount service will get extra methods.

In order to follow the open/closed principle we will create a discount interface.

package com.gkatzioura.solid.ocp;

import java.math.BigDecimal;

public interface Discount {

    BigDecimal apply(BigDecimal price);
}

The default discount will be renamed to the AdultDiscount and implement the discount interface.

package com.gkatzioura.solid.ocp;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class AdultDiscount implements Discount {

    @Override
    public BigDecimal apply(BigDecimal price) {

        BigDecimal percent = new BigDecimal("0.10");
        BigDecimal discount = price.multiply(percent);
        return price.subtract(discount.setScale(2, RoundingMode.HALF_UP));
    }
}

The SeniorDiscount will also implement the Discount interface.

package com.gkatzioura.solid.ocp;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class SeniorDiscount implements Discount {

    @Override
    public BigDecimal apply(BigDecimal price) {

        BigDecimal percent = new BigDecimal("0.20");
        BigDecimal discount = price.multiply(percent);
        return price.subtract(discount.setScale(2, RoundingMode.HALF_UP));
    }
}

Last but not least our DiscountService will be refactored in order to apply discounts based on the Discount interface.

package com.gkatzioura.solid.ocp;

import java.math.BigDecimal;

public class DiscountService {

    public BigDecimal applyDiscounts(BigDecimal price,Discount[] discounts) {

        BigDecimal discountPrice = price.add(BigDecimal.ZERO);

        for(Discount discount:discounts) {

            discountPrice = discount.apply(discountPrice);
        }

        return discountPrice;
    }
}

By this way the discount service will be able to apply different discounts without altering its source code.

The same principle can be applied to the Discount.
Supposing we want to have a basic discount to be applied extra when a discount is applied.

package com.gkatzioura.solid.ocp;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class BasicDiscount implements Discount {

    @Override
    public BigDecimal apply(BigDecimal price) {

        BigDecimal percent = new BigDecimal("0.01");
        BigDecimal discount = price.multiply(percent);
        return price.subtract(discount.setScale(2, RoundingMode.HALF_UP));
    }
}

By extending the BasicDiscount class we are able to have more discounts with the behaviour of the BasicDiscount and also extend this behaviour without modifying the BasicDiscount sourcecode.

You can find the source code on github. The next principle is the liskov substitution principle.

Also I have compiled a cheat sheet containing a summary of the solid principles.
Sign up in the link to receive it.

Solid Principles: Single responsibility principle

The single responsibility principle is the first principle from the solid acronym.

“A class should have only one reason to change.”

Every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.

For example imagine the scenario of a navigation software.
We have a position which based on the direction given (north, south, west, east) the position should change.

The Position class contains values regarding the x and y axis position.

package com.gkatzioura.solid.single;

public class Position {

    private Integer xAxis;
    private Integer yAxis;

    public Position(Integer xAxis, Integer yAxis) {
        this.xAxis = xAxis;
        this.yAxis = yAxis;
    }

    public Integer getxAxis() {
        return xAxis;
    }

    public void setxAxis(Integer xAxis) {
        this.xAxis = xAxis;
    }

    public Integer getyAxis() {
        return yAxis;
    }

    public void setyAxis(Integer yAxis) {
        this.yAxis = yAxis;
    }
}

The direction is an enum representing the direction towards North, East, South and West.

package com.gkatzioura.solid.single;

public enum Direction {
    N,W,S,E
}

And at last there is a Navigator class which is responsible for navigating according to the direction and position change.


public class Navigator {

    public Position navigate(Position position, Direction direction) {
        ....
    }

}

In order to navigate properly the navigator should resolve the next position based on the direction. Also the navigator should fix the position in cases of values below 0.


public class Navigator {

    public Position navigate(Position position, Direction direction) {

        Position nextPosition = resolve(position,direction);
        Position fixedPosition =fix(nextPosition);
        return fixedPosition;
    }

    public Position resolve(Position position,Direction direction) {

        switch (direction) {
            case N:
                return new Position(position.getxAxis(),position.getyAxis()+1);
            case S:
                return new Position(position.getxAxis(),position.getyAxis()-1);
            case W:
                return new Position(position.getxAxis()-1,position.getyAxis());
            case E:
                return new Position(position.getxAxis()+1,position.getyAxis());
            default:
                throw new IllegalArgumentException();
        }
    }

    public Position fix(Position position) {

        return new Position(
                position.getxAxis()<0?0:position.getxAxis(),
                position.getyAxis()<0?0:position.getyAxis()
        );
    }

}

The problem with this approach is that in case the position validity criteria changes we have to change the Navigator class. The same applies in case of the position movement mechanisms changes. The navigator instead of just navigating is responsible for both resolving the next position and for fixing the new position.

An approach that does not break the single responsibility principle is to create a class that will resolve the next position and a class responsible for fixing the new position.

The NextPositionResolver class will resolve the next position based on the direction given.

package com.gkatzioura.solid.single;

public class NextPositionResolver {

    public Position resolve(Position position,Direction direction) {

        switch (direction) {
            case N:
                return new Position(position.getxAxis(),position.getyAxis()+1);
            case S:
                return new Position(position.getxAxis(),position.getyAxis()-1);
            case W:
                return new Position(position.getxAxis()-1,position.getyAxis());
            case E:
                return new Position(position.getxAxis()+1,position.getyAxis());
            default:
                throw new IllegalArgumentException();
        }
    }

}

The PositionRepairer class will fix the position in case of invalid x or y values.

package com.gkatzioura.solid.single;

public class PositionRepairer {

    public Position fix(Position position) {

        return new Position(
                position.getxAxis()<0?0:position.getxAxis(),
                position.getyAxis()<0?0:position.getyAxis()
        );
    }

}

The Navigator class will have as dependencies the NextPositionResolver and PositionRepairer classes in order to perform the navigation properly.

package com.gkatzioura.solid.single;

public class Navigator {

    private NextPositionResolver nextPositionResolver;
    private PositionRepairer positionRepairer;

    public Navigator(NextPositionResolver nextStepResolver,PositionRepairer positionRepairer) {
        this.nextPositionResolver = nextStepResolver;
        this.positionRepairer = positionRepairer;
    }

    public Position navigate(Position position, Direction direction) {

        Position nextPosition =  nextPositionResolver.resolve(position,direction);
        Position fixedPosition = positionRepairer.fix(nextPosition);
        return fixedPosition;
    }

}

You can find the source code on github. Next principle is the open/closed principle.

Also I have compiled a cheat sheet containing a summary of the solid principles.
Sign up in the link to receive it.