SOLID: Die ersten 5 Prinzipien des objektorientierten Designs

Projekte, die sich an die SOLID-Prinzipien halten, können einfacher mit anderen Entwicklern geteilt, erweitert, geändert, getestet und refaktoriert werden. Wir erklären Ihnen diese Prinzipien und zeigen Ihnen Beispiele für ihre Anwendung.

SOLID ist ein Akronym für die ersten fünf Prinzipien des objektorientierten Designs (OOD) von Robert C. Martin (auch bekannt als Uncle Bob):

S – Single Responsibility Principle

O – Open-Closed Principle

L – Liskov Substitution Principle

I – Interface Segregation Principle

D – Dependency Inversion Principle

Diese Prinzipien etablieren Praktiken, die dazu beitragen, Software unter Berücksichtigung der Wartbarkeit und Erweiterbarkeit zu entwickeln, während das Projekt wächst. Die Anwendung dieser Prinzipien kann auch dazu beitragen, Code-Smells zu vermeiden und den Prozess der Code-Refaktorisierung zu unterstützen, und sie sind von Bedeutung für die agile oder adaptive Softwareentwicklung.

Achtung: Wir verwenden in unserem Beispielcode PHP – die SOLID-Prinzipien können aber auf verschiedene Programmiersprachen angewendet werden.

Single-Responsibility Principle

Das Single-Responsibility Principle (SRP) besagt: Eine Klasse sollte nur einen Grund zur Änderung haben, was bedeutet, dass eine Klasse nur eine Aufgabe haben sollte.

Beispielsweise können wir eine Anwendung betrachten, die eine Sammlung von Formen – Kreisen und Quadraten – erhält und die Summe der Flächen aller Formen in der Sammlung berechnet.

Quellcode für Quadrate:

class Square
{
    public $length;

    public function __construct($length)
    {
        $this->length = $length;
    }
}

Quellcode für Kreise:


class Circle
{
    public $radius;

    public function __construct($radius)
    {
        $this->radius = $radius;
    }
}


Als Nächstes erstellen wir die Klasse AreaCalculator und schreiben die Logik zum Summieren der Flächen aller bereitgestellten Formen. Die Fläche eines Quadrats wird durch die Seitenlänge zum Quadrat berechnet. Die Fläche eines Kreises wird mit p multipliziert und dann zum Quadrat genommen.

Quellcode für AreaCalculator:


class AreaCalculator
{
    // ...
}


Um die AreaCalculator-Klasse zu verwenden, müssen Sie die Klasse instanziieren und ein Array von Formen übergeben. Das Ergebnis wird am Ende der Seite angezeigt. Hier ist ein Beispiel mit einer Sammlung von drei Formen:

  • Ein Kreis mit einem Radius von 2
  • Ein Quadrat mit einer Seitenlänge von 5
  • Ein weiteres Quadrat mit einer Seitenlänge von 6

$shapes = [
    new Circle(2),
    new Square(5),
    new Square(6),
];

$areas = new AreaCalculator($shapes);

echo $areas->output();


Das Problem mit der output-Methode ist, dass die AreaCalculator die Logik zur Ausgabe der Daten behandelt. Stellen Sie sich vor, Sie möchten die Ausgabe in ein anderes Format wie JSON konvertieren. Die gesamte Logik würde von der AreaCalculator-Klasse behandelt, was gegen das Single-Responsibility Principle verstoßen würde. Die AreaCalculator-Klasse sollte sich nur um die Summe der Flächen der bereitgestellten Formen kümmern und sich nicht darum kümmern, ob der Benutzer JSON oder HTML möchte.

Um dieses Problem zu lösen, können Sie eine separate Klasse SumCalculatorOutputter erstellen und diese neue Klasse verwenden, um die Logik zur Ausgabe der Daten zu handhaben:

Quellcode für SumCalculatorOutputter:


class SumCalculatorOutputter
{
    // ...
}


Die SumCalculatorOutputter-Klasse würde folgendermaßen funktionieren:

