The SOLID principles are a set of five design principles intended to make software designs more understandable, flexible, and maintainable. Introduced by Robert C. Martin (also known as Uncle Bob), these principles are essential for creating robust and scalable software systems.
- Single Responsibility Principle (SRP)
- Open-Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
- Conclusion
A class should have only one reason to change.
The Single Responsibility Principle states that every class or module in a program should have responsibility over a single part of the program's functionality. This means that a class should not take on multiple responsibilities, as this can lead to a higher risk of bugs and decreased maintainability.
Consider a User
class that handles user data and also manages user notifications. To adhere to SRP, you should separate these responsibilities into different classes, such as User
and NotificationService
.
class User {
private $name;
private $email;
public function __construct($name, $email) {
$this->name = $name;
$this->email = $email;
}
// User-related methods
}
class NotificationService {
public function sendNotification(User $user, $message) {
// Send notification logic
}
}
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
The Open-Closed Principle suggests that you should design your modules, classes, and functions in a way that allows you to extend their behavior without modifying their source code. This can be achieved through inheritance, interfaces, and abstract classes.
Suppose you have a Shape
class with a draw
method. To add a new shape without modifying the existing code, you can create a new class that implements the Shape
interface.
interface Shape {
public function draw();
}
class Circle implements Shape {
public function draw() {
// Draw circle logic
}
}
class Square implements Shape {
public function draw() {
// Draw square logic
}
}
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. This means that subclasses should maintain the behavior expected by the superclass.
If you have a Bird
class with a fly
method, a Penguin
subclass should not inherit from Bird
because penguins cannot fly. Instead, you might have a FlyingBird
class and a Bird
class without the fly
method.
class Bird {
public function speak() {
// Speak logic
}
}
class FlyingBird extends Bird {
public function fly() {
// Fly logic
}
}
class Penguin extends Bird {
public function swim() {
// Swim logic
}
}
No client should be forced to depend on methods it does not use.
The Interface Segregation Principle suggests that you should create specific interfaces for each client, rather than one general-purpose interface. This prevents clients from having to implement methods they do not need.
Instead of having a single Worker
interface with work
and eat
methods, you can split it into Workable
and Eatable
interfaces.
interface Workable {
public function work();
}
interface Eatable {
public function eat();
}
class Human implements Workable, Eatable {
public function work() {
// Work logic
}
public function eat() {
// Eat logic
}
}
class Robot implements Workable {
public function work() {
// Work logic
}
}
High-level modules should not depend on low-level modules. Both should depend on abstractions.
The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions. This can be achieved through the use of interfaces and dependency injection.
Instead of having a Car
class depend directly on a Engine
class, you can have the Car
class depend on an Engine
interface.
interface Engine {
public function start();
}
class ElectricEngine implements Engine {
public function start() {
// Start electric engine logic
}
}
class Car {
private $engine;
public function __construct(Engine $engine) {
$this->engine = $engine;
}
public function start() {
$this->engine->start();
}
}
The SOLID principles provide a framework for creating software that is modular, maintainable, and scalable. By adhering to these principles, developers can design systems that are easier to understand, test, and extend.