Entwurfsmuster (Design Patterns)
Beim Programmieren stellt man fest, dass bestimmte Schemata sich oft wiederholen (meist mit geringen Unterschieden) - diese Schemata bzw. Muster nennt man Entwurfsmuster (Design Patterns).
Entwurfsmuster beschreiben die Kommunikation von Objekten in einer Art die einem eine flexibel und leicht erweiterbare Software Architektur gewährleistet. Sie helfen einem beim Entwickeln bzw. Entwerfen eines gültigen Systems.
Viele Entwurfsmuster wurden über die Jahre dokumentiert und sollen hier nun vorgestellt werden. Hier werden die wichtigsten Entwurfsmuster vorgestellt, wie sie zu verwenden sind und warum man sie nutzen sollte.
Hierbei möchte ich doch darauf hinweisen, dass diese Auflistung nur eine kleine Auswahl sein kann und auch nicht auf jedes Pattern in Tiefe eingegangen wird (z.B. Sinn & Unsinn eines Patterns) - dafür sollte dann die Literatur zu Rate gezogen werden.
Es werden auch keine J2EE Patterns vorgestellt... über die gibt es hier eine gute Übersicht.
Inhaltsverzeichnis
Singleton
Das Singleton Pattern stellt sicher, dass es von einer Klasse nur eine Instanz gibt.
Oft verwendete Beispiele sind Datenbankmanager Klassen (um nicht in den Konflikt zu kommen mehrer DB Connections handeln zu müssen o.ä.) oder eine Configurations Klasse, die für mehrere Klassen in der Anwendung wichtige Informationen bereit stellt.
Um ein Singleton zu erstellen, gibt es zwei Möglichkeiten: <code=java>public class Singleton {
public static final Singleton instance = new Singleton();
private Singleton() { }
}</code=java>
<code=java>public class Singleton {
private static final Singleton instance = new Singleton(); private Singleton() { }
public static Singleton getInstance() { return instance; }
}
</code=java>
In beiden Fällen ist der private Konstruktor wichtig, der verhindert, dass die Klasse von außerhalb instanziiert werden kann. Beide Versionen unterscheiden nur in der static final
Variable. Deklariert man sie als private
, muss eine public
Methode gegegeben sein, um die Instanz zu erhalten. Ansonsten sind beide Versionen equivalent.
Der Vorteil der zweiten Version ist aber, dass man sie leicht abändern kann, wenn man z.b. nicht nur eine, sondern für jeden vorhandenen Thread eine eindeutige Instanz erzeugen will.
Oft sieht man auch folgende Variante: <code=java>public class Singleton {
private static Singleton instance; private Singleton() { }
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }
}</code=java>
Wichtig hierbei ist die Methode synchronized
zu definieren, so dass die Methode Thread sicher ist!
Eine Möglichkeit wäre, die Verwendung einer final
Klasse mit statischen Methoden (wie Math Klasse), diese Art behindert aber das Umschalten zwischen Singleton Klasse und "normaler" Klasse.
Anmerkung von Bleiglanz:
Die 1. und 2. Lösung sind für fast alle einfachen Fälle - das ist fast immer am besten (wobei die Variante mit getInstance zu bevorzugen ist).
die synchronized Lösung NUR DANN, wenn "lazy" Sinn macht [weil der Konstruktor "zu lange braucht"]
Auf das sog. Double Checked Locking sollte komplett verzichtet werden - siehe Double Checked Locking Is Broken
Observer
Das Observer Pattern hilft bei der Kommunikation von Objekten, ohne dass Instanzen voneinander bekannt sind.
Vor allem in der GUI Programmierung hat man meist das Problem, dass Daten, die in einer GUI angezeigt werden sich im Laufe des Programms ändern. Diese Änderungen sollen dann in der GUI sichtbar gemacht werden.
Die häufigste Lösung dieses Problems ist es eine doppelte Assoziation der beiden Klassen zu verwenden, dh. die GUI kennt direkt die Datenklasse und die Datenklasse kennt die GUI...
Das löst zwar das Problem der Kommunikation, bringt aber andere einige Probleme mit sich. Durch die Assoziation macht man die beiden Klassen von einander abhängig, d.h. die eine Klasse kann nur arbeiten bzw. kompilieren, wenn die andere Klasse vorhanden ist. Bzw. wenn man später z.B. die GUIKlasse komplett ändern will, muss man immer auch die Datenklasse ändern, oder wenn man die Änderungen nicht nur in einer GUI anzeigen, sondern auch in einer anderen Klasse sichtbar machen will (z.b. in eine Datenbank schreiben), hat man hier unzählige Änderungen und aufgeblähten Code...
Man spricht hier dann von einem Verstoß gegen das MVC Prinzip. MVC steht für Model - View - Controller und bedeutet, dass man die drei Ebenen einer Anwendung (die Datenebene = Model / die GUI = View / die kontrollierende Ebene = Controller) nicht mit einander mischen darf.
Das Observer Pattern ist nun eine gute Möglichkeit, die Kommunikation zwischen den Ebenen zu gewährleisten ohne gegen das MVC Prinzip zu verstoßen:
Man erstellt sich eine abstrakte Klasse Observable, die eine Reihe von Observer hält. Diese Klasse braucht nur Methoden um Observer hinzuzufügen bzw. zu entfernen und ihren Observer Nachrichten zu schicken. Die Klasse Observer wiederum ist ein Interface, das nur die Methode update()
definiert, die dann aufgerufen wird, wenn der Observerable Nachrichten an die Observer schickt. Jede Klasse die "observiert" werden will, implementiert das Observer Interface und meldet sich bei einem Observable an.
Beispiel: <code=java>import java.util.Observable; import java.util.Observer;
import javax.swing.JFrame;
/**
* Die Klasse WetterAnzeige ist die GUI um die Wetterdaten * anzuzeigen. Wenn sich die Daten ändern soll sich auch die GUI ändern. Der * Vorteil des Observer Patterns ist, dass die Klassen kommunizieren können ohne * sich gegenseitig zu kennen ! */
public class WetterAnzeige extends JFrame implements Observer {
public WetterAnzeige() { }
/** * Methode update vom Interface Observer wird * aufgerufen wenn ein Observer notifyObserver * aufruft. Der Observer kann sich bei mehreen * Observable anmelden und man kann der Action ein beliebiges * Object mitgeben. */ public void update(Observable o, Object update) { // im Object update kann z.b. die ßnderungen sein - oder ein spezielles // Event das informationen zu den ßnderungen speichert }
}
class WetterDaten extends Observable {
public void esPassiertWasMitDenDaten() { // es passiert was - Daten ändern sich // der Zustand wird als geänder markiert setChanged();
//alle Observer werden benachrichtigt notifyObservers("Das Wetter wird schön"); }
}
class WetterController {
public static void main(String[] args) { WetterAnzeige view = new WetterAnzeige(); WetterDaten daten = new WetterDaten(); daten.addObserver(view); }
}</code=java> Java stellt das Pattern direkt über die Klassen Observer und Observable zur Verfügung (z.B. beruht die gesamte Listener Struktur in Java auf diesem Pattern). Ein häufiger Fehler ist, das Pattern zwar zu verwenden, aber dennoch den Observer direkt bei dem konkreten Observable anzumelden! Die Anmeldung sollte daher über eine dritte Ebene - der Controller Ebene geschehen!
Eine Anmeldung der Ebenen muss nicht in der Controller Ebene stattfinden, wenn man als Parameter Interfaces benutzt.
D.h. folgendes wäre falsch:
<code=java>class WetterDaten extends Observable {
public WetterDaten(WetterAnzeige zeige) { addObserver(zeige); }
public void esPassiertWasMitDenDaten() { // es passiert was - Daten ändern sich // der Zustand wird als geänder markiert setChanged();
//alle Observer werden benachrichtigt notifyObservers("Das Wetter wird schön"); }
}</code=java>
Hingegen wäre richtig: <code=java>class WetterDaten extends Observable {
public WetterDaten(Observer zeige) { addObserver(zeige); }
public void esPassiertWasMitDenDaten() { // es passiert was - Daten ändern sich // der Zustand wird als geänder markiert setChanged();
//alle Observer werden benachrichtigt notifyObservers("Das Wetter wird schön"); }
}</code=java>
Iterator
Fasade
General Hierarchie
Player Role Pattern
Immutable
Read-Only
Factory
MVC (Architekturmuster)
Visitor
Mit dem Visitor-Pattern können Elemente einer komplexeren Datenstruktur besucht werden.
Hat man eine Datenstruktur wie einen Baum (oder einen Graphen), so möchte man aus verschiedensten Gründen alle Knoten besuchen, und irgendetwas mit den Knoten berechnen (sie z.B. zählen).
Die Knoten können aber durch mehrere Klassen beschrieben werden, und natürlich gibt es verschiedene Gründe alle Knoten zu besuchen.
Ein Algorithmus, der alle Knoten besucht und mit ihnen etwas macht, kann man folgendermaßen aufteilen:
Den Teil der nur die einzelnen Knoten sucht, und den Teil der effektiv etwas mit ihnen macht.
Der erste Teil ist immer derselbe, der zweite muss für jede Knoten-Klasse, und für jeden Algorithmus von Neuem geschrieben werden.
Das Visitor-Pattern erlaubt diese Trennung: die Traversierung der Knoten wird an einem zentralen Ort beschrieben.
Jedem Knoten wird ein Visitor-Interface übergeben, wo er eine zu ihm passende Methode aufrufen soll: Der Knoten des Types X ruft die Methode "handleX( X x )
" auf (mit x = sich selbst).
Natürlich kann das Objekt, das hinter dem Interface steht, beliebig oft ausgetauscht werden. Man kann also neue Algorithmen implementieren, ohne den Code der Knoten-Klassen überhaupt zu kennen!
- Der Baum stellt eine Möglichkeit zur Verfügung, den Visitor allen Knoten zu überreichen.
- Die Knoten übergeben sich selbst einer bestimmten Methode des Visitors.
- Der Visitor stellt für jeden Typ Knoten eine Methode zur Verfügung, in der er den Knoten in irgendeiner Weise verarbeitet.
Beispiel
Eine Gleichung "2+4+3" kann man als Baum darstellen. Die Operatoren sind Knoten, die Zahlen sind die Blätter des Baumes.
Eine entsprechende Datenstruktur, bereits mit Visitor, sieht so aus: <code=java>/**
* Der Besucher, der durch den Baum gereicht wird. */
public interface Visitor{
/** * Behandelt einen Operator-Knoten. * @param operator Der Operator. */ public void handleOperator( Operator operator ); /** * Behandelt einen Number-Knoten. * @param number Die Nummer */ public void handleNumber( Number number );
//----- Nur damit das Beispiel eine schöne Ausgabe kriegt, --------------------/ //----- die Methode "getResult" gehört normalerweise nicht zu einem Visitor. --/
/** * Das Resultat des Besuches. * @return Das Resultat */ public String getResult();
}</code=java> <code=java>/**
* Jedes Element eines Baumes ist ein Knoten. Ein Blatt kann einfach als * Knoten ohne Kinder angeschaut werden. */
public interface Node{
public Node[] getChildren(); /** * Ruft die passende Methode des Visitors auf. * @param visitor Der Besucher. */ public void visit( Visitor visitor );
}</code=java> <code=java>/**
* Ein Operator ist ein Knoten, der zwei Kinder hat. */
public class Operator implements Node{
private Node[] children; private String name; public Operator( Node left, String name, Node right ){ this.name = name; children = new Node[]{ left, right }; } public Node[] getChildren() { return children; } public void visit( Visitor visitor ) { visitor.handleOperator( this ); } public String getName(){ return name; }
}</code=java> <code=java>/**
* Eine Zahl ist ein Blatt, also ein Knoten ohne Kinder */
public class Number implements Node{
public static final Node[] EMPTY = new Node[0]; private double value; public Number( double value ){ this.value = value; } public Node[] getChildren() { return EMPTY; } public void visit( Visitor visitor ) { visitor.handleNumber( this ); } public double getValue(){ return value; }
}</code=java>
Für verschiedene Aktionen: Summe aller Zahlen, Anzahl Elemente der Gleichung, sowie eine Liste verschiedener Operatoren, benötigt nun sehr wenig Code:
<code=java>import java.util.Arrays; import java.util.HashSet; import java.util.Set;
public class Test{
public static void main( String[] args ) { // 5 * 3 + 9 + 6 Node equation = new Operator( new Operator( new Number( 5 ), "*", new Number( 3 ) ), "+", new Operator( new Number( 9 ), "+", new Number( 6 ) )); // Summer alle Zahlen System.out.println( traverse( equation, new Visitor(){ private double sum = 0; public void handleOperator( Operator operator ) { } public void handleNumber( Number number ) { sum += number.getValue(); } public String getResult() { return "Summe aller Zahlen: " + sum; } })); // Anzahl Knoten System.out.println( traverse( equation, new Visitor(){ private int count; public void handleOperator( Operator operator ) { count++; } public void handleNumber( Number number ) { count++; } public String getResult() { return "Anzahl Knoten: " + count; } })); // Verschiedene Operatoren System.out.println( traverse( equation, new Visitor(){ private Set<String> operators = new HashSet<String>(); public void handleOperator( Operator operator ) { operators.add( operator.getName() ); } public void handleNumber( Number number ) { } public String getResult() { return "Verschiedene Operatoren: " + Arrays.toString( operators.toArray() ); } })); } public static String traverse( Node node, Visitor visitor ){ node.visit( visitor ); for( Node child : node.getChildren() ) traverse( child, visitor ); return visitor.getResult(); }
}</code=java>
Ausgabe: <code=ini>Summe aller Zahlen: 23.0 Anzahl Knoten: 7 Verschiedene Operatoren: [+, *]</code=ini>
-- bygones 30.06.2004, | Bleiglanz | Illuvatar 02.09.2004 | Beni 16.07.2005 | [[Benutzer:André Uhres | André Uhres] 21.11.2009 | [[Benutzer:Marco13 | Marco13] 30.12.2009