Tutorials  /  Databases

SOLID: Die ersten 5 Prinzipien des objektorientierten Designs

Ccentron Redaktion · Oktober 2023 ·8 Min. Lesezeit ·Databases, Tutorial

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

DB

Passende Infrastruktur bei centron

Datenbanken im Betrieb wollen gepflegt werden: centron übernimmt auf Wunsch Updates, Backups und Monitoring – von der einzelnen Instanz bis zum Cluster. Managed Cluster ansehen →

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:

Code
class Square
{
    public $length;

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

Quellcode für Kreise:

Code
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:

Code
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
Code
$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:

Code
class SumCalculatorOutputter
{
    // ...
}

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

Code
$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:

Code
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:

Code
class Square
{
    // ...
}

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

Code
class Circle
{
    // ...
}

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

Code
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:

Code
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:

Code
class Square implements ShapeInterface
{
    // ...
}

Und hier ist das Update für Circle:

Code
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:

Code
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:

Code
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.

Code
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:

Code
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:

Code
interface DBConnectionInterface
{
    public function connect();
}

Die MySQLConnection-Klasse implementiert diese Schnittstelle:

Code
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:

Code
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.

Jetzt 200 € Guthaben sichern

Testen Sie Ihr Setup auf ccloud³

Registrieren Sie sich in der ccloud³ und erhalten Sie 200 € Startguthaben für Ihr Projekt – z. B. für eine PostgreSQL-VM mit automatischen Backups.

centron Redaktion Technische Redaktion

Das Redaktionsteam von centron schreibt Anleitungen aus dem Betriebsalltag: getestet auf unserer eigenen Plattform, betrieben im Rechenzentrum in Hallstadt bei Bamberg.

Kategorie Databases
Teilen
Noch offene Fragen?

Our team will help you with your specific setup - in German or English, by people who run the platform themselves.

War dieses Tutorial hilfreich?

Your answer is stored anonymously and helps us improve our tutorials.

Kommentare

No comments yet - be the first to ask a question about this tutorial.

Sign in to comment

Comments are open to centron customers. Sign in to your account to ask a question about this tutorial.

Weiterlesen

Das könnte Sie auch interessieren

Jetzt kostenlos anfangen

Melden Sie sich an und erhalten Sie in den ersten 60 Tagen ein Guthaben von 200 € bei centron.

Dieses Werbeangebot gilt nur für neue Konten. Angebot ausschließlich für Gewerbetreibende.

Jetzt loslegen Sales kontaktieren