Das Performance-Handbuch: Softwaredesign und Performance

Aus Wikibooks
Zur Navigation springen Zur Suche springen
Wikibooks buchseite.svg Zurück zu Analyse und Performance | One wikibook.svg Hoch zu Inhaltsverzeichnis | Wikibooks buchseite.svg Vor zu Implementierung und Performance


Design ist ein Thema, welches sich immer (wieder) großer Beliebtheit erfreut. Betrachtet werden dabei jedoch meist die Vor- und Nachteile in Bezug auf Wartbarkeit, Entwicklungszeit und Fehleranfälligkeit. Unbestritten haben Entwurfsmuster Vorteile. Die entwickelte Anwendung baut auf einem bereits bekannten und erfolgreich eingesetzten Prinzip der Klassenmodellierung auf. Dadurch wird die Fehleranfälligkeit und Entwicklungszeit deutlich verringert, während die Wartbarkeit zunimmt. Nur selten fließen dabei jedoch Performancebetrachtungen ein. Diesem Thema werden wir im Folgenden nachgehen.

Das Optimieren des Designs ist dabei Grundlage für eine optimal performante Anwendung. Ihre Designentscheidungen beeinflussen wesentlich die Performanceeigenschaften Ihrer Anwendung in Bezug auf benötigte Bandbreite, Arbeitsspeicher und auch Geschwindigkeit. Eine performante Anwendung kann natürlich auch ohne das optimale Design entstehen und ein optimales performantes Design hat nichts mehr mit Objektorientierung zu tun, wie wir sehen werden. Trotzdem ist es sinnvoll, zumindest einen Blick auf Performancemöglichkeiten im Design zu werfen.

Vor- und Nachteile[Bearbeiten]

Der große Vorteil in einem performanten objektorientierten Design ist, dass Sie sich die Vorteile der Objektorientierung wahren und gleichzeitig eine performante Anwendung entwickeln können. Objektorientierte Anwendungen gelten allgemein als wesentlich langsamer als Programme, welche mit herkömmlichen Programmiersprachen / bzw. -konzepten entwickelt wurden. Dies muss nicht sein. Leider müssen Sie dazu Ihr ansonsten gutes OOD nochmals betrachten und ggf. abändern. Zudem ist der Schritt von einem performanten objektorientierten Design zu einem performanten „Etwas“ nicht sehr groß. Hier heißt es Mäßigung üben. Auf einige der Stolperstellen werden wir zum Schluss des Teils noch einmal zurückkommen.

Primitive und Referenzen[Bearbeiten]

Je nachdem mit wie viel Bit Ihre Laufzeitumgebung per Default arbeitet ist es sinnvoll, auch die Datentypen zu definieren.

Als Designer obliegt es Ihnen somit zu prüfen, ob Sie einen Ganzzahltypen, auch wenn er als mögliche Wertbereiche beispielsweise nur 0..10 hat, einen performanteren Datentyp zu deklarieren. Hierbei kann dann auch der Datentyp für die gleiche Fachlichkeit sich ändern, so dass zur Laufzeit der schnellere Datentyp, zum Speichern der kleinere Datentyp zum Einsatz kommt. Dies kann zu erheblichen Geschwindigkeitsvorteilen führen. Natürlich sollen Sie die Fachanforderungen dabei nicht außer Acht lassen. Möglichkeiten können in Bezug auf andere Performancepunkte, wie Netzwerklast, genutzt werden.

Erweiterungen[Bearbeiten]

Es gibt Punkte, welche in der Analysephase keine oder nur geringe Beachtung verdienen, jedoch für eine performante Anwendung zu berücksichtigen sind.

Hierzu zählen insbesondere die Prüfung des Einbaus von Caches bzw. Objektpools.

Cache[Bearbeiten]

Ein Cache ist eine sehr effiziente Möglichkeit, die Geschwindigkeit einer Anwendung zu erhöhen. Ein Cache ist hier als Halten einer Referenz auf ein Objekt einer Klasse zu verstehen. Immer wenn Sie ein Objekt dieser Klasse benötigen, prüfen Sie zuerst, ob bereits ein entsprechendes Objekt vorliegt. Falls dies nicht der Fall ist, erzeugen Sie ein Objekt und sichern dies im Cache. Nun arbeiten Sie mit dem Objekt im Cache. Das Erzeugen von Objekten ist eine sehr zeitaufwendige Angelegenheit. Um ein Objekt zu erzeugen, müssen die Konstruktoren der Klasse und aller Superklassen verarbeitet werden. Dies führt zu einem enormen Overhead, welcher durch einen Cache in der Anzahl verringert wird.