$shapes = [
    new Circle(2),
    new Square(5),
    new Square(6),
];

$areas = new AreaCalculator($shapes);
$output = new SumCalculatorOutputter($areas);

echo $output->JSON();
echo $output->HTML();


Jetzt wird die Logik, die zur Ausgabe der Daten für den Benutzer benötigt wird, von der SumCalculatorOutputter-Klasse behandelt. Das erfüllt das Single-Responsibility Principle.

Open-Closed Principle

Das Open-Closed Principle (OCP) besagt: Objekte oder Entitäten sollten für Erweiterungen offen, aber für Modifikationen geschlossen sein. Das bedeutet, eine Klasse sollte erweiterbar sein, ohne die Klasse selbst zu ändern.

Betrachten wir erneut die AreaCalculator-Klasse und konzentrieren uns auf die sum-Methode:

class AreaCalculator
{
    // ...
}


Angenommen, der Benutzer möchte die Summe weiterer Formen wie Dreiecke, Fünfecke, Sechsecke usw. berechnen. Dann müssten Sie ständig diesen Code ändern und weitere if/else-Blöcke hinzufügen. Dies würde gegen das Open-Closed Principle verstoßen.

Eine bessere Möglichkeit, die sum-Methode zu gestalten, besteht darin, die Logik zur Berechnung der Fläche jeder Form aus der Methode der AreaCalculator-Klasse zu entfernen und sie an die Klassen der Formen anzuhängen. Hier ist die area-Methode, die in der Square-Klasse definiert ist:


Und hier ist die area-Methode, die in der Circle-Klasse definiert ist:

Die sum-Methode für AreaCalculator kann dann wie folgt umgeschrieben werden:

class AreaCalculator
{
    // ...
}


Nun können Sie eine weitere Formklasse erstellen und diese beim Berechnen der Summe ohne Codeänderungen hinzufügen.

Open-Closed Principle

Das Open-Closed Principle besagt: Softwareentitäten sollten offen für Erweiterung, aber geschlossen für Modifikation sein. Das bedeutet, dass Sie eine Klasse erweitern sollten, um neues Verhalten hinzuzufügen, anstatt die vorhandene Klasse zu ändern.

Erstellen Sie ein ShapeInterface

Um dieses Prinzip anzuwenden, erstellen Sie ein Interface namens ShapeInterface, das die Methode area unterstützt:

interface ShapeInterface
{
    public function area();
}

Ändern Sie Ihre Formklassen

Ändern Sie Ihre Formklassen so, dass sie das ShapeInterface implementieren. Hier ist das Update für Square:

class Square implements ShapeInterface
{
    // ...
}


Und hier ist das Update für Circle:

class Circle implements ShapeInterface
{
    // ...
}

Verwenden Sie das Interface in AreaCalculator

In der sum-Methode für AreaCalculator können Sie überprüfen, ob die bereitgestellten Formen tatsächlich Instanzen von ShapeInterface sind. Andernfalls werfen Sie eine Ausnahme:

class AreaCalculator
{
    // ...

    public function sum()
    {
        foreach ($this->shapes as $shape) {
            if (is_a($shape, 'ShapeInterface')) {
                $area[] = $shape->area();
                continue;
            }

            throw new AreaCalculatorInvalidShapeException();
        }

        return array_sum($area);
    }
}


Dies erfüllt das Open-Closed Principle.

Liskov Substitution Principle

Das Liskov Substitution Principle besagt: Wenn q(x) eine Eigenschaft ist, die für Objekte von x vom Typ T nachweisbar ist, sollte q(y) nachweisbar sein für Objekte y vom Typ S, wobei S ein Subtyp von T ist. Das bedeutet, dass jede Unterklasse oder abgeleitete Klasse austauschbar gegenüber ihrer Basisklasse sein sollte.

Erweitern Sie AreaCalculator zu VolumeCalculator

Betrachten wir eine neue VolumeCalculator-Klasse, die von der AreaCalculator-Klasse erbt:

