Entwurfsmuster (Design Patterns)

Status
Nicht offen für weitere Antworten.
B

bygones

Gast
Die Muster im Überblick:
  • Singleton
  • Observer
  • Iterator
  • Fasade
  • General Hierarchie
  • Player Role Pattern
  • Immutable
  • Read-Only
  • Factory
  • MVC (Architekturmuster)
  • Visitor
Beim Programmieren stellt man fest das bestimmte Schemen sich oft wiederholen (meist mit geringen Unterschieden) - diese Schemen 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 bei dem Entwickeln bzw. Entwerfen eines gültigen Systems.

Viele Entwurfsmuster wurden über die Jahre dokumentiert und sollen hier nun vorgestellt werden. Ich werde in regelmäßigen Abständen einige Entwurfsmuster vorstellen, 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.
 
Zuletzt bearbeitet von einem Moderator:
B

bygones

Gast
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 bereitstellt.

Um ein Singleton zu erstellen gibt es zwei Möglichkeiten:
  1. Java:
    public class Singleton {
      public static final Singleton instance = new Singleton();
    
      private Singleton() {
      }
    }
  2. Java:
    public class Singleton {
      private static final Singleton instance = new Singleton();
      
      private Singleton() {
      }
    		
      public static Singleton getInstance() {
        return instance;
      }
    }
In beiden Fällen ist der private Konstruktor wichtig, der verhindert, dass die Klasse von außerhalb iniziert werden kann, unterscheiden tun sich die beiden Versionen 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 z. B. für jeden vorhandenen Thread eine eindeutige Instanz erzeugen will.

Oft sieht man auch folgende Variante:
  • Java:
    public class Singleton {
      private static Singleton instance;
      
      private Singleton() {
      }
    		
      public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
      }
    }
Wichtig hierbei ist die Methode synchronized zu machen, 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 Umschaltung zwischen Singleton Klasse und "normaler" Klasse

Bleiglanz hat gesagt.:
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
 
B

bygones

Gast
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, zieht aber 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 will sondern auch in einer anderen Klasse sichtbar machen will (z.b. in eine Datenbank schreiben) hat man hier unzählige ßnderungen und aufgebählten Code...

Man spricht hier dann von einem Verstoß gegen das MVC Prinzip. MVC steht für Model - View - Control und bedeutet, dass man die drei Ebenen einer Anwendung (die Datenebene = Model / die GUI = View / die kontrollierende Ebene = Control) 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 verstossen:

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 hat, 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:
Java:
import java.util.Observable;
import java.util.Observer;

import javax.swing.JFrame;

/**
 * Die Klasse <tt>WetterAnzeige</tt> ist die GUI um die <tt>Wetterdaten</tt>
 * 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 <tt>update</tt> vom Interface <tt>Observer</tt> wird
   * aufgerufen wenn ein <tt>Observer</tt> <tt>notifyObserver</tt>
   * aufruft. Der <tt>Observer</tt> kann sich bei mehreen
   * <tt>Observable</tt> 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);
  }
}
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 dritte Ebene - der Controller Ebene geschehen !

Nachtrag: Eine Anmeldung der Ebenen muss nicht in der Controller Ebene stattfinden, wenn man die Paramter als Interfaces nimmt.

D.h. folgendes wäre falsch:

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");
  }
}

hingegen wäre richtig:
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");
  }
}
 
B

bygones

Gast
Iterator

Das Iterator Pattern erlaubt den sequentiellen Zugriff auf eine Aggregation ohne deren internen Struktur zu kennen.

Das Iterator Pattern ist eins der bekanntesten und weitverbreitesten Pattern in Java. Der Client muss nicht wissen wie die interne Struktur der Aggregation aussieht bzw. wie die Elemente angeordnet sind, kann aber über einen Iterator auf diese Element zugreifen.

Der Iterator ist meist ein Interface der die beiden methoden "next()" and "hasNext()" definiert, wobei hasNext testet ob noch auf Elemente zugegriffen werden kann und next() liefert dann das nächste element.

Bsp:

Java:
package pattern;

interface IntIterator {
    public boolean hasNext();

    public int nextInt();
}

public class IntListe {
    private int[] array;

    public IntListe(int[] a) {
        array = a;
    }

    public IntIterator iterator() {
        return new IntIterator() {
            int index = 0;

            public boolean hasNext() {
                return index < array.length;
            }

            public int nextInt() {
                return array[index++];
            }
        };
    }

    public IntIterator backIterator() {
        return new IntIterator() {
            int index = array.length - 1;

            public boolean hasNext() {
                return index > -1;
            }

            public int nextInt() {
                return array[index--];
            }

        };
    }

    public static void main(String[] args) {
        int[] a = { 1, 2, 3, 4, 5 };
        IntListe il = new IntListe(a);
        for (IntIterator iter = il.iterator(); iter.hasNext();) {
            int next = iter.nextInt();
            System.out.print(next + " ");
        }
        System.out.println();
        for (IntIterator iter = il.backIterator(); iter.hasNext();) {
            int next = iter.nextInt();
            System.out.print(next + " ");
        }
    }
}

Das Pattern ist in Java fest integriert und kann über den aufruf iterator() auf jede Collection angewandt werden.
 
B

bygones

Gast
Fassade

Das Fassade erlaubt den einfachen Zugriff auf ein komplexes Subsystem.

Ein Programm unterteilt sich meist in Subsysteme. Je nach Größe und Komplexität dieser Systeme ist es manchmal schwierig zu verstehen welche Methoden wie und warum in diesem System definiert wurden. Lässt man Clients nun auf die einzelnen Methoden des Subsystems zugreifen erschwert das die Wartung des kompletten Systems, da bei jeder ßnderung des Subsystems alle nach außen gehenden Assoziation getestet werden müssen.

Daher erschafft man sich eine sog. Fassaden Klasse die einige der wichtigen bzw. benötigten public Methoden des Subsystems definieren. Clients greifen nun nicht direkt in Klassen des Subsystems, sondern nutzen nur die Fassaden Klasse. Das macht das gesamte System stabiler da weniger Abhängigkeiten existieren und dadurch auch die Wartbarkeit verbessert wird.

fassade.gif
 
B

bygones

Gast
General Hierarchie

Das General Hierarchie Pattern hilft beim modellieren von Objekten die in einer Hierarchischen Struktur zu finden sind.

Jedes Objekt in einer Hierarchie kann einen Vorgänger (superior) haben oder einen Nachfolger (subordinate). Manche Elemente haben beides, manche haben nur einen "superior". Um dies zu modellieren hilft das General Hierarchie Pattern.

Betrachtet man einen Baum so existieren Knoten (node) und Blätter (leaf). Knoten können wieder Knoten bzw. Blätter haben, Blätter hingegen haben keine Nachfolger. Um dies zu modellieren erstellt man sich eine abstrakte Klasse, die die gemeinsamen Eigenschaften der beiden Klassen vereinigt (TreeElement). Die beiden Klassen Node und Leaf erben von TreeElement, wobei Node eine mehrfach Assoziation wieder zur Oberklasse "TreeElement" hat, was impliziert dass ein Knoten wiederum Knoten oder Blätter besitzen kann.
generalhierarchie.gif

Ein weiteres Beispiel des Patterns ist das Verhältnis von Angestellten und Manager. Ein Manager ist ein Angestellter in einer Firma, der wiederum Angestellte unter sich hat.
 
B

bygones

Gast
Player Role Pattern

Das Player Role Pattern ordnet einer Klasse verschiedene Rollen zu.

Das Pattern lässt sich am einfachsten an einem Beispiel erklären.

Ein Internet User kann zur selben Zeit unterschiedliche Eigenschaften (Rollen) besitzen. Zum einen kann man den User unter dem Aspekt betrachten, wie er ins Internet gelangt. So kann er zum Beispiel per Flatrate das Internet nutzen oder sich per Modem einwählen. Weiterhin kann er aber auch in einem Board aktiv sein. Dort kann er entweder User, Moderator oder Admin sein.

Also kann ein sog. Player (der Internetuser) gleichzeitig mehrere untersch. Rollen einnehmen, die sich im Laufe der Zeit auch ändern können.

Der erste Ansatzpunkt dies zu lösen ist Vererbung. Doch stößt man hier schnell an die Grenzen, da Java keine Mehrfachvererbung zulässt. Da aber die Rollen auch eigenständige Objekte sind will man sie in eigene Klassen packen.

Die Lösung dazu ist es für jede Rolle eine abstrakte Oberklasse zu erstellen die eine Assoziation zu dem Player hat. Dadurch kann der Player gleichzeitig mehrere Rollen einnehmen, die sich auch während des Programms ändern können.
playerRole.gif
 
B

bygones

Gast
Immutable

Das Immutable Pattern stellt sicher, dass eine Instanz der Klasse nach der Initialisierung nicht
mehr geändert werden kann.


Um eine Klasse immutable zu machen müssen folgende Punkte eingehalten werden:
  1. Es darf keine Methoden geben die die Instanz verändern können, will eine Methode die Instanz ändern, so muss eine neue Instanz erstellt werden.
  2. Es muss sicher gestellt werden dass Methoden nicht überschrieben werden können (z.b. die Klasse final deklarieren)
  3. Alle Instanzvariable werden private final deklariert
  4. Wenn die Klasse ein mutables Object hält, muss sichergestellt werden dass der Client nicht direkt auf die Referenz diese Objekts zugreifen kann.

Bsp:

Java:
import java.util.BitSet;

public final class Immutable {
    private final BitSet set;

    /**
     * Hier wird ein neues BitSet angelegt, clone() sollte nicht verwendet
     * werden, da Parameter pSet auch eine SubKlasse von BitSet sein kann und
     * daher womöglich nicht das korrekte clone Ergebnis liefern würde
     */
    public Immutable(BitSet pSet) {
        this.set = new BitSet();
        // alle gesetzten Bits des Parameters werden im set auch gesetzt
        for (int i = pSet.nextSetBit(0); i >= 0; i = pSet.nextSetBit(i + 1)) {
            set.set(i);
        }
    }

    /**
     * Hier kann geklont werden, da man weiß das es sich um ein BitSet handelt
     */
    public BitSet getSet() {
        return (BitSet) set.clone();
    }

    /**
     * Methode ändert den Zustand der Instanz, daher muss eine neue Instanz
     * erstellt werden
     */
    public Immutable bitAnd(BitSet set) {
        set.and(this.set);
        return new Immutable(set);
    }

    public String toString() {
        return set.toString();
    }
}

Die Klasse wird als final deklariert, so dass keine Klassen von ihr erben können und somit die Methoden überschreiben könnten.

Der Konstruktor und die Methode 'getSet' verdienen genauerer Betrachtung. Würde man z.b. im Konstruktor schreiben:
Java:
public Immutable(Bitset pSet) {
	set = pSet;
}
könnte man folgendes machen:
Java:
public static void main(String[] args) {
	BitSet set = new BitSet();
	// erstes bit wird gesetzt
        set.set(1);
        // immutable objekt wird erstellt
        Immutable im = new Immutable(set);
        // ausgabe {1};
        System.out.println(im);
        // alle bits im set werden auf false gesetzt
        set.clear();
        // ausgabe {};
        System.out.println(im);
}
d.h. man kann über das mutable BitSet Objekt das eigentlich immutable Objekt ändern, da sie mit den
gleichen Referenzen arbeiten.

Das gleiche gilt für die Methode 'getSet()' - würde man schreiben:
Java:
public BitSet getSet() {
	return set;
}
könnte man folgendes machen:
Java:
public static void main(String[] args) {
        BitSet set = new BitSet();
        set.set(1);
        Immutable im = new Immutable(set);
        System.out.println(im);
        set = im.getSet();
        set.clear();
        System.out.println(im);
}
auch hier wird die immutable Instanz geändert ! Daher müssen sog. 'defensive copies' von mutable Objekte verwendet werden.

Nun stellt sich die Frage warum dieser Aufwand ?

Zitat aus 'Effective Java - Programming Language Guide' von Joshua Bloch:
"Classes should be immutable unless there's a very good reason to make then mutable."

Aus folgenden Gründe sollte man immutable Klassen verwenden:
  1. Sie sind einfach: Die Objekte haben keine großen bzw. komplexen Zustandsraum
  2. Sie sind Thread sicher: man muss z.b.kein synchronized verwenden
  3. Sie sind vertrauenswürdig: Andere Objekte können sich darauf verlassen, dass diese Objekte immer so sein werden wie sie erstellt wurden. Das macht sie z.b. zu perfekten keys in einer Map oder Elemente in einem Set. Da ihr Zustand sich nie ändern kann läuft man nicht Gefahr dass das mapping oder die Reihenfolge eines Sets sich ändert.

Ein offensichtlicher Nachteil ist, dass man möglicherweise eine große Anzahl von Instanzen benötigt, die möglicherweise noch kostenspielig sind zu Initiieren. Dies ist aber bei kleineren Objekte vernachlässigbar und man kann z.B. eine mutable Variante der Klasse bereitstellen um diese Einbußen wegzubekommen (siehe String <-> StringBuffer).

Als Faustregel kann gesehen werden:
  • kleine Model - Klassen sollten immer immutable sein (v.a. Klassen ohne mutable Objekte)
  • bei größere Model - Klassen sollte man es in Betracht ziehen. Möglicherweise eine mutable Variante bereitstellen
  • falls eine Klasse nicht immutable gemacht werden kann, so sollte man den Grad der Veränderbarkeit versuchen zu minimieren.
 
B

bygones

Gast
Read-Only Interface

Das Read-Only Interface stellt sicher, dass nur bestimmte priviligierte Klassen eine Instanz ändern können.

Das Read-Only Interface ist dem Immutable Pattern ähnlich, nur dass hier bestimmte Klassen das Recht haben eine Instanz zu ändern.

Die "halb-immutable" Klasse nennen wir Mutable, Klassen die ßnderungen vornehmen dürfen nennen wir Mutator. Wichtig ist das die beiden Klassen in einem package liegen, da die setter Methoden der Mutable Klasse über den Zugriffsoperator package private definiert werden, so dass nur Klassen im selben Package darauf zugreifen können. Die Mutable Klasse implementiert das sog. Read-Only Interface das die getter Methoden definiert. Alle "unpriviligierten" Klassen arbeiten nur über das Interface und haben keinen direkten Zugriff auf die Mutable Klasse
readonly.gif

Bsp:
Java:
package pattern.readonly;

public interface Person {
    public String getName();
}
Java:
package pattern.mutable;

import pattern.readonly.ReadOnlyInterface;

public class MutablePerson implements Person {
    public String name;
    
    public MutablePerson(String s) {
        name = s;
    }
    
    public String getName() {
        return name;
    }
    
    void setName(String s) {
        name = s;
    }
}
Java:
package pattern.readonly;

public class UnpriviligedClass {
    public UnpriviligedClass(Person p) {
        // Kann nur auf die get Methode zugreifen
       System.out.println(p.getName());
    }
}
Java:
package pattern.mutable;

public class Mutator {
    private MutablePerson mutable;

    public Mutator(MutablePerson mp) {
        mutable = mp;
        // Kann auch auf die set Methoden zugreifen
        mutable.setName("deathbyaclown");
    }
}
Häufig setzt man das Pattern ein, wenn man Daten an eine GUI sendet, die diese dann anzeigen, aber selbst keine Veränderungen vornehmen soll.
 

Illuvatar

Top Contributor
Factory

Das Factory - Pattern kann verwendet werden, wenn es nicht möglich und/oder sinnvoll ist, Instanzen mit new zu erzeugen (es könnten ja langwierige Konfigurationen vorgenommen werden müssen). So kann zum Beispiel für gleiche Objekte auch leicht die gleiche Instanz verwendet werden.

Bei diesem gibt es meist eine statische Methode, um das jeweilige Objekt zu erzeugen.
Ein [post=27517]Singleton[/post] ist eine Form des Factory-Patterns, wobei hier die Factory - Methode sicherstellt, dass es nur eine Instanz gibt.

Es gibt verschiedene Möglichkeiten, wo sich die Factory-Methode befinden kann.

  • In der eigenen Klasse:
    Java:
    public class Foo
    {
      private Foo () //Foo soll nur von der factory-Methode instanziiert werden
      {
      }
      public static Foo getFoo (Settings s) //statische factory - Methode
      {
        Foo bar = new Foo();
        //Wende s auf bar an
        return bar;
      }
    }
    Das wird zum Beispiel in der Klasse java.net.InetAddress angewandt.
  • In einer factory - Klasse:
    Java:
    public class Foo
    {
      Foo() //Konstruktor nur für package zugänglich, da factory Methode in andere Klasse im selben Package liegt
      {
      }
    }
    public class Bar
    {
      Bar() //Konstruktor nur für package zugänglich, da factory Methode in andere Klasse im selben Package liegt
      {
      }
    }
    public class FooBarFactory
    {
      public static Foo getFoo (Settings s) //Factory - Methode für Foo
      {
        Foo f = new Foo(); //erlaubt
        //intialisiere f
        return f;
      }
      public static Bar getBar (Settings s) //Factory - Methode für Bar
      {
        Bar b = new Bar(); //erlaubt
        //intialisiere b
        return b;
      }
    }
  • Mit Hilfe einer "Abstract Factory":
    AbstractFactory.gif

    Das Grundkonzept sieht so aus, wie bei der Factory-Klasse oben. Allerdings sind in diesem Falle alle Klassen abstrakt.
    Es kann jetzt sowohl von der Factory-Klasse als auch von den faktorisierten (Achtung, meine Wortschöpfung) Klassen mehrere unterschiedliche Ableitungen A, B, C... geben. Die Klasse FooBarFactoryA stellt dann eben FooA und BarA her, und FooBarFactoryB stellt FooB und BarB her. Am deutlichsten macht das folgender Beispielcode aus dem Javabuch:
    Java:
    /* Listing1010.java */
    
    //------------------------------------------------------------------
    //Abstrakte Produkte
    //------------------------------------------------------------------
    abstract class Product1
    {
    }
    
    abstract class Product2
    {
    }
    
    //------------------------------------------------------------------
    //Abstrakte Factory
    //------------------------------------------------------------------
    abstract class ProductFactory
    {
      public abstract Product1 createProduct1();
    
      public abstract Product2 createProduct2();
    
      public static ProductFactory getFactory(String variant)
      {
        ProductFactory ret = null;
        if (variant.equals("A")) {
          ret = new ConcreteFactoryVariantA();
        } else if (variant.equals("B")) {
          ret = new ConcreteFactoryVariantB();
        }
        return ret;
      }
    
      public static ProductFactory getDefaultFactory()
      {
        return getFactory("A");
      }
    }
    
    //------------------------------------------------------------------
    //Konkrete Produkte für Implementierungsvariante A
    //------------------------------------------------------------------
    class Product1VariantA
    extends Product1
    {
    }
    
    class Product2VariantA
    extends Product2
    {
    }
    
    //------------------------------------------------------------------
    //Konkrete Factory für Implementierungsvariante A
    //------------------------------------------------------------------
    class ConcreteFactoryVariantA
    extends ProductFactory
    {
      public Product1 createProduct1()
      {
        return new Product1VariantA();
      }
    
      public Product2 createProduct2()
      {
        return new Product2VariantA();
      }
    }
    
    //------------------------------------------------------------------
    //Konkrete Produkte für Implementierungsvariante B
    //------------------------------------------------------------------
    class Product1VariantB
    extends Product1
    {
    }
    
    class Product2VariantB
    extends Product2
    {
    }
    
    //------------------------------------------------------------------
    //Konkrete Factory für Implementierungsvariante B
    //------------------------------------------------------------------
    class ConcreteFactoryVariantB
    extends ProductFactory
    {
      public Product1 createProduct1()
      {
        return new Product1VariantB();
      }
    
      public Product2 createProduct2()
      {
        return new Product2VariantB();
      }
    }

Ich hoffe ich habe Deathbyaclowns Beitrag hiermit keine Schande gemacht :wink:
 
B

bygones

Gast
[Edit Ebenius] MVC ist in diesem Beitrag falsch beschrieben. Der Beitrag wird überarbeitet. MVC ist kein Entwurfsmuster, sondern ein Architekturmuster. Dieser Beitrag wird also verschoben werden.

Der neue große MVC-Beitrag ist nun hier zu finden
[DUKE]Prädikat "Ausgezeichnet"[/DUKE]



______________________________________________________________________________________
MVC - Pattern

Das MVC (Model - View - Controll) Pattern beschreibt den Ansatz, die verschiedenen Ebenen der GUI Programmierung zu trennen, um ein flexibles und stabiles System zu entwickeln.

Das MVC Pattern ist wohl das bekannteste und meitverbreiteste Pattern.
Es beschreibt den Ansatz, die verschiedenen Ebenen der GUI Programmierung zu trennen, um ein flexibles und stabiles System zu entwickeln. Ziel ist es, dass die Ebenen Model und View von einander unabhängig sind, d.h. dass man ohne das System groß zu ändern die beiden Ebenen austauschen kann. Einzig und allein die Controller Ebene "vermittelt" zwischen den beiden anderen, sie dient sozusagen zur Kommunikation zwischen den beiden Ebenene.

Zu den einzelnen Ebenen:

Model - Ebene:
In dieser Ebene befinden sich alle Klassen, die die Daten des Systems repräsentieren. Sie speichern die Daten und sind für ihre Manipulation zuständig. Es befinden sich keinerlei Informationen in den Klassen, wie ihre Visualisierung aussieht.

View - Ebene:
In dieser Ebene befinden sich alle Klassen, die für die Visualisierung der Daten zuständig sind, d.h. alle GUI Klassen befinden sich in diesem Package. Es befinden sich keine Informationen über die Speicherung bzw. Manipulation der Daten.

Controll - Ebene:
Die Klassen regeln die Kommunikation zwischen den Model und View. Sie dienen dazu ßnderungen in den Model Klassen den View Klassen mitzuteilen bzw. vice versa.

Folgendes (äüßerst simple) Beispiel soll dies demonstrieren:

Die Klasse Wind ist die ModelKlasse. In ihr werden Windrichtung und Windgeschwindigkeit gespeichert. Die Klasse WindViewer ist die ViewKlasse. Sie dient dazu die Daten der Wind Klasse anzuzeigen und man kann über die Buttons die Daten ändern.

Die Klasse WindController ist die Controller Klasse. Sie verbindet die beiden Model und View Klassen. Wenn im View eine ßnderung angeregt wird (durch drücken eines Buttons) gibt der Controller das an die Model Klasse weiter und benachrichtigt den View dann, dass es ßnderungen gibt (was über das Pattern Observer geregelt wird - siehe dessen Beschreibung). Des Weiteren implementiert der Controller ein Interface, das dem View übergeben wird.
Damit ist man auch flexibel bei der Implementierung des Controllers, da nun die beiden Implementierungen unabhängig voneinander sind.

Java:
package test.model;

/**
 * @author deathbyaclown
 */
 public class Wind {
	private Direction dir = Direction.NORTH;
	private int speed = 0;
	
	/**
	 * @return Returns the dir.
	 */
	public Direction getDir() {
		return dir;
	}

	/**
	 * @param dir The dir to set.
	 */
	public void setDir(Direction dir) {
		this.dir = dir;
	}

	/**
	 * @return Returns the speed.
	 */
	public int getSpeed() {
		return speed;
	}

	/**
	 * @param speed The speed to set.
	 */
	public void setSpeed(int speed) {
		this.speed = speed;
	}
}
Java:
package test.model;

/**
 * @author deathbyaclown
 */
public enum Direction {
	NORTH, EAST, SOUTH, WEST
}
Java:
package test.view;


/**
 * @author deathbyaclown
 */
public class WindViewer extends JFrame implements Observer {
	private WindControllable controller;
	private JLabel direction;
	private JLabel speed;

	public WindViewer( WindControllable controller ) {
		super( "WindoController" );
		this.controller = controller;
		setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		init();
		pack();
		setVisible( true );
	}

	private void init() {
		// code für die elemente wurde weggelassen

		JButton button = new JButton( "Change Direction" );
		button.addActionListener( new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				controller.changeDirection();
			}
		} );
		buttonPanel.add( button );

		button = new JButton( "Change Speed" );
		button.addActionListener( new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				controller.changeSpeed();
			}
		} );
		buttonPanel.add( button );
		getContentPane().add( buttonPanel, BorderLayout.SOUTH );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.Observer#update(java.util.Observable, java.lang.Object)
	 */
	public void update(Observable arg0, Object arg1) {
		Wind wind = ( Wind ) arg1;
		direction.setText( wind.getDir().toString() );
		speed.setText( String.valueOf( wind.getSpeed() ) );
	}
}
Java:
package test.controll;

/**
 * @author deathbyaclown
 */
public interface WindControllable {
	public void changeSpeed();
	public void changeDirection();
}
Java:
package test.controll;


/**
 * @author deathbyaclown
 */
public class WindController extends Observable implements WindControllable {
	private Wind wind;

	public WindController() {
		WindViewer viewer = new WindViewer( this );
		addObserver( viewer );
		wind = new Wind();
	}

	public void changeDirection() {
		Direction[] dir = Direction.values();
		wind.setDir( dir[ ( int ) ( Math.random() * 4 ) ] );
		setChanged();
		notifyObservers( wind );
	}

	public void changeSpeed() {
		wind.setSpeed( ( int ) ( Math.random() * 100 ) );
		setChanged();
		notifyObservers( wind );
	}

	public static void main(String[] args) {
		new WindController();
	}
}

Imports wurden weggelassen
 
Zuletzt bearbeitet von einem Moderator:
B

Beni

Gast
Visitor

Mit dem Visitor-Pattern können Elemente einer komplexeren Datenstruktur besucht werden.

Hat man eine Datenstruktur wie ein 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 folgendermassen 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 Visitorpattern 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:
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();
}
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 );
}
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;
    }
}
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;
    }
}

Für verschiedene Aktionen: Summe aller Zahlen, Anzahl Elemente der Gleichung, sowie eine Liste verschiedener Operatoren, benötigt nun sehr wenig 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();
    }
}

Ausgabe:
Summe aller Zahlen: 23.0
Anzahl Knoten: 7
Verschiedene Operatoren: [+, *]
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Oben