{{announcement.body}}
{{announcement.title}}

Introduction to SOLID Design Principles for Java Developers

DZone 's Guide to

Introduction to SOLID Design Principles for Java Developers

Check out this introduction to SOLID design principles, for Java developers.

· Java Zone ·
Free Resource

SOLID Design for Java devs

Check out this introduction to SOLID design principles, for Java developers.

S.O.L.I.D. principles and design patterns are not easy to understand or get used to it when you are a newcomer to software engineering. We all had problems and it was hard to grasp the ideas of SOLID+DP, and even more difficult to implement them correctly. Indeed, the whole concept of "why SOLID?" and how to implement design patterns it’s something that requires time and a lot of practice.

You may also like: The SOLID Principles in Real Life

One thing I can honestly say about SOLID design patterns, along with some other fields like TDD, is that by nature, they are very hard to teach. It’s very difficult to teach and transfer all this knowledge and information to young minds in the correct way. 

SOLID Made Easy

In this post, I will aim to teach every letter of SOLID in as simplest terms as possible, with straightforward and easy-to-grasp examples.

The S of SOLID

The S stands for SRP (Single Responsibility Principle). The basic idea is to apply a separation of concerns, which means that you should try and separate the concerns into different classes. A class should be focusing on a single problem, piece of logic, or a single domain. When the domain, specification, or logic changes, it should only affect one class.

Before Implementing SRP

Below, we have a violation of SRP. The class VehicleServiceResource has implemented two different things and ended up with two roles. As we can see, the class has two annotations marking its usage.

One is the role of exposing and serving HTTP endpoint vehicles to clients.

The second is the role of a vehicle service, which is fetching the vehicles from storage getVehicles() and calculates the total value calculateTotalValue():

@EndPoint("vehicles")
@Service
public class VehicleServiceResource {
     …
     @GET
     public List getVehicles(){
     }  
     public double calculateTotalValue(){}
     …  
}


The simple goal to achieve SRP is to separate the VehicleServiceResource into two different classes: one for the endpoint and the other for the service.

After the Implementation of SRP

What we did was to take the VehicleServiceResource class and split it into two different classes.

VehicleResource class has one and one job only. To expose and serve HTTP resource vehicles to clients, all business-logic-related methods lead to the VehicleService class.

@EndPoint("vehicles")
public class VehicleResource {
  @Service
  private VehicleService service;
  @GET
  public List getVehicles() {
      return this.service.getVehicles(); 
  }
  ...  
}

We created a new class with a name VehicleService . This class implements all the vehicle-related logic.

@Service
public class VehicleService {
    ...
    public List getVehciles() {} 
    public double calculateTotalValue(){}    
    ...
}


The O of SOLID

The O stands for OCP (Open-Closed Principle). The Open-Closed Principle states that:

 " ...software entities such as modules, classes, functions, etc. should be open for extension, but closed for modification."

The term “Open for extension” means that we can expand and include extra cases/functionalities in our code without altering or affecting our existing implementation.

The term “Closed for modification” means that after we add the extra functionality, we should not modify the existing implementation.

A simple violation of the OCP:

public class VehicleValueCalculator {
    // lets assume a simple method to calculate the total value of a vehicle
    // with extra cost depending the type.
    public double calculateVehicle(Vehicle v){
        double value = 0;
        if(v instanceof Car){
            value = v.getValue() + 2.0;
        } else if(v instanceof MotorBike) {
            value = v.getValue() + 0.4;
        } 
        return value;
    }
}


The OCP violation raises when we want to include a new type of vehicle a Truck. Refactoring and code modification on calculateVehicle method is needed.

Solution

public interface IVehicle {
      double calculateVehicle();
}
public class Car implements IVehicle {
    @Override
    public double calculateVehicle() {
        return this.getValue() + 2.0;
    }
}
public class MotorBike implements IVehicle {
    @Override
    public double calculateVehicle() {
        return this.getValue() + 0.4;
    }
}

Our new Truck Vehicle

public class Truck implements IVehicle {
    @Override
    public double calculateVehicle() {
        return this.getValue() + 3.4;
    }
}

This way by having a method that accepts an IVehicle , there is no need for refactoring/code modification in the future every time we add a new type of vehicle.

Example code

public class Main {
    public static void main(String[] args){
        IVehicle car = new Car();
        IVhecile motorBike = new MotorBike();
        //new addition
        IVhecile truck = new Truck();
        double carValue       = getVehicleValue(car);
        double motorBikeValue = getVehicleValue(motorBike);
        double truckValue     = getVehicleValue(truck);
    }
    public double getVehicleValue(IVehicle v) {
        return v.calculateVehicle();
    }
}


The L of SOLID

The L stands for LSP (Liskov Substitution Principle):