Ein Cache bietet sich für Netzwerkanwendungen an, da so die Netzwerkkommunikation minimiert werden kann. Objekte können lokal beim Client im Cache gehalten werden und müssen nicht bei erneutem Gebrauch wieder über das Netzwerk geladen werden.

Objektpool[Bearbeiten]

Ein Objektpool kann ebenfalls eine Anwendung wesentlich beschleunigen. In einem Objektpool befinden sich Objekte in einem definierten Ausgangszustand (anders Cache). Objekte, die sich in einem Objektpool befinden, werden aus diesem entliehen und nach der Verwendung wieder zurückgegeben. Der Objektpool ist dafür zuständig, seine Objekte nach dem Gebrauch wieder in den vordefinierten Zustand zu bringen.

Für die Verbindung zu Datenbanken sind Objektpools gut zu verwenden und werden dort insbesondere im Rahmen von Connection-Pools verwendet. Eine Anwendungen/ein Objekt, welches eine Anbindung zur Datenbank benötigt, entleiht sich ein Objekt, führt die nötigen Operationen aus und gibt es wieder an den Objektpool zurück.


Caches und Objektpools bieten eine gute Möglichkeit, Ihre Anwendung zu beschleunigen.

Ein Cache sollten Sie verwenden, wenn der Zustand eines Objektes/einer Ressource für Ihre Anwendung nicht von Interesse ist.

Objektpools sollten Sie in Ihrer Anwendung verwenden, wenn Sie Objekte/Ressourcen mit einem definierten Zustand benötigen.

Netzwerkdesign[Bearbeiten]

Auch für das Netzwerk müssen Sie als Designer bestimmte Überlegungen anstellen. Sobald Sie Daten über ein Netzwerk austauschen, können bisher kleinere Performance-Einbußen sich deutlicher auswirken. Hier müssen Sie als Designer einige Punkte optimieren bzw. vorsehen, die während der Analyse vorbereitet wurden. Während bei lokalen (nicht verteilten) Anwendungen der Methodenaufruf keine relevanten Performance-Engpässe auslöst (und trotzdem optimiert werden kann), ist bei verteilten Anwendungen ein Methodenaufruf anders zu betrachten. Bei der Kommunikation in lokalen Anwendungen besteht die Zeit, in welcher der Aufrufer blockiert wird, asynchronen Aufrufen lediglich aus der Zeit, in welcher die Methoden aufgerufen wird. Bei synchronisierten Aufrufen ist noch die Zeit der Ausführung der Methode hinzuzurechnen. Bei verteilten Anwendungen kommen noch weitere Zeitfaktoren hinzu - ggf. müssen noch Objekte serialisiert werden. Der gleiche Effekt ist das Verpacken der Informationen, wie die Signatur und benötigten Daten bzw. Referenzen, sowie zusätzlich das Transformieren dieser in das IIOP-Format unter CORBA. Hinzu kommt ebenfalls die benötigte Zeit für den Transfer über das Netzwerk.

Bei einer verteilten Anwendung wirken sich bestimmte Modellierungen wesentlich performancekritischer aus, als bei lokalen Anwendungen. Eine enge Kopplung von Objekten bedeutet hier nicht nur viel Kommunikation zwischen den Objekten sondern auch eine höhere Netzwerkauslastung. Dies können Sie durch verschiedene technologische Ansätze bzw. Verbesserungen und durch ein gutes Design optimieren.

Callback[Bearbeiten]

Wenn Sie sich bei der Analyse die Arbeit gemacht haben, die asynchronen von den synchronen Methodenaufrufen zu trennen, werden Sie als Designer nun die Möglichkeit haben, die Infrastruktur aufzubauen.

Callbacks sind eine (in CORBA bereits weit verbreitete) Möglichkeit, die Netzwerklast zu verringern. Dabei wird dem Server eine Referenz auf den Client übergeben. Während ansonsten der Client in regelmäßigen Abständen den Server nach Neuigkeiten fragt, informiert nunmehr der Server den Client über Änderungen, die für ihn relevant sind. Optimalerweise verbindet man das Ganze noch mit dem Observer-Pattern (Beobachter-Muster), um den Server unabhängig vom jeweiligen Client zu machen.

Das Performance Handbuch Softwaredesign und Performance Callback.png

Das Callback-Objekt und Client-Objekt befinden sich dabei lokal. Bei der Anfrage an das Server-Objekt, wird dem Server das Callback-Objekt übergeben und durch den Client mit seiner weiteren Aufgabe fortgefahren. Ihr Server-Objekt führt nun, ohne das Client-Objekt zu blockieren, die aufgerufene Methode aus. Nachdem diese ausgeführt wurde, benachrichtigt er das Callback-Objekt. Dieses ruft nunmehr auf dem Aufrufer eine entsprechende Methode auf. Die Vorteile sind dabei klar erkennbar. Während es normalerweise zu einem Blockieren des Aufrufers bis zur Abarbeitung der aufgerufenen Methode kommt, kann dieser weiter arbeiten.

Das Performance Handbuch Softwaredesign und Performance Callback Strukturierung.png

Natürlich muss man auch hier die bessere Netzwerkperformance mit einem mehr an Code bezahlen. Dies dürfte jedoch bei den meisten Anwendungen – und den heutigen Rechnern – eher in den Hintergrund treten. Bei kurzen Antwortzeiten können Sie jedoch die Performance Ihrer Anwendung verschlechtern. In diesen Fällen kann der Overhead der Netzwerkübertragung nicht durch das nebenläufige Weiterarbeiten des Clients ausgeglichen werden. Noch besser ist es, eine entsprechende Adapterklasse zu erstellen. Diese wird über das Netzwerk angesprochen und kann nun ohne den Netzwerkoverhead das lokale Objekt Meier ansprechen.

Um den Overhead durch die Netzwerkübertragung zu verringern, sollten wir ein besonderes Augenmerk auf die Methodenaufrufe legen.

Methodenaufruf[Bearbeiten]

Eine wichtige Optimierungsmöglichkeit bei allen Anwendungen, welche sich jedoch besonders bei verteilten Anwendungen bemerkbar machen, ist die Verwendung von asynchronen Methodenaufrufen (siehe Callback und Asynchrone Methodenaufrufe).

Eine weit verbreitete Möglichkeit, die Performance zu erhöhen, ist das Teilen der Ergebnismengen. Dabei werden zuerst die wahrscheinlich gewünschten Ergebnisse geliefert. Benötigt der Anwender/Aufrufer weitere Ergebnisse, so werden diese auf Anfrage nachgeliefert. Ein Vorteil ist, dass während der Zusammenstellung des Ergebnisses bereits der Anwender ein Ergebnis sieht und subjektiv die Anwendung schneller wirkt. Sofern der Anwender bereits durch die ersten übermittelten Daten zufrieden gestellt werden kann, wird die Netzwerklast wesentlich verringert. Sofern Sie ein Informationssystem entwickeln werden Sie, ein derartiges Verfahren vermutlich verwenden. Dies ist einer der Gründe, weshalb Internetsuchmaschinen dieses Prinzip nutzen. Der Speicherverbrauch beim Aufrufer wird ebenfalls vermindert, da nur die gerade angeforderten Daten im System gehalten werden müssen. Bei der Umsetzung in grafischen Oberflächen sind u.a. noch andere Punkte ausschlaggebend. Stellen Sie sich eine Personenrecherche in einem umfangreichen System vor, die einige hundert Ergebnisse liefert, die Sie optisch darstellen wollen. Hier kann der Aufbau der entsprechenden visuellen Komponenten Ihr System ausbremsen.

Methodenaufrufe können noch weitere Probleme erzeugen. Eine große Anzahl von Methodenaufrufen in verteilten Objekten führt zu einer hohen Netzwerklast. Hierzu kann es insbesondere in Verbindung mit CORBA sinnvoll sein, diese in einem Methodenaufruf zu binden. Hierbei bieten erweitern Sie die Schnittstelle Ihrer Klasse bzw. Ihres Subsystem derart, dass Sie für mehrere Operationsaufrufe eine gemeinsame Operation anbieten.