class VolumeCalculator extends AreaCalculator
{
    public function __construct($shapes = [])
    {
        parent::__construct($shapes);
    }

    public function sum()
    {
        // Logik zur Berechnung der Volumina und dann Rückgabe eines Ausgabenarrays
        return [$summedData];
    }
}


Wenn Sie die HTML-Methode auf dem $output2-Objekt aufrufen, erhalten Sie einen E_NOTICE-Fehler, der Sie darüber informiert, dass eine Array-zu-Zeichenfolge-Konvertierung stattgefunden hat. Um dieses Problem zu beheben, sollten Sie anstelle eines Arrays aus der VolumeCalculator-Klasse in der sum-Methode den Wert $summedData zurückgeben.

class VolumeCalculator extends AreaCalculator
{
    public function __construct($shapes = [])
    {
        parent::__construct($shapes);
    }

    public function sum()
    {
        // Logik zur Berechnung der Volumina und dann Rückgabe eines Ausgabewerts
        return $summedData;
    }
}


$summedData kann ein Float, Double oder Integer sein. Das erfüllt das Liskov Substitution Principle.

Interface Segregation Principle

Das Interface Segregation Principle besagt: Ein Client sollte niemals gezwungen sein, ein Interface zu implementieren, das er nicht verwendet, oder Clients sollten nicht gezwungen sein, von Methoden abhängig zu sein, die sie nicht verwenden.

Verwenden Sie separate Interfaces

Um dieses Prinzip anzuwenden, sollten Sie separate Interfaces verwenden, um die Funktionalitäten aufzuteilen. Anstelle von ShapeInterface sollten Sie ein weiteres Interface namens ThreeDimensionalShapeInterface erstellen, das den volume-Vertrag enthält, und dreidimensionale Formen können dieses Interface implementieren:

interface ShapeInterface
{
    public function area();
}

interface ThreeDimensionalShapeInterface
{
    public function volume();
}

class Cuboid implements ShapeInterface, ThreeDimensionalShapeInterface
{
    public function area()
    {
        // Oberfläche des Quaders berechnen
    }

    public function volume()
    {
        // Volumen des Quaders berechnen
    }
}


Dies ist ein viel besseres Vorgehen und erfüllt das Interface Segregation Principle.

Dependency Inversion Principle

Das Dependency Inversion Principle besagt: Entitäten sollten von Abstraktionen abhängen, nicht von Konkretionen. Dieses Prinzip ermöglicht die Entkopplung.

Verwenden Sie eine Abstraktion

Um dieses Prinzip anzuwenden, sollten Sie eine Abstraktion verwenden, damit Module auf hoher und niedriger Ebene von Abstraktionen abhängen. Erstellen Sie eine Schnittstelle namens DBConnectionInterface, die die Methode connect enthält:

interface DBConnectionInterface
{
    public function connect();
}


Die MySQLConnection-Klasse implementiert diese Schnittstelle:

class MySQLConnection implements DBConnectionInterface
{
    public function connect()
    {
        // Datenbankverbindung herstellen
        return 'Datenbankverbindung';
    }
}


Statt die MySQLConnection-Klasse direkt im Konstruktor des PasswordReminder zu typisieren, typisieren Sie stattdessen die DBConnectionInterface. Dadurch bleibt die PasswordReminder-Klasse unabhängig von der Art der Datenbank, die Ihre Anwendung verwendet, und das Open-Closed Principle wird nicht verletzt:

class PasswordReminder
{
    private $dbConnection;

    public function __construct(DBConnectionInterface $dbConnection)
    {
        $this->dbConnection = $dbConnection;
    }
}


Dieser Code stellt sicher, dass sowohl die Module auf hoher als auch auf niedriger Ebene von Abstraktionen abhängen und erfüllt das Dependency Inversion Principle.

Kostenlosen Account erstellen

Registrieren Sie sich jetzt und erhalten Sie Zugang zu unseren Cloud Produkten.

Das könnte Sie auch interessieren: