0% found this document useful (0 votes)
58 views23 pages

The SOLID Principles

The document describes the SOLID principles of object-oriented design: - S - Single Responsibility Principle: A class should have one and only one responsibility. - O - Open/Closed Principle: Software entities should be open for extension but closed for modification. - L - Liskov Substitution Principle: Subtypes must be substitutable for their base types without altering desired functionality.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
58 views23 pages

The SOLID Principles

The document describes the SOLID principles of object-oriented design: - S - Single Responsibility Principle: A class should have one and only one responsibility. - O - Open/Closed Principle: Software entities should be open for extension but closed for modification. - L - Liskov Substitution Principle: Subtypes must be substitutable for their base types without altering desired functionality.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 23

The S.O.L.I.

D Principles

S – Single-Responsibility Principle.

O – Open-Closed Principle.

L – Liskov Substitution Principle.

I – Interface Segregation Principle.

D – Dependency Inversion Principle.

1. Single-Responsibility Principle

“There should be never more than one reason for a class


to change.”

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

2. Open-Closed Principle

“Software entities (classes, modules, functions, etc.)


should be open for extension, but closed for modification.”

3. Liskov Substitution Principle

“Subtypes must be substitutable for their base types.”


4. Interface Segregation Principle

“Classes that implement interfaces, should not be forced


to implement methods they do not use.”

5. Dependency Inversion Principle

“High-level modules should not depend on low level


modules, rather both should depend on abstraction.

Abstraction should not depend on details; rather detail


should depend on abstraction.”
Single Responsibility Principle
We’re going to start with the first one, the Single Responsibility
Principle (SRP) that is defined by Robert C. Martin in his book “Agile
Software Development, Principles, Patterns, and Practices”. The
principle is actually a very simple concept to explain, but it can be
difficult to implement. As its name suggests, it implies that a class or
module must have a unique responsibility.

Within the context of the Single Responsibility Principle,


responsibility is defined as a reason to change. That is why I consider
the phrase of Robert C. Martin as a perfect definition of the Single
Responsibility Principle:

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

By following the Single Responsibility Principle, you make sure that


your class or module has high cohesion, which means that your class
does not do more than it should. In short, it should have one unique
reason to change. If, on the contrary, you build a class with more than
one responsibility, you’re engaging these responsibilities. This leads
to a design that is fragile and difficult to maintain, with all that that
entails.

Example

In this simple example, you can see a rectangle class that has two
methods; Area () and Draw ().

 Area () has the responsibility to return the area of a rectangle


 Draw () has the responsibility to draw the rectangle itself
In this design, it can be seen that, if there are changes in the GUI, we
must modify the Rectangle class, then we are obliged to test the other
application that attacks the same class again. The solution to this
problem is to divide the class in two so that each class has a unique
responsibility. One will be responsible for calculating and another
one for painting:

It’s important to keep in mind that this principle leads to many other
nuances and concepts, and not only those that fall within SOLID, but
others that should be known and able to apply to get an application
with a solid design.
Open/closed 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.
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);
Interface segregation principle

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");
}

}
1. DRY (Don't repeat yourself)
Our first object-oriented design principle is DRY, as the name suggests DRY
(don't repeat yourself) means don't write duplicate code, instead
use Abstraction to abstract common things in one place. If you have a block of
code in more than two places consider making it a separate method, or if you
use a hard-coded value more than one time make them public final constant.

The benefit of this Object oriented design principle is in maintenance. It's


important not to abuse it, duplication is not for code, but for functionality. It
means if you used common code to validate OrderID and SSN it doesn’t
mean they are the same or they will remain the same in future.

By using common code for two different functionality or thing you closely
couple them forever and when your OrderId changes its format, your SSN
validation code will break.

So beware of such coupling and just don’t combine anything which uses
similar code but are not related

2. Encapsulate What Changes


Only one thing is constant in the software field and that is "Change", So
encapsulate the code you expect or suspect to be changed in future. The
benefit of this OOP Design principle is that It's easy to test and maintain proper
encapsulated code.

If you are coding in Java then follow the principle of making variable and
methods private by default and increasing access step by step e.g. from
private to protected and not public.

Several of the design patterns in Java uses Encapsulation, the Factory


design pattern is one example of Encapsulation which encapsulates object
creation code and provides flexibility to introduce a new product later with no
impact on existing code.
3. Open Closed Design Principle
Classes, methods or functions should be Open for extension (new functionality)
and Closed for modification. This is another beautiful SOLID design principle,
which prevents someone from changing already tried and tested code.

Ideally, if you are adding new functionality only than your code should be
tested and that's the goal of Open Closed Design principle. By the way, the
Open-Closed principle is "O" from the SOLID acronym.

In Simple language Open closed design principles says that new functionality
should be added by introducing new classes, methods or fields instead of
modifying already tried and tested code. One of the way to achieve this is
Inheritance where class is extended to introduce new functionality on top of
inherited basic features.

Benefit or Open Closed Design Principle:


1) Application will be more robust because we are not changing already tested
class.
2) Flexible because we can easily accommodate new requirements.
3) Easy to test and less error prone.
4. Single Responsibility Principle (SRP)
Single Responsibility Principle is another SOLID design principle, and
represent "S" on the SOLID acronym. As per SRP, there should not be more
than one reason for a class to change, or a class should always handle single
functionality.

If you put more than one functionality in one Class in Java it


introduces coupling between two functionality and even if you change one
functionality there is a chance you broke coupled functionality, which requires
another round of testing to avoid any surprise on the production environment

7. Liskov Substitution Principle (LSP)


According to the Liskov Substitution Principle, Subtypes must be
substitutable for supertype i.e. methods or functions which uses superclass
type must be able to work with the object of subclass without any issue".

LSP is closely related to the Single responsibility principle and Interface


Segregation Principle. If a class has more functionality than subclass might
not support some of the functionality and does violate LSP.
In order to follow LSP SOLID design principle, derived class or subclass
must enhance functionality, but not reduce them. LSP represents "L" on the
SOLID acronym. If you are interested in a more real-world example, then
the SOLID Principles of Object-Oriented Design course on Pluarlsight is a
good course to start with.

Btw, you would need a Pluralsight membership to get access this course,
which cost around $29 per month or $299 annually (14% discount).

If you don't have Pluralsight membership, I encourage you to get one because
it allows you to access their 5000+ online courses on all the latest topics like
front-end and back-end development, machine learning, etc. It also includes
interactive quizzes, exercises, and latest certification material.

It's more like Netflix for Software Developers and Since learning is an
important part of our job, Plurlasight membership is a great way to stay ahead
of your competition.

They also provide a 10-day free trial without any commitment, which is a great
way to not just access this course for free but also to check the quality of
courses before joining Pluralsight

8. Interface Segregation Principle (ISP)


Interface Segregation Principle stats that, a client should not
implement an interfaceif it doesn't use that. This happens mostly when one
interface contains more than one functionality, and the client only needs one
functionality and no other.
Interface design is a tricky job because once you release your interface you
can not change it without breaking all implementation.

Another benefit of this design principle in Java is, the interface has the
disadvantage of implementing all method before any class can use it so having
single functionality means less method to implement. If you don't the get the
benefit of the interface in coding then I suggest you read my blog post, the real
usage of an interface in Java to learn more.

You might also like