Der Vorteil liegt in der Senkung des Overheads bei einem Methodenaufruf. Da die Daten für das Senden über das Netzwerk zuerst verpackt werden müssen, können Sie hier Performancegewinne erreichen.

Das Vorgehen entspricht dem Fassade-Muster, da die Kommunikation hier durch eine kleinere einheitliche Schnittstelle verringert wird. Dies lässt sich neben der Erweiterung der Schnittstelle auch durch eine Verkleinerung erreichen, indem ein Objekttyp zum Übertragen verwendet wird, welcher die benötigten Informationen beinhaltet. Der selbe Ansatz lässt sich auch durch das Verpacken in XML-Dokumente umsetzen.

Entwurfsmuster[Bearbeiten]

Auch die sogenannten Entwurfsmuster (Design Pattern) können bei der Erstellung von performanten Anwendungen helfen. Wir haben im vorigen Abschnitt bereits das Callback-Muster näher betrachtet.

Fliegengewicht-Muster[Bearbeiten]

Zweck[Bearbeiten]

Es sollen mit Hilfe des Musters Fliegengewicht große Mengen von Objekten optimal verwendet bzw. verwaltet werden.

Verwendungsgrund[Bearbeiten]

Die durchgehende Verwendung von Objekten ist für Ihre Anwendung grundsätzlich vorteilhaft, die Implementierung führt jedoch zu Schwierigkeiten. Durch das Fliegengewicht-Muster, können Sie ein Objekt in unterschiedlich vielen Kontexten verwenden. Dieses Fliegengewicht-Objekt enthält dabei die kontextunabhängigen Informationen und kann nicht von einem anderen Fliegengewicht-Objekt unterschieden werden. Zu diesem Fliegengewicht-Objekt existiert ein weiteres Objekt, welches die kontextabhängigen Informationen beinhaltet.

Vorteile[Bearbeiten]

Das Anlegen von Objekten ist nicht gerade eine sehr performante Angelegenheit und kann mit Hilfe des Fliegengewicht-Musters deutlich verringert werden. Dies führt auch in Bezug auf die Erstellung und Zerstörung von Objektinstanzen zu besseren Zeiten und kann Ihre Anwendung daher beschleunigen.

Nachteile[Bearbeiten]

Mir sind keine Nachteile bekannt.

Fassade-Muster[Bearbeiten]

Das Fassade-Muster ermöglicht es, sowohl die Anzahl der verteilten Objekte als auch die Anzahl der Methodenaufrufe an die verteilten Objekte zu verringern. Dieses Muster befasst sich mit dem Problem, dass ein Client viele Methoden auf verteilten Objekten aufruft, um seine Aufgabe zu erfüllen.

Zweck[Bearbeiten]

Die Verwendung des Fassade-Musters soll eine einheitliche Schnittstelle zu einem Subsystem bieten. Dabei ist es nicht Aufgabe des Fassade-Objektes, Aufgaben selbst zu implementieren. Vielmehr soll die Fassade die Aufgaben an die Objekte des Subsystems weiterleiten. Diese Klassen kennen jedoch das Fassade-Objekt nicht. Das Process-Entity-Muster ermöglicht, die Anzahl der Methodenaufrufe auf verteilten Objekten, sowie die Anzahl der verteilten Objekte selbst zu verringern.

Verwendungsgrund[Bearbeiten]

Das Fassade-Muster soll, als abstrakte Schnittstelle, die Kopplung zwischen einzelnen Subsystemen verringern. Zusätzlich soll mit Hilfe der Fassade es Fremdsubsystemen möglich sein, ohne Kenntnis über den Aufbau eines Subsystems dieses allgemein verwenden zu können. Das Process-Entity-Muster soll die Anzahl der verteilten Objekte und der Methodenaufrufe auf verteilten Objekten verringern. Zu diesem Zweck wird für eine Aufgabe ein Fassadenobjekt erstellt (Process), welches die Schnittstelle zur Bewältigung der Aufgabe anbietet. Dies ist das einzige verteilte Objekt. Das Process-Objekt übernimmt serverseitig die Aufgabe und delegiert diese an die (nun) lokalen Entity-Objekte.

