Understanding SOLID Principles in Programming
May 20, 2024As someone who is learning about these concepts myself, I hope this guide will be helpful for both you and me in understanding these fundamental principles.
What Are SOLID Principles?
SOLID is an acronym that represents five principles of object-oriented programming and design. These principles are intended to make software designs more understandable, flexible, and maintainable. Here’s what SOLID stands for:
- S - Single Responsibility Principle (SRP)
- O - Open/Closed Principle (OCP)
- L - Liskov Substitution Principle (LSP)
- I - Interface Segregation Principle (ISP)
- D - Dependency Inversion Principle (DIP)
By adhering to these principles, developers can create software that is easier to manage and extend over time. Below, I’ll explain each principle and provide examples in PHP.
1. Single Responsibility Principle (SRP)
Definition
A class should have only one reason to change. This means that a class should have only one job or responsibility. By ensuring that a class is focused on a single task, you can make your code easier to understand and maintain.
Example in PHP
Consider a simple User
class:
<?php
class User {
private $email;
public function __construct($email) {
$this->email = $email;
}
public function getEmail() {
return $this->email;
}
public function sendEmail($message) {
// Logic to send an email
}
}
?>
In this example, the User
class has two responsibilities: storing user information and sending emails. To adhere to SRP, we should separate these responsibilities:
<?php
class User {
private $email;
public function __construct($email) {
$this->email = $email;
}
public function getEmail() {
return $this->email;
}
}
class EmailService {
public function sendEmail(User $user, $message) {
// Logic to send an email
}
}
?>
Now, the User
class is only responsible for user data, while the EmailService
handles email sending.
2. Open/Closed Principle (OCP)
Definition
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification. This means you should be able to add new functionality to a class without changing its existing code.
Example in PHP
Imagine you have a PaymentProcessor
class that processes payments for different methods:
<?php
class PaymentProcessor {
public function processPaypalPayment($amount) {
// Process PayPal payment
}
public function processEurobankPayment($amount) {
// Process Eurobank payment
}
}
?>
To add more payment methods, you’d have to modify the PaymentProcessor
class, violating the OCP. Instead, you can use interfaces to make it open for extension:
<?php
interface PaymentMethod {
public function processPayment($amount);
}
class PaypalPayment implements PaymentMethod {
public function processPayment($amount) {
// Process PayPal payment
}
}
class EurobankPayment implements PaymentMethod {
public function processPayment($amount) {
// Process Eurobank payment
}
}
class PaymentProcessor {
public function process(PaymentMethod $paymentMethod, $amount) {
$paymentMethod->processPayment($amount);
}
}
?>
Now, to add new payment methods, you simply create new classes that implement the PaymentMethod
interface without modifying existing code.
3. Liskov Substitution Principle (LSP)
Definition
Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. This means that subclasses should be substitutable for their base classes.
Example in PHP
Consider a base class Bird
and a subclass Penguin
:
<?php
class Bird {
public function fly() {
echo "Flying";
}
}
class Penguin extends Bird {
public function fly() {
throw new Exception("Penguins can't fly");
}
}
?>
In this example, substituting a Penguin
for a Bird
breaks the functionality because penguins can’t fly. To adhere to LSP, we can refactor the design:
<?php
abstract class Bird {
abstract public function move();
}
class FlyingBird extends Bird {
public function move() {
echo "Flying";
}
}
class Penguin extends Bird {
public function move() {
echo "Swimming";
}
}
?>
Now, both FlyingBird
and Penguin
conform to the Bird
interface, and the program will behave correctly when substituting one for the other.
4. Interface Segregation Principle (ISP)
Definition
Clients should not be forced to depend on interfaces they do not use. This means that you should create specific interfaces rather than a large, general-purpose interface.
Example in PHP
Consider a Worker
interface with multiple responsibilities:
<?php
interface Worker {
public function work();
public function eat();
}
class HumanWorker implements Worker {
public function work() {
// Working
}
public function eat() {
// Eating
}
}
class RobotWorker implements Worker {
public function work() {
// Working
}
public function eat() {
// Robots don't eat
throw new Exception("Robots don't eat");
}
}
?>
The RobotWorker
class doesn’t need the eat
method, violating the ISP. We can fix this by creating more specific interfaces:
<?php
interface Workable {
public function work();
}
interface Eatable {
public function eat();
}
class HumanWorker implements Workable, Eatable {
public function work() {
// Working
}
public function eat() {
// Eating
}
}
class RobotWorker implements Workable {
public function work() {
// Working
}
}
?>
Now, HumanWorker
implements both interfaces, while RobotWorker
only implements Workable
.
5. Dependency Inversion Principle (DIP)
Definition
High-level modules should not depend on low-level modules. Both should depend on abstractions. This principle aims to reduce the dependency between high-level and low-level layers of a system.
Example in PHP
Consider a Keyboard
and a Computer
class:
<?php
class Keyboard {
// Keyboard implementation
}
class Computer {
private $keyboard;
public function __construct() {
$this->keyboard = new Keyboard();
}
}
?>
Here, the Computer
class is tightly coupled with the Keyboard
class. To follow DIP, we should depend on abstractions:
<?php
interface KeyboardInterface {
public function type();
}
class Keyboard implements KeyboardInterface {
public function type() {
// Typing
}
}
class Computer {
private $keyboard;
public function __construct(KeyboardInterface $keyboard) {
$this->keyboard = $keyboard;
}
}
?>
Now, the Computer
class depends on the KeyboardInterface
, not the concrete Keyboard
class, allowing us to inject any implementation of the KeyboardInterface
.
This post was part of my learning journey about SOLID principles, and I hope it helps you understand them better too.