Entwurfsmuster (Design Patterns)

Aus Byte-Welt Wiki
Zur Navigation springenZur Suche springen

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.

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

-- bygones 30.06.2004, | Bleiglanz