SOLID – Die 5 Prinzipien für objektorientiertes Softwaredesign
Beim Thema Softwaredesign trennt sich die Spreu vom Weizen. Kein Framework, kein Tool dieser Welt kann die Fähigkeit Software ingenieursmäßig zu konzipieren ersetzen. Lösen Sie sich vom neusten Framework-Hype! Erkunden Sie mit uns in dieser sechsteiligen Artikelserie die SOLID-Prinzipien. Schritt für Schritt entdecken wir gemeinsam die Tiefen der objektorientierten Software-Kunst. Wir lernen Prinzipien des Softwaredesigns und Clean Code Development anhand von Praxis-Beispielen kennen. Das Liskovsches Substitutionsprinzip bietet uns ein erstes Beispiel.
Einführendes Beispiel
SOLID, das sind fünf Prinzipien zum Entwurf und der Entwicklung wartbarer Softwaresysteme. Das Wissen um und über diese Prinzipien sollte zur Basis jedes Softwareentwicklers gehören. Sie zweifeln noch, ob Sie von den SOLID-Prinzipien profitieren können? Werfen Sie einen Blick auf das folgende Codebeispiel:
public static void CalculateArea(Rectanle rectangle) { rectangle.Height = 5; rectangle.Width = 10; var area = rectangle.Area(); Console.WriteLine(area); Console.ReadLine(); }
Die Methode CalculateArea() ist denkbar einfach. Sie berechnet den Flächeninhalt eines Rechtecks indem sie die Höhe und Breite setzt und danach die Methode Area des Rechtecks aufruft:
public class Rectanle { public virtual int Width { get; set; } public virtual int Height { get; set; } public int Area() { return Width*Height; } }
Welches Ergebnis würden Sie erwarten? Regelmäßig wird diese Frage mit 50 beantwortet. Wie wir sehen, ist die Methode Area() korrekt implementiert und multipliziert Höhe mit Breite. Die Schulmathematik aus der vierten Klasse lässt nur 50 als Ergebnis zu. Unser Beispiel liefert jedoch das Ergebnis 100. 100? Unmöglich. Wie kann das sein? Da der bisherige Code offensichtlich korrekt ist, muss das Problem an anderer Stelle liegen. Unsere CalculateArea-Methode wird wie folgt aufgerufen:
public static void Main(string[] args) { var square = new Square(); CalculateArea(square); }
Wie wir sehen, wird ein Quadrat an CalculateArea übergeben. CalculateArea erwartet ein Rechteck – und ein Quadrat ist ein Rechteck. Aus ihrer mikroskopischen Sicht funktioniert die CalculateArea-Methode also korrekt. Und auch die Main-Routine weist keinen Fehler auf. Wenn die Breite des Quadrats gesetzt wird, wird jedoch auch die Höhe gesetzt:
public override int Width { get { return base.Width; } set { base.Width = value; base.Height = value; } }
Kurz vor dem Aufruf von Area() sind also Breite und Höhe gleich zehn. Unser Quadrat leitet übrigens von Rechteck ab, so dass es die Methode zum Fläche berechnen gar nicht implementieren muss:
public class Square : Rectanle { public override int Height { get { return base.Height; } set { base.Height = value; base.Width = value; } } public override int Width { get { return base.Width; } set { base.Width = value; base.Height = value; } } }
In diesem zugegebenermaßen konstruierten, aber dafür auch eindrucksvollen Beispiel, wurde das Liskovsche Substitutionsprinzip [1] verletzt – mehr dazu zu gegebener Zeit. Das Beispiel ist typisch für die SOLID-Prinzipien. Aus mikroskopischer Sicht scheint ein bestimmtes Quellcodefragment korrekt. Im Zusammenspiel mit anderen Klassen ergeben sich jedoch Probleme.
Wozu brauchen wir die SOLID-Prinzipien?
Das Rectangle-Square-Beispiel hat gezeigt: Für sich betrachtet funktionieren viele Programmteile fehlerfrei. Bereits bei drei Klassen können aber konzeptionelle Probleme auftreten.
Erweiterbarkeit
Bei größeren Softwareprojekten existiert eine Vielzahl von semantischen oder strukturellen Codeeinheiten (Komponenten, Bibliotheken, Klassen, Methoden usw.). Es ist das (teilweise komplexe) Zusammenspiel dieser Codeeinheiten, welches bei falschem Design Erweiterungen erschwert oder sogar zu schwer diagnostizierbaren Fehlern führt. Um zukunftsfähige und nachhaltige Softwareprodukte zu entwickeln, sollten Sie Ihre Klassen und deren Zusammenspiel deswegen so modellieren, dass sich leicht und fehlerfrei Erweiterungen durchführen lassen. Hierbei geben uns die fünf SOLID-Prinzipien wichtige Hilfestellungen. Diese fünf Prinzipien sind:
- Single Responsibility Principle (SRP)
- Open Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP) (*Erklärung im nächsten Artikel)
Viele der SOLID-Prinzipien beschäftigen sich mit Aspekten der Aggregation sowie Komposition von Klassen. Auch der sinnvolle Einsatz von Vererbung, oder auch deren Auflösung durch Komposition ist ein wichtiger Aspekt der SOLID-Prinzipien.
Wiederverwendbarkeit
Häufig wird die Wiederverwendbarkeit von Programmcode mit einem einfachen Extract-Method-Refactoring gleichgesetzt. Dies adressiert die Wiederverwendbarkeit aber auf eine Ebene mit sehr vielen Details. Die echte Wiederverwendbarkeit eines domänespezifischen Aspekts kann zum Beispiel durch das Open Closed Principle gefördert werden.
Wartbarkeit
Wenn Änderungen oder Fehlerkorrekturen nur schwer vorgenommen werden können, spricht man von einer schlechten Wartbarkeit des Softwaresystems. Sofern die Umsetzung einer neuen User Story eine fachlich getrennte User Story beeinflusst und damit Erweiterungen nicht isoliert durchführbar sind, ist die Wartbarkeit des Programms bereits beeinflusst.
SOLID hilft, solche Seiteneffekte zu reduzieren. Zusätzlich sollten Sie bedenken: Die SOLID-Prinzipien können auf fachlicher Ebene keine Wunder bewirken. Auch sind die SOLID-Prinzipien kein Allheilmittel, wenn ein System an größeren architekturellen Problemen leidet. Wohl aber helfen die SOLID-Prinzipien, die fachlichen Anforderungen an Ihr System besser (erweiterbarer und damit auch wartbarer) zu modellieren, indem sie Prinzipien definieren, nach denen Entwickler vorgehen können, um Klassen zu modellieren.
Verständlichkeit und Verständnis
Software muss nicht immer kompliziert sein. Die SOLID-Prinzipien helfen Ihnen, Ihren Programmcode einfacher zu gestalten. Sie tragen zu einem effizienten und verständlichen Umgang mit technischen Details bei und stellen damit die für Sie wichtige Fachlichkeit in den Vordergrund. Wie Sie in den folgenden Artikeln sehen werden, ist Verständlichkeit auch automatisch eine Konsequenz aus Erweiterbarkeit und Wiederverwendbarkeit. Viele Technologien und Methodiken der objektorientierten Programmierung – wie zum Beispiel Design-Patterns oder auch die Dependency Injection (nicht mit Dependency Inversion Principle zu verwechseln) – basieren auf den SOLID-Prinzipien bzw. werden durch deren Verständnis gefördert.
SOLIDe Agilität
Sie entwickeln nach einem agilen Vorgehensmodell wie zum Beispiel Scrum? Dann gewinnen die SOLID-Prinzipien zusätzlich an Gewicht. Bei klassischen Vorgehensmodellen wie dem Wasserfall oder dem V-Modell wird zuerst die komplette Softwarearchitektur und ein ausführliches Softwaredesign spezifiziert. Erweiterungen sollten in der Theorie deswegen seltener sein als bei agilen Vorgehensmodellen (bei kleineren Projekten kann das auch zutreffen).
Sehr stark vereinfacht bedeutet Agilität das "flexible und schnelle Reagieren auf geänderte Anforderungen". Agile Prozesse leben von einer iterativen Produktentwicklung und stellen deshalb erhöhte Anforderungen an die Erweiterbarkeit des Softwaremodells. Mit jeder neuen Iteration wird das Softwareprodukt erweitert. Nicht umsonst werden die SOLID-Prinzipien von Uncle Bob als die "Agile Principles of Object Oriented Software" bezeichnet [2]. Die SOLID-Prinzipien schaffen die Basis für spätere Erweiterungen und erfüllen zusätzlich das YAGNI-Prinzip (You Aren‘t Gonna Need It). Mit SOLID schaffen Sie keine unnötige Funktionalität, sondern sehen durch ein durchdachtes Klassendesign Erweiterungsmöglichkeiten vor.
Software sollte so komplex wie nötig, aber so einfach wie möglich sein.
Während unserer langjährigen Tätigkeit hat sich gezeigt, dass sich bereits ab einem halben Mannjahr Entwicklungszeit Fehler im Design massiv auswirken und die Produktivität auf ein Minimum drücken können. Das Wissen um und über die SOLID-Prinzipien sollte deswegen zur Grundausbildung eines jeden Softwareentwicklers gehören.
Wissen für Ihre Softwarequalität
Nach dieser kurzen Einführung werden wir Ihnen in den nächsten fünf Artikeln anhand eines zusammenhängenden Beispiels alle SOLID-Prinzipien anschaulich erklären. Als Grundlage für unser Codebeispiel dient eine Stereoanlage. In jedem Artikel identifizieren wir Schwachstellen im Softwaredesign und zeigen einen Lösungsvorschlag auf. Stück für Stück kommen wir dabei einer erweiterbaren und wartbaren Softwarelösung immer näher.
Ein Klassendiagramm veranschaulicht sehr gut die wichtigsten Aspekte unseres Beispiels:
Wie bei einer echten Stereoanlage bietet auch unsere Stereoanlage dem Benutzer verschiedene Interaktionsmöglichkeiten: Medium Abspielen bzw. Pausieren, Stoppen, (Medium) vorwärts bzw. rückwärts und Lautstärke verändern. Eine Controller-Klasse nimmt diese Benutzereingaben von einer Benutzeroberfläche entgegen. Der Controller steuert daraufhin verschiedene Audioquellen (Kassette, CD, Radio) an. Der Controller erlaubt zudem das Umschalten zwischen den Audioquellen, indem er den Audio-Stream, den ein Medium liefert, an den Lautsprecher anschließt.
Fazit
Bereits bei der Umsetzung kleiner Softwareprojekte kann sich das Zusammenspiel mehrerer Klassen zu einem großen Hindernis entwickeln. Abhilfe schafft Erfahrung, sowie das Wissen um die SOLID-Prinzipien. Software muss nicht kompliziert sein. Bei BinaryGears haben wir die Einfachheit von Software sogar zu unserem Firmenslogan gemacht: Software Made Simple. Software sollte so komplex wie nötig, aber so einfach wie möglich sein. Die SOLID-Prinzipien helfen Ihnen, Komplexität unkompliziert zu gestalten.
Das in unseren nächsten Artikeln vermittelte Wissen lässt Sie von einer Reihe von Vorteilen profitieren: Durch die SOLID-Prinzipien werden Erweiterungen einfach, die Wiederverwendbarkeit von Software-Teilen verbessert und eine produktive Softwareentwicklung erst möglich. Die SOLID-Prinzipien passen perfekt zu agilen Prozessen und legen die Grundlage für eine iterative Produktentwicklung.
Lernen Sie im nächsten Artikel eine mögliche Implementierung des Stereoanlagen-Beispiels und eines der wichtigsten Prinzipien im objektorientierten Entwurf kennen: das Dependency Inversion Principle. Wir empfehlen: Lesen Sie alle folgenden Artikel, denn die verschiedenen SOLID-Prinzipien stehen miteinander in Verbindung. Pushen Sie Ihre Skills und werden Sie zum Softwaredesign-Guru!
- Wikipedia: Liskovsches Substitutionsprinzip
- Youtube: Bob Martin SOLID Principles of Object Oriented and Agile Design (12:41 min.)