Prinzipien objektorientierten Designs
# Prinzipien objektorientierten Designs

Prinzipien objektorientierten Designs sind Prinzipien, die zu gutem objektorientierten Design führen sollen. Sie wurden neben anderen von Robert C. Martin, Bertrand Meyer und Barbara Liskov publiziert und propagiert. Viele Techniken der Objektorientierung wie Entwurfsmuster, Domain-driven Design oder Dependency Injection basieren auf diesen Prinzipien objektorientierten Designs.

Für eine Gruppe dieser Prinzipien wurde von Robert C. Martin das Akronym “SOLID” geprägt. Diese Prinzipien gemeinsam angewandt führt laut Robert C. Martin zu einer höheren Wartbarkeit und somit Lebensdauer von Software. Diese Prinzipien sind das “Single Responsibility Prinzip”, das “Open-Closed Prinzip”, das “Liskovsches Substitutionsprinzip”, das “Interface Segregation Prinzip” und das “Dependency Inversion Prinzip”.

Darüber hinaus gibt es noch eine Reihe weiterer mehr oder weniger bekannter Prinzipien objektorientierten Designs wie Design by contract oder das Gesetz von Demeter. Eine Sonderstellung unter den Prinzipien objektorientierten Designs haben die sogenannten Packaging Prinzipien, da diese sich alle mit der Gruppierung von Klassen zu Packages beschäftigen.

## SOLID-Prinzipien

### Single-Responsibility-Prinzip

Das Single-Responsibility-Prinzip besagt, dass jede Klasse nur eine einzige Verantwortung haben solle. Verantwortung wird hierbei als „Grund zur Änderung“ definiert:

   “There should never be more than one reason for a class to change.”
   „Es sollte nie mehr als einen Grund dafür geben, eine Klasse zu ändern.“

Mehr als eine Verantwortung für eine Klasse führt zu mehreren Bereichen, in denen zukünftige Änderungen notwendig werden können. Die Wahrscheinlichkeit, dass die Klasse zu einem späteren Zeitpunkt geändert werden muss, steigt zusammen mit dem Risiko, sich bei solchen Änderungen subtile Fehler einzuhandeln. Dieses Prinzip führt in der Regel zu Klassen mit hoher Kohäsion, in denen alle Methoden einen starken gemeinsamen Bezug haben.

### Open-Closed-Prinzip

Das Open-Closed-Prinzip besagt, dass Software-Einheiten (hier Module, Klassen, Methoden usw.) Erweiterungen möglich machen sollen (dafür offen sein), aber ohne dabei ihr Verhalten zu ändern (ihr Sourcecode und ihre Schnittstelle sollte sich nicht ändern). Es wurde 1988 von Bertrand Meyer folgendermaßen formuliert:

   “Modules should be both open (for extension) and closed (for modification).”
   „Module sollten sowohl offen (für Erweiterungen), als auch geschlossen (für Modifikationen) sein.“

Eine Erweiterung im Sinne des Open-Closed-Prinzips ist beispielsweise die Vererbung. Diese verändert das vorhandene Verhalten einer Klasse nicht, erweitert sie aber um zusätzliche Funktionen oder Daten. Überschriebene Methoden verändern auch nicht das Verhalten der Basisklasse, sondern nur das der abgeleiteten Klasse. Folgt man weiter dem Liskovschen Substitutionsprinzip, verändern auch überschriebene Methoden nicht das Verhalten, sondern nur die Algorithmen.

### Liskovsches Substitutionsprinzip

Das Liskovsche Substitutionsprinzip (LSP) oder Ersetzbarkeitsprinzip fordert, dass eine Instanz einer abgeleiteten Klasse sich so verhalten muss, dass jemand, der meint, ein Objekt der Basisklasse vor sich zu haben, nicht durch unerwartetes Verhalten überrascht wird, wenn es sich dabei tatsächlich um ein Objekt eines Subtyps handelt. Es wurde 1993 von Barbara Liskov und Jeannette Wing formuliert. In einem nachfolgenden Artikel wurde es folgendermaßen formuliert:

   “Let $q(x)$ be a property provable about objects $x$ of type $T$. Then $q(y)$ should be true for objects $y$ of type $S$ where $S$ is a subtype of $T$.”
   „Sei $q(x)$ eine Eigenschaft des Objektes $x$ vom Typ $T$, dann sollte $q(y)$ für alle Objekte $y$ des Typs $S$ gelten, wobei $S$ ein Subtyp von $T$ ist.“

