SOLID principles in a nutshell

Azraar Azward
5 min readFeb 24, 2022

--

SOLID is an acronym that stands for five key design principles:

  1. Single Responsibility Principle
  2. Open-Closed Principle
  3. Liskov Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

Why SOLID?

When applied correctly, your software infrastructure will tolerate changes, be easier to understand, be flexible to scale and focus on reusable components.

Single Responsibility Principle

  • In this case, each class should be responsible for one thing. There shouldn’t be more than one reason to change a class. Yes, you can add whatever you want to the class, but you don’t have to. But splitting large classes into smaller ones and avoiding the god object is a good practice to adhere.
// Wrong Exampleclass Employee {    private $name;

public function getName()
{
// ..
}

public function printTimeSheetReport()
{
// ..
}
}
// Correct Exampleclass Employee { private $name;

public function getName()
{
// ..
}
}
class TimeSheetReport {

public function print(Employee $employee)
{
// ..
}
}

Open-Closed Principle

  • Software objects to be open for extension but closed for modification. This means that when you write class A, and then your teammates want to make changes to its function, they can easily do so by extending class A without making any changes.
// Wrong Exampleclass SavingAccount
{
public function CalculateInterest($accountType)
{
if($accountType == "Regular")
{
// ..
}
else if($accountType == "Salary")
{
// ..
}
}
}


// Correct Example
interface ISavingAccount
{
public function CalculateInterest();
}

class RegularSavingAccount implements ISavingAccount
{
public function CalculateInterest()
{
// ..
}
}


class SalarySavingAccount implements ISavingAccount
{
public function CalculateInterest()
{
// ..
}
}

Liskov Substitution Principle

  • This means that every subclass or derived class should be substitutable for their base or parent class. In other words application behaves correctly if an object of a child class replaces an object of its base class. In very simple terms, an object of class apple, should be able to replace an object of class fruit without breaking the application.
// Wrong Exampleclass Rectangle
{
protected $width;
protected $height;

public function setWidth($width) {
// ..
}

public function setHeight($height) {
// ..
}

public function calculateArea()
{
// ..
}
}

class Square extends Rectangle
{
public function setWidth($width)
{
// ..
}

public function setHeight($height)
{
// ..
}
}
// Correct Exampleclass Rectangle implements CalculableArea
{
protected $width;
protected $height;

public function __construct($width, $height)
{
// ..
}

public function calculateArea()
{
// ..
}
}

class Square implements CalculableArea
{
protected int $edge;

public function __construct($edge)
{
// ..
}

public function calculateArea()
{
// ..
}
}

interface CalculableArea
{
public function calculateArea();
}

Interface Segregation Principle

  • Clients should not be forced to implement interfaces they do not use.
    In other words, it is better to have many smaller interfaces, than fewer, fatter interfaces.
// Wrong Example

interface RestaurantInterface
{
public function acceptOnlineOrder();
public function payOnline();
public function walkInCustomerOrder();
public function payInPerson();

}

class OnlineClient implements RestaurantInterface
{
public function acceptOnlineOrder()
{
// ..
}

public function payOnline()
{
// ..
}
}


// Correct Example

interface OnlineClientInterface
{
public function acceptOnlineOrder();
public function payOnline();
}

interface WalkInCustomerInterface
{
public function walkInCustomerOrder();
public function payInPerson();
}

class OnlineClient implements OnlineClientInterface
{
public function acceptOnlineOrder()
{
// ..
}

public function payOnline()
{
// ..
}
}

class WalkInCustomer implements WalkInCustomerInterface
{
public function walkInCustomerOrder()
{
// ..
}

public function payInPerson()
{
// ..
}
}

Dependency Inversion Principle

  • high-level (fundamental logic) components should not depend on low-level (specific logic) components.
  • Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
// Wrong exampleclass DatabaseLogger
{
public function logError(string $message)
{
// ..
}
}

class MailerService
{
private DatabaseLogger $logger;

public function __construct(DatabaseLogger $logger)
{
$this->logger = $logger;
}

public function sendEmail(): void
{
try {
// ..
} catch (SomeException $exception) {
$this->logger->logError($exception->getMessage());
}
}
}
// Correct example

interface LoggerInterface
{
public function logError(string $message): void;
}

class DatabaseLogger implements LoggerInterface
{
public function logError(string $message): void
{
// ..
}
}

class MailerService
{
private LoggerInterface $logger;

public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}

public function sendEmail(): void
{
try {
// ..
} catch (SomeException $exception) {
$this->logger->logError($exception->getMessage());
}
}
}

There are many other rules and standards that we can follow,

  • PSR (PHP Standards Recommendations) — PHP Framework Interop Group (PHP-FIG) is a group of people associated with the largest PHP projects who jointly develop PSR. I think every PHP programmer should know coding styles standards PSR-1 and PSR-12 (formerly PSR-2). You can find all the current sets of standards here
  • KISS (Keep It Simple Stupid) — Don’t complicate the code. The code should be its documentation itself. Any new programmer on the team should be able to get into the project quickly.
  • DRY (Don’t Repeat Yourself) — Do not code using the Copy-Paste principle (there is no such rule). See that the same code repeats in several places? Extract code for a separate function.
  • YAGNI (You Aren’t Gonna Need It) — 17th-century German philosopher Johannes Clauberg formulated a principle called Occam’s Razor (I was also surprised Ockham was not its author ;) ) “entities should not be multiplied beyond necessity”. I think this sentence expresses the YAGNI principle well. We should not write code “for the future”. Such code is not needed at the moment.

Failing to follow these principles would result in Technical debts in your project. I found this interesting article that explains deeply about technical debts with real world example.

Bonus:

Laracon session by Katerina Trajchevsk on Becoming a better developer by using the SOLID design principles.

--

--

Azraar Azward

An eager Software Engineer always loving to reach the impossible.