Liebe Freunde der gepflegten Programmierung
Um dieses Lernvideo zu verstehen sollten Sie grundsätzliche Erfahrung in objekt orientierter Programmierung haben
Begriffe wie Klassen, Schnittstellen, Vererbung, Polyphormismus, abstrakte sollten keine Fremdwörter sein. Einfache UML Diagramme wie Anwendungsfälle (UseCases) und Klassendiagramme sollten Sie lesen können. Die Beispiele sind in Java aber sicher auch verständlich von Programmierer anderer objekt orientierter Programmiersprachen
Prinzipien sind keine Gesetze sondern Richtlinien. Die Einhaltung von Anforderungen schlagen z.B. die Prinzipien. Der Preis der losen Kopplung ist häufig ein etwas langsameres Laufzeitverhalten. Je flexibler und konfigurierbarer ein System ist, umso schwerer wird die Testbarkeit. Dann ist es natürlich ein Unterschied ob es sich um ein Prototyp handelt, der eh bald weggeschmissen wird, oder ein Softwaresystem das im Optimum Jahrzehnte laufen soll
Es gibt nie den optimalen Code und immer nur Lösungen mit mehr oder weniger Nachteilen. Gibt es vernünftige Gründe solle man den Mut haben gegen ein Prinzip zu ignorieren. Allerdings kann Ich garantieren dass man absolut schwer wartbaren Code erhält, wenn man generell sich nicht an die Prinzipien orientiert
Das SRP besagt, dass es nur einen Grund geben darf eine Klasse zu ändern. Es führt meist zu kleinen Klassen, mit hohen Grad an Wiederwendung
Das OCP nutzt Vererbung und Polymorphismus um Programme so zu gestalten, dass diese offen für Erweiterungen sind und geschlossen gegenüber Änderungen. Es ist die Illusion dass bei Änderungen der Code einfach erweitert werden kann aber nicht gändert werden muss
Das Liskov Substitution Prinzip, zeigt die Gefahren durch falsche Verwendung von Vererbung. Für mich war es das Prinzip mit dem höchsten Überraschungseffekt. Sie werden lernen warum zumindest aus IT-Sicht ein Quadrat kein Rechteck ist, bzw. ein Tesla kein Auto
Das ISP wird häufig als SRP für Schnittstellen bezeichnet. Es geht darum passgenaue Schnittstellen zu erzeugen. D.h. eine Schnittstelle sollte nicht mehr Parameter erzeugen, als ein Client benötigt
Als Softwarearchitekt bewerten sie Software durch Analyse der Abhängigkeiten. Das DIP besagt dass ein stabiles Teil nie abhängig von einem instabilen Teil sein soll.
Eigentlich könnte Ich jetzt mit dem Vortrag aufhören. Das Wesentliche ist gesagt. Allerdings war das jetzt extrem kurz und ggf. unverständlich. Es folgen noch ca. 40 Folien
Diese Folie liegt einer sehr persönlichen Einschätzung zu Grunde. Das SRP ist in meinen Augen das wichtigste Prinzip. Das Open Closed Prinzip ist sehr umstritten. Es gibt in der Literatur sogar Einsschätzungen dass dieses Prinzip sogar schädlich ist. Ich teile diese Kritik. Die anderen Prinzipien sehe ich als etwa gleich bedeudent an.
Dass ich das SRP extrem ausführlich behandle hat zwei Gründe. Der erste Grund ist die Wichtigkeit dieses Prinzips. Der zweite Grund ist dass Ich zur Vorbereitung dieses Lernvideos eine böse Überraschung erlbebte, und bemerkte dass Robert Cecile Martin etwas anderes meinte, als ich es jahrelang gelehrt hatte. Dies liegt u.a. daran dass das SRP in der Literatur falsch wiedergegeben wird
Die erste Folie zeigt die Definitionen von Robert Cecile Martin und die Definition nach meinem bisherigen Verständnis.
Danach folgt ein Gegenbeispiel das gegen beide Verständisse verstösst
Ich verweise danach auf ein Beispiel das Robert Cecile Martin in seinem Buch Clean Architecture verwendet hat, und die Unterschiede beider Sichtweisen erläutert
Die Refactoring Methoden Extract und Inline Class zeigen die Vorgehensweise falls eine Klasse zu viel oder zu wenig Verantwortung hat
Ich erwähne absichtlich das MethodObject Pattern, das Ich schon aus Smalltalk Zeiten von Kent Beck kennen gelernt hatte. Manchmal ist es aufgrund der Verwendung von temporären Variablen extrem schwierig Code in eine eigene Klasse zu extrahieren. Das Method Object Pattern ist ein rein schematischer und automatisierter Vorgang um eine Methode einfach in eine neue Klasse umzuwandeln. Da durch diesese Vorgehensweise aus den temporären Variablen Membervariablen werden, wird danach das Refactoring der neu erzeugten Klasse zum Kinderspiel. Zur Erinnerung: Refactoring ist der Fachbegriff für das Umgestalten von Code, so dass sich zwar die Funkionalität nicht ändert - aber der Code wartbarer wird
Ich dachte immer das SRP wäre identisch mit dem Prinzip Separation of Concerns. Eine Klasse sollte eine und genau eine Verantwortlichkeit haben. Dies führt zu vielen kleinen Klassen, mit einem hohen Grad der Wiedervwendung. Die Methoden dieser Klassen, dienen alle dazu diese Aufgaben zu erfüllen. Ohne Zweifel ist dies ein iwichtiges Prinzip und es ist dem SRP, wie Martin Fowler es propagiert, sehr ähnlich.
Die ursprüngliche Definition ist aber dass es immer nur einen Grund geben darf eine Klasse zu ändern. Der entscheidende Unterschied werde Ich ab der übernächsten Folie erläutern
Dies sind die Methoden einer Klasse Mitarbeiter und ein typischens Beispiel einer Klasse, die zu viel Verantwortung hat.
Häufig sind Erzeugung und Gebrauch einer Klasse unterschiedliche Aspekte. Sobald Constructor Methoden mehr machen, als die Membervariablen mit den Parametern zu initialisieren, ist für mich dies ein Indiz die Erzeugung in einer eigenen Factory Klasse zu verlagern, deren einzige Aufgabe es ist die entsprechende Instanz zu erzeugen. Dabei sollte man sich fragen, ob es Sinn macht die Erzeugung explizit zu testen. Wenn es Sinn macht, macht es auch Sinn eine eigene Klasse zu kreiren
Das Auslesen und Sichern eines Mitarbeiters in XML Dateien sind i.a. eigene Aspekte und gehören in eigene Klassen.Dasselbe gilt für Auslesen und Speichern der Daten in Dateien. Auch ist muss i.a. ein Mitarbeiter nicht wissen, wie er in Reports dargestellt wird.
Ein einfacher Trick ist es zu analyisieren, wlche Instanzvariablen zu analyiseren die eine Methode verwendet. Greifen Methoden immer auf dieselben Teilmenge von Instanzvariablen zu, ist dies ein Indiz daraus eine eigene Klasse zu machen. Greifen Methoden auf Instanzvariablen anderer Klassen zu, ist es ein Indiz eine Methode zu verschieben. Es folgt wenig später ein Beispiel mit einem Mitarbeiter und Telefon an der ich das verdeutlichen kann.
Ich weiss dass Oracle selber mit der Java Persistence API und den Annotations gegen dieses Prinzip verstoesst. Da werden Annotations in den Domain Objekten eingefuegt. Diese Annotations werden von einem Framework, zum Lesen und Speichern i.a. in eine Datenbank, analyisiert. Ich bin unglücklich über diese Technologien, denn eine Entscheidung die Information in anderen Tabellen abzuspeichern, erzwingen Änderungen an den Geschäftsobjekten. Allerdings erlaubt auch die Java Persistance API die Auslagerung der Speichertransformation in XML Dateien. Damit haben wir eine weitgehende Entkopplung der Domainobjekte mit der Datenbankstruktur. Wie es in der Softwarebranche häufig der Fall ist, so ist die schnelle Lösung häufig nicht die beste und rächt sich bei notwendigen zukünftigen Änderungen.
Es folgt nun ein Beispiel direkt aus dem Buch Clean Architecture von Robert Cecil Martin, das den Unterschied beider Sichtweisen verdeutlicht. Jetzt kann Ich schlecht behaupten dass meine bisherige Sichtweise im Sinne von Seperation von Concerns die Richtigere ist, denn das SRP kommt direkt von Robert Cecile Martin, auch als Uncle Bob bekannt ist. Ich muss gestehen dieses Anwendungsfalldiagramm habe ich selber gezeichnet und kommt nicht von seinem Buch.
Wir haben 3 verschiedene Akteure. Derr Betreiber der die Mitarbeiter speichert ist für die folgende Diskussion unerheblich. Bleibt die Personalabteilung und die Lohnbuchhaltung, die beide für Ihre Aufgaben ermitteln wieviel Stunden die Mitarbeiter gearbeitet haben
Jetzt empfielhlt Robert Cecil Martin zu meiner Überraschung den Code berechne Stunden doppelt zu halten. Damit riskiert er natürlich dass die Berechnung der Stunden bei Lohnbuchhaltung und Personalabteilung auseinander läuft. Dies ist ohne Zweifel dann sinnvoll, wenn eine Unterschiedliche Interpretation kein Problem darstellt.Gibt es keinen Bedarf die Anforderungen abzustimmen, so ist eine Verdoppelung des Codes sinnvoll. Auch wenn es in der Software das DRY Prinzip gibt, das Don't repeat yourself heisst.Gibt es in der Lohnbuchhaltung eine Änderung der Anforderung, die dem Personalbüro nichts angeht, ist eine Verdoppelung des Codes nicht nur zu rechtfertigen, sondern auch angebracht
Jetzt gibt es aber Stakeholder wie z.B. Gewerkschaft oder Betriebsrat, die eine unterschiedliche Berechnung der Stunden unbedingt vermeiden wollen.Nach Robert Cecil Martin ist jede Anforderung genau einem Akteur zugeordnet. Vielleicht liegt die Lösung darin, dass Robert Cecile Martin bei einer notwendigen Abstimmung der Anforderung einen dritten Akteur einführen würde
Ich persönlich habe Schwierigkeiten mit der Sichtweise von Robert Cecil Martin, denn eine Abstimmung der Anforderungen zwischen verschiedenen Stakeholdern ist heutzutage die Normalität.
Es ist auch ein gutes Besipiel, das zeigt dass sich Prinzipien auch häufig gegenseitig widersprechen. Der Autor empfiehlt in diesem Beispiel explizit auf das DRY Prinzip zu verzichten.
Jetzt wirken hoffentlich gesetzliche Änderungen auch auf die Stundenberechnung aus. Ich weiss es gibt die Tendenz gesetzliche Änderungen einfach zu ignorieren, oder im eigenen Sinne umzuinterpretieren. Aer das soll nicht Gegenstand des Vortrags sein. Bei neuen gesetzlichen Bestimmungen müsste sowohl die Stundenberechnung des Personalbüros, als auch die der Finanzbuchhaltung geändert werden.
Gerade die Digitalisierung und die Plattformökonomie ändern die Arbeitswelt stark, und kein Achitekt kann in die Zukunft achauen. Es gibt wahrscheinlich keine optimale Architektur und falls es diese geben sollte, ändert sie sich mit der Zeit - da sich die Welt ändert.Ich beobachte dass die Welt sich ändert, aber grundsätzliche Optimierungen an der Softwararchitekur bleiben trotzdem aus.Es ist abzusehen dass die Defintion was Arbeitsstunden sind, sich in Zukunft noch häufig angepasst werden müsste.
In den vorherigen Folien habe ich das SRP und die unterschiedlichen Sichtweisen erläutert, jetzt geht es um Techniken wie man das umsetzt. Das folgende Beispiel stammt aus einem Kurs den ich um die Jahrtausendwende gehalten hatte. Die Frage ist ob man Mitarbeiter und Telefon in einer einzigen Klasse oder in zwei aufspalten soll. Wer mich kennt weiss dass Ich selten Fragen stelle, die eine eindeutige Lösung haben.
Jede Lösung hat seine Vor- und Nachtile. Wer das Buch über Refactoring von Martin Fowler studiert, wird wahrscheinlich bemerken dass es zu jeder Refactoring Methode eine Gegenoperation gibt. Erst eine Analyse der Methoden gibt Hinweise darauf ob es sinnvoll ist, ein oder zwei Klassen zu verwenden. Ich sage absichtlich Hinweise, denn ich will die Entscheidung nicht automatisieren. Manchmal hat Wartbarkeit eine hohe Priorität, manchmal ist es Performance oder Sicherheit.
Man sollte die Methoden der Klasse beobachten und analysieren auf welche Instanzvariablen sie zugreifen. Dabei sollten Klassen die nur setter und getter Methoden haben, aber ansonsten nichts machen vermeiden. Hat man dagegen nur eine Klasse und Methoden die sich nur auf eine Teilmenge der Instanzvariablen zugreifen, so deutet es darauf hin dass eine Aufspaltung in mehrere Klassen sinnvoll sind.
Hat man sich z.B. für 2 Klassen entschieden und in der Klasse Mitarbeiter wahle. Diese Methode benutzt die Instanzvariable aus der Klasse Telefon. Dann ist es sinnvoll die Methode waehle in die Klasse Telefon zu verschieben und in die alte Methode waehle einfach so umzuschreiben, dass sie die Aufgabe an des Waehlens an die Klasse Telefon delegiert.
Diese Folie beschreibt im Wesentlichen nur das was Ich in der letzten Folie erwähnt hatte. Mir ist der Verweis auf das Refactoring Buch von Martin Fowler sehr wichtig. Ich halte dieses Buch für mit das wichtigste Buch für Programmierer. Refactoring ist der Fachbegriff für das Umgestalten des Codes, ohne dass sich dabei das Verhalten von Software ändert. Häufig verschlechtert sich sogar etwas das Laufzeitverhalten. Für Menschen die rein betriebswirtschaflich denken ist dies erst einmal nur verschwendete Arbeitszeit. Das Ziel der Refaktorisierungen ist aber die Wartbarkeit zu erhöhen. Es ist also eine Investition in die Zukunft. Als Softwareentwickler denke Ich anders als Betriebswirte. Für mich ist fehlerhafter Code der leicht wartbar ist, deutlich lieber als fehlerfreier Code der extrem schwer zu warten ist. Das Wesentliche an Software ist das Wort Soft. Das Wesentliche ist nun einmal die Änderbarkeit. Softwaresysteme leben häufig über Jahrzehnte und da sich Technologie und Anforderung immer schneller und stärker ändern, muss auch die Software anpassbar sein.
Dies zeigt ein anderes Szenarion das danach schreit eine Klasse aufzuspalten. Im vorherigen Beispiel habe ich die Methoden untersucht. Im jetzigen Beispiel analysiere Ich die konkreten Instanzen der Klasse. Ich sehe dass viele Instanzen zu dieser Klasse nicht passen. Es gibt Methoden, die nicht zu allen Instanzen passen. Die Geschäftsführer müssen i.a. niemanden um einen Urlaub bitten. Meine Wenigkeit und ich denke Susi Tippse beantragt ihren Urlaub, hat aber nicht das Recht Urlaub zu genehmigen.
Da die Klasse nicht zu den Instanzen passt, schreit dies danach diese Klasse aufzuspalten. Der Versuch einer Lösung kommt auf der nächsten Folie
Diese Lösung zeigt einen Versuch mit diesem Problem umzugehen. Es werden passgenaue Klassen erzeugt, die für die jeweiligen Mitarbeiter passen. Die Geschäftsführung genehmigt Urlaube aus dem mittleren Management, muss aber niemanden um Urlaub bitten. Das mittlere Management beantragt den eigenen Urlaub bei der Geschäftsführung und genehmigt den Urlaub aus den Mitarbeiter Ihrer Abteilungen. Ich kann Ihnen versichern dass meine Wenigkeit, noch nie einen Urlaub genehmigt hat.
Man könnte den doppelten Code genehmigeUrlaub dadurch vermeiden, indem man ihn in die abstrakte Oberklasse schiebt. Allerdings würde man dann gegen das Listkov Prinzip verstossen, dass später erläutert wird. Da die Oberklasse Mitarbeiter abstrakt ist, würde Ich diesen Verstoss prinzipiell auch akzeptieren. Grundlegend halte ich es für sinnvoll Oberklasse so weit wie möglich abstrakt zu halten, und nur die Blätter des Klassenbaumes konkret zu halten.
Die vorgeschlagene Lösung zeigt aber ein anderes Problem, nämlich die Problematik der Vererbung. Eine Vererbung kann nie aufgelöst werden. In dieser Firma kann kein Mitarbeiter je befördert werden - und auch keiner degradiert. Ich beobachte dass Anfänger sehr häufig und gerne die Vererbung verwenden, weil sie dann glauben die Objektorientierung verstanden zu haben. Erfahrene Entwickler gehen dagegen sehr vorsichtig mit Vererbung um, und versuchen diese meist zu vermeiden.
Aus meiner Sicht ist der entscheidente Vorteil der Objektorientierung die Datenkapselung. Die Vererbung wirkt aber dieser Kapselung entgegen. Alles was in der Oberklasse geändert wird, wirkt sich auf alle Instanzen der Unterklassen aus.
Dass sinnvolle Aktionen unterbleiben, weil das zugrunde liegende IT-System dies nicht unterstützen, erlebe Ich im Alltag immer wieder. Und es macht mich regelmässig wütend. Wenn die Realität der IT angepasst wird, und nicht umgekehrt, stimmt etwas grundsätzlich niccht. Ein Beispiel geschah in meiner Reha nach dem schlaganfall im Umgang mit meiner Diabetis. Ich habe ein System das die Zuckerwerte permanent misst, d.h. ich habe eine Kurve - die Ärztin konnte nur einzelne Werte manuell eingeben. Ich hatte deshalb versucht die Extremwerte und Wendepunkte zu erfassen,aber irgendwann hat die Arzthelferin sich geweigert täglich über 20 Werte einzugeben. Irgendwann war es mir egal ob die Werte im Computer mit der Realität überein stimmten, und Ich hatte mit der Ärztin vereinbart selbständig regelmässig zu messen und zu spritzen. Ich befürchte in Zukunft werden die Werte der Krankenkasse gemeldet und Algorithmen bestimmen ob die Weiterbehandlung finanziert wird. Das Grundproblem ist dass es unmöglich ist eine komplexe Welt in wenigen Zahlen auszudrücken. Ich sehe die grosse Gefahr dass man Verantwortung abschiebt, weil man sich nur noch auf Algorithmen oder Verfahren konzentriert. Firmen, die überwiegend mit externistischen Bewertungsmethoden und vereinbarten Verfahren arbeiten, sind meist Ausdruck vom Mangel Verantwortung zu übernehmen.
Entschuldigung dass Ich vom eigentlichen Stoff abgekommen bin - aber es ist nun einmal ein Thema das mich persönlich umtreibt. Tatsache ist dass eine unglückliche Softwarearchitektur die Prozesse ändern und häufig verschlechtern, anstatt die sinnvollen Prozesse unterstützten. Das ist mit ein Grund dass mir eine sinnvolle Architektur, die sich durch hohe Änderbarkeit und Anpassbarkeit auszeichnet, wichtig ist. Die IT sollte die Realität unterstützen und die Realität sollte nicht der IT angepasst werden.
Erfahrene Programmierer setzen Vererbung eher spärlich ein, und versuchen als Oberklassen immer abstrakte Klassen zu verwenden bzw. sie setzen Schnittstellen ein. Das Profil eines Mitarbeiters kann zur Laufzeit geändert werden. So ist nun eine Beförderung eines Mitarbeiters möglich. Die Aufgabe des Profils ist es, den Mitarbeitern erlaubte Tätigkeiten zuzuordnen.
Die letzten Folien zum SRP beschäftigt sich mit dem Muster des Method Objects. Für mich war im ersten Berufsjahr der Augenöffener, der mir half das Eigentliche zur Objektorientierung zu verstehen. Ich hatte mir damals schon eingebildet dass Ich ein Experte in Objektorientierung war, allerdings erst durch Erlernen dieses Musters ist mir bewusst geworden, dass auch jede Methode eines Objekts in eine eigene Klasse umgewandelt werden kann.
Die Verwendung von temporären Variablen, kann der Grund sein - warum die wichtigste RefactoringMethode ExtractMethod nicht funktioniert. Angenommen der farbige Teil des Codes soll in einer eigenen Methode extrahiert werden, so wird dies nicht möglich sein. Dies liegt daran, dass der Gültigkeitsbereich einer temporären Variablen nie über 2 Methoden gehen kann.
Für das Method Object, ist eine eigene Animation geplant, die ich einem Video veröffentlichen will. Deshalb verzicht ich hier und in den nächsten Folien auf einen Audion Type.
Ich habe eine Klasse, die ich "AlteKlasse" nenne, mit einer Methode die so kompliziert ist, dass deren Komplexität in einer eigenen Klasse verschieben will. Diese eigeneKlasse werde ich später "NeueKlasse" nennen. Ich bitte Sie in der Realität bessere Namen zu wählen, die sich an der Funktionalität orienteren. Die Namen alte und neue Klasse sind nur deshalb gewählt, weil Ich prinzipiell das Verfahren erklären will.
Aus didaktischen Gründen zeige Ich hier in der ersten Folie, nicht wie die NeueKlasse aussieht. Dies kommt in den nächsten beiden Folien. Ich erläutere zunächst erst einmal nur die Kommunikation zwischen Alter und Neuer Klasse, wenn das Verfahren abgeschlossen ist. Die komplizierte Methode der alten Klasse, die Ich ersetzen will, hat in diesem Beispiel zwei Eingabeparameter input1 und input2
Die neue Klasse muss erst einmal erzeugt werden. Dazu benötigt sie einen Constructor der u.a. die Eingabeparamter der komplizierten Methode aus der alten Klasse hat. Allerdings reicht dies im Allgemeinen nicht. Die komplizierte Methode der alten Klasse greift natürlich auf die Membervariablen zu. Deshalb muss die Instanz der alten Klasse selber auch als Constructor mitgegeben werden.
Die neue Methode compute macht genau dasselbe wie die zu ersetzende Methode in der alten Klasse. Wie diese genau aussieht zeige Ich auf der nächsten Folie.
Die kompozierte Methode der AltenKlasse wird ersetzt durch den Aufruf der neuen Klasse nit den Constructor und dem anschliessenden Aufruf der Methode compute()
In dieser Animation zeige Ich wie die neue Klasse aussieht.
Die Eingabeparameter der komplizierten Methode der neuen Klasse, werden Membervariablen, der neuen Klassse.
Genauso benötigen wir für jede temporäre Variable der komplizierten Methde aus der Alten Klasse, eine Membervariable in der neuen Klasse
Auch brauchen wir eine Membervariable für die Instanz der alten Klasse
Damit sollten die Aufgabe der Constructor Methode der NeuenKlasse klar sein. Sie initisiert lediglich die entsprechenden Membervariablen
Für die Membervariablen der Eingangsparameter werden nur getter Methoden benötigt
Temporäre Variablen können sowohl gelesen, als auch geschrieben werden. Also sind sowohl getter als auch setter Methoden notwendig
Die Membervariable die die alte Klasse enthält muss nur gelesen werden, also reicht eine getter Methode.
Wie die Methode compute aussieht wird in der folgenden Folie erläutert.
Ich habe eine Klasse, die ich "AlteKlasse" nenne, mit einer Methode die so kompliziert ist, dass deren Komplexität in einer eigenen Klasse verschieben will. Diese eigeneKlasse werde ich später "NeueKlasse" nennen. Ich bitte Sie in der Realität bessere Namen zu wählen, die sich an der Funktionalität orienteren. Die Namen alte und neue Klasse sind nur deshalb gewählt, weil Ich prinzipiell das Verfahren erklären will.
Aus didaktischen Gründen zeige Ich hier in der ersten Folie, nicht wie die NeueKlasse aussieht. Dies kommt in den nächsten beiden Folien. Ich erläutere zunächst erst einmal nur die Kommunikation zwischen Alter und Neuer Klasse, wenn das Verfahren abgeschlossen ist. Die komplizierte Methode der alten Klasse, die Ich ersetzen will, hat in diesem Beispiel zwei Eingabeparameter input1 und input2
Die neue Klasse muss erst einmal erzeugt werden. Dazu benötigt sie einen Constructor der u.a. die Eingabeparamter der komplizierten Methode aus der alten Klasse hat. Allerdings reicht dies im Allgemeinen nicht. Die komplizierte Methode der alten Klasse greift natürlich auf die Membervariablen zu. Deshalb muss die Instanz der alten Klasse selber auch als Constructor mitgegeben werden.
Die neue Methode compute macht genau dasselbe wie die zu ersetzende Methode in der alten Klasse. Wie diese genau aussieht zeige Ich auf der nächsten Folie.
Die kompozierte Methode der AltenKlasse wird ersetzt durch den Aufruf der neuen Klasse nit den Constructor und dem anschliessenden Aufruf der Methode compute()
Hier eine Zusammenfassung der Lösung nach Anwendung des Method Objekts
Das OpenClosed Prinzip von Bertrand Meyer, dem Entwickler von der Computersprache Eiffel. Dieses Prinzip ist in der Literatur umstritten. Es gibt Einige die meinen man sollte dieses Prinzip sogar streichen, da es sogar schädlich ist. Ich teile dieses Kritik. Ich halte aber die Beschäftigung mit dieser Diskussion für sehr lehrreich.
Eiffel war Ende der 80 Jahre die Alternative zur Programmiersprache Smalltalk. Die erste Version von Java kam erst Jahre später (1995) heraus. Eiffel ist benannt worden nach dem Erbauer des Eiffel Turms in Paris. Heute hat die Programmiersprache Eiffel keine Bedeutung mehr.
Laut Definition sollte Software geschlossen gegenüber Änderungen sein und offen gegenüber Erweiterung.
Diese Folie zeigt eine Verletzung dieses Prinzips. Möchte Ich die Funktion berechneFläche um eine neue geometrische Form erweitern, so muss Ich den Code ändern.
Die Lösung ist sicherlich nicht falsch, aber wie schon beim SRP erwähnt birgt die Vererbung Gefahren. Eine Oberklasse so zu bauen, dass diese auch für zukünftige Erweiterungen sinnvoll bleibt, scheint mir zu gefährlich zu sein. Besonders da kein Mensch in die Zukunft schauen kann, und künftige Änderungen schwer zu erahnen sind.
Eine API muss so gebaut sein, dass unabhängig von Aufruf der Reihenfolge der öffentlichen Methoden das Objekt in sich konsistent bleibt. Unterklassen können die Instanzvariablen der Oberklasse ändern, also müssen diese geschützt werden. Dies ist extrem schwierig wenn man die zukünftige Erweiterungen nicht kennt
Meine persönliche Erfahrung ist dass eine Architektur mit der Zeit degeneriert, weil die Entwickler nicht den Mut haben Änderungen durchzuführen. Der technologische Fortschritt wird immer rasanter und die Welt ändert sich in einer Geschwindigkeit, die uns alle überfordert. Wir wissen nicht wie Digitalisierung und Globalisierung unsere Arbeitsweilt ändern wird. Wir wissen eigentlich nur dass diese Änderungen massiv sein werden. In der Zeit als Bertrand Meyer das Open Closed Prinzip einforderte, gab es noch keine Smartphones, keine Plattformökonomie und das Internet war auf Universität, Militär und grossen Firmen beschränkt.
Dazu kommt die Erfahrung dass Software häufig länger lebt als je damit gerechnet wurde. In den Projekten, in denen Ich vor meinen Schlaganfall gearbeitet hatte, waren Spezifikation 2 1/2 Jahrzehnte alt. Inzwischen haben viele Entwickler die Firma gewechselt oder sind in Rente gegangen.
Ein Prinzip das die Änderungen von Code als eine Art Niederlage erscheinen lässt, empfinde Ich auch als falsch. Der Sinn von Software ist nun einmal die Anpassbarkeit an neue Anforderungen und neuen Technologien.
Es gibt in der IT wenig Frauen, die durch ihre Leistung bekannt geworden sind. Dies liegt einzig und allein darin begründet dass die IT eine Männer Domaine ist. Barbara Listkov wurde die Ehre zu Teil, und wurde Namensgeberin eines interessanten Prinzips.
Das Listkov Subsitution Prinzip besagt dass Methoden einer Oberklasse prinzipiell für alle Instanzen seiner Unterklassen gültig sein müssen.
Das Beispiel habe ich aus dem Internet gefunden, und kommt nicht von der Original Literatur. Elektroautos spielten vor wenigen Jahren keine Rolle. Das Beispiel zeigt auch dass man als Entwickler Fachkenntnisse braucht, denn das Beispiel hat mich etwas überrascht.
Ich bin kein Autofahrer und seit meinem Schlaganfall ist mir auch das Radfahren verboten worden. Mir war es nicht bewusst dass Elektroautos keine Gangschaltung haben. Ich bin früher Elektrofahrrad gefahren und dieses hatte Gänge deshalb wunderte mich diese Aussage. Als Softwareentwickler wäre Ich gar nicht auf die Idee gekommen fem Kunden zu fragen ob Elektroautos eine Gangschaltung haben. Meistens scheitert man an Tatsachen, am denen man fälschlicherweise glaubt man kenne die Lösung. Deshalb ist es notwendig die Architektur den Stakeholdern zu zeigen. Dann erkennt man Probleme an denen man vorher gar nicht dachte, dass diese Probleme entstehen.
Das Listkow Prinzip erlaubt von aussen über alle Autos iterieren zu können, ohne sich um die Dtails der Unterklassen zu kümmern zu müssen.
Das Listkow Prinzip hat Auswirktungen auf Modifier und Exceptions. Wobei wir Java Entwickler das Glück haben dass schon der Compiler Verstösse gegen dieses Prinzip erkennt. Bertrand Meyer arbeitet in seiner Computersprache Eiffel mit Vor- und Nachbedingungen von Methoden. Ich hatte Folien dazu erzeugt, aber dann wieder gelöscht. Wenn Sie in der Unterklasse Vorbedingungen abschwächen, ist es kein Problem. Ein Problem erhalten wir aber wenn Unterklassen Vorbedingungen verschärfen. Führen wir eine Methode der Unterklasse aus, obowhl diese als private deklariert ist, führt dieses zu einem Fehler. Es ist dagegen nicht verboten eine Methode der Unterklasse nicht auszuführen, obwohl dieses erlaubt ist.
Das Listkow Substitution Prinzip hat auch Auswirkungen auf Exceptions. Zum Verständnis des nachfolgenden Beispiels zeige ich einen Teil der Java Exception Hierarchie. Eine Java IOException schliesst ein FileNotFound Exception mit ein. Neben der FileNotFound Exception und der UnknownHost Exception gibt es noch viele andere direkt Unterexceptions, aber zum Verständnis reicht das Vorhandensein nur einer zusätzlichen Exception aus.
Das Listkov Substitution Prinzip hat Auswirkungen auf die Exceptions bei überschriebenen Methoden. Die überschriebene Methode darf weniger Exceptions als die entsprechende Methode in der Oberklasse, aber keine zusätzliche Exception werfen. Da die FileNotFoundException eine Teilmenge von IOException ist, ist die Methode saveToDisc kein Problem. Umgeht wird aber ein Prpblem. wenn eine Methode der Unterklasse mehr Exceptions wirft, als die entprechende Methode der Oberklasse.
In der Original Literatur wird zur Erläuterung des LSP auf das Rechteck. Quadrat Problem hingewiesen. Jetzt wissen wir alle aus dem Mathematik Unterricht dass ein Quadrat ein spezielles Rechteck ist, deshalb würden selbst erfahrene Programmierer dazu tendieren hier eine Vererbungsbeziehung einzuführen. I.a. führt eine Unterklasse der Oberklasse eine oder mehrere Eigenschaften hinzu. In speziell diesem Fall wird eine Eigenschaft aus der Oberklasse (nämlich die zweite Seite) weggenommen. Dies führt zu einer Verletzung des Listkov Substitution Prinzips.
Aus IT-Sicht wäre eine Umdrehung der Vererbung ggf. eine Lösung. Rein intuitiv tendiere Ich aber eher dafür auf eine Vererbungshierarchie ganz zu verzichten.
Das ISP wird manchmal auch das SRP für Interfaces genannt.Es ist sehr einfach, schnell erklärt aber deswegen nicht weniger wichtiger.
Rede ich im folgenden von Schnittstellen, meine ich weniger als das was bei der Zertifikation zum Softwarearchitekten gemeint ist. Die Nicht Funktionalen Anforderungen an Schnittstellen in der Architekturdokumentation zu beschreiben ist ganz entscheident. Bei der Wahl eines Sortieralgorithmus kann es ganz entscheident sein, ob dieser auch für enorm grosse Datenmenge geeignet ist. Die einzelnen Sortieralogrithmen unterscheiden sich extrem in Zeit- und Speicherverhalten. Bei riesigen Datenmengen kann ich die Implementierung nicht mit einem einfachen Algorithmus austauschen, der für geringe Datenmengen genügend ist. Und dies obwohl das Interface Objekt genau dasselbe ist.
Ich glaube es war Kent Beck der zuerst geschrieben hat, do not comment bad code, rewrite it". Es gibt Dinge die man dem Code nicht ansieht und genau diese gehören in die Architekturdokumentation.
In wohl jedem Lehrbuch über Softwarearchitektur wird die Verwendung von Interfaces empfohlen. Sind die Interfaces glücklich gewählt, und sind Klassen nur von Interfaces abhängig, dann haben Sie gewonnen. Dann können Sie jede unglückliche Implementierung austauschen, ohne dass es negative Auswrirkungen auf das Gesamtsystem hat. Allerdings ziehen Änderungen an Schnittstellen immer Änderungen an vielen Stellen nach sich. Sie müssen jeden Aufrufer und jede Klasse die diese Schnittstelle implementiert ändern.
Deshalb lohnt es sich sich explizit mit Schnittstellen zu beschäftigen und Schnittstellen immer passgenau zum Bedarf der Clienten anzubieten.
Eine bessere Lösung ist das Aufspalten der Schnittselle I_Einkaufskorb in zwei getrennte Schnittstellen und die Wiederverwendung der Schnittstelle I_Korb.
Damit ist schon alles zum ISP gesagt. Nur weil es einfach und in sich logisch ist, ist es nicht weniger wichtig.
Lädt ihr Unternehmen eine Beraterfirma wie McKinsey oder Roland Berger ein, so analysiert diese die Kommunikationsstrukturen der Firma. Bewerten Sie als Softwarearchitekt eine Architektur so machen Sie dasselbe mit Objekten. Sie analysieren die Abhängigkeiten. Ein Modul A ist von Modul B abhängig, falls A eine Funktion oder ein Objekt von B benutzt.
Es geht hier um Stabilität. Ist an für sich stabiles Teil, von einem instabilen Teil abhängig, so wird dieses Teil selbst wieder instabil. Es gibt im Alltag den Spruch dass ein Kette so stark ist, wie das schwächste Glied.
Als stabile Teile gelten Schnittstellen und abstrakte Klassen. Als stabile Teile gelten sogenannte ValueObjects, die nie ihren Zustand ändern. In Java ist ein String immer ein ValueObject. Ändern Sie in Java einen String, so wird ein neues String Objekt erzeugt. Die Aussage dass eine Klasse nur auf Interfaces oder abstrakte Klassen verweisen soll, ist schon zu speziell. Ein Verweis auf ValueObjekte wie Strings ist auch kein Problem.
Ich habe es mir bei dieser Folie etwas einfach gemacht, und die deutsche Übersetzung entsprechend Wikipedia abgeschrieben. Aber Ich bin mit dieser Übersetzung etwas unglücklich. Bei einer Schichtenarchitektur ruft selbstverständlich eine Funktion der oberen Schicht die Funktionen der nächst unteren Schicht auf. Im OSI Schichtenmodell rufen z.B. selbstverständlich Schichten aus der Ebene 7 Funktionen aus der Ebene 6 aus, aber es werden keine Ebenen übersprungen. Die Funktionen einer Ebene bilden immer Dienste für die nächst höheren Ebene.
Mir ist es bewusst dass es problematisch ist ein Produkt als Negativ Beispiel zu nehmen. Aber Ich hätte ein Spitzenhandy von jeder Marke wählen können. Ein einfach austauschbarer Akku gibt es heutzutage Stand November 2019 bei keinem Hersteller bei ihrem Spitzenprodukt. Wer bereit ist 1000 Euro dür Spitzenhandy zu zahlen, wird wahrscheinlich wiesowieso ein paar Jahre später wieder das Top Handy kaufen. Aber früher wurden Spitzenhandys wenige Jahre nach dem Erscheinen als Mitteklassehandy weiterverkauft. Es gibt zwar Spezialgeschäfte die trotzdem den Akku austauschen und es gibt Powerbanks aber es macht die Benutzung und Weitervergabe älterer Spitzenhandys unnötig schwerer bzw. teurer. Etwas was betriebswirtschaftlich durchaus sinnvoll sein kann, kann sich volkswirtschaftlich als schädlich herausstellen.
Diese Folie zeigt ganz allgemein den Schritt zur Einhaltung des DIP falls eine Klasse A von einer konkreten Klasse B abhängig ist. Man erzeugt eine Schnittstelle, ich nenne sie I für Interface, die genau die Methoden enthält die A von B benutzt. Statt direkt auf B zu verweisen, ändert man die Klasse so ab, dass sie auf die neue Schnittstelle I verweist. Es muss dann nur noch die Klasse B von der neuen Schnittstelle I erbt.
Der grösste Vorteil ist dass man nun die Klasse A testen kann, ohne die Klasse B zu benutzen. Es ist relativ einfach eine Testklasse zu schreiben, die die Schnittstelle I erfüllt und überprüft ob die an die Testklasse vorhandenen Parameter der Methoden korrekt sind.
In Java gibt es ein Framework namens Mockito, mit der es auch ohne Interfaces möglicht sogenannte Spy oder Mock Objekte zu schreiben. Dies ist u.a. dann sinnvoll, wenn man selbst nicht Herrscher über das Objekt B ist. Die Verwendung von mockito ist einfach und ein eigenes Lernvideo wert. Ich vermute für andere objektoreientierte Sprachen gibt es ähnliche Frameworks.
Es folgt zum Schluss noch ein konkretes extrem einfaches Beispiel für eine Lampe. Die Lampe hat genau enau 2 mögliche Zustände. Es gibt noch einen Schalter um die Lampe ein- und auszuschalten.
sIn dieser Folie sehen Sie die Klasse Schalter, die die konkrete Klasse Lampe benutzt. Sie verstösst gegen das Dependency Inverion Prinzip. Der Schalter hat genau einen Zustand. Er ist entweder gedrückt oder nicht gedrückt und der Zustand leuchtet der Lampe, folgt dem Zustand des Schalters.
Auf dieser Folie wird eine Lösung präsentiert, die die Verfehlung gegen das DIP wieder auflöst. Allerdings ist die Klasse Lampe so klein, dass man sich wirklich fragen kann ob ein Verstoss gegen das DIP in diesem Fall wirklich schlecht ist. Nur eine neue Schnittstelle einzuführen um unabhängig von der winzigen Klasse Lampe zu sein erscheint mir auf dem ersten Blick als Prinzipienreiterei. Es sind Prinzipien und keine Gesetze. Allerdings wer sagt dass es nur eine Lampe gibt?
Man muss sich bewusst sein, dass es bei Design Entscheidungen selten eine perfekte Lösung gibt. Gibt es gute Gründe sollte man auch den Mut haben gegen diese Prinzipien zu veerstossen. Allerdings kann ich grantieren dass das Nicht Beachten der Prinzipien aus Bequemlichkeit oder Zeitdruck zu nicht wartbaren Code führt, und der Aufwand später umso grösser wird.
Zu wirklich guten Entscheidungen kommt man nicht durch Metriken. Metriken können gute Hinweise bringen. Sie sind aber dann gefährlich wenn Manager oder Personalabteilungen die von der Fachlichkeit und Softwareentwicklung keine Ahnung haben daraufhin Entscheidungen treffen. Meine persönliche Erfahrung ist dass es zum Scheitern verurteilt ist, wenn man komplexe Dinge auf wenige Zahlen herunterbrechen will. Die besten Lösungen erhält man durch Kommunikation, bei dem Versuch eine Architektur zu erklären und begründen. Ich empfinde dabei gerade Anfänger als sehr hilfreich. Rechlin sagte dass wenn man etwas nicht erklären kann, hat man es entweder nicht verstanden, oder es ist zu kompliziert dass es funktionieren kann. Deshalb wird in fast jedem Lehrbuch über Softwarearchitektur und bei agilen Methoden das Prinzip der Einfachheit (KISS steht für Keep it small and simple) gepriesen. Gerade bei sehr teuren oder Sicherheitsrelevanten Projekten ist es ein Risiko wenn eine Software nur von den Gehirnzellen Einzielner abhängt.