Damit ist garantiert, dass Operationen vom Typ Superklasse, die auf ein Objekt des Typs Subklasse angewendet werden, auch korrekt ausgeführt werden. Dann lässt sich stets bedenkenlos ein Objekt vom Typ Superklasse durch ein Objekt vom Typ Subklasse ersetzen. Objektorientierte Programmiersprachen können eine Verletzung dieses Prinzips, die aufgrund der mit der Vererbung verbundenen Polymorphie auftreten kann, nicht von vornherein ausschließen. Häufig ist eine Verletzung des Prinzips nicht auf den ersten Blick offensichtlich.

### Interface-Segregation-Prinzip

Das Interface-Segregation-Prinzip dient dazu, zu große Interfaces aufzuteilen. Die Aufteilung soll gemäß den Anforderungen der Clients an die Interfaces gemacht werden – und zwar derart, dass die neuen Interfaces genau auf die Anforderungen der einzelnen Clients passen. Die Clients müssen also nur mit Interfaces agieren, die das und nur das können, was die Clients benötigen. Das Prinzip wurde von Robert C. Martin 1996 folgendermaßen formuliert:

   “Clients should not be forced to depend upon interfaces that they do not use.”
   „Clients sollten nicht dazu gezwungen werden, von Interfaces abzuhängen, die sie nicht verwenden.“

Mit Hilfe des Interface-Segregation-Prinzips ist es möglich eine Software derart in entkoppelte und somit leichter refaktorisierbare Klassen aufzuteilen, dass zukünftige fachliche oder technische Anforderungen an die Software nur geringe Änderungen an der Software selbst benötigen.

### Dependency-Inversion-Prinzip

Das Dependency-Inversion-Prinzip beschäftigt sich mit der Reduktion der Kopplung von Modulen. Es besagt, dass Abhängigkeiten immer von konkreteren Modulen niedriger Ebenen zu abstrakten Modulen höherer Ebenen gerichtet sein sollten. Es wurde von Robert C. Martin erstmals im Oktober 1994 beschrieben und später folgendermaßen formuliert:

   “A. High-level modules should not depend on low level modules. Both should depend on abstractions. B. Abstractions should not depend upon details. Details should depend upon abstractions.”
   „A. Module hoher Ebenen sollten nicht von Modulen niedriger Ebenen abhängen. Beide sollten von Abstraktionen abhängen. B. Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen.“

Damit ist sichergestellt, dass die Abhängigkeitsbeziehungen immer in eine Richtung verlaufen, von den konkreten zu den abstrakten Modulen, von den abgeleiteten Klassen zu den Basisklassen. Damit werden die Abhängigkeiten zwischen den Modulen reduziert und insbesondere zyklische Abhängigkeiten vermieden.

## Weitere Prinzipien

Neben der von Robert C. Martin propagierten SOLID-Gruppe von Prinzipien Objektorientierten Designs sind noch folgende Prinzipien als Prinzipien Objektorientierten Designs bekannt:

### Gesetz von Demeter

Das Gesetz von Demeter (englisch: Law of Demeter, kurz: LoD) besagt im Wesentlichen, dass Objekte nur mit Objekten in ihrer unmittelbaren Umgebung kommunizieren sollen. Dadurch soll die Kopplung in einem Softwaresystem verringert und dadurch die Wartbarkeit erhöht werden.

Das Gesetz wurde von Karl J. Lieberherr und Ian Holland 1989 im Paper Assuring Good Style for Object-Oriented Programs folgendermaßen beschrieben:

   “A supplier object to a method M is an object to which a message is sent in M. The preferred supplier objects to method M are: The immediate parts of self or the argument objects of M or the objects which are either objects created directly in M or objects in global variables. Every supplier object to a method must be a preferred supplier.”
   „Ein Anbieterobjekt der Methode M ist ein Objekt, an welches aus M eine Nachricht gesendet wird. Die bevorzugten Anbieterobjekte der Methode M sind: Die unmittelbaren Bestandteile des Objekts von M, Objekte, die M als Argumente übergeben wurden und Objekte, die entweder direkt in M erzeugt wurden oder sich in globalen Variablen befinden. Jedes Anbieterobjekt einer Methode muss ein bevorzugtes Anbieterobjekt sein.“

Durch die formale Spezifikation lässt sich das Gesetz von Demeter leicht als automatisch geprüfte Softwaremetrik umsetzen. Es bietet sich somit zur Früherkennung von Kopplungsproblemen an.

### Design by Contract

Das Design-by-contract-Prinzip, englisch für Entwurf gemäß Vertrag, auch Programming by Contract genannt, hat das reibungslose Zusammenspiel einzelner Programmmodule durch die Definition formaler „Verträge“ zur Verwendung von Schnittstellen, die über deren statische Definition hinausgehen zum Ziel. Entwickelt und eingeführt wurde es von Bertrand Meyer mit der Entwicklung der Programmiersprache Eiffel.

Das reibungslose Zusammenspiel der Programmmodule wird durch einen „Vertrag“ erreicht, der beispielsweise bei der Verwendung einer Methode einzuhalten ist. Dieser besteht aus

* Vorbedingungen (englisch precondition), also den Zusicherungen, die der Aufrufer einzuhalten hat
* Nachbedingungen (englisch postcondition), also den Zusicherungen, die der Aufgerufene einhalten wird, sowie den
* Invarianten (englisch class invariants), quasi dem Gesundheitszustand einer Klasse.

Der Einsatz des Design-by-Contract-Prinzips führt zu sicherer und weniger fehleranfälliger Software, da eine Verletzung des Vertrages zu einem sofortigen Fehler (Fail-Fast) bereits in der Softwareentwicklungsphase führt. Weiters kann die explizite Definition des Vertrages als Form der Dokumentation des Verhaltens des entsprechenden Moduls gesehen werden. Das wiederum führt zu einer besseren Erlernbarkeit und Wartbarkeit der Software.

### Datenkapselung

Datenkapselung (engl. encapsulation), nach David Parnas auch bekannt als information hiding, ist nach Bertrand Meyer ein weiteres Prinzip objektorientierten Designs. Es beschreibt das Verbergen von Daten oder Informationen vor dem Zugriff von außen. Der direkte Zugriff auf die interne Datenstruktur wird unterbunden und erfolgt stattdessen über definierte Schnittstellen. Die in der Objektorientierung verwendeten Zugriffsarten (private, protected, …) sowie eine Reihe von Entwurfsmustern – beispielsweise Facade – unterstützen bei der Umsetzung der Datenkapselung.

### Linguistic-Modular-Units-Prinzip

Das Linguistic-Modular-Units-Prinzip (englisch für Prinzip linguistisch-modularer Einheiten) besagt, dass Module durch syntaktische Einheiten der verwendeten Sprache – sei es eine Programmier-, Design- oder Spezifikationssprache – repräsentiert werden müssen. Im Falle einer Programmiersprache müssen Module durch separat voneinander kompilierbare Einheiten dieser gewählten Programmiersprache repräsentiert werden:

   “Modules must correspond to syntactic units in the language used.”
   „Module müssen den syntaktischen Einheiten der verwendeten Sprache entsprechen.“

### Self-Documentation-Prinzip

Das Self-Documentation-Prinzip (englisch für Selbstdokumentierungsprinzip) besagt, dass alle Informationen zu einem Modul in diesem selbst enthalten sein sollten. Damit wird gefordert, dass der Code entweder selbst für sich spricht (d. h. keine weitere Dokumentation nötig hat) beziehungsweise die technische Dokumentation möglichst nahe beim Sourcecode (beispielsweise bei der Verwendung von Javadoc) liegt. Dadurch wird einerseits gewährleistet, dass die technische Dokumentation dem Code entspricht und andererseits, dass die Komplexität des Codes so gering ist, dass keine komplexe Dokumentation nötig ist:

   “The designer of a module should strive to make all information about the module part of the module itself.”
   „Beim Entwurf eines Modules sollte man danach trachten, dass alle Informationen über das Modul selbst Teil des Moduls sind.“

### Uniform-Access-Prinzip

Das Uniform-Access-Prinzip (englisch für Prinzip des gleichartigen Zugriffes) fordert, dass auf alle Services eines Modules mittels einer gleichartigen Notation zugegriffen werden kann – ohne dass dadurch preisgegeben wird, ob die Services dahinter auf Datenbestände zugreifen, oder Berechnungen durchführen. Services sollten also nach außen nicht bekanntgeben, wie sie ihre Serviceleistung erbringen, sondern nur was ihre Serviceleistung ist:

   “All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation.”
   „Alle Services eines Moduls sollten mittels einer gleichartigen Notation angeboten werden; eine Notation, welche nicht preisgibt, ob sie über Datenzugriffe oder Berechnungen umgesetzt wurden.“

### Single-Choice-Prinzip

Das Single-Choice-Prinzip besagt, dass verschiedene Alternativen (beispielsweise von Daten oder Algorithmen) in einem Softwaresystem in nur einem einzigen Modul abgebildet werden sollten. Beispielsweise unterstützt das Entwurfsmuster Abstrakte Fabrik dieses Prinzip, indem in der Fabrik einmal aus einem Set an Alternativen entschieden wird, welche Klassen und welche Algorithmen zu verwenden sind. Diese Entscheidung muss an keiner weiteren Stelle im Programm mehr getroffen werden:

   “Whenever a software system must support a set of alternatives, one and only one module in the system should know their exhaustive list.”
   „Immer wenn eine Software unterschiedliche Alternativen unterstützen muss, sollte es genau ein Modul geben, welches die vollständige Liste der Alternativen kennt.“

### Persistence-Closure-Prinzip

Das Persistence-Closure-Prinzip besagt, dass Persistenzmechanismen Objekte mit all ihren Abhängigkeiten speichern und auch wieder laden müssen. Damit ist sichergestellt, dass Objekte ihre Eigenschaften durch Speichern und darauffolgendes Laden nicht verändern.

   “Whenever a storage mechanism stores an object, it must store with it the dependents of that object. Whenever a retrieval mechanism retrieves a previously stored object, it must also retrieve any dependent of that object that has not yet been retrieved.”
   „Wenn ein Speichermechanismus ein Objekt speichert, muss es auch dessen Abhängigkeiten speichern. Wenn ein Lademechanismus ein vorher gespeichertes Objekt lädt, muss er auch dessen bisher noch nicht geladenen Abhängigkeiten laden.“

### Command-Query-Separation-Prinzip

Das Command-Query-Separation-Prinzip (CQS) besagt, dass eine Methode entweder als Abfrage (query) oder als Kommando (command (auch modifier oder mutator genannt)) implementiert werden soll. Eine Abfrage muss hierbei Daten zurückliefern und darf keine Nebeneffekte auf dem beobachtbaren Zustand des Systems aufweisen, während ein Kommando beobachtbare Nebeneffekte aufweist und keine Daten zurückliefert.

   “Functions should not produce abstract side effects…only commands (procedures) will be permitted to produce side effects.”
   „Funktionen sollten keine Nebenwirkungen haben … nur Kommandos (Prozeduren) dürfen Nebenwirkungen haben.“

### Principle of Least Surprise

Das Principle of Least Surprise (deutsch Prinzip der geringsten Überraschung) ist eigentlich ein Prinzip in der Software-Ergonomie, der Mensch-Computer-Interaktion und dem Interfacedesign. Es wird aber oft auch auf den Quellcode erweitert und betrifft im objektorientierten Design die Benennung von Variablen, Funktionen, Methoden, Parameter, Klassen und dergleichen. Sie sollen derart benannt werden, dass deren Funktion und mögliche Seiteneffekte alleine aus dem Namen klar erkenntlich sind.

## Packaging-Prinzipien

Die folgenden von Bertrand Meyer und Robert C. Martin definierten Prinzipien beschäftigen sich mit der Frage, wie man Klassen zu Modulen (Packages) zusammenführen sollte. Sie führen insbesondere zu einer hohen Kohäsion innerhalb der Module und geringen Kopplung zwischen den Modulen.

### Reuse-Release-Equivalence-Prinzip

   “Either all of the classes in a package are reusable or none of them are.”
   „Entweder sind alle oder gar keine Klassen eines Packages wiederverwendbar.“

Das Reuse-Release-Equivalence-Prinzip bezieht sich auf die Struktur von Packages im Sinne eines Moduls als kleinste Einheit eines Releases. Es gibt vor, dass sich Packages nur aus Klassen zusammensetzen dürfen, von denen entweder alle, oder gar keine zur Wiederverwendung gedacht sind.

Handelt es sich um ein wiederverwendbares Package, fordert das Prinzip zum einen, dass alle Änderungen nachvollzogen werden können und der Versionsverlauf zurückverfolgt werden kann, und zum anderen, dass nur Klassen enthalten sind, die auch für dieselbe Benutzergruppe gedacht sind (ein Benutzer, der eine Container-Class-Library wiederverwenden will, benötigt kein Finanz-Framework).

### Common-Closure-Prinzip

Das Common Closure Prinzip fordert, dass alle Klassen in einem Modul gemäß dem Open-Closed-Prinzip geschlossen gegenüber derselben Art von Veränderungen sein sollten. Änderungen an den Anforderungen einer Software, welche Änderungen an einer Klasse eines Moduls benötigen, sollten auch die anderen Klassen des Moduls betreffen. Die Einhaltung dieses Prinzips ermöglicht es die Software derart in Module zu zerlegen, dass (zukünftige) Änderungen in nur wenigen Modulen umgesetzt werden können. Damit ist sichergestellt, dass Änderungen an der Software ohne große Seiteneffekte und somit relativ kostengünstig gemacht werden können.

   “The classes in a package should be closed together against the same kinds of changes. A change that affects a package affects all the classes in that package.”
   „Klassen eines Packages sollten gemeinsam geschlossen gegenüber denselben Arten von Veränderung sein. Eine Änderung, die ein Package betrifft, sollte alle Klassen dieses Packages betreffen.“

### Common-Reuse-Prinzip

Das Common-Reuse-Prinzip beschäftigt sich mit der Verwendung von Klassen. Es besagt, dass diejenigen Klassen, welche gemeinsam verwendet werden, auch gemeinsam zu einem Modul zusammengefasst werden sollten. Durch die Einhaltung dieses Prinzips wird eine Unterteilung der Software in fachlich bzw. technisch zusammengehörende Einheiten sichergestellt.

   “The classes in a package are reused together. If you reuse one of the classes in a package, you reuse them all.”
   „Die Klassen eines Packages sollten gemeinsam wiederverwendet werden. Wenn man eine Klasse eines Packages wiederverwendet, sollte man alle Klassen des Packages wiederverwenden.“

### Acyclic-Dependencies-Prinzip

Das Acyclic-Dependencies-Prinzip fordert, dass die Abhängigkeiten zwischen Modulen zyklenfrei sein müssen. D. h. wenn Klassen in einem Modul von anderen Klassen in einem anderen Modul beispielsweise durch Vererbungs- oder Relationsbeziehungen abhängen, dann dürfen keine Klassen des anderen Moduls direkt oder indirekt von Klassen des ersteren Moduls abhängen.

   “The dependency structure between packages must be a directed acyclic graph (DAG). That is, there must be no cycles in the dependency structure.”
   „Die Abhängigkeiten zwischen Packages müssen einem gerichteten azyklischen Graphen entsprechen. Das bedeutet, dass es keine Zyklen in der Abhängigkeitsstruktur geben darf.“

Derartige Zyklen kann man immer aufbrechen. Dafür gibt es prinzipiell zwei Möglichkeiten:

* Anwendung des Dependency-Inversion-Prinzips: Wenn die Abhängigkeit von Package A auf Package B invertiert werden soll, so führe im Package von A Interfaces ein, welches die von B benötigten Methoden hält. Implementiere diese Interfaces in den entsprechenden Klassen von Package B. Somit wurde die Abhängigkeit zwischen Package A und B invertiert.
* Restrukturierung der Packages: Sammle alle Klassen eines Zyklus in einem eigenen Package zusammen und führe ein oder mehrere neue Packages ein, befüllt mit den Klassen von denen die Klassen außerhalb des Zyklus abhängen

### Stable-Dependencies-Prinzip

Das Stable-Dependencies-Prinzip besagt, dass die Abhängigkeiten zwischen Modulen in Richtung der größeren Stabilität der Module gerichtet sein sollten. Ein Modul sollte also nur von Modulen abhängig sein, welche stabiler sind als es selbst.

   “The dependencies between packages in a design should be in the direction of the stability of the packages. A package should only depend upon packages that are more stable that it is.”
   „Die Abhängigkeiten zwischen Packages sollten in derselben Richtung wie die Stabilität verlaufen. Ein Package sollte nur von Packages abhängen, welche stabiler als es selbst sind.“

Unter Stabilität versteht man hier das umgekehrte Verhältnis der ausgehenden Abhängigkeiten zur Summe aller Abhängigkeiten. Je weniger Abhängigkeiten ein Modul zu anderen hat und je mehr Abhängigkeiten andere Module zu diesem Modul haben, umso stabiler ist das Modul. Die Stabilität berechnet sich indirekt über die Instabilität folgendermaßen:

$I={\frac {Aa}{(Ae+Aa)}}$ I … Instabilität eines Moduls Ae … eingehende Abhängigkeiten (englisch afferent couplings) Aa … ausgehende Abhängigkeiten (efferent couplings)

wobei sich $Ae$ und $Aa$ folgendermaßen berechnen:

* $Ae$ = Klassen außerhalb des Moduls, welche von Klassen innerhalb des Moduls abhängen
* $Aa$ = Klassen des Moduls, welche von Klassen außerhalb des Moduls abhängen

Die Instabilität liegt im Bereich von 0 bis 1, eine Instabilität von 0 weist auf ein maximal stabiles Modul hin, eine von 1 auf ein maximal instabiles Modul.

### Stable-Abstractions-Principle

Das Stable-Abstractions-Prinzip fordert, dass die Abstraktheit eines Moduls direkt proportional zu seiner Stabilität sein muss.

   “Packages that are maximally stable should be maximally abstract. Instable packages should be concrete. The abstraction of a package should be in proportion to its stability.”
   „Packages die maximal stabil sind sollten maximal abstrakt sein. Instabile Packages sollten konkret sein. Die Abstraktheit eines Packages sollte proportional zu seiner Stabilität sein.“

Je abstrakter ein Modul ist – d. h. je mehr Interfaces, abstrakte Klassen und Methoden es hat –, desto stabiler sollte es sein. Generell errechnet sich die Abstraktheit eines Moduls folgendermaßen:

$A={\frac {Ka}{K}}$ A … Abstraktheit eines Moduls Ka … Anzahl abstrakter Klassen eines Moduls K … Gesamtanzahl der Klassen eines Moduls

Für jedes Modul lässt sich die Distanz zur idealen Linie – englisch Main Sequence genannt – zwischen maximaler Stabilität und Abstraktheit und maximaler Instabilität und Konkretheit errechnen. Diese reicht von 0 bis ≈0,707:

$D={\frac {A+I-1}{\sqrt {2}}}$ D … Distanz zur idealen Linie A … Abstraktheit eines Moduls I … Instabilität eines Moduls

Je größer die Distanz ist, desto schlechter ist das Stable-Abstractions-Prinzip erfüllt.