In order for this post to serve as an introduction to SOLID, and not get confusing, I will try to keep LSP as simple as possible and exclude a lot of the gritty details since LSP is a whole other discussion and debate for another day.

LSP states that the software should not alter the desirable results when we replace a parent type with any of the subtypes.

LSP is more of a problem definition than being a design pattern and what we can do to prevent undesirable effects.

To make this more clear, we are going to check out the simple example below:

/**
 * The Base Rectangle class
 * This class defines the structure and properties of all types of rectangles
 */
public class Rectangle {

    private int width;
    private int height;

    public Rectangle(){}

    public Rectangle(int w,int h) {
        this.width = w;
        this.height = h;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getArea() {
        return this.height * this.width;
    }

    /**
     * LSP violation is case of a Square reference.
     */
    public final static void setDimensions(Rectangle r,int w,int h) {
          r.setWidth(w);
          r.setHeight(h);
          //assert r.getArea() == w * h
    }
}


/**
 * A Special kind of Rectangle
 */
public class Square extends Rectangle {
    @Override
    public void setHeight(int h){
        super.setHeight(h);
        super.setWidth(h);
    }

    @Override
    public void setWidth(int w) {
        super.setWidth(w);
        super.setHeight(w);
    }
}


When talking about LSP, we have the method setDimensions in the Rectangle class that accepts a type of Rectangle object and sets the width and height. This is a violation because the behavior changed and we have inconsistent data when we pass a square reference.

There are many solutions. Some of them are to apply the Open-Closed Principle and a design through the Contract pattern.

There are many other solutions to the LSP violations as well, but I am not going to explain it here because it’s out of the scope of this article.

The I of SOLID

The I stands for ISP (Interface Segregation Principle). The Interface Segregation Principle was defined by Robert C. Martin while consulting for Xerox. He defined it as:

”Clients should not be forced to depend upon interfaces that they do not use.”

ISP states that we should split our interfaces into smaller and more specific ones.

Below is an example of an interface representing two different roles. One is the role of handling connections like opening and closing, and the other is sending and receiving data.

public interface Connection {
    void open();
    void close();
    byte[] receive();
    void send(byte[] data);  
}


After we applied ISP, we ended up with two different interfaces, with each one representing one exact role.

public interface Channel {
    byte[] receive();
    void send(byte[] data);  
}

public interface Connection {
    void open();
    void close();  
}


The D of SOLID

The D stands for DIP (Dependency Inversion Principle). The DIP states that we should depend on abstractions (interfaces and abstract classes) instead of concrete implementations (classes).

Next is a violation of DIP. We have an Emailer class depending on a direct SpellChecker class:

public class Emailer{
    private SpellChecker spellChecker;    
    public Emailer(SpellChecker sc) {
        this.spellChecker = sc;  
    }
    public void checkEmail() {
        this.spellChecker.check();
    }
}


And the Spellchecker class:

public class SpellChecker {
    public void check() throws SpellFormatException {
    }  
}


It may work at the moment, but after a while, we have two different implementations of spellcheckers we want to include. We have the default spell checker and a new greek spell checker.

With the current implementation, refactoring is needed because the Emailer class uses only the SpellChecker class.

A simple solution is to create the interface for the different spell checkers to implement.

// The interface to be implemented by any new spell checker.
public interface ISpellChecker {
    void check() throws SpellFormatException; 
}


Now, the Emailer class accepts only an ISpellChecker reference on the constructor. Below, we changed the Emailer class to not care/depend on the implementation (concrete class) but rely on the interface (ISpellChecker)

public class Emailer{
    private ISpellChecker spellChecker;    
    public Emailer(ISpellChecker sc) {
        this.spellChecker = sc;  
    }
    public void checkEmail() {
        this.spellChecker.check();
    }
}


And we have many implementations for the  ISpellChecker:

public class SpellChecker implements ISpellChecker {
    @Override
    public void check() throws SpellFormatException {
    }  
}

public class GreekSpellChecker implements ISpellChecker {
    @Override
    public void check() throws SpellFormatException {
    }  
}


Here's another example by code. We are passing the ISpellChecker type to the Emailer constructor — no matter what the implementation is.

public static class Main{
    public static void main(String[] a) {
        ISpellChecker defaultChecker = new SpellChecker();
        ISpellChecker greekChecker = new GreekSpellChecker();
        new Emailer(defaultChecker).checkEmail();
        new Emailer(greekChecker).checkEmail();
    }
}


And that's it! There you have it! We hope you enjoyed this simple overview of SOLID design principles in Java code. Please share your thoughts and feedback in the comments section.

Further Reading

The SOLID Principles in Real Life

SOLID Principles: Single-Responsibility Principle

Topics:
solid ,design pattens ,software engineering ,java ,principles ,liskov substitution principle ,open-closed principle ,single responsibility principle ,interface segregation principle ,dependency inversion principle

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}