SOLID-Grundlagen der Klassen SOLID



1 Prinzip (OCP)

3. L: Liskov-Substitutionsprinzip (LSP)

4 (ISP)

D: Abhängigkeitsinversionsprinzip (DIP)

SOLID

wird von Michael Feathers empfohlen. Erinnerungsfähiges Akronym für Robert Die fünf wichtigsten objektorientierten Coding-Design-Prinzipien, die Martin benannt hat Prinzip (OCP)

SOLID L: Richter-Substitutionsprinzip (LSP)

  • I: Interface Isolation Principle (ISP)

  • D: Dependency Inversion Principle (DIP)

  • 1. Prinzip der Einzelverantwortung
  • Wie es in Clean Code heißt: „Das Ändern einer Klasse sollte nur aus einem Grund erfolgen.“ Es ist immer einfach, einen Kurs mit einer Menge Methoden vollzustopfen, so wie wir im Flugzeug nur einen Koffer mitnehmen können (alles in den Koffer stopfen). Das Problem dabei ist, dass eine solche Klasse konzeptionell nicht sehr kohärent ist und viele Gründe für eine Änderung bietet. Es ist wichtig, die Häufigkeit, mit der Sie eine Klasse ändern müssen, zu minimieren. Dies liegt daran, dass es schwierig ist, zu wissen, welche abhängigen Module in der Codebasis betroffen sind, wenn eine Klasse viele Methoden enthält und eine davon geändert wird.

  • Schlecht:

class UserSettings
{
    private $user;
 
    public function __construct(User $user)
    {
        $this->user = $user;
    }
 
    public function changeSettings(array $settings): void
    {
        if ($this->verifyCredentials()) {
            // ...
        }
    }
 
    private function verifyCredentials(): bool
    {
        // ...
    }
}
Gut:

class UserAuth
{
    private $user;
 
    public function __construct(User $user)
    {
        $this->user = $user;
    }
    
    public function verifyCredentials(): bool
    {
        // ...
    }
}
 
class UserSettings
{
    private $user;
    private $auth;
 
    public function __construct(User $user)
    {
        $this->user = $user;
        $this->auth = new UserAuth($user);
    }
 
    public function changeSettings(array $settings): void
    {
        if ($this->auth->verifyCredentials()) {
            // ...
        }
    }
}
Single Responsibility Principle (SRP)

2. Open-Closed-Prinzip

Wie Bertrand Meyer feststellte: „Softwareartefakte ( usw.) sollten für Erweiterungen offen und für Änderungen geschlossen sein.“ " Allerdings ist dieser Satz Was bedeuten Wörter? Dieses Prinzip bedeutet im Allgemeinen, dass Sie Es sollte erlaubt sein, neue Funktionen hinzuzufügen, ohne vorhandenen Code zu ändern

Schlecht:
abstract class Adapter
{
    protected $name;
 
    public function getName(): string
    {
        return $this->name;
    }
}
 
class AjaxAdapter extends Adapter
{
    public function __construct()
    {
        parent::__construct();
 
        $this->name = 'ajaxAdapter';
    }
}
 
class NodeAdapter extends Adapter
{
    public function __construct()
    {
        parent::__construct();
 
        $this->name = 'nodeAdapter';
    }
}
 
class HttpRequester
{
    private $adapter;
 
    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }
 
    public function fetch(string $url): Promise
    {
        $adapterName = $this->adapter->getName();
 
        if ($adapterName === 'ajaxAdapter') {
            return $this->makeAjaxCall($url);
        } elseif ($adapterName === 'httpNodeAdapter') {
            return $this->makeHttpCall($url);
        }
    }
 
    private function makeAjaxCall(string $url): Promise
    {
        // request and return promise
    }
 
    private function makeHttpCall(string $url): Promise
    {
        // request and return promise
    }
}

Gut:

Open/Closed Principle (OCP)

interface Adapter
{
    public function request(string $url): Promise;
}
 
class AjaxAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}
 
class NodeAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}
 
class HttpRequester
{
    private $adapter;
 
    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }
 
    public function fetch(string $url): Promise
    {
        return $this->adapter->request($url);
    }
}

classes, modules, functions

3. Richter-Substitutionsprinzip

Dies ist ein einfaches Prinzip, aber es verwendet einen Begriff, der schwer zu verstehen ist. Seine formale Definition lautet: „Wenn S ein Untertyp von T ist, dann kann jedes Objekt vom Typ T durch ein Objekt vom Typ S ersetzt werden, ohne die ursprünglich festgelegten Eigenschaften des Programms (Überprüfung, Ausführung von Aufgaben usw.) zu ändern (z. B. Die Verwendung eines Objekts von S kann ein Objekt von T ersetzen)“ Diese Definition ist schwieriger zu verstehen :-).

Die beste Erklärung für dieses Konzept ist: Wenn Sie eine übergeordnete Klasse und eine untergeordnete Klasse haben, können die übergeordnete und untergeordnete Klasse ausgetauscht werden, ohne dass sich die Richtigkeit des ursprünglichen Ergebnisses ändert. Das klingt immer noch etwas verwirrend, also schauen wir uns ein klassisches Quadrat-Rechteck-Beispiel an. Mathematisch gesehen ist ein Quadrat ein Rechteck, aber das trifft nicht zu, wenn Ihr Modell die „ist-ein“-Beziehung durch Vererbung verwendet.

Schlecht:

class Rectangle
{
    protected $width = 0;
    protected $height = 0;
 
    public function setWidth(int $width): void
    {
        $this->width = $width;
    }
 
