1 of 33

SOLID Principles

by Ronni Kahalani, Copenhagen School of Design & Technology.

The 5 best practice software quality habits.

2 of 33

Who am I?

Thank you for stopping by.

I’m Ronni. I hope you’re well and wish you a safe and worthy journey.

This presentation is part of the Software Engineering Series, from my lectures at Copenhagen School of Design & Technology.

You can view the Introducing Myself, if you want to know a little more about who I am.

All my presentations and materials are free and available at my blog post: Software Engineering.

Don’t let me uphold you,

continue your journey, go to next slide.

2

3 of 33

Agenda

  • 5 SOLID Principles
  • Single Responsibility Principle
  • Open-Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

3

4 of 33

5 SOLID Principles

Bad code design foster inflexible and brittle solutions, which de-motivate developers. Small changes in software can result in major bugs and consequences.

SOLID is the foundation for great testable code.

A Test Driven Development (TDD) mindset, based on 5 principles:

4

Single Responsibility (SR)�Open-Closed (OC)�Liskov Substitution (LS)�Interface Segregation (IS)�Dependency Inversion (DI)

5 of 33

Single Responsibility Principle

A class should serve only one purpose.

6 of 33

6

7 of 33

Single Responsibility Principle (Definition)

A class serving multiple purposes or responsibilities, should be split into multiple classes.

Goals

  • Assure all methods and properties should share the same main goal.
  • Separate behaviors, so that changes won’t affect other unrelated behaviors.
  • Make better testable units.

7

8 of 33

Single Responsibility Principle (Violation)

Violations

  • This class currently handles specifics on generate reports, getting- and formatting the data.
  • It should not know where data comes from, instead it should only depend on an order repository instance.
  • Formatting is not the responsibility of this class, we need to support different formats (XML,JSON, HTML, etc.).

8

public class OrderReport {

public String getOrdersInfo(Date startDate, Date endDate) { …}

protected List<Order> queryDBForOrders(Date startDate, Date endDate) { … }

protected String format( Orders orders) { … }

}

9 of 33

Single Responsibility Principle (Solution) 1/3

Creating an Order repository

If you’re not using JPA - Java Persistence API, like in Spring Boot, where you only define the interface, and JPA/Hibernate produces the implementation at runtime and JPA’s repository query method naming syntax.

�This is how you could do the filtering yourself via Java streams:

9

public class OrderRepository extends JpaRepository<Order, Long> {

public List<Order> findByOrderDateBetween(Date startDate, Date endDate);

}

public List<Order> findByOrderDateBetween(Date startDate, Date endDate) {

return orders.stream()

.filter(order -> order.getOrderDate().isAfter(startDate) || order.getOrderDate().isEqual(startDate))

.filter(order -> order.getOrderDate().isBefore(endDate) || order.getOrderDate().isEqual(endDate))

.collect(Collectors.toList());

}

10 of 33

Single Responsibility Principle (Solution) 2/3

public class OrderHTMLFormatter implements OutputFormatter<Order> {

private static final String HTML_ORDER =

"<div class=\"order-item\" id=\"order-%d\"><div>Order Date</div><div>%s</div>

<div>Ordered By</div><div>%s</div>…</div>";

@Override

public String output(List<Order> orders) {

SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");

return orders.stream()

.map(order -> String.format(HTML_ORDER, order.getId(), dateFormat.format( order.getOrderDate() ), order.getOrderedBy()))

.collect(Collectors.joining("\n")); // Join results with a newline

}

}

public interface OutputFormatter<T> {

String output(List<T> elements);

}

10

11 of 33

Single Responsibility Principle (Solution) 3/3

So now, we’ve created a repository and an output formatter. Here is a refactored order report class, using the new .

public class OrderReport {

protected OrderRepository repository;� protected OutputFormatter<Order> formatter;

public OrderReport(OrderRepository repository, OutputFormatter<Order> formatter) {

this.repository = repository;�this.formatter = formatter;

}

public String getOrdersInfo(Date startDate, Date endDate) {

List<Order> orders = repository.findByOrderDateBetween(startDate, endDate);�return formatter.output(orders);

}

}

11

12 of 33

Open-Closed Principle

Classes should be open for extension but closed for modification.

13 of 33

13

14 of 33

Open-Closed Principle (Definition)

Classes should be extendable, via sub classes, not by changing the contents of the superclass you want to extend.

Goal

  • Extend a class’s behavior without changing the existing behavior of that class, to avoid causing bugs wherever the class is being used.

14

15 of 33

Open-Closed Principle (Violation)

15

public class CostManager {

public double costPerUnit = 1.5;

public double calculate(Shape shape) {

double area = 0;

if (shape instanceof Rectangle) {

area = shape.width * spape.height;

} else if(shape instanceof Circle){

area = shape.radius * shape.radius * Math.PI;

} else { throw new ShapeNotFoundException(…);}       

return costPerUnit * area;

}

}

Violations

  • When we want to calculate the area of a square, we have to modify the calculate method in the CostManager class. It breaks the open-closed principle because the area behavior difference is not implemented via the sub classes and polymorphic behavior.

public class Shape {…}

public class Rectangle extends Shape {

public int width, height;

public Rectangle(int width, int height) { }

}

public class Circle extends Shape {

public double radius;

public Circle(double radius) { … }

}

16 of 33

Open-Closed Principle (Solution)

16

public class CostManager {

public double costPerUnit = 1.5;

public double calculate(IShape shape) {

return costPerUnit * shape.getArea();

}

}

public interface IShape {

public double getArea();

}

public class Rectangle implements IShape {

public int width, height;

public Rectangle(int width, int height) {...}

public double getArea() { return height * width;}

}

public class Circle implements IShape {

public double radius;public Circle(double radius) {...}

    

public double getArea() { return radius * radius * Math.PI;}

}

17 of 33

Liskov Substitution Principle

A subclass should be able to imitate its superclass.

18 of 33

18

19 of 33

Liskov Substitution Principle (Definition)

Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true for objects y of type S where S is a subtype of T.

Goal

  • Enforce consistency so that the superclass or its subclasses can be used in the same way without any errors.

19

20 of 33

Liskov Substitution Principle (Violation)

20

public interface IData {

public Object getAll();

}

Violations

  • The getAll() method in the derived FileData- and DbData subclasses return different unrelated types.

public class FileData implements IData {

public Object getAll() {

return new List();

}

}

public class DbData implements IData {

public Object getAll() {

return new MyOwnType();

}

}

21 of 33

Liskov Substitution Principle (Solution)

21

public interface IData {

public List getAll();

}

public class FileData implements IData {

public List getAll() {

return new List();

}

}

public class DbData implements IData {

public List getAll() {

return new List<MyOwnType()>;

}

}

22 of 33

Interface Segregation Principle

A client should not be forced to implement an interface that it doesn’t use.

23 of 33

23

24 of 33

Interface Segregation Principle (Definition)

To force a class to implement useless actions, is wasteful and may produce unexpected bugs if the class does not have the ability to perform those actions.

Goal

  • Minimize side consequences and repetition by splitting interfaces into many smaller ones.
  • Classes should NOT execute unrequired actions.

24

25 of 33

Interface Segregation Principle (Violation)

25

Violations

  • The RobotWorker doesn’t need sleep, but the subclass have to implement the sleep method because we know that all methods are abstract in the interface.

public interface IWorker {

public void work();

public void sleep();

}

public class HumanWorker implements IWorker {

public void work() { Logger.log(‘Working…’); }

public void sleep() { Logger.log(‘Sleeping…’); }

}

public class RobotWorker implements IWorker {

public void work() { Logger.log(‘Working…’); }

public void sleep() { 

// Not needed for robots, they don’t sleep.

Logger.log(‘I am still working! I do not sleep!...’); 

}

}

26 of 33

Interface Segregation Principle (Solution)

26

public interface IWorkable {

public void work();

}

public class HumanWorker implements IWorkable, ISleepable {

public  void work() { Logger.log(‘Working…’); }

public void sleep() { Logger.log(‘Sleeping…’); }

}

public interface ISleepable {

public void sleep();

}

public class RobotWorker implements IWorkable {

public  void work() { Logger.log(‘Working…’); }

}

Interface segregation, segregated a bigger and broader interface into single method interfaces. So now the consumer can get exactly the features it needs. No less no more.

27 of 33

Dependency Inversion Principle

High-level classes should not depend on low-level classes. Both should depend on abstractions.

28 of 33

28

29 of 33

Dependency Inversion Principle (Definition)

A class should not be fused with the tool it uses to execute an action. It should be fused to an interface that will allow the tool to connect to the class.

Goal

  • Dependencies can easily be replaced by others, just by changing the dependency.
  • Changes to the low-level class won’t affect the high-level class.

29

30 of 33

Dependency Inversion Principle (Violation)

30

Violations

  • The DataConsumer class, depends explicitly on the MySQLConnection class instead of an interface. That is not flexible and can make unit tests harder, complying to the implicit implementation (class) behavior.
  • We also want to integrate other data sources, than MySQL, like MongoDB, Postgress, DB2, MicrosoftSQL…
  • It should not be the DataConsumer class who decides what datasource to use. It should instead be defined in a persistence layer configuration and accessed via an interface, rather than hard-coded into the class.

public class DataConsumer {

private MySQLConnection connection;

public DataConsumer(MySQLConnection connection)  {

this.connection = connection;

}

}

public class MySQLConnection {

public Connection connect() {

System.out.println(‘Connecting…’);

}

}

31 of 33

Dependency Inversion Principle (Solution)

31

public class DataConsumer {

private IDataConnection connection;

public DataConsumer(IDataConnection connection) {

this.connection =  connection;

}

}

public interface IDataConnection {

public Connection connect(String url, String userId, String password);

}

public class PostgreSQLConnection implements IDataConnection {

public Connection connect(String url, String userId, String password) {

Logger.log(‘Connecting to messaging broker…’);

// Ex: url="jdbc:postgresql://host:port/db?ssl=true"

this.connection = DriverManager.getConnection(url, userId, password);

return this.connection;

}

}

public class MySQLConnection implements IDataConnection {

public Connection connect(String url, String userId, String password) {

Logger.log(‘Connecting to MySQL database…’);

// Ex: url="jdbc:mysql://host:port/db?ssl=true"

this.connection = DriverManager.getConnection(url, userId, password);

return this.connection;

}

}

Now the DataConsumer class depends on an abstraction (interface), enabling it to change connection type dynamically at run-time. Like switching from MySQL to PostgreSQL or any other JDBC-based implementation.

It is more testable and easier to unit-test, when mocking the connection interface (with no logic/behavior) vs. a class implementation (with logic/behavior) having behaviors that generate side effects, which the test has to adjust to.

32 of 33

Questions?

Anything? What’s on your mind? Come on ask me anything…

33 of 33

Feedback?

Thank you for your precious time.

I hope it was worth it and would love to get your feedback.

Please share your feedback here