Hierarchische Zerlegung

Sie sollten nie sofort damit beginnen, Kurse für Ihre Bewerbung zu schreiben. Zuerst muss es entworfen werden. Der Entwurf sollte mit einer durchdachten Architektur enden. Und um diese Architektur zu erhalten, müssen Sie das System konsequent zerlegen.

Die Zerlegung muss hierarchisch erfolgen – zunächst wird das System in große Funktionsmodule/Subsysteme unterteilt, die seine Funktionsweise in allgemeinster Form beschreiben. Anschließend werden die resultierenden Module genauer analysiert und in Submodule bzw. Objekte unterteilt.

Unterteilen Sie das System vor der Auswahl von Objekten zumindest gedanklich in grundlegende semantische Blöcke. Bei kleinen Anwendungen ist dies meist sehr einfach zu bewerkstelligen: Ein paar Hierarchieebenen genügen völlig, da das System zunächst in Subsysteme/Pakete und Pakete in Klassen unterteilt wird.

Hierarchische Zerlegung

Diese Idee ist nicht so trivial, wie es scheint. Was ist beispielsweise die Essenz eines so gängigen „Architekturmusters“ wie Model-View-Controller (MVC)?

Es geht darum, die Präsentation von der Geschäftslogik zu trennen . Erstens ist jede Benutzeranwendung in zwei Module unterteilt – eines ist für die Implementierung der Geschäftslogik selbst (Modell) und das zweite für die Interaktion mit dem Benutzer (Benutzeroberfläche oder Ansicht) verantwortlich.

Dann stellt sich heraus, dass die Module irgendwie interagieren müssen. Dazu fügen sie einen Controller hinzu, dessen Aufgabe es ist, die Interaktion der Module zu verwalten. Auch in der mobilen (klassischen) Version von MVC wird das Observer-Muster hinzugefügt, sodass die Ansicht Ereignisse vom Modell empfangen und die angezeigten Daten in Echtzeit ändern kann.

Typische Top-Level-Module, die durch die erste Aufteilung des Systems in die größten Komponenten entstehen, sind genau:

  • Geschäftslogik;
  • Benutzeroberfläche;
  • Datenbank;
  • Nachrichtensystem;
  • Objektcontainer.

Beim ersten Split wird in der Regel die gesamte Bewerbung in 2-7 (maximal 10 Teile) aufgeteilt. Wenn wir es in mehr Teile aufteilen, besteht der Wunsch, sie zu gruppieren, und wir erhalten wieder 2-7 Module der obersten Ebene.

Funktionale Zersetzung

Die Aufteilung in Module/Subsysteme erfolgt am besten anhand der Aufgaben, die das System löst . Die Hauptaufgabe ist in ihre Teilaufgaben gegliedert, die unabhängig voneinander autonom gelöst/durchgeführt werden können.

Jedes Modul sollte für die Lösung einer Teilaufgabe verantwortlich sein und die entsprechende Funktion erfüllen . Neben dem funktionalen Zweck zeichnet sich das Modul auch durch einen Satz von Daten aus, die für die Erfüllung seiner Funktion erforderlich sind, nämlich:

Modul = Funktion + Daten, die zur Ausführung benötigt werden.

Wenn die Zerlegung in Module korrekt erfolgt, ist die Interaktion mit anderen Modulen (die für andere Funktionen verantwortlich sind) minimal. Dies kann zwar der Fall sein, aber sein Fehlen sollte für Ihr Modul nicht kritisch sein.

Ein Modul ist kein beliebiger Codeabschnitt, sondern eine separate, funktional sinnvolle und vollständige Programmeinheit (Unterprogramm), die eine Lösung für eine bestimmte Aufgabe bereitstellt und im Idealfall unabhängig oder in einer anderen Umgebung arbeiten und wiederverwendet werden kann. Das Modul sollte eine Art „Integrität sein, die zu relativer Unabhängigkeit in Verhalten und Entwicklung fähig ist“. (Christopher Alexander)

Eine kompetente Zerlegung basiert also zunächst auf der Analyse der Systemfunktionen und der zur Ausführung dieser Funktionen erforderlichen Daten. Funktionen sind in diesem Fall keine Klassenfunktionen und Module, da sie keine Objekte sind. Wenn Sie nur ein paar Kurse in einem Modul haben, dann haben Sie es übertrieben.

Starke und schwache Konnektivität

Es ist sehr wichtig, es mit der Modularisierung nicht zu übertreiben. Wenn Sie einem Anfänger eine monolithische Spring-Anwendung geben und ihn bitten, sie in Module zu unterteilen, wird er jede Spring Bean in ein separates Modul herausnehmen und davon ausgehen, dass seine Arbeit abgeschlossen ist. Aber das ist nicht so.

Das Hauptkriterium für die Qualität der Zerlegung ist die Fokussierung der Module auf die Lösung ihrer Aufgaben und ihre Unabhängigkeit.

Dies wird üblicherweise wie folgt formuliert: „Die durch Zerlegung erhaltenen Module sollten intern maximal konjugiert sein (hohe interne Kohäsion) und minimal miteinander verbunden sein (geringe externe Kopplung).“

Hohe Kohäsion, hohe Kohäsion oder „Kohäsion“ innerhalb des Moduls, bedeutet, dass sich das Modul auf die Lösung eines engen Problems konzentriert und nicht mit der Ausführung heterogener Funktionen oder unabhängiger Verantwortlichkeiten beschäftigt ist.

Zusammenhalt charakterisiert den Grad, in dem die vom Modul ausgeführten Aufgaben miteinander in Beziehung stehen.

Eine Konsequenz von High Cohesion ist das Single- Responsibility-Prinzipdas erste der fünf SOLID-Prinzipien , wonach jedes Objekt/Modul nur eine Verantwortung haben sollte und es nicht mehr als einen Grund für eine Änderung geben sollte.

Low Coupling , lose Kopplung, bedeutet, dass die Module, in die das System unterteilt ist, möglichst unabhängig oder lose miteinander gekoppelt sein sollten. Sie sollen interagieren können, gleichzeitig aber möglichst wenig voneinander wissen.

Jedes Modul muss nicht wissen, wie das andere Modul funktioniert, in welcher Sprache es geschrieben ist und wie es funktioniert. Um das Zusammenspiel solcher Module zu organisieren, wird häufig ein bestimmter Container verwendet, in den diese Module geladen werden.

Wenn Sie bei ordnungsgemäßem Design ein Modul ändern, müssen Sie andere nicht bearbeiten, da diese Änderungen sonst minimal sind. Je lockerer die Kopplung, desto einfacher ist es, das Programm zu schreiben/verstehen/erweitern/reparieren.

Man geht davon aus, dass gut gestaltete Module die folgenden Eigenschaften haben sollten:

  • Funktionale Integrität und Vollständigkeit – jedes Modul implementiert eine Funktion, implementiert diese jedoch gut und vollständig, das Modul führt unabhängig einen vollständigen Satz von Operationen aus, um seine Funktion zu implementieren.
  • Eine Eingabe und eine Ausgabe – am Eingang empfängt das Programmmodul einen bestimmten Satz Ausgangsdaten, führt eine sinnvolle Verarbeitung durch und gibt einen Satz Ergebnisdaten zurück, d. h. das Standard-IPO-Prinzip wird implementiert – Eingabe –\u003e Prozess –\u003e Ausgang.
  • Logische Unabhängigkeit – das Ergebnis der Arbeit des Programmmoduls hängt nur von den Ausgangsdaten ab, hängt jedoch nicht von der Arbeit anderer Module ab.
  • Schwache Informationsverknüpfung mit anderen Modulen – der Informationsaustausch zwischen Modulen sollte möglichst minimiert werden.

Für einen Anfänger ist es sehr schwierig zu verstehen, wie man die Konnektivität von Modulen noch weiter reduzieren kann. Teilweise kommt dieses Wissen durch Erfahrung, teilweise durch die Lektüre kluger Bücher. Am besten ist es jedoch, die Architekturen vorhandener Anwendungen zu analysieren.

Komposition statt Vererbung

Kompetente Zerlegung ist eine Art Kunst und für die meisten Programmierer eine schwierige Aufgabe. Einfachheit täuscht hier, und Fehler sind teuer.

Es kommt vor, dass dedizierte Module stark miteinander gekoppelt sind und nicht unabhängig voneinander entwickelt werden können. Oder es ist nicht klar, für welche Funktion jeder von ihnen verantwortlich ist. Wenn Sie auf ein ähnliches Problem stoßen, wurde höchstwahrscheinlich die Partitionierung in Module falsch durchgeführt.

Es sollte immer klar sein, welche Rolle jedes Modul spielt . Das zuverlässigste Kriterium dafür, dass die Zerlegung korrekt durchgeführt wird, besteht darin, dass es sich bei den Modulen um unabhängige und wertvolle Unterroutinen handelt, die isoliert vom Rest der Anwendung verwendet (und daher wiederverwendet) werden können.

Bei der Zerlegung eines Systems ist es wünschenswert, seine Qualität zu überprüfen, indem man sich die Fragen stellt: „Welche Aufgabe erfüllt jedes Modul?“, „Wie einfach sind die Module zu testen?“, „Ist es möglich, die Module einzeln zu verwenden.“ oder in einer anderen Umgebung? „Andere beeinflussen?“

Sie müssen versuchen, die Module so autonom wie möglich zu halten . Wie bereits erwähnt, ist dies ein Schlüsselparameter für eine ordnungsgemäße Zerlegung . Daher muss es so durchgeführt werden, dass die Module zunächst schwach voneinander abhängig sind. Wenn es dir gelungen ist, dann bist du großartig.

Wenn nicht, ist auch hier nicht alles verloren. Es gibt eine Reihe spezieller Techniken und Muster, mit denen Sie die Verbindungen zwischen Subsystemen weiter minimieren und schwächen können. Im Fall von MVC wurde zu diesem Zweck beispielsweise das Observer-Muster verwendet, es sind jedoch auch andere Lösungen möglich.

Man kann sagen, dass Techniken zur Entkopplung den wichtigsten „Werkzeugkasten des Architekten“ darstellen. Man muss nur verstehen, dass es sich um alle Subsysteme handelt und dass die Verbindung auf allen Ebenen der Hierarchie geschwächt werden muss , also nicht nur zwischen Klassen, sondern auch zwischen Modulen auf jeder Hierarchieebene.