    public function setHeight(int $height): void
    {
        $this->height = $height;
    }
 
    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}
 
class Square extends Rectangle
{
    public function setWidth(int $width): void
    {
        $this->width = $this->height = $width;
    }
 
    public function setHeight(int $height): void
    {
        $this->width = $this->height = $height;
    }
}
 
function printArea(Rectangle $rectangle): void
{
    $rectangle->setWidth(4);
    $rectangle->setHeight(5);
 
    // BAD: Will return 25 for Square. Should be 20.
    echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()).PHP_EOL;
}
 
$rectangles = [new Rectangle(), new Square()];
 
foreach ($rectangles as $rectangle) {
    printArea($rectangle);
}

Gut:

Behandeln Sie diese beiden Vierecke am besten getrennt und verwenden Sie eines, das für beide geeignet ist . Stattdessen ein allgemeinerer Untertyp des Typs.

Obwohl Quadrate und Rechtecke ähnlich aussehen, sind sie unterschiedlich. Ein Quadrat ähnelt eher einer Raute, während ein Rechteck eher einem Parallelogramm ähnelt. Aber sie sind keine Untertypen. Obwohl sie ähnlich sind, handelt es sich bei Quadraten, Rechtecken, Rauten und Parallelogrammen um unterschiedliche Formen mit jeweils eigenen Eigenschaften.

interface Shape
{
    public function getArea(): int;
}
 
class Rectangle implements Shape
{
    private $width = 0;
    private $height = 0;
 
    public function __construct(int $width, int $height)
    {
        $this->width = $width;
        $this->height = $height;
    }
 
    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}
 
class Square implements Shape
{
    private $length = 0;
 
    public function __construct(int $length)
    {
        $this->length = $length;
    }
 
    public function getArea(): int
    {
        return $this->length ** 2;
    }
}
 
function printArea(Shape $shape): void
{
    echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL;
}
 
$shapes = [new Rectangle(4, 5), new Square(5)];
 
foreach ($shapes as $shape) {
    printArea($shape);
}

4. Prinzip der Schnittstellenisolation

Interface Segregation Principle (ISP)

Das Prinzip der Schnittstellenisolation besagt: „Der Anrufer sollte nicht gezwungen werden, sich auf Dinge zu verlassen.“ Das muss er nicht. Die Schnittstelle „

hat ein klares Beispiel, das dieses Prinzip demonstriert. Wenn eine Klasse eine große Anzahl von Einstellungen erfordert, ist es praktisch, vom Aufrufer nicht das Festlegen einer großen Anzahl von Optionen zu verlangen, da normalerweise nicht alle Einstellungen erforderlich sind. Wenn wir die Einstellungen optional machen, können wir die Schaffung von „fetten Schnittstellen“ vermeiden.

Schlecht:

interface Employee
{
    public function work(): void;
 
    public function eat(): void;
}
 
class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....working
    }
 
    public function eat(): void
    {
        // ...... eating in lunch break
    }
}
 
class RobotEmployee implements Employee
{
    public function work(): void
    {
        //.... working much more
    }
 
    public function eat(): void
    {
        //.... robot can't eat, but it must implement this method
    }
}

Gut:

Nicht jeder Arbeiter ist ein Angestellter, aber jeder Angestellte ist ein Arbeiter

interface Workable
{
    public function work(): void;
}
 
interface Feedable
{
    public function eat(): void;
}
 
interface Employee extends Feedable, Workable
{
}
 
class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....working
    }
 
    public function eat(): void
    {
        //.... eating in lunch break
    }
}
 
// robot can only work
class RobotEmployee implements Workable
{
    public function work(): void
    {
        // ....working
    }
}

5. Abhängigkeitsinversionsprinzip

Dependency Inversion Principle (DIP)

Dieses Prinzip veranschaulicht zwei grundlegende Punkte:

High-Level-Module sollten nicht von Low-Level-Modulen abhängen; Sollte von der Abstraktion abhängen

Abstraktion sollte nicht von der Implementierung abhängen, die Implementierung sollte von der Abstraktion abhängen

Dies mag auf den ersten Blick etwas unklar erscheinen, aber wenn Sie es verwendet haben In PHP-Frameworks (wie Symfony) sollten Sie Dependency Injection (DI) gesehen haben, eine Implementierung dieses Konzepts. Obwohl es sich nicht um genau gleichwertige Konzepte handelt, trennt das Abhängigkeitsinversionsprinzip die Implementierungsdetails und die Erstellung von Modulen höherer Ordnung von Modulen niedrigerer Ordnung. Dies kann mithilfe der Abhängigkeitsinjektion (DI) erreicht werden. Der größte Vorteil besteht darin, dass die Module voneinander entkoppelt werden. Die Kopplung erschwert die Umgestaltung und ist ein sehr schlechtes Entwicklungsmodell.

Schlecht:

class Employee
{
    public function work(): void
    {
        // ....working
    }
}
 
class Robot extends Employee
{
    public function work(): void
    {
        //.... working much more
    }
}
 
class Manager
{
    private $employee;
 
    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }
 
    public function manage(): void
    {
        $this->employee->work();
    }
}

Gut:

interface Employee
{
    public function work(): void;
}
 
class Human implements Employee
{
    public function work(): void
    {
        // ....working
    }
}
 
class Robot implements Employee
{
    public function work(): void
    {
        //.... working much more
    }
}
 
class Manager
{
    private $employee;
 
    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }
 
    public function manage(): void
    {
        $this->employee->work();
    }
}