27. Januar 2013

Fedora: Gamepads und Joysticks verwenden

Ab Fedora 17 fehlt dem, von der Distribution verwendeten, Default-Kernel  das Modul "joydev" zur Unterstützung von Gamepads und Joysticks. Beim  Anschließen eines solchen Eingabegeräts wird dieses deshalb nicht  erkannt. Das fehlende Kernel-Modul wird jedoch als Paket über das  Standard-Repository des Paketmanagers YUM angeboten. Um das Paket  "kernel-modules-extra", das das benötigte joydev-Modul beinhaltet, zu  installieren, reicht daher der folgende Terminal-Befehl aus:
 $ sudo yum install kernel-modules-extra  
Desweiteren sollte man noch das Paket "joystick" installieren, um testen zu können, ob ein angeschlossenes Gampad bzw. ein Joystick korrekt funktioniert. Auch dies ist wie folgt über YUM möglich.
 $ sudo yum install joystick  
Nach der Installation und dem Anschließen des jeweiligen Geräts, sollte dafür das Verzeichnis /dev/input/jsX angelegt worden sein (wobei das X eine fortlaufende Nummer ist). Um die korrekte Funktion zu testen, kann das Konsolen-Programm "jstest" verwendet werden, das folgendermaßen gestartet wird:
 $ jstest --event /dev/input/jsX  
Dadurch werden alle, vom Gamepad oder Joystick empfangenen Signale visualisiert.

Zur Verwendung der, auf diese Weise installierten, Geräte kann das Programm "Qjoypad" hilfreich sein. Es ermöglicht die Abbildung von Controllereingaben auf Tastatureingaben und Mausbewegungen. Die folgenden Schritte sind nötig um Qjoypad unter Fedora zu installieren:

  1. Über den folgenden Befehl müssen Qt-Packages installiert werden:
     $ sudo yum install qt qt-devel  
    
  2. Die aktuelle Version von Qjoypad muss unter der Adresse http://sourceforge.net/projects/qjoypad/ heruntergeladen werden.
  3. Das so heruntergeladene tar-Archiv muss zunächst entpackt werden, was beispielsweise über folgenden Befehl möglich ist:
     $ tar xvfz qjoypad-VERSION.tar.gz  
    
  4. Das Unterverzeichnis "src" aus dem soeben entpackten Archiv muss geöffnet werden.
  5. Darin befindet sich ein Skript namens "config", das in einem Texteditor geöffnet werden muss, um die Zeilen 4 bis 7 und 14 bis 17 durch das voranstellen des Zeichens # auszukommentieren, da sie zu Problemen führen können.
  6. Das eben editierte Skript muss über folgenden Befehl ausgeführt werden:
     $ ./config  
    
  7. Daduch wird eine Datei mit dem Namen "Makefile" erzeugt, die ebenfalls editiert werden muss, indem "-lX11" an das Ende der 19. Zeile angehängt wird. Diese sollte danach etwa folgendermaßen aussehen:
     LIBS = $(SUBLIBS) -L/usr/lib64 -lXtst -lQtGui -lQtCore -lpthread -lX11  
    
  8. Das Programm muss anschließend kompiliert und installiert werden, was über folgenden Befehl funktioniert:
     $ sudo make install  
    
    Falls dabei ein Fehler auftritt, dass das Programm "g++" nicht gefunden wird, muss dieses zunächst über folgenden Befehl installiert werden:
     $ sudo yum install gcc-c++  
    
    Wenn das Kompilieren mit dem Fehler, dass die Datei "X11/extensions/xTest.h" nicht gefunden wird, abbricht, muss zuvor noch folgende Installation durchgeführt werden:
     $ sudo yum install libXtst-devel  
    
  9. Wenn die Installation erfolgreich abgeschlossen wurde, lässt sich Qjoypad über den folgenden Befehl starten:
     $ qjoypad  
    

26. Januar 2013

Fedora: Paketmanagement mit YUM

Yum (Yellow Dog Updater Modified) ist der Paketmanager, der unter der Linux-Distribution "Fedora" zum Einsatz kommt. Über das Konsolen-Frontend können RPM-Pakete (RPM Package Manager, früher Red Hat Package Manager) gesucht, installiert, aktualisiert und deinstalliert werden. Die wichtigsten Befehle hierzu sollen im Folgenden kurz vorgestellt werden. Eine vollständige Dokumentation findet sich unter http://www.fedorawiki.de/index.php/Yum.

Pakete suchen: 
Der folgende Befehl kann zur Suche von Paketen nach einem bestimmten Schlüsselwort genutzt werden.
 yum search Suchwort  
Dabei werden sowohl Paketnamen, als auch Paketinformationen durchsucht. Sollen nur Pakete mit einem bestimmten Namen ausgegeben werden, kann der unten stehende Befehl verwendet werden.
 yum list *Suchwort*  
Die *-Zeichen vor und nach dem Suchwort bewirken, dass davor und danach noch weitere Buchstaben stehen dürfen. Der Suchbegriff kann auf diese Weise durch reguläre Ausdrücke näher definiert werden.
Soll lediglich die Paketinformation, die nähere Informationen über ein Paket enthält ausgegeben werden, so kann der folgende Befehl genutzt werden.
 yum info Paketname  
Pakete installieren:
Zur Installation eines bestimmten Paketes aus den Repositories dient der folgende Befehl.
 yum install Paketname  
Handelt es sich stattdessen um ein lokal vorliegendes Paket, muss stattdessen dieser Befehl genutzt werden:
 yum localinstall Verzeichniszweig/Name.rpm  
Pakete aktualisieren:
Um zu überprüfen, ob eine Aktualisierung für ein bestimmtes, bereits installiertes Paket vorhanden ist und diese eventuell zu installieren, kann folgender Befehl genutzt werden.
 yum update Paketname  
Wird der Befehl, wie unten dargestellt, ohne die Angabe eines Paketnamens ausgeführt, so werden alle installierten Pakete aktualisiert.
 yum update  
Wenn lediglich überprüft werden soll, für welche Pakete Updates vorliegen, ohne diese automatisch zu installieren, kann folgender Befehl genutzt werden. Er listet alle installierten Pakete auf, für die Aktualisierungen gefunden werden konnten.
 yum check-update  
Pakete entfernen:
Um ein, auf dem System installiertes, Paket wieder zu entfernen, kann folgender Befehl aufgerufen werden. Er deinstalliert das angegebene Paket, sowie alle damit verbundenen Abhängigkeiten.
 yum remove Paketname

22. Januar 2013

Entwurfsmuster: Fabrikmethode

Das Entwurfsmuster "Fabrikmethode" (engl. "Factory-Method") gehört zur Gruppe der Erzeugungsmuster und beschreibt, wie Objekte über Methoden-, statt über Konstruktoraufrufe erzeugt werden können. Manchmal wird diese Technik auch "virtueller Konstruktor" (engl. "Virtual Constructor") genannt. Der folgende Code zeigt diesen Unterschied:
 // Instanziierung über Konstruktor  
 Object object = new Objekt();  
   
 // Instanziierung über Fabrikmethode  
 Object object = new ObjectFactory().createObject();  
Seine Anwendung findet dieses Erzeugungsmuster, wenn dem Klienten die Art der Objekte, die erzeugt werden sollen, nicht bekannt ist, da durch dessen Benutzung eine dedizierte Schnittstelle zum Erzeugen von Objektinstanzen definiert wird. Das Prinzip der Fabrikmethoden kann, wie im Folgenden dargestellt, in Form eines Klassendiagramms dargestellt werden:

Klassendiagramm des Entwurfsmusters "Fabrikmethode"

Das Interface "Product" beschreibt hierbei die Schnittstelle, die jedes Objekt, das über die verschiedenen Fabrikmethoden erzeugt werden soll, implementieren muss. Die Erzeugung der jeweiligen Objekte selbst geschieht durch konkrete Fabrik-Implementierungen, die von einer abstrakten Fabrik, die die einheitliche Fabrikmethode definiert, abgeleitet werden. Es sind hierbei auch mehrere parametrisierte Fabrikmethoden denkbar.

Vorteile:
  • Die Objekterzeugung wird in der jeweiligen Factory-Klasse gekapselt und wird somit austauschbar, da der Klient nur deren Schnittstelle kennt.
  • Weitere Konstruktionsvarianten können mit wenig Aufwand durch die Einführung einer neuen abgeleiteten Fabrik hinzugefügt werden.
  • Die verschiedenen Factory-Klassen können unabhängig voneinander gewartet bzw. geändert werden.
  • Die Factory-Klasse kann zur Erzeugung verschiedene Parameter oder innere Zustände heranziehen.
Nachteile:
  • Ein konkreter Erzeuger ist stark an das zu erzeugende Produkt gebunden, was dazu führt, dass neue Produkte stets auch neue Erzeuger erfordern. 

Der Begriff der Fabrikmethode wird teilweise auch verwendet, wenn nur die Objektinstanziierung über eine Methode, statt über einen Konstruktor, ohne die Verwendung von Unterklassen, gemeint ist, was jedoch nicht der eigentlichen Definition des Pattern entspricht. 

Entwurfsmuster: Memento

Ein weiteres Entwurfsmuster, das in dieser Serie vorgestellt werden soll, ist das Memento-Pattern (teilweise auch "Token" genannt). Es gehört zur Familie der Verhaltensmuster und dient dazu den Zustand eines Objekts zu einem bestimmten Zeitpunkt zu speichern, so dass dieser später wiederhergestellt werden kann. Mithilfe des Patterns lässt sich die Anforderung umsetzen, ohne die Kapselung der objektinternen Daten zu verletzen. Vorstellbar ist eine solche Implementierung für die Umsetzung von Haltepunkten oder von Undo-Mechanismen. Im Folgenden ist das UML-Klassendiagramm des Memento-Patterns zu sehen:

Das UML-Diagramm des Memento-Patterns (Klicken zum Vergrößern)

Die Klasse "Originator" stellt die Klasse dar, von der ein Memento erstellt werden soll. Die Verantwortlichkeit hierfür liegt bei der Klasse selbst, da nur sie ihren internen Zustand kennt. Die Klasse "Memento" stellt das Memento dar, in dem der Objektzustand gespeichert werden kann. Dafür müssen die notwendigen Attribute, sowie jeweils Getter- und Setter-Methoden vorhanden sein. Für die Erstellung eines Mementos bietet die Klasse "Originator" eine Methode an. Auch für die spätere Wiederherrstellung ihres vorherigen Zustandes besitzt sie eine Methode, die ein Memento-Objekt entgegennimmt. Die Klasse "CareTaker" ist für die Zwischenspeicherung eines oder mehrerer Mementos zuständig, bis sie wieder gebraucht werden. Dabei darf sie die Integrität der Memento natürlich nicht durch deren Veränderung verletzen.

Vorteile:
  • Die interne Repräsentation des Objekts, dessen Zustand gespeichert werden soll, wird nicht nach außen hin offen gelegt.
Nachteile:
  • Abhängig von den Daten, die das Objekt beinhaltet, kann die Erstellung eines Memento-Objektes sehr aufwändig sein.
  • Es ist zusätzliche Logik nötig um die erstellten Mementos bis zu ihrer Wiederverwendung zwischenzuspeichern. 
Alternativen zu einem Memento stellen der Zugriff über einen Proxy, der die Sicherung und die spätere Wiederherstellung des Objektzustandes vornimmt, oder die Serialisierung bzw. Deserialisierung des Objekts dar.

Das folgende einfache Java-Beispiel soll die Verwendung des Memento-Patterns, anhand der Implementierung einer Undo-Funktionalität, verdeutlichen. Zunächst muss die Klasse definiert werden, deren Zustand gespeichert werden soll. In diesem Beispiel handelt es sich bei dem Zustand lediglich um einen einzigen String.
 public class Originator {  
   
   private String state = "";  
   
   public void setState(String state) {  
     this.state = state;  
   }  
   
   public String getState() {  
     return state;  
   }  
   
   public Memento createMemento() {  
     return new Memento(state);  
   }  
   
   public void setMemento(Memento memento) {  
     state = memento.getState();  
   }  
   
 }  
Das entsprechende Memento zu der Klasse sieht folgendermaßen aus:

 public class Memento {  
   
   private final String state;  
   
   public Memento(final String state) {  
     this.state = state;  
   }  
   
   public String getState() {  
     return state;  
   }  
   
 }  
Außerdem wird noch eine Klasse benötigt, die Mementos speichern und wieder zurückgeben kann. Die folgende Implementierung benutzt hierfür eine Liste und besitzt eine Methode zum Hinzufügen Mementos und eine zum Zurückgeben des jeweils zuletzt hinzugefügten Mementos.
 public class CareTaker {  
   
   private List<Memento> mementos = new ArrayList<>();  
   
   public void addMemento(Memento memento) {  
     mementos.add(memento);  
   }  
   
   public Memento getLastMemento() {  
     Memento result = mementos.get(mementos.size() - 1);  
     mementos.remove(result);  
     return result;  
   }  
   
 }  
Eine beispielhafte Verwendung all dieser Klassen könnte in etwa so aussehen:
 CareTaker careTaker = new CareTaker();  
 Originator = new Originator();  
 originator.setState("State1");  
 careTaker.addMemento(originator.createMemento());  
 originator.setState("State2");  
 careTaker.addMemento(originator.createMemento());  
 originator.setState("State3");  
   
 System.out.println(originator.getState());  
 originator.setMemento(careTaker.getLastMemento());  
 System.out.println(originator.getState());  
 originator.setMemento(careTaker.getLastMemento());  
 System.out.println(originator.getState());  
Insgesamt wird der Zustand drei mal geändert und jedes mal wird ein Memento erstellt und dem CareTaker übergeben. Anschließend werden alle Mementos nacheinander wieder zurückgespielt, bis der Ausgangszustand wieder erreicht ist. Das Programm liefert folgende Ausgabe: 
 State3  
 State2  
 State1  

Entwurfsmuster: Proxy

Der Begriff eines Proxys bedeutet so viel wie "Stellvertreter", was dem Prinzip des gleichnamigen Strukturmusters entspricht. Ein Proxy fungiert dabei als ein Platzhalter für ein anderes Objekt und leitet Zugriffe auf dieses weiter. Dies führt dazu, dass das originale Objekt hinter dem Proxy-Objekt versteckt wird. Dessen Aufgabe ist es nicht, neue Eigenschaften oder Funktionalitäten hinzuzufügen, sondern den Zugriff auf das Original für den Benutzer zu erleichtern. Der Proxy stellt also eine Art intelligenten "Zeiger" dar, der je nach Bedarf angepasst werden kann.

Umgesetzt wird ein solcher Proxy, indem er die selbe Schnittstelle implementiert, wie das Objekt, als dessen Platzhalter er fungiert. Für die Delegation der Methodenaufrufe, die an den Proxy erfolgen, verfügt dieser über eine Referenz auf das Original. Das unter stehende Klassendiagram verdeutlicht diesen Zusammenhang:

Zusammenspiel der Klassen des Proxy-Entwurfsmusters

Vorteile:
  • Ein Proxy kann Aspekte implementieren, die über die Verantwortlichkeit der Originals hinaus gehen. Dazu gehören technische Aspekte wie z.B. Caching, Sicherheitsabfragen, Pooling oder Logging. 
Nachteile:
  • Für jede Methode des Originals muss der Proxy eine Methode mit der selben Signatur anbieten, was zu Redundanz führen kann, falls keine zusätzliche Funktion vom Proxy umgesetzt wird. 

21. Januar 2013

Entwurfsmuster: Adapter

Das Entwurfsmuster "Adapter", dient, wie der Name bereits andeutet, dazu, eine Schnittstelle einer anderen, inkompatiblen Schnittstelle anzupassen. Damit können Klassen zusammenarbeiten, die sonst aufgrund ihrer Schnittstellen nicht dazu in der Lage wären. 

Häufig wird man zu der Verwendung eines Adapters geradezu gezwungen, wenn man mit vorgegebenen Klassen, etwa die eines Frameworks, arbeiten muss, deren Schnittstelle aber nicht der benötigten entspricht. Andererseits kann man den Ansatz auch vorrauschauend nutzen, um etwa Klassen mit erhöhter Wiederverwendbarkeit zu erstellen, die im Folgenden mit beliebigen anderen Klassen zusammenarbeiten können. Auch denkbar wäre die Verwendung eines Adapters wenn verschiedene Implementierungen, die sich jedoch nur schwierig unter einer gemeinsamen Schnittstelle zusammenfassen lassen, genutzt werden sollen.

Bei einem Adapter handelt sich um ein Strukturmuster, das in zwei verschiedenen Varianten existiert: Einer klassenbasierten, die auf Mehrfachvererbung basiert, sowie einer objektorientierten, die auf Delegation setzt. Beide Varianten und ihre Besonderheiten sollen im Folgenden erläutert werden.

Klassenbasierter Adapter:

Da die Umsetzung eines klassenbasierten Adapters auf Mehrfachvererbung basiert, was von vielen Programmiersprachen, wie z.B. Java nicht unterstützt wird, kann er nur unter solchen Sprachen, die Merhfachvererbung anbieten, sinnvoll genutzt werden. 

Das folgende Klassendiagramm zeigt die Realisierung eines solchen Adapters, indem er sowohl von der Ziel-Schnittstelle, als auch von der bereits vorhandenen abgeleitet wird:

Das Klassendiagram eines klassenbasierten Adapters

Neben der Einschränkung, dass ein solcher Adapter nur in Programmiersprachen eingesetzt werden kann, die Mehrfachvererbung unterstützen, existiert noch der weitere Nachteil, dass, anders als bei dem objektorientierten Adapter, der im Folgenden noch vorgestellt werden wird, Unterklassen des anzupassenden Dienstes nicht automatisch von dem Adapter abgedeckt werden. Dieser Nachteil kann allerdings auch als Vorteil gewertet werden, da der Adapter genau einer einzigen Zielklasse angepasst ist und nur deren Verhalten überschreiben kann.

Objektorientierter Adapter:

Für alle Programmiersprachen, die keine Mehrfachvererbung anbieten, kommt nur diese Variante des Adapters in Frage. Dabei besitzt der Adapter, anstatt von der anzupassenden Klasse zu erben, eine Referenz auf diese. Das folgende Klassendiagramm verdeutlicht den Unterschied zu dem klassenbasierten Adapter:

Das Klassendiagramm eines objektorientierten Adapters

Die Referenz auf die anzupassende Klasse wird dabei in der Regel beim Konstruktoraufruf an den Adapter übergeben. Der folgende Beispielcode, soll das verdeutlichen:
 public interface Ziel {  
   
   void operation();  
   
 }  
   
 public class Adapter implements Ziel {  
   
   private Dienst dienst;  
   
   public Adapter(Dienst dienst) {  
     this.dienst = dienst;  
   }  
   
   @Override  
   public void operation() {  
     dienst.action();  
   }  
   
 }  
Neben dem offensichtlichen Vorteil, dass diese Adapter-Variante in jeder beliebigen Programmiersprache seinen Einsatz finden kann, kann auch die Eigenschaft, dass an den Adapter auch Unterklassen des anzupassenden Dienstes übergeben werden können, von Vorteil sein.

Generell bieten alle Adapter den Vorteil, dass der dahinterliegende Dienst und auch sie selbst zur Laufzeit ausgetauscht werden können, was Varianten verschiedener Implementierungen erlaubt. Nachteiligt wirkt sich aus, dass Adapter sehr komplex werden können, falls die zu adaptierende Schnittstelle dementsprechend groß ist, oder die Zielschnittstelle technisch stark von der bestehenden Schnittstelle abweicht.

Entwurfsmuster: Strategie

Das Verhaltensmuster "Strategie" (engl. Strategy-Pattern) findet seinen Einsatz in der Softwareentwicklung, wenn es darum geht, Algorithmen in Klassen zu kapseln und sie damit, abhängig vom Kontext, austauschbar zu machen. Die Anwendung bietet sich besonders dann an, wenn viele verwandte Klassen sich nur in ihrem Verhalten unterscheiden, welches durch das Pattern ausgelagert werden kann.

Die Verwendung dieses Patterns ist relativ simpel. Es muss nur, wie im unten stehenden Klassendiagramm verdeutlicht, eine einheitliche Schnittstelle definiert werden, die für alle Algorithmen einer Familie gilt. Ein Client kann dann diese verschiedenen Algorithmus-Implementieren nutzen, wobei diejenigen, die von der gleichen Schnittstelle erben, untereinander austauschbar sind.

Klassendiagramm des Strategie-Entwurfmusters

Vorteile:
  • Der Klient, der die Algorithmen nutzen soll, ist nur von der Schnittstelle und nicht von den konkreten Implementierungen abhängig.
  • Somit können verschiedene Algorithmen (auch zur Laufzeit) ausgewählt werden.
  • Daten, die vom Algorithmus verwendet werden, müssen dem Klienten nicht offen gelegt werden.
  • Aufwändige switch-Anweisungen zur Auswahl von Algorithmen können vermieden werden.
Nachteile:
  • Der Klient, der die Algorithmen nutzen soll, muss alle Algorithmen kennen, um eine Auswahl treffen zu können.
  • Es besteht ein gewisser Kommunikationsaufwand zwischen dem Klienten und der Strategie.
  • Die Schnittstelle muss für alle Algorithmen (egal wie komplex) einer Familie gleich sein. 

Ein Beispiel für die Verwendung dieses Entwurfsmuster wäre z.B. der Einsatz von Sortieralgorithmen, von denen es viele verschiedene Implementierungen, mit jeweils unterschiedlichen Vor- und Nachteilen gibt. Abhängig von den vorliegenden Daten, die sortiert werden sollen, kann es sinnvoll sein, den zu verwendenden Algorithmus frei wählen zu können.

Entwurfsmuster: Iterator

Das Verhaltensmuster "Iterator", was in diesem Post vorgestellt werden soll, ist eines der meist verwendeten Entwurfsmuster überhaupt. Es dient dazu sequentiell auf eine Datenstruktur (Aggregat) zuzugreifen, ohne dass dazu deren interne Repräsentation offen gelegt werden muss. Das dazugehörige Klassendiagramm ist im Folgenden dargestellt:

Die Klassen und Interfaces eines Iterators

Das Interface "Aggregate" muss dabei von Datenstrukturen (in diesem Beispiel eine Liste) implementiert werden, die Iteratoren anbieten sollen, und definiert eine einzelne Methode, die einen solchen Iterator zurückgibt. Das Interface "Iterator" defniert die Schnittstelle, die von einer Iterator-Implementierung, von der es für jeden Datenstrukturtyp jeweils eine geben wird, implementiert werden muss. Die in diesem Beispiel definierte Schnittstelle stellt nur die mindestens nötigen Funktionen bereit, es wären noch viele weitere denkbar. Jede Datenstruktur, die das "Aggregate"-Interface implementiert, liefert beim Aufruf der iterator()-Methode eine Instanz auf einen, zur Datenstruktur passenden, Iterator, wie es in dem Code-Fragment angedeutet ist.

Vorteile:
  • Die Schnittstelle des Aggregats wird vereinfacht, da es selbst keine Schnittstelle für den Zugriff auf die Daten anbieten muss.
  • Es sind mehrere Traversierungen zur selben Zeit möglich, da jeder Iterator seinen eigenen Traversierungszustand verwaltet.
Nachteile: 
  • Der Iterator muss damit umgehen können, wenn Daten während des Iterierungsvorgangs hinzugefügt oder gelöscht werden. Dazu muss der interne Zustand des Iterators vom Aggregat angepasst werden, sobald dessen interne Repräsentation geändert wird.
  • Ein Iterator benötigt oft priviligierten Zugriff auf die zu iterierende Datenstruktur. Dieses Problem kann entweder gelöst werden, indem man den Iterator als Inner-Class des Aggregats implementiert (in diesem Fall kann der Iterator dann explizit über einen Aufruf wie  new List.Iterator() oder polymorph über eine Fabrikmethode des Aggregats erzeugt werden), oder indem man den Iterator in einer separaten Klasse implementiert, die eine Referenz auf das Aggregat beim Konstruktoraufruf übergeben bekommt.

Der folgende Code-Ausschnitt zeigt eine Iterator-Implementierung, wie sie üblicherweise vorkommen könnte:
 class ListIterator implements Iterator {   
   
   private List list;   
   
   int count = 0;   
   
   public ListIterator(List list) {   
     this.list = list;   
   }   
   
   pubic boolean hasNext() {   
     return count < list.size();   
   }   
   
   public Object next() {   
     synchronized (list) {   
       if (count < list.size()) {   
         return list.get(count++);   
       }   
       return null;   
     }   
   }   
   
 }   

Dabei wird dem Iterator eine Referenz auf die Liste, über deren Daten iteriert werden soll, beim Konstruktoraufruf übergeben. Bei dem Attribut count handelt es sich um einen Zeiger auf den Index des Elements, auf das der Iterator gerade verweist. Die Methode hasNext() gibt zurück, ob es nach diesem Index noch weitere Elemente in der Datenstruktur gibt. Die Methode next() inkrementiert den internen Zeiger und gibt das jeweils nächste Element der Datenstruktur zurück. Dabei erfolgt der Zugriff über ein synchronized, damit es nicht zu Komplikationen zwischen, zeitgleich auf der Datenstruktur arbeitenden, Iteratoren gibt.

Im folgenden soll noch kurz gezeigt werden, wie die Verwendung eines Iterators in Java üblicherweise aussieht:
 Vector<Object> data = new Vector<>();  
 // TODO: Code um Datenstruktur zu füllen  
   
 Iterator<Object> iterator = data.iterator();  
   
 while (iterator.hasNext()) {  
   Object object = iterator.next();  
 }  

Entwurfsmuster: Besucher

In diesem Post, der sich wie die vorherigen auch, in die Serie über Software-Entwurfsmuster eingliedert, soll ein Abriss über das Verhaltensmuster "Besucher" (engl. Visitor-Pattern) gegeben werden. Dessen Verwendung zielt auf Objektstrukturen ab, auf deren Elemente verschiedene, nicht miteinander verwandte Operationen ausgeführt werden sollen. Die Definition der Klassen, die als Elemente der Objektstruktur instanziiert werden gestaltet sich oft schwierig, da sie unter einer gemeinsamen Schnittstelle zusammenzufassen sind, was im Widerspruch zu der Anforderung steht, verschiedene Operationen anzubieten.

Der Lösungsansatz, den das Besucher-Pattern vorsieht, ist die Auslagerung der Operationen in externe Besucherklassen. Die zu besuchenden Klassen wiederrum implementieren eine Schnittstelle, die zum Empfang eines solchen Besuchers dient. Das folgende Klassendiagramm zeigt das Zusammenspiel der daraus resultierenden Klassen und Interfaces.

Die Klassen und Interfaces des Besucher-Patterns als Klassendiagramm (Klicken zum Vergrößern)

Das Interface "Visitor" definiert die Schnittstelle, die von allen Besuchern implementiert werden muss. Dies beinhaltet je eine visit(..)-Methode für jeden vorhandenen Elementtyp der Struktur. Die einzelnen Element-Klassen implementieren wiederrum das Interface "VisitableElement", das eine Methode vorschreibt, über die beliebige Besucher, wie in dem Code-Fragment angedeutet, entgegen genommen werden können, um auf den Daten des Elements zu operieren.

Vorteile:
  • Neue Operationen lassen sich sehr leicht über die Implementierung neuer Besucherklassen hinzufügen, ohne dass die zu besuchenden Klassen angepasst werden müssen.
  • Die Operationen werden zentral verwaltet und von besucherfremden Operationen getrennt.
  • Besucher können wiederverwendet werden und auf verschiedene Objektstrukturen angewendet werden.
  • Die Elementklassen bleiben übersichtlich.
Nachteile:
  • Da die Besucher alle möglichen Elementtypen der Struktur kennen muss um jeweils eine passende Methode anbieten zu können, müssen bei der Einführung eines neuen Strukturelements alle bestehenden Besucherklassen um eine entsprechende Methode erweitert werden.
  • Das Kapselungsprinzip wird verletzt, da die Elemente der Objektstruktur all ihre Attribute über öffentliche Methoden den Besuchern zugänglich machen müssen.
Bei der Umsetzung des Besucher-Patterns ist es wichtig, darüber Klarheit zu schaffen, wer für die Traversierung über die Objektstruktur verantwortlich sein soll. Dies kann einerseits die Struktur selbst sein, die einem an sie übergebenen Visitor nacheinander alle Elemente vorlegt, was jedoch zur Folge hat, dass immer über die gesamte Struktur traversiert werden muss. Diese Einschränkung kann umgangen werden, indem die Traversierung in jedem Besucher implementiert wird, was aber wiederrum den Nachteil hat, dass in jedem Besucher zusätzlich der dafür notwendige Code anfällt, insofern dies nicht in einer abstrakten Basisklasse erledigt werden kann. Die dritte Möglichkeit besteht darin, die Traversierung komplett getrennt zu behandeln, indem etwa ein Iterator benutzt wird.

Besucher können beim Besuchen der Elemente Informationen sammeln und für die weitere Verarbeitung speichern. Diese Information muss dann nicht als Argument durch alle Operationen durchgereicht werden oder in globalen Variablen gehalten werden.

Über die Definition einer abstrakten Visitor-Basisklasse können Leerimplementierungen der visit-Methoden eingeführt werden, die erst später bei Bedarf in den konkreten Besuchern implementiert werden.

20. Januar 2013

Entwurfsmuster: Fliegengewicht

Bei dem zweiten Entwurfsmuster, das in dieser Serie vorgestellt werden soll, handelt es sich um das "Fliegengewicht" (eng. Flyweight-Pattern). Es ist ein sogenanntes Strukturmuster und wird in der Softwareentwicklung verwendet, wenn eine große Anzahl von Objekten verwaltet werdern soll, die sich variable Informationen teilen. Das unten stehende Klassendiagramm zeigt die Beziehung zwischen den, für das Entwurfsmuster notwendigen, Klassen.

UML-Klassendiagramm des Entwurfsmusters "Fliegengewicht" (Klicken zum Vergrößern)

Das Interface "Flyweight" definiert dabei die Schnittstelle für die einzelnen Objekte, die ausgelagerte Zustände empfangen sollen. Diese Schnittstelle wird von konkreten Klassen implementiert, die bei Bedarf außerdem noch einen internen Zustand besitzen können und definiert Methoden, die mit einem ausgelagerten Zustand, der als Parameter übergeben wird, arbeiten. Die Klasse "FlyweightFactory" erzeugt und verwaltet Fliegengewichte. Dazu übergibt der Client, der den ausgelagerten Zustand verwaltet, einen Schlüssel an die Klasse. Für jeden Schlüssel wird lediglich ein einziges Fliegengewicht instanziiert und zurückgegeben.

Vorteile:
  • Die Anzahl der benötigten Objektinstanzen, und damit auch der Speicherbedarf, kann drastisch reduziert werden. Bei Persitierung der Objektstruktur kann außerdem der benötigte Speicherplatz und die Übertragungszeit reduziert werden.
  •  Oftmals werden die betreffenden Klassen schlanker, da Informationen ausgelagert wurden.
Nachteile:
  • Die Komplexität der Implementierung wird teils stark erhöht und bei jeder späteren Ergänzung muss bedacht werden, dass der ausgelagerte Zustand nicht um Daten ergänzt werden darf, die kontextabhängig sind.
  • Der Einsatz von Flyweight-Instanzen (beispielsweise als Hash-Wert) kann unerwartete Folgen haben, da diese in der Regel mehrfach referenziert sind.
  •  Die Laufzeitkosten steigen an, da der ausgelagerte Zustand bei jedem Zugriff erst zusammengestellt werden muss.
Da die Nachteile dieses Entwurfsmusters bei falscher Anwendung gravierend sein können, sollte im Vornhinein genau überlegt werden, ob sich der Einsatz lohnt. Dies ist nur der Fall, wenn eine ausreichend große Anzahl an Objektinstanzen benötigt wird und die Kosten für deren Speicherung so groß werden, dass die Speicheinsparung spürbar wird. Außerdem sollte darauf geachtet werden, dass die internen und ausgelagerten Zustände so verteilt werden können, dass nur wenige Fliegengewicht-Implementierungen benötigt werden.

Entwurfsmuster: Fassade

Die folgenden Posts sollen eine Serie bilden, in der etablierte Entwurfsmuster (engl. Design-Patterns), die sich für den alltäglichen Einsatz in der Softwareentwicklung bewährt haben, vorgestellt werden sollen. Entwurfsmuster sind Lösungsmodelle für immer wiederkehrende Probleme beim Entwurf objektorientierter Software und bieten Lösungsansätze, die meist durch die Beschreibung von Klassen und Interfaces, sowie deren Funktion und Beziehung zueinander, beschrieben werden.

Den Anfang dieser Reihe von Posts soll die sogenannte "Fassade", ein relativ simples Entwurfsmuster bilden. Die Fassade (engl. Facade-Pattern) ist ein Entwurfsmuster aus dem Bereich der Softwareentwicklung, das zu der Kategorie der Strukturmuster gehört und dessen Ziel es ist, eine Vielzahl verschiedener Schnittstellen eines Subsystems über eine einzige, einheitliche und oftmals vereinfachte Schnittstelle, verfügbar zu machen.

Dazu wird eine Schnittstelle definiert, die nur ausgewählte, von den Clients benötigte, Methoden anbietet. Aufrufe dieser Methoden werden intern an die jeweils zuständigen Klassen des Subsystems delegiert. Das folgende UML-Diagram verdeutlicht das Prinzip des Fassade-Patterns und zeigt beispielhaft wie die Delegation an die einzelnen Komponenten des Subsystems aussehen könnte.

Das Fassade-Pattern als UML-Klassendiagramm


Vorteile:
  • Die Fassade entkoppelt die einzelnen Klassen des Subsystems von den Clients, die sie verwenden. Dadurch können die Implementierungen und Schnittstellen der Subsystem-Klassen geändert werden, ohne die Clients anpassen zu müssen.
  • Die Verwendung des Subsystems wird erleichtert, weil dessen Komplexität durch die Fassade versteckt.
Nachteile:
  • Wenn Clients die Fassade umgehen und das Subsystem direkt ansprechen, wird das komplette Entwurfsmuster ausgehebelt. Normalerweise ist diese Möglichkeit technisch jedoch nicht zu verhindern. 
  • Wenn sich Schnittstellen innerhalb des Subsystems ändern, so muss jedes Mal die Fassade angepasst werden.
Fassaden können außerdem nach Bedarf weitere Verantwortlichkeiten wie Zugangskontrolle, Transaktionsmanagement oder Session-Handling übernehmen und die Clients von dem Subsystem, durch die Verwendung asynchroner Kommunikationsmechanismen, entkoppeln.