Softwarearchitektur: Unterschied zwischen den Versionen
Aus Das Sopra Wiki
Thomas (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Roth (Diskussion | Beiträge) |
||
| (7 dazwischenliegende Versionen von einem anderen Benutzer werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
<q>Softwarearchitektur […] beschreibt die grundlegenden Komponenten und deren Zusammenspiel innerhalb eines Softwaresystems.</q><ref>https://de.wikipedia.org/wiki/Softwarearchitektur</ref> | |||
Den Softwareaspekt mal außen vorgelassen, so weckt der Begriff „Architektur“ doch gewisse Vorstellungen in uns. Der Architekt – hochgebildet und erfahren – plant und entwirft Bauwerke nach praktischen und ästhetischen Gesichtspunkten und überwacht die korrekte Ausführung. Doch so sehr sich die Konstruktion von Bauwerken und die von Software auf den ersten Blick ähneln mögen und so sehr sich entsprechende Metaphern aufdrängen<ref>„Metaphern sind wie Lügen, nur ausschmückender.“ (Terry Pratchett: Wachen! Wachen!)</ref>, so gibt es doch insbesondere einen entscheidenden Unterschied: Bauwerke sind vorwiegend statisch. Es sind Immobilien (lateinisch im-mobilis ‚unbeweglich‘). Software ist ''soft'', weich, veränderbar. Und so sehr man sich auch einbilden mag, den Use Case zu kennen, allein das Wissen um diese Veränderbarkeit sorgt dafür, dass stets Bedarf für Veränderung gefunden wird. | Den Softwareaspekt mal außen vorgelassen, so weckt der Begriff „Architektur“ doch gewisse Vorstellungen in uns. Der Architekt – hochgebildet und erfahren – plant und entwirft Bauwerke nach praktischen und ästhetischen Gesichtspunkten und überwacht die korrekte Ausführung. Doch so sehr sich die Konstruktion von Bauwerken und die von Software auf den ersten Blick ähneln mögen und so sehr sich entsprechende Metaphern aufdrängen<ref>„Metaphern sind wie Lügen, nur ausschmückender.“ (Terry Pratchett: Wachen! Wachen!)</ref>, so gibt es doch insbesondere einen entscheidenden Unterschied: Bauwerke sind vorwiegend statisch. Es sind Immobilien (lateinisch im-mobilis ‚unbeweglich‘). Software ist ''soft'', weich, veränderbar. Und so sehr man sich auch einbilden mag, den Use Case zu kennen, allein das Wissen um diese Veränderbarkeit sorgt dafür, dass stets Bedarf für Veränderung gefunden wird. | ||
| Zeile 26: | Zeile 28: | ||
=== Grenzen abstecken (Boundaries) === | === Grenzen abstecken (Boundaries) === | ||
Die Hauptaufgabe einer Architektur: Man hat eine Menge Komponenten – in unserem Fall vorwiegend Klassen – die bestimmte Funktionalität implementieren und wiederum von anderen Komponenten verwendet werden. Wenn nun also eine Klasse viele verschiedene Methoden unterschiedlichster Natur bereitstellt, dann wird sie auch von sehr vielen anderen Klassen verwendet. Es muss sehr viel an dieser einen "Über-Klasse" gearbeitet werden. Es gibt viele Konflikte und selbst einfache Änderungen betreffen die vielen anderen Klassen, die auf diese eine Komponente zugreifen. Damit ist die Software Veränderungen gegenüber sehr "instabil". | |||
Wenn andererseits sehr viele kleine Klassen mit jeweils sehr wenigen Methoden verwendet werden, dann ist jede einzelne Klasse für sich genommen "nutzlos". Viele Klassen müssen für einfachste Dinge zusammenarbeiten, was nun wieder das Verständnis erschwert. | |||
Schlussendlich geht es also darum, Komponenten (Klassen) und ihre Interaktion (Methodenaufrufe) so voneinander abzugrenzen, dass ein vertretbares Mittel zwischen "Instabilität" und "Nutzlosigkeit" gefunden wird. Oder positiv ausgedrückt: Es geht darum eine optimale Architektur zu finden unter den Gesichtspunkten der "Stabilität" und "Nützlichkeit" der einzelnen Komponenten. | |||
=== In Use Cases denken statt in Implementierungsdetails === | === In Use Cases denken statt in Implementierungsdetails === | ||
Funktionen und Komponenten können auf verschiedene Weisen gruppiert werden: einzelne Methoden werden in Klassen zusammengefasst. Klassen werden in Namespaces gehalten. Und eventuell wird all das in Bibliotheken verteilt (letzteres aber wohl nicht im Softwarepraktikum). Als Richtlinie gilt hier, dass diese Kategorisierung danach gewählt werden sollte, wie und von wem die Funktionen verwendet werden. Nicht danach, wie genau die Funktion implementiert wurde. | |||
Selbiges gilt natürlich auch für Methoden, die von Klassen bereitgestellt werden ("public"). In der Praxis interessiert es den Nutzer einer Datenstruktur meist nicht, ob diese eine ausgeklügelte Baumstruktur hat, Hashing verwendet oder einfach nur ein Array durchläuft. Wichtig ist bei der Verwendung nur, dass die erwartete Funktionalität bereitstellt (und in diesem Fall ihr Laufzeitversprechen hält). Beim Design von Komponenten sollte also immer der spätere Nutzer im Blick behalten werden. Beim Softwarepraktikum sind das zuallererst die anderen Mitglieder des Entwicklerteams. | |||
=== Trennung nach Funktionalität === | === Trennung nach Funktionalität === | ||
Es hat sich herausgestellt, dass es eine gute Idee ist unterschiedliche Funktionalitäten (oder vielleicht besser: Funktionalitätsarten) voneinander zu treffen. Am einfachsten verständlich ist das an einem Beispiel: | |||
Hat eine Komponente die Aufgabe Daten auf die Festplatte zu schreiben, dann tut sie auch nur das. In dieser Komponente ist keine Spiellogik enthalten. Auch kommt kein Code vor, der irgendwas mit Grafik oder Sound macht. Damit wird die Verwendung erleichtert und Fehler sind ebenfalls leichter zu finden. Ein weiterer ganz besonderer Vorteil ist außerdem, dass hier später auch Interfaces verwendet werden können. Haben wir erst eine stabile DiskWriter-Klasse, kann diese schnell mit einem WriterInterface ausgetauscht werden. So können später auch weitere Klassen einfach eingesetzt werden, die z.B. einen Netzwerksockel beschreiben (für Netzwerk-Kommunikation) oder einfach nur Dinge im temporären Speicher ablegen. | |||
== Besonderheiten bei der Entwicklung von Videospielen == | == Besonderheiten bei der Entwicklung von Videospielen == | ||
Videospiele nehmen in der Softwarewelt eine Besonderheit ein. Nicht nur technologisch, da unterschiedlichste Subsysteme in Echtzeit zusammenarbeiten müssen. Auch die Entwicklung ist davon geprägt, dass es sich | Videospiele nehmen in der Softwarewelt eine Besonderheit ein. Nicht nur technologisch, da unterschiedlichste Subsysteme in Echtzeit zusammenarbeiten müssen. Auch die Entwicklung ist davon geprägt, dass es sich an sich um ein verbrauchbares Konsumgut handelt (die meisten Videospiele hat man irgendwann ausgespielt). Entsprechend unwichtiger ist langfristige Wartbarkeit, da sich der Lebenszyklus des Spiels kaum vorhersehen lässt. Dies äußert sich natürlich auch in der Architektur:<ref>[https://godotengine.org/article/how-actually-make-your-dream-game How to make your dream game, publish it and not die in the process]</ref> | ||
* Auf '''Applikationsebene''' (also der Code, der unmittelbar für das Spielgeschehen verantwortlich ist) möchte man flexibel bleiben. Hier muss es nicht immer hübsch und korrekt zugehen. Man möchte schnell herausfinden ob Dinge funktionieren und ob es Spaß macht. | * Auf '''Applikationsebene''' (also der Code, der unmittelbar für das Spielgeschehen verantwortlich ist) möchte man flexibel bleiben. Hier muss es nicht immer hübsch und korrekt zugehen. Man möchte schnell herausfinden ob Dinge funktionieren und ob es Spaß macht. | ||
| Zeile 41: | Zeile 57: | ||
'''Vorsicht!''' Anforderungen ändern sich. Anfangs mögen z.B. wenige Einheitentypen geplant sein und entsprechend wird das irgendwie zurechtgehackt. Doch irgendwann wird doch beschlossen, dass man mehr möchte und dann werden Veränderungen schwierig. Ob und wo sich die Anforderungen verändern, lässt sich jedoch kaum vorhersehen. | '''Vorsicht!''' Anforderungen ändern sich. Anfangs mögen z.B. wenige Einheitentypen geplant sein und entsprechend wird das irgendwie zurechtgehackt. Doch irgendwann wird doch beschlossen, dass man mehr möchte und dann werden Veränderungen schwierig. Ob und wo sich die Anforderungen verändern, lässt sich jedoch kaum vorhersehen. | ||
== Beispiel == | |||
Eine erste Architektur entsteht meist ganz natürlich, wenn es darum geht Code zu strukturieren und Dopplungen zu vermeiden. Am Beispiel eines beliebigen Videospiels könnte das so aussehen, dass wir erst mal allen Code in unserer Game-Klasse gerade herunterschreiben. So würde für jeden Schritt/Tick Folgendes ausgeführt werden: | |||
* Eingabe wird erfasst (eine großes If- oder Switch-Anweisung) | |||
* Update der Spiellogik | |||
* Aktualisierung der Anzeige (Bilder werden ausgewählt und an die entsprechende Grafik-Schnittstelle weitergereicht) | |||
Nun bietet es sich an, diese verschiedenen Funktionalitäten in entsprechenden Klassen abzubilden, z.B. InputHandler, GameLogicUpdater, DisplayDrawer. Nun sind nur noch drei Zeilen Code in unserer Game-Klasse selbst nötig: | |||
* <code>inputHandler.handle(...)</code> | |||
* <code>gameLogicUpdater.update(...)</code> | |||
* <code>displayDrawer.draw(...)</code> | |||
Als nächstes stellen wir fest, dass in unserer GameLogicUpdater-Klasse viele verschiedene Einheiten bewegt werden: King, Queen, Knight... Alle davon haben eine Methode <code>move()</code>, die immer etwas anders funktioniert. Damit man die aber alle in eine Liste stecken kann, bietet sich hier die Verwendung eines Interfaces "Movable" an. Statt für jeden einzeln <code>move()</code> aufrufen zu müssen lässt sich das nun in einem einfachen Loop machen: | |||
<code>foreach (Movable movableUnit in units) { movableUnit.move(); }</code> | |||
Ebenso lässt sich zum Beispiel wieder der InputHandler unterteilen, in einen KeyboardInputHandler und einen MouseInputHandler. Im DisplayDrawer könnte zwischen Hintergrund, Vordergrund und GUI unterschieden werden und so weiter. Irgendeine Architektur kommt also in jedem Fall zustande. Die Qualität dieser Architektur kann schlussendlich nur dadurch bestimmt werden, wie leicht sich damit arbeiten lässt. | |||
[[Datei:Softwarearchitektur-Beispiel.png]] | |||
So ein UML-Klassendiagramm sieht so zwar erst mal hübsch aus, aber natürlich braucht die Spiellogik Zugriff auf die Eingabe. Und die Bewegung der verschiedenen Einheiten muss natürlich auch angezeigt werden. Die Komponenten müssen irgendwie aufeinander zugreifen und so gehen die Probleme los. Außerdem: Wie kompliziert wäre eine Änderung, wenn nicht nur Vordergrund und Hintergrund sondern eine beliebige Anzahl von Ebenen angezeigt werden soll? | |||
== Referenzen == | == Referenzen == | ||
<references /> | <references /> | ||
