SOLID principles in Java

solid principles java

This article explains SOLID principles, they are very popular in Object-Oriented Design. We will explore these principles in the context of the Java programming language.

SOLID principles of object-oriented design provide the more extendable and clear architecture of your application.

The author of SOLID principles is Rober C. Martin. He is the father of a lot of significant publications and books: Clean Code and others.

Single Responsibility Principle

This principle states that the class must have only one responsibility and it should only have one reason to change. As a result, we get the following preferences:

  • Testing – there are more possibilities to test your class with less amount of tests.
  • Flexibility – more segmented classes are better to manage than a monolith. Some parts of the code can be reused more times instead of duplication.
  • Organization – you can provide more clean architecture when using single-responsible small classes.

Lest’s overview one example, where we have a simple class to work with orders:

class OrderProcessor {

    BigDecimal price;
    Date date;
    Integer amount;

    BigDecimal calculateTotalOfOrder() {

    }

    void makeOrder() {

    }
    
    ...
}

We see in code some related fields to an order: date, amount, price, and methods to confirm and make the order. This class has a direct assignment to make orders in the system.

But let us consider more incorrect example, with not single responsibility.

class OrderProcessor {

    BigDecimal price;
    Date date;
    Integer amount;

    BigDecimal calculateTotalOfOrder() {

    }

    void makeOrder() {

    }

    ...
    
    void exportOrderToFile() {

    }

    
}

We see a new method exportOrderToFile. Probably, we need this functionality to export our order to the external system, after the order has been done. As we see this method isn’t related to the responsibility to process or confirm the current order.

The best solution for this case will be to extract this functionality to separate class as a part of subsystem. See example:

class OrderExportProcessor {
    
    void exportOrderToFile(Order order) {
...
    }
}

Open / Closed Principle

The open-closed principle means that classes should be closed for modification and open for extension.

Let’s explore a short code example.

class Aircraft {

    String engine;
    String owner;
    Date produced;
}

For instance, with time, we need to add additional properties to the aircraft, because we started transporting passengers and goods. The best way will be to extend our aircraft instead of modifying it. Let’s describe the example with extended properties.

class PassengersAircraft extends Aircraft {

    Integer seats;
    Integer luggage;
}

Liskov Substitution Principle

This principle means that all implementations of the interface must be applicable for all inherited classes. Let us consider several examples.

interface Aircraft {
    String passengers;
    String luggage;

    void makeCivilFlight();
    void deliverLuggage();
}

In following example we see an interface Aircraft with possibility to make civil flights and option to deliver luggage.

class PassengersAircraft extends Aircraft {

    String passengers;
    String luggage;

    void makeCivilFlight() {
        passengers.deliver();
    }

    void deliverLuggage() {
        luggage.deliver();
    }
}

But if we have to implement military aircraft. Does it an appropriate interface we can apply?

class MilitaryAircraft extends Aircraft {

    Integer weapons;

    void shoot() {

    }

    void makeCivilFlight() {
        throw NoImplementatioinException();
    }

    void deliverLuggage() {
        throw NoImplementatioinException();
    }

}

As we can see the military aircraft doesn’t implement the interface Aircraft. The interface describes more specific information related more for civil aircrafts.

As a solution we need to extract more general information about aircrafts or add military properties for this interface, where it’s not the best way.

Interface Segregation Principle

Large interfaces should be divided to smaller with more specific information. Let’s see some example.

interface Aircraft {
    void makeCivilFlight();
    void deliverLuggage();
    void shoot();
}

In this interface, we see that the aircraft can deliver passengers and luggage but meanwhile, the aircraft can shoot. The best way to maintain this principle will be to divide military and civil functionality between aircraft.

Let’s see a more appropriate example.

interface CivilAircraft {
    void makeCivilFlight();
    void deliverLuggage();
    
}

interface MilitaryAircraft {
    void shoot();
}

class PassengersAircraft extends CivilAircraft {

    String passengers;
    String luggage;

    void makeCivilFlight() {
        passengers.deliver();
    }

    void deliverLuggage() {
        luggage.deliver();
    }
}


class MilitaryAircraft extends MilitaryAircraft {

    Integer seats;
    Integer weapons;

    void shoot() {

    }  
}

Dependency Inversion Principle

This principle means that between high level and low modules we need some abstract layer, in order to provide stability and reusability.

Conclusion

By using these principles you can achieve stability and the possibility to extend your applications.

Related posts

Leave a Comment