Kurs PHP 8.4 w praktyce - czym jest SOLID
SOLID jest skrótem oznaczającym 5 zasad projektowania w programowaniu obiektowym wymyślonych przez Roberta C. Martina. Kolejne litery odnoszą się do następujących zasad
- 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)
Definicje klas i interfejsów
Przyjmijmy następujące definicje klas:
<?php
interface ShapeContract
{
public float $area { get; }
}
interface CalculatorContract
{
public function calculate(): float;
}
interface TextContract
{
public function text(): string;
}
interface JsonContract
{
public function json(): string;
}
class Square implements ShapeContract
{
public function __construct(private readonly int $length){}
public float $area {
get => $this->length * $this->length;
}
}
class Circle implements ShapeContract
{
public function __construct(private readonly int $radius){}
public float $area {
get => $this->radius * $this->radius * pi();
}
}
readonly class AreaCalculator implements CalculatorContract
{
/**
* @param array<int, ShapeContract> $shapes
*/
public function __construct(protected array $shapes)
{
}
public function calculate(): float
{
$data = array_map(fn(ShapeContract $shape) =>
$shape->area, $this->shapes);
return array_sum($data);
}
}
readonly class BigAreaCalculator extends AreaCalculator
{
public function calculate(): float
{
$initial = parent::calculate();
//some additional logic and filters like not less than
return $initial;
}
}
readonly class SumCalculatorOutputter implements TextContract, JsonContract
{
public function __construct(private CalculatorContract $calculator){}
public function text(): string
{
return "The sum of the areas of the shapes is: " . $this->calculator->calculate();
}
public function json(): string
{
return json_encode(['sum' => $this->calculator->calculate()]);
}
}
Zasada jednej odpowiedzialności - SRP
Klasa powinna mieć tylko jedną odpowiedzialność. Jedyne co potrafią klasy Square i Square to policzenie swojego pola. Zadaniem klasy AreaCalculator jest wyliczenie pola dla tablicy figur i bez znaczenia jest tutaj, w jaki sposób każda z figur liczy swoje pole. Natomiast jedynym zadaniem klasy SumCalculatorOutputter jest wyświetlenie wyniku jako tekst lub json.
Zasada otwarte-zamknięte - OCP
Klasy powinny być otwarte na rozszerzenia i zamknięte na modyfikacje. Ponieważ klasy Square i Circle uzywają interfejsu do obliczenia swojego pola, dodanie nowej klasy (np trójkąta) nie wymaga od nas modyfikacji klasy kalkulatora. Dodatkowo użycie array_map gwarantuje, że wszystkie obiekty będą miały zaimplementowany getter dla pola $area.
Zasada podstawienia Liskova - LSP
Funkcje które używają wskaźników lub referencji do klas bazowych, muszą być w stanie używać również obiektów klas dziedziczących po klasach bazowych, bez dokładnej znajomości tych obiektów. Klasa BigAreaCalculator spełnia tą zasadę ponieważ nie ma większego znaczenia co dokładnie robi klasa bazowa
Zasada segregacji interfejsów - ISP
Wiele dedykowanych interfejsów jest lepsze niż jeden ogólny. Klasa SumCalculatorOutputter spełnia tą zasadę, ponieważ za każdy z rodzajów zwracanego wyjścia odpowiada dedykowany interfejs.
Zasada odwrócenia zależności - DIP
Wysokopoziomowe moduły nie powinny zależeć od modułów niskopoziomowych - zależności między nimi powinny wynikać z abstrakcji. Każdy z modułów spełnia tą zasadę. Kalkulatory w konstruktorze przyjmują interfejs dla kształtu, natomiast klasa do wyświetlania wyników przyjmuje w konstruktorze interfejs kalkulatora. Należy dodatkowo zwrócić uwagę, że wszystkie klasy otrzymują swoje zależności już w konstruktorze. Przykładem zastosowania może być kontener zależności