Performancegründe[Bearbeiten]

Für die Verwendung in Netzwerken können Sie mittels des Fassade-Musters die Anzahl der Operationsaufrufe verringern. Bei der Kommunikation über das Netzwerk werden, anstatt mehrere Operationen auf entfernten Objekten eines Subsystems, eine oder wenige Operation des entfernten Fassade-Objektes aufgerufen. Dieses delegiert nunmehr die einzelnen Aufgaben an die lokalen Klassen des Subsystems. Das Process-Entity Muster ist die Umsetzung des Fassade Muster speziell für Netzwerke und wird daher besonders in Zusammenhang mit CORBA und EJB verwendet.

Vorteile[Bearbeiten]

Die Verwendung des Subsystems wird erleichtert, ohne dass Funktionalität verloren geht. Dies wird realisiert, da die Schnittstellen der Subsystemklassen nicht verkleinert werden. Außerdem wird die Netzwerkkommunikation verringert, wodurch der Overhead des entfernten Operationsaufrufes verringert wird. Die Kopplung zwischen den einzelnen Subsystemen kann verringert werden, wodurch die Wartbarkeit der Anwendung erhöht wird.

Nachteile[Bearbeiten]

Es entsteht mehr Code. Als Entwickler des Subsystems, welches die Fassade-Klasse enthält haben Sie mehr Arbeit, da Sie auch die Wartung dieser Klasse übernehmen müssen. Während die Netzwerkgeschwindigkeit steigt, sinkt jedoch durch die Delegation der Aufgabe durch die Fassade an die Objekte des Subsystems die Geschwindigkeit der Ausführung nach außen. Lokale Anwendungen werden somit etwas langsamer als ohne das Fassade Muster. Dies ist jedoch lokal durch den direkten Zugang an die Objekte des Subsystems lösbar.

Process-Entity-Muster[Bearbeiten]

Das Process-Entity-Muster ist eine spezielle Ausprägung des Fassade-Musters . Dabei wird eine Adapterklasse bereitgestellt, die auf dem Server steht und als Ansprechpartner über das Netzwerk zu Verfügung steht. Objekte dieser Klasse delegieren die entfernten Aufrufe ihrer Methoden an die lokalen Objekte und vermindert somit den Netzwerkoverhead.

Null Pattern[Bearbeiten]

Auch das Null Pattern kann für einen Performancegewinn sorgen. Dieses Muster wirkt jedoch nicht bei allen Programmiersprachen - unter Java führt die Verwendung z.B. zu keinen Geschwindigkeitsgewinnen, da die JVM immer strikt gegen null prüft, bevor diese Methoden auf Objekten aufruft.

Zweck[Bearbeiten]

Das Null Pattern soll vor allem unnötige if then else Verzweigungen vermeiden.

Verwendungsgrund[Bearbeiten]

Damit Operationen/Methoden auf Objekten aufgerufen werden können, müssen diese zwangsläufig erst einmal erstellt werden. Als Entwickler können Sie jedoch nicht zwangsläufig davon ausgehen, dass auch immer eine Instanz vorliegt. Das typische Vorgehen ist dabei die Prüfung der Referenz auf null. Dies führt dabei meist zu if then else Blöcken, die den Quelltext schlecht lesbar machen. Um dies zu vermeiden, können in vielen Fällen auch Null-Objekte angelegt werden. Diese haben üblicherweise nur leere Methodenrümpfe und werfen keine Exception. In einigen Fällen ist es auch möglich, dieses Muster mit dem Singleton-Muster zu verknüpfen.

Performancegründe[Bearbeiten]

Nicht jede Programmiersprache wird über eine eigene virtuelle Maschine gestartet, welche prüft, ob eine Instanz vorliegt, auf die die angesprochene Referenz verweist. Hierbei kann es u.U. schneller sein, dafür zu sorgen, dass stets eine gültige Referenz vorliegt.

Vorteile[Bearbeiten]

Insbesondere die Lesbarkeit des Quelltextes nimmt zu. Daneben kann aber auch die Ausführungsgeschwindigkeit erhöht werden.

Nachteile[Bearbeiten]

Der Speicherverbrauch erhöht sich durch die vermehrten Objekte. Außerdem müssen Sie beachten, dass ggf. die Zeit zur Erstellung der Objekte den tatsächlichen Zeitgewinn wettmachen kann. Hier müssen Sie Tests machen.

Singleton-Muster[Bearbeiten]

Das Singleton-Muster hat den sekundären Zweck die Performance Ihrer Anwendung zu erhöhen.

Zweck[Bearbeiten]

Ein Instanz einer Klasse soll zur Laufzeit nur einmal existieren.

Verwendungsgrund[Bearbeiten]

In bestimmten Fällen ergibt es zur Laufzeit keinen Sinn mehr als eine Instanz einer Klasse zu erzeugen. In diesen Fällen wird das Singleton-Muster verwendet um das mehrfache Erzeugen von Instanzen der Klasse zur Laufzeit zu verhindert. Das Singleton Muster ist dabei eine spezielle Art des Fabrikmusters (meist Methoden Fabrik), bei welchem stets das selbe Objekt an den Aufrufer zurückgegeben wird.

Performancegründe[Bearbeiten]

Das Singleton-Muster ermöglicht es unnötige Instanzen einer Klasse zu vermeiden. Es ist jedoch nicht unbedingt, auch wenn es der Name ausdrückt, auf eine Instanz beschränkt, sondern kann beliebig erweitert werden, um lediglich eine bestimmte Anzahl von Instanzen zuzulassen. Dies ermöglicht es Ihnen unnötige Speicherverschwendung zu unterbinden.

Vorteile[Bearbeiten]

Der Speicherverbrauch der Anwendung kann bei bestimmten Klassen gezielt vermindert werden, um unnötige Objekterzeugung zu unterbinden. Das Singleton wirkt außerdem wie ein Ein-Objekt-Cache und vermindert daher die Zugriffszeit auf dieses Objekt.

Nachteile[Bearbeiten]

Das Singleton-Muster ist ein Erzeugungsmuster. Sie sollten daher überlegen, ob Sie wirklich diese eine Instanz der Klasse benötigen oder direkt mit Klassenoperationen arbeiten können. Dies würde die Zeit für die Objekterstellung einsparen.

Sichtbarkeiten[Bearbeiten]

Jetzt kommen wir in die Teile, die zwar die Performance erhöhen können, jedoch nicht unbedingt besseres Design darstellen.

Die Sichtbarkeiten von Referenzen, also auch der Beziehungen und der primitiven Datentypen endgültig festzulegen, ist ebenfalls Ihre Aufgabe als Designer. Die Sichtbarkeit kann Ihre Anwendung beschleunigen, da die Zugriffsgeschwindigkeit u.a. von dieser Abhängig ist. Es gibt jedoch neben der Sichtbarkeit auch noch andere Optimierungsmöglichkeiten. Lokale Variablen sind am schnellsten, so dass der Einsatz des Memory Access Pattern hier zu Geschwindigkeitsvorteilen führen kann. Auch Klassenvariablen sind schneller als Variablen auf Objektebene. Dies führt jedoch schon wieder in die OO Abgründe der Performanceoptimierungen.

Inlining und Sichtbarkeit[Bearbeiten]

Das Inlining ist eine sehr gute Möglichkeit die Geschwindigkeit Ihrer Anwendung zu optimieren. Damit das Inlining durch den Compiler erfolgen kann, ist jedoch die Sichtbarkeit einzuschränken. Hierbei gilt, dass Variablen, die private sind, sowie private Methoden grundsätzlich optimiert werden können. Konstanten und innere Operationen können einige Compiler ebenfalls optimieren. Als Beispiel kann der Compiler javac von Sun Microsystem dienen, welcher bereits kleine Operationen und Variablen inlinen kann, wenn als Parameter -o verwendet wird.

Vererbungshierarchie[Bearbeiten]

Die Vererbungshierarchie ist ebenfalls ein wichtiger Ansatzpunkt. Als Grundsatz gilt, dass eine flache Klassenhierarchie performanter ist. Natürlich ist dies eine Prüfung des Design und sollte von Ihnen nicht nur aufgrund von Performanceüberlegungen nicht oder unzureichend berücksichtigt werden. Es kann sich jedoch anbieten eine spezialisierte Klasse einer weiteren Vererbung vorzuziehen.

