1. 程式人生 > >How to write SOLID code that doesn’t suck

How to write SOLID code that doesn’t suck

Single Responsibility Principle

This is the first and easiest one, it’s the Single Responsibility Principle that states

A class should have only one reason to change

Before start writing code we should be aware of what are the responsibilities of our objects. The single responsibility principle helps us to understand how important is encapsulation and abstraction for our. There are several ways to accomplish such objective. One those I want to point out is

GRASP (General Responsibility Assignment Software Principles) which is a set of Software Principles guidelines that give us a simple understanding on what does it mean to assign responsibilities to objects in our system. Those guidelines are Controller, Creator, High Cohesion, Indirection, Information Expert, Low coupling, Polymorphism, Protected Variations
and Pure Fabrication. I’m going to briefly explain just some of those

Creator - Object creation and instantiation is not always just simple as you think, that’s because it’s often in the creation process that we define the dependencies of a particular class and how it should be initialized. That’s the reason why there are specific kind of classes designed to handle such responsibility (like

Factory, Builder [..] Design Patterns)

Low Coupling - As we said in Creator principle, object instantiation comprise the definition of class dependencies. With Low Coupling we want to achieve a number of dependencies our class is depending on as low as possible, as well we want our classes to be interchangeable as much as we can (achieved thanks to interfaces) and class changes should not affect other dependent classes

As a small example, I personally consider the Active Record (Anti) Pattern a SRP violation

<?php
$user = User::find(2);
$user->name = 'Bob';
$user->save();

A viable solution would be using a different objects responsible for the persistence logic, not coupling the User Entity with other stuff like knowing how to save itself

<?php
$user = $userRepository->find(2);
$user->name = 'Bob';
$user = $userRepository->update($user);

Open Closed Principle

The Open/Closed principle is somehow related also with the single responsibility, since if we have an object which has only one responsibility, we reduce the possibilities to change it to one. The Open Closed principle states indeed

Objects or entities should be open for extension, but closed for modification.

But how do we can achieve the full extensibility of an object without applying modification to it? Well, there are many ways to do that, and it always depends on the context. The main aim is whenever requirements change, we shouldn’t change our objects but extend them. Is here that we can use two categories of Design Patterns called Behavioural Design Patterns and Structural Design Patterns. I’m going to briefly illustrate the Decorator Pattern from the Structural ones.

Decorator Pattern - The aim of Decorator is to extend (indeed) the functionality of an object without modifying its original implementation

We also have one of the previously quoted GRASP principles, that relates well with the Open/Closed principle. It’s the Protected Variations pattern

Protected Variations - It protects elements from the variations on other elements (objects, systems, subsystems) by wrapping the focus of instability with an interface and using polymorphism to create various implementations of this interface

Liskov Substitution Principle

The Liskov Substitution Principle is interesting as it’s not obvious as we could think, it describe the relation between objects and their subtypes, which states

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 T

At first sight doesn’t sound easy to understand but if you think and analyze it in chunks it become simple. We are talking about inheritance here where S is a subtype of T, so we have a class ElectricCar (S) which is a subtype of Car (T), then instances of Car can be replaced by instances of ElectricCar without breaking the correctness of the program. I hope it’s more clear now

<?php
class Square extends Rectangle {}
// Calculate Rectangle area replacing Rectangle (T) with subtype (S)
$square->setWidth(2);
$square->setHeight(6);
$square->getArea(); // 36 instead of 12

The one over here is a typical LSP violation, where a class Sqaure (S) subtype of Rectangle (T) cannot replace instances of Rectangle (T)

Interface Segregation Principle

The Interface Segregation Principle states that a class shouldn’t have dependencies on methods that doesn’t use directly. Applying the ISP you’ll end up splitting your big interfaces to smaller and more specific ones

Clients should not be forced to depend upon interfaces that they don’t use

This principle aims to create really specific interfaces which will have specific and clear responsibility, not gluing up many unrelated methods in one single interface definition. This is also related with the High Cohesion principle in the GRASP principles mentioned earlier

High Cohesion - The pattern tries to keep object mission properly focused on their original and main important responsibility, what they have been designed for. Keeping the object’s responsibility highly focused. Objects which are not properly focused end up to be hard to maintain, comprend and reuse

Dependency Inversion Principle

This is my favourite one, the Dependency Inversion principle which states

High-level modules should not depend on low-level modules. Both should depend on abstraction.
Abstractions should not depend upon details. Details should depend upon abstractions.

This doesn’t sound simple to understand at first sight, but if you read it multiple times, you end up understanding what’s it does mean with these two sentences. Basically it tries to say that modules, (classes) should not be coupled to each other but should interact and depend upon Abstractions (Interfaces). Obviously this is not always possible otherwise we would have no interaction at all among modules in our system, but it does apply when it’s possible. Coupling classes to abstraction instead to concrete implementations brings us the possibility to interchange the components without breaking the functionalities of our system

<?php
class UserRepository {
public function __construct(MySQLStorage $mysqlStorage) { ... }
}

This is an example on how we could decouple UserRepository class from our persistence strategy (in this case MySQLStorage)

<?php
class UserRepository {
public function __construct(StorageInterface $storage) { ... }
}

As you can see we made UserRepository depended not on low-level module MySQLStorage but instead on an abstraction of what we need there: a general Storage strategy for our UserRepository instead of a specific one

Disclaimer

As you know, all software principles aren’t an exact science. The application of those should depend on your specific use case/scenario.

Thanks for reading!

Hope you’ve liked the article, stay tuned for next interesting and opinionated writings!