What are SOLID principles?
sobes.tech AI
Answer from AI
SOLID is a mnemonic acronym representing five core principles of object-oriented design aimed at creating flexible, maintainable, and scalable systems:
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
Single Responsibility Principle (SRP): A class should have only one reason to change, meaning it should perform only one specific task or have only one responsibility.
<?php
// Bad: Customer class manages customer data and email sending
class BadCustomer {
public function saveData(array $data) { /* ... */ }
public function sendEmail(string $message) { /* ... */ }
}
// Good: responsibilities are separated
class CustomerData {
public function saveData(array $data) { /* ... */ }
}
class EmailService {
public function sendEmail(string $message) { /* ... */ }
}
?>
Open/Closed Principle (OCP): Software entities (classes, modules, functions) should be open for extension but closed for modification. This is achieved through the use of abstractions (interfaces, abstract classes).
<?php
// Bad: adding a new shape type requires modifying the AreaCalculator class
class BadAreaCalculator {
public function calculateArea(array $shapes): float {
$totalArea = 0;
foreach ($shapes as $shape) {
if ($shape instanceof Rectangle) {
$totalArea += $shape->getWidth() * $shape->getHeight();
} elseif ($shape instanceof Circle) {
$totalArea += pi() * $shape->getRadius() ** 2;
}
// What if we add Triangle? We need to change this class
}
return $totalArea;
}
}
// Good: using an interface for extension without modification
interface Shape {
public function calculateArea(): float;
}
class Rectangle implements Shape {
private float $width;
private float $height;
public function __construct(float $width, float $height) {
$this->width = $width;
$this->height = $height;
}
public function calculateArea(): float {
return $this->width * $this->height;
}
}
class Circle implements Shape {
private float $radius;
public function __construct(float $radius) {
$this->radius = $radius;
}
public function calculateArea(): float {
return pi() * $this->radius ** 2;
}
}
class GoodAreaCalculator {
public function calculateTotalArea(array $shapes): float {
$totalArea = 0;
foreach ($shapes as $shape) {
// All shapes implementing Shape work uniformly
$totalArea += $shape->calculateArea();
}
return $totalArea;
}
}
?>
Liskov Substitution Principle (LSP): Objects in a program should be replaceable with instances of their subtypes without altering the correctness of the program. Derived classes should extend, not replace, the behavior of the base class.
<?php
// Bad: Square is not a full substitute for Rectangle, violates invariants
class Rectangle {
protected float $width;
protected float $height;
public function setWidth(float $width): void { $this->width = $width; }
public function setHeight(float $height): void { $this->height = $height; }
public function calculateArea(): float { return $this->width * $this->height; }
}
class Square extends Rectangle {
public function setWidth(float $width): void {
$this->width = $width;
$this->height = $width; // Violates expectations for Rectangle
}
public function setHeight(float $height): void {
$this->width = $height;
$this->height = $height; // Violates expectations for Rectangle
}
}
function enforceRectangleArea(Rectangle $r): void {
$r->setWidth(5);
$r->setHeight(10);
// Expect area 50, but for Square will be 100
echo "Area: " . $r->calculateArea() . "\n";
}
// Good: use separate classes or reconsider hierarchy
?>
Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use. It's better to have many small, specific interfaces than one large, general interface.
<?php
// Bad: One interface for all worker types, including those who do not eat
interface BadWorker {
public function work();
public function eat();
}
class HumanWorker implements BadWorker {
public function work() { /* ... */ }
public function eat() { /* ... */ }
}
class RobotWorker implements BadWorker {
public function work() { /* ... */ }
public function eat() { // Robots do not eat, but must implement the method
throw new Exception("Robots don't eat!");
}
}
// Good: Segregated interfaces
interface Workable {
public function work();
}
interface Eatable {
public function eat();
}
class HumanWorkerISP implements Workable, Eatable {
public function work() { /* ... */ }
public function eat() { /* ... */ }
}
class RobotWorkerISP implements Workable {
public function work() { /* ... */ }
// Does not implement Eatable
}
?>
Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.
<?php
// Bad: High-level class Report directly depends on low-level DatabaseConnection class
class DatabaseConnection {
public function connect() { /* ... */ }
public function getData() { /* ... */ return ['data']; }
}
class ReportGenerator {
private DatabaseConnection $dbConnection;
public function __construct() {
$this->dbConnection = new DatabaseConnection(); // Tight dependency
}
public function generateReport(): array {
$this->dbConnection->connect();
return $this->dbConnection->getData();
}
}
// Good: High-level class Report depends on an abstraction, which is also implemented by the low-level class DatabaseConnection
interface DataProvider {
public function getData(): array;
}
class DatabaseConnectionDIP implements DataProvider {
public function connect() { /* ... */ }
public function getData(): array { /* ... */ return ['data from db']; }
}
class ApiConnectionDIP implements DataProvider {
public function getData(): array { /* ... */ return ['data from api']; }
}
class ReportGeneratorDIP {
private DataProvider $dataProvider;
public function __construct(DataProvider $dataProvider) {
$this->dataProvider = $dataProvider; // Dependency is inverted via injection
}
public function generateReport(): array {
return $this->dataProvider->getData();
}
}
?>
Applying SOLID principles helps create code that is more understandable, testable, and easily extendable.