Insbesondere das Erzeugen von Objekten kann hier zu hohen Geschwindigkeitseinbussen führen. Beim Erzeugen eines Objekts wird üblicherweise der Konstruktor aufgerufen. Der Aufruf eines Konstruktors hat jedoch stets auch den Aufruf eines Konstruktors in jeder Superklasse zur Folge. Dies zieht entsprechende Folgen nach sich. Sinnvoll ist es daher ebenfalls möglichst wenig – besser noch gar keine Funktionalität in den Standardkonstruktor zu legen.

Ein kleines Beispiel (für Java) hierzu verdeutlicht die Aufrufkette, welche entsteht bei einer kleinen Vererbungshierarchie. Der Aufruf des Standardkonstruktor muss dabei nicht explizit angegeben werden.

Das Performance Handbuch Softwaredesign und Performance Konstruktorenaufruf.png

 package de.wikibooks.vererbung;
 public class Konstruktoraufruf extends Konstruktoraufruf2{
   public Konstruktoraufruf() {
     System.out.println(Konstruktoraufruf.class);
   }
   public static void main(String [] args){
     Konstruktoraufruf k = new Konstruktoraufruf();
   }
 }
 class Konstruktoraufruf2 extends Konstruktoraufruf3{
   public Konstruktoraufruf2() {
     System.out.println(Konstruktoraufruf2.class);
   }
 }
 class Konstruktoraufruf3 extends Konstruktoraufruf4{
   public Konstruktoraufruf3() {
     System.out.println(Konstruktoraufruf3.class);
   }
 }
 class Konstruktoraufruf4 extends Konstruktoraufruf5{
   public Konstruktoraufruf4() {
     System.out.println(Konstruktoraufruf4.class);
   }
 }
 class Konstruktoraufruf5 {
   public Konstruktoraufruf5() {
     System.out.println(Konstruktoraufruf5.class);
   }
 }

Beim Start der Klasse Kontruktoraufruf erhalten Sie nun diese Ausgabe:

class de.wikibooks.vererbung.Konstruktoraufruf5
class de.wikibooks.vererbung.Konstruktoraufruf4
class de.wikibooks.vererbung.Konstruktoraufruf3
class de.wikibooks.vererbung.Konstruktoraufruf2
class de.wikibooks.vererbung.Konstruktoraufruf

Antidesign[Bearbeiten]

Die Überschrift Antidesign ist vielleicht etwas hart, drückt jedoch gut aus, welche Betrachtungen wir im folgenden Anstellen.

Direkter Variablenzugriff[Bearbeiten]

Der direkte Variablenzugriff ist eine Möglichkeit die letzten Geschwindigkeitsoptimierungen aus Ihrer Anwendung herauszukitzeln. Dabei rufen andere Objekte nicht mehr die Zugriffsoperationen auf, um die Informationen von einem bestimmten Objekt zu erfragen, sondern nehmen sich die Informationen direkt.

Dies widerspricht dem OO Gedanken der Kapselung derart, dass ich hier nicht näher darauf eingehen möchte. Da jedoch der Overhead des Operationsaufrufes hier wegfällt, ist diese Zugriffsart schneller. Sie müssen jedoch alle Referenzen / primitive Datentypen hierfür mit der öffentlichen Sichtbarkeit belegen.

Vermischung von Model und View[Bearbeiten]

Hier gilt ähnliches wie bei dem direkten Variablenzugriff. Sofern Sie Model und View nicht trennen, sondern dies innerhalb eines einzigen Objektes gestalten erhalten Sie eine schnellere Anwendung. Hier entfällt neben dem Methodenaufruf (innerhalb einer Klasse, können Sie ja auch nach dem OO Ansatz direkt auf die Informationen zugreifen) auch das Erstellen des zweiten Objektes und der Zugriff auf dieses.

Dies ist nicht so weit herbeigezogen, wie Sie vielleicht denken. Die java.awt.List hält, wie alle AWT Klassen auch die Daten in sich. Die javax.swing.JList hingegen trennt die Repräsentation von dem eigentlichen Modell (javax.swing.ListModel).


Wikibooks buchseite.svg Zurück zu Analyse und Performance | One wikibook.svg Hoch zu Inhaltsverzeichnis | Wikibooks buchseite.svg Vor zu Implementierung und Performance