SOLID Code
Die agile Softwareentwicklung beginnt mit einem Prototypen, der über viele Sprints stetig an die Bedürfnisse des Kunden angepasst wird. Grundlage dafür ist stark modularisierter Code, in welchem dank loser Kopplung bestehende gegen aktualisierte und erweiterte Implementierungen von Modulen ausgetauscht werden können.
Um modularen Code mit loser Kopplung zu entwickeln, sind Grundregeln beim Entwurf zu befolgen und Entwurfsmuster anzuwenden, die unter dem Akronym SOLID zusammengefasst werden:
SOLID ist ein Akronym, das sich auflöst in:
- Single responsibillity prinzip (SRP)
- Open Closed Princip (OCP)
- Liskov substitution princip
- Interface Segregation
- Dependency Injection (DI)
Single responsibillity prinzip (SRP)
"Jede Klasse ist für eine einzige Aufgabe/Problemstellung zuständig."
Z.B. hat die Klasse Bankkonto
hat die Methoden Bankkonto.Abheben
und Bankkonto.Einzahlen
. Es gibt Situationen, in denen die Methodenaufrufe zu
protokollieren sind. Eine einzige Klasse Bankkonto mit einem Schalter Bankkonto.ProtokollierungEin(bool ja)
verletzt das SRP, da die Aufgaben
"Transaktionen mit Protokollierung" und "Transaktionen ohne Protokollierung" erfüllt werden.
Indem zwei Klassen implementiert werden, eine ohne, und eine mit Protokollierung, wird die Gültigkeit des SRP im Code wiederhergestellt.
→effiziente Implementierung mittels Decorator Pattern
Open Closed Princip (OCP)
"Eine Klasse sollte offen für Erweiterungen und geschlossen für Modifikationen sein."
Ziel ist, das Fehlerkorrekturen im Server bzw. neue Implementierungen dem Client verborgen bleiben. So führen Überarbeitungen der Impl Die öffentliche Schnittstelle einer Klasse (public Interface), deren Objekte von Clients verarbeitet werden, sollte fixiert/unveränderlich sein (= geschlossen für Modifikationen).
Durch Polymorphie können Methoden und Eigenschaften in abgeleiteten Klassen überschrieben und somit erweitert werden (= offen für Erweiterungen).
OCP ist Basis für die Implementierung loser Kopplung. Zwei Klassen A und B sind lose gekoppelt, wenn A von B abhängig ist, und wenn eine Änderung an B nicht zwangsläufig eine Änderung an A erzwingt.
→Implementierung mittels Implementierung- oder Schnittstellenvererbung
Liskov substitution princip
"Wenn S eine von T abgeleitete Klasse ist, dann sollte ein Client der Dienste eines Servers vom Typ T nutzt, diese ohne Einschränkungen auch von Servern des Typs S nutzen können." →Implementierung mittels Verträgen die auf allen Ebenen der Vererbung eingehalten werden.
Eine klassische Schnittstelle eines Repositories der folgenden Art trägt die Gefahr der Verletzung des Liskov- Prinzips in sich tragen:
interface ICrud<TBo> { TBo Create(); void Add(TBo); //... }
Interface Segregation
Schnittstellen IA
auftrennen in Schnittstellen IA1 ... IAn
,
wenn es Implementierungen der Schnittstelle IA
gibt, in denen nur eine Teilmenge der Member fachlich sinnvoll ist.
Dependency Injection (DI)
Die Implementierung einer Klasse A nutzt Dienste einer Klasse B. Dazu kann A
-
eine Instanz von B selbst erzeugen:
class A{ B b; A() { b = new B(); }}
.
Nachteile:- Ist B schwer testbar, da zur Laufzeit an einen bestimmten Kontext gebunden (z.B. Datenbank), wird auch A schwer testbar.
- Ersatz von B durch ein dekoriertes B nicht möglich
-
eine Instanz von B als Parameter vom Konstruktor erhalten, wobei gilt, dass B die Schnittstelle IB implementiert, und A
letztendlich von dieser Schnittstelle abhängt:
class A{ IB b; A(IB b) { this.b = b; }} var a = new A(new B())
Vorteile:- Die originale Klasse B kann zu Testzecken durch die Klasse BMockUp ersetzt werden, die ebenfalls die Schnittstelle IB implementiert
- A funktioniert auch mit belibigen Dekoratoren von IB, solange das Liskovsche Substitutionsprinzip erfüllt ist.