123 Invest Gruppe: Kommentar

Software-Architektur: Solide Systeme dank SOLID-Prinzipien

Zu Beginn eines Projektes erarbeiten die Architekten und das Entwicklungsteam eine zugeschnittene Architekturblaupause für die anstehende Entwicklung. In der Praxis zeigt sich oft, dass während der Implementierung häufig ungewollt von dieser Vorgabe abgewichen wird. Daraus entstehen technische Schulden und Wartung sowie Erweiterungen der Software werden zunehmend aufwendiger. Um ein solches Szenario bei der Umsetzung der Architektur zu vermeiden und langlebige Architekturen zu entwerfen oder bei bestehenden Systemen zu langlebigen Architekturen zu gelangen, werden die SOLID-Prinzipien angewendet.

SOLID-Prinzipien: Was heißt das?

Der Begriff steht für die in der Softwareentwicklung eingesetzten Designvorgaben. Laut Robert C. Martin, der von fünf wichtigen Prinzipien spricht und diese geprägt hat, steht das SOLID-Prinzip für die Klassifizierung und Klassenverbindung der Datenstrukturen und Funktionen.

Die Zielsetzung beruht auf einer verständlichen Lösung, die Modifikationen erlaubt und die Komponentenbasis für den Einsatz in zahlreichen Systemen bildet. Hier möchte ich einen kleinen Einblick in die Thematik geben und dem Leser ohne komplexe Details die SOLID vorstellen.

Folgende SOLID-Prinzipien prägen die Software-Architektur und sind entscheidende Faktoren für das definierte Ziel.

+ Single-Responsibility-Prinzip SRP
+ Open-Closed-Prinzip OCP
+ Liskov’sches Substitutions-Prinzip LSP
+ Interface-Segregation-Prinzip ISP
+ Dependency-Inversion-Prinzip DIP

SRP | Das am häufigsten missverstandene SOLID-Prinzip

Nach den Ausführungen von Robert C. Martin gehen viele Entwickler davon aus, dass das Single-Responsibility-Prinzip eine Einzelaufgabe erfüllt. Auch wenn sich durch Refactoring funktionsreiche Umfänge auf eine geringere Ebene aufteilen lassen, meint man mit dieser Methodik nicht SRP.

In der Beschreibung von SRP geht es vielmehr darum, dass es für die Modifizierung einer Klasse nie mehr als einen Grund geben sollte.

Robert C. Martin empfiehlt ehr die Aussage, dass: „Ein Modul für einen – und nur für einen – Akteur verantwortlich sein soll“. Seine Empfehlung beruht darauf, dass die Bezeichnung Klasse zu spezifisch ist und man beachten muss, dass die Software im Regelfall für Kunden und Nutzer ohne technische Kernexpertise geschrieben wird.

Anhand des folgenden Beispiels erschließt sich die Aussage der Botschaft. Nimmt man ein Modul, das für unterschiedliche Geschäftsbereiche Funktionen aufweist, könnte die Veränderung in einem Geschäftsbereich mit Strukturveränderung der Klasse dazu führen, dass es zu Auswirkungen auf einen anderen Klassenteil kommt. Daraus ergäben sich Abhängigkeiten, die im Endeffekt zu einer Blockierung in der Softwareentwicklung führen könnten. Konstruktiver ist es, die Methoden und Klassen so einzuteilen, dass alle Anpassungen und Anforderungen von einem einzelnen Akteur verantwortet werden.

Explizit geht es beim SRP darum, alle Klassen und Funktionalitäten in der Verbindung zum verantwortlichen Akteur und dessen Anforderungen zu erzeugen.

OCP | Offen für Erweiterungen – geschlossen für Modifikationen

„Die Softwareidentität sollte offen für Erweiterungen und gleichzeitig geschlossen für Modifikationen sein.“

Was sich auf den ersten Blick zu widersprechen scheint, wird von Bertrand Meyer plausibel erläutert. Konkret heißt es, dass eine optimal erweiterbare Softwareidentität gar keine Modifizierung benötigt. Erfordert die Änderung an einer Software einen massiven Eingriff in Form einer Modifizierung, kann die Architektur als gescheitert betrachtet werden.

Nun stellt sich der Leser die Frage, wie eine Erweiterung ohne Modifizierung überhaupt möglich ist. In den 90er Jahren übernahm Martin das Prinzip Bertrand Meyers und setzte es technisch aus einer anderen Perspektive heraus um. Meyer sah die Lösung in Bezug auf die Vererbung in einem objektorientierten Umfeld. Was seinerzeit ein essenzieller Faktor für Software-Erweiterungen und die Wartung war, ist heute nicht mehr zeitgemäß. Das folgende Beispiel verdeutlicht die Aussage.

Wir blicken auf einen konventionellen PKW und einen Sportwagen. Die Sportwagenklasse erbt alle grundlegenden Eigenschaften und Funktionen der PKW-Klasse. Zusatzfunktionen und besondere Eigenschaften werden der Sportwagenklasse, aber nicht der PKW-Klasse hinzugefügt. Hier zeigt sich eine einseitige, nur in eine Richtung führende Abhängigkeit – die in ihrer Betrachtung gut und richtig ist.

Martin bringt ein weiteres Beispiel in Bezug auf die Anwendung von Interfaces an. In der erweiterten Ausführung ist OCP nach wie vor sinnvoll und wird weltweit eingesetzt. Eine Vererbung zieht immer eine Aufteilung nach sich, doch am Beispiel von Java liegt keine wirkliche Mehrfachvererbung vor. Hier kommen Interfaces zum Einsatz, deren Implementierung in Klassen eine hierarchische Sicherheit schafft. Auf den Punkt gebracht heißt das, dass höher klassifizierte Interfaces vor jeglichen Modifikationen geschützt sind, die in niedriger klassifizierten Hierarchien vorgenommen werden.

LSP | Das Vererbungsprinzip der Eigenschaften bei Unter- und Oberklassen

Das nach Barbara Liskow benannte Liskov’sche Prinzip beruht auf der Vererbung der Eigenschaften mit Hierarchievorgabe.

„Existiert für jedes Objekt O1 / Typ S ein Objekt O2 / Typ T, bleibt das Verhalten aller in T definierten Programme (P) unverändert, wenn eine Substitution von O1 zu O2 erfolgt. In diesem Fall ist S ein Subtyp von T.“

Um die Aussage zu vereinfachen, lässt sich die Beschreibung wie folgt entschlüsseln. Bei einer Vererbung muss die Unterklasse alle Eigenschaften der Oberklasse erhalten, damit eine umfassende Verwendung durch die Oberklasse möglich ist. Werden Funktionalitäten der Oberklasse verändert, bleibt die Unterklasse hiervon unberührt. Die Oberklasse selbst darf jederzeit erweitert und mit neuen Funktionen bestückt werden.

Am Beispiel des PKWs wird das Prinzip vereinfacht. Es gibt einen PKW der Oberklasse, der bestimmte Funktionen, beispielsweise die Beschleunigung und das Abbremsen, aufweist. In der Hierarchie darunter gibt es mit einem Sportwagen und einem Kleinwagen zwei Unterklassen. Sowohl der Sportwagen, wie auch der Kleinwagen können ebenfalls beschleunigen und bremsen und weisen somit zu Oberklasse identische Eigenschaften auf. Erweiterte Eigenschaften in der Unterklasse sind erlaubt, sodass der Sportwagen beispielsweise über eine Zusatzfunktion zur Aktivierung des Sportmodus verfügen darf. Die Oberklasse bleibt hiervon unbeeindruckt, da sich die Erweiterung nur auf den Sportwagen und dessen Fahreigenschaften bezieht.

LSP stellt bei der Mehrfachvererbung Bedingungen durch die Oberklasse an untergeordnete Klassen und geht über die Funktionen von OCP hinaus.

ISP | Nützliche Schnittstellen

Im Interface-Segregation-Prinzip geht es darum, dem Nutzer die Möglichkeit der Implementierung notwendiger Schnittstellen zu erlauben. Durch ISP wird der Anwender nicht länger gezwungen, zu große Schnittstellen oder geschrumpfte, auf einen Spezialnutzen abgestimmte Schnittstellen zu implementieren.

Die folgende Darstellung verdeutlicht die Implementierung von drei Operationen in der Oberklasse. Dabei greift Anwender 1 auf nur auf Op1 zu, während Anwender 2 Op2 und Anwender 3 Op3 nutzt. Anwender 1 ist in dieser Abbildung von Op2 und Op3 abhängig, auch wenn sie nicht aufgerufen werden. Bei einer Veränderung der Implementierung von Op2 würden sich für Anwender 1 ebenfalls ein neues Deployment und eine neue Kompilierung ergeben. Faktisch ist keine Änderung der von Anwender 1 verwendeten Module gegeben.

Die Problemlösung kann hier nur eine Spaltung der Operationen in Schnittstellen sein. In der entsprechenden Sprache, beispielsweise in Java, wäre für Anwender 1 nur der Quellcode seiner Klasse und der Op1 von Relevanz. Eine Abhängigkeit von übergeordneten Klassen entfiele.

Durch ISP werden nicht notwendige Modulabhängigen mit hohem Problempotenzial verhindert. Die Reduzierung der Abhängigkeiten sorgt dafür, dass es bei Veränderungen der Codes nicht zu umfangreichen und äußerst komplexen Gesamtveränderungen kommt. Ein weiterer Vorteil der zusätzlichen Schicht beruht auf einem besseren Umgang der Architektur mit der Modifikation.

DIP | Das Prinzip flexibler Systeme

Beziehen sich Abhängigkeiten von Quellcodes ausschließlich auf Abstraktionen und nicht auf Konkretionen, erhöht dieser Umstand die Flexibilität der Systeme. Hierauf beruht das Dependency-Inversion-Prinzip, das letzte der hier aufgeführten SOLID-Prinzipien.

In der Nutzung von Java soll eine Abhängigkeit von konkreten Modulen vermieden werden, in dem sich jede Anweisung (beispielsweise import, include oder use) nur auf das Quellcodemodul bezieht. Das kann eine Schnittstelle, eine Klassenabstraktion oder ein anderweitiges Modul sein.

Allerdings sind Softwaresysteme durchaus von konkretisierten Entitäten abhängig, wodurch dieses SOLID-Prinzip keine Regel ist. Am Beispiel der Klasse String in Java wird deutlich, dass die konkrete Gestaltung nicht prinzipiell auszuschließen ist. Eine abstrakte Gestaltung von String wäre nicht zweckmäßig und würde die Abhängigkeit zum String-Objekt unterbinden – die aber gewünscht ist.

DIP bezieht sich daher eher auf die Softwareteile, in denen Modifikationen gewünscht sind und an denen gearbeitet wird.

Was wir aus den SOLID-Prinzipien ableiten

Jedes einzelne Prinzip nimmt einen nicht zu unterschätzenden Einfluss auf die Architektur der Software. Eine korrekte Deutung und die Betrachtung im Kontext nehmen eine wichtige Position ein. Ich betrachte die SOLID-Prinzipien als gute Basis, sehe sie aber nicht als Grundpfeiler an.

Eine teilweise rekursive Verbindung der Prinzipien ist nicht von der Hand zu weisen. Bei umfangreichen Themen der Architektur von Software sind die SOLID-Prinzipien häufig anzutreffen. Für Entwickler und Software-Designer stellen sie ein wichtiges Handwerkzeug dar.

Herzlichst

Ihre Algopioniere
erstellt von Julia Rosen in Zusammenarbeit mit unserem Team

Weitere Informationen über die 123 Invest Gruppe erhalten Sie unter www.1-2-3-invest.de