SOLID principles in simple words

15 mins
983

SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible, and maintainable. The principles are a subset of many principles promoted by American software engineer and instructor Robert C. Martin.

Wikipedia

Generally, I don’t want to show a deep analysis of each principle. It’s just a review in simple words which in my opinion must have to know for OOP languages as for example PHP.

The target of the SOLID principles

The SOLID principles are a set of rules for OOP that are recommended to be applied while working on software for better maintainability and scalable of your product.

The rules aren’t simple and vague, and all developers understand them differently. With a new experience, developers have a new understanding of how these rules can be applied. The most important thing is to understand, learn, and start using them, and rethinking these rules and improving your skills will come later in the course of work.

Surprisingly, the principles were formulated several decades ago and are still relevant today. This can only speak of their effectiveness.

The Single Responsibility Principle

Each software module should have one and only one reason to change

Wikipedia

In the classical explanation by Robert Martin, in short, the main reason for changes its people. You need to think about a person who needed this feature or who can ask about changes or influences it.

But many sources talk about function/class size, but this is not the case. You can have a function/class the size of Saturn and it will follow this principle. Let’s see it for example.

Example

We’ve got an Order class:

<?php

class Order {

	public function calculate_total_sum() {/*...*/}

	public function get_items() {/*...*/}

	public function get_item_count() {/*...*/}

	public function add_item( $item ) {/*...*/}

	public function delete_item( $item ) {/*...*/}

	public function print_order() {/*...*/}

	public function show_order() {/*...*/}

	public function load() {/*...*/}

	public function save() {/*...*/}

	public function update() {/*...*/}

	public function delete() {/*...*/}

}

We can separate the logic of the Order to an entity, part for work with a database, and part for different views.

<?php

class Order {

	public function calculate_total_sum() {/*...*/}

	public function get_items() {/*...*/}

	public function get_item_count() {/*...*/}

	public function add_item( $item ) {/*...*/}

	public function delete_item( $item ) {/*...*/}

}

class OrderRepository {

	public function load( $orderID ) {/*...*/}

	public function save( $order ) {/*...*/}

	public function update( $order ) {/*...*/}

	public function delete( $order ) {/*...*/}

}

class OrderViewer {

	public function print_order( $order ) {/*...*/}

	public function show_order( $order ) {/*...*/}

}

It means if something happens with a database this is a reason for changing the OrderRepository class, with a view – OrderViewer, with the entity – Order.

The Open-Closed Principle

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

Wikipedia

In a perfect world, it would be necessary to add new code to add new functionality but not change the old.

Bug fixing, refactoring, and performance improvements don’t violate this principle. The principle says exactly about changing the business logic of the software.

Example

Method load of the class OrderRepository described the logic for creating the order:

<?php

class OrderRepository {

	public function load( $orderID ) {

		$pdo       = new PDO(
			$this->config->getDsn(),
			$this->config->getDBUser(),
			$this->config->getDBPassword()
		);
		$statement = $pdo->prepare( "SELECT * FROM `orders` WHERE id=:id" );
		$statement->execute( array( ":id" => $orderID ) );

		return $query->fetchObject( "Order" );
	}

	public function save( $order ) {/*...*/}

	public function update( $order ) {/*...*/}

	public function delete( $order ) {/*...*/}

}

Then the business wants to create the orders by the 3rd party API. What do we need to do?

  1. Create the interface IOrderSource
  2. Create 2 different classes MySQLOrderSource and ApiOrderSource which implement the current interface
  3. Pass to the constructor of OrderRepository class the object which implements the IOrderSource inteface.
<?php

interface IOrderSource {

	public function load( $orderID );

	public function save( $order );

	public function update( $order );

	public function delete( $order );

}

class MySQLOrderSource implements IOrderSource {

	public function load( $orderID ) {/*...*/}

	public function save( $order ) {/*...*/}

	public function update( $order ) {/*...*/}

	public function delete( $order ) {/*...*/}

}

class ApiOrderSource implements IOrderSource {

	public function load( $orderID ) {/*...*/}

	public function save( $order ) {/*...*/}

	public function update( $order ) {/*...*/}

	public function delete( $order ) {/*...*/}

}

class OrderRepository {

	private $source;

	public function __constructor( IOrderSource $source ) {

		$this->source = $source;
	}

	public function load( $orderID ) {

		return $this->source->load( $orderID );
	}

	public function save( $order ) {/*...*/}

	public function update( $order ) {/*...*/}

}

The Liskov Substitution Principle

If S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e. an object of type T may be substituted with any object of a subtype S) without altering any of the desirable properties of the program (correctness, a task performed, etc.)

Wikipedia

Sounds good are you agree? In simple terms, the extended class must have the same output format such as parent methods.

Example

We’ve got a LessonRepository class that returns an array of all lessons from a file in the get_all method. There was a need to get lessons from the database. Create the DatabaseLessonRepository class, inherit from LessonRepository, and rewrite the get_all method.

<?php

class LessonRepository {
	
	public function get_all() {

		return $files; //return array of lesson through file system.
	}

}

class DatabaseLessonRepository extends LessonRepository {

	public function get_all() {

		return Lesson::all(); //return a Collection type instead of array
	}

}

In the get_all method of the DatabaseLessonRepository class, instead of a collection, we must return an array. For example, we can use the type hinting:

<?php

interface LessonRepositoryInterface {

	public function get_all(): array;

}

class FilesystemLessonRepository implements LessonRepositoryInterface {

	public function get_all(): array {

		return $files;
	}

}


class DatabaseLessonRepository implements LessonRepositoryInterface {

	public function get_all(): array {

		return Lesson::all()->toArray();
	}

}

The Interface Segregation Principle

Split interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them.

Wikipedia

Example

Really hard to find an example that can easily describe this principle without diving into context. I want to show the main trigger of this principle that in my opinion will be the best explanation.

<?php

interface IInterface {

	public function method1();

	public function method2();

}

class SomeClass1 implements IInterface {

	public function method1() { /*...*/ }

	public function method2() { /*...*/ }

}

When the business logic grew and we’ve been adding yet one class. But unfortunately, to create the implementation of method2 we can’t for any reason.

<?php

class SomeClass2 implements IInterface {

	public function method1() { /*...*/ }

	public function method2() {
		throw new Exception( 'I can\'t implement this method' );
	}

}

The right way decompose the interface into a few smaller interfaces

<?php

interface IInterface1 {

	public function method1();

}

interface IInterface2 {

	public function method2();

}

class SomeClass1 implements IInterface1, IInterface2 {

	public function method1() { /*...*/ }

	public function method2() { /*...*/ }

}

class SomeClass2 implements IInterface1 {

	public function method1() { /*...*/ }

}

Generally, to decide this problem need a lot of code movements and refactor the code. That’s means you need to get down to interface description at the project design stage and make it serious attention to scalable and maintenance.

The Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces). Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

Wikipedia

The best way to start to use this principle is the dependency injection pattern. If you don’t understand this pattern then just immediately diving into it. This can help you be promoted to the next higher development level.

Example

We’ve got an EBookReader class with the read method. When it becomes necessary to read a class other than just a PDF file, an observer is needed.

<?php

class EBookReader {

	public function read() {
		$pdf_book = new PDFBook();
		return $pdf_book->read();
	}

}

class PDFBook {

	public function read() { /*...*/}

}

Let’s have resolved the first part of this principle. So who is who? The EBookReader class is a high-level module that depends on PDFBook a low-level module. What we can do? Use the dependency injection pattern:

<?php

class EBookReader {

	private $book;

	public function __construct( PDFBook $book ) {
		$this->book = $book;
	}

	public function read() {
		return $this->book->read();
	}

}

We haven’t got abstraction but only the detail is PDFBook object. Just need to create an abstraction for a more flexible solution.

<?php

interface EBook {

	public function read();

}

class EBookReader {

	private $book;

	public function __construct( EBook $book ) {
		$this->book = $book;
	}

	public function read() {
		return $this->book->read();
	}

}

class PDFBook implements EBook {

	public function read() {/*...*/}

}

class MobiBook implements EBook {

	public function read() {/*...*/}

}

Conclusion

The SOLID principles are very important to use. You need to get to know them as early as possible and use them in your work. As you gain experience, these principles will become clearer to you.

Leave a Reply

Your email address will not be published. Required fields are marked *