5

Ich weiß, dass dieses Thema zu Tode gedeckt wurde, aber ich bin immer noch kämpfen und könnte mit einer bestimmten Hilfe tun.Locker gekoppelt Beobachter Muster

Mein Ziel ist es, ein einfaches Observer-Muster zwischen einer Art beobachtbaren (sagen wir ein Hund) und einer Art von Listener (sagen wir Besitzer) zu implementieren.

Schließlich wäre der Besitzer eine "Ansicht" und der Hund ein "Modell" in einem MVC-Paradigma. Ich benutze Hund und Besitzer, nur um die Dinge hier zu vereinfachen.

Ich habe versucht, Javas eingebaute Observer/Observable-Klassen zu verwenden, habe aber festgestellt, wie schlecht die updates() -Methode ist - sie empfängt ein POJO und ich müsste dieses POJO in der update() -Methode prüfen/umwandeln. Ich würde es viel lieber haben, wenn meine 'update()' Methode etwas erhält, was sie erwarten kann.

So folgte ich ein paar Tutorials, auch diese, die den Hund/Besitzer als Beispiel verwendet:

http://www.youtube.com/watch?v=qw0zZAte66A

Hier habe ich gezeigt, wie meine eigene Beobachter/Beobachtet Klassen rollen. In Pseudo-Code, was ich jetzt habe, ist dies:

Dog/Model { 

    List listeners; 

    public fireDogHungryEvent() { 

     foreach listener { 
      listener.onDogHungry(this); 
     } 
    } 

    public fireDogPeeEvent() { 

     foreach listener { 
      listener.onDogNeedsToPee(this); 
     } 
    } 

    public getHungerLevel() { return hungerLevel; } 
    public getBladderCapacity() { return bladderCapacity; } 
} 

Owner/View { 

    public onDogHungry(model) { 
     println(model.getHungerLevel()); 
    } 

    public onDogNeedsToPee(model) { 
     println(model.getBladderCapacity()); 
    } 
} 

So, jetzt eher als ein Update() -Methode, ich habe Methoden, die bestimmte Ereignisse behandeln. Brillant. Ich bin derzeit mit der Owner/View-Klasse zufrieden. Es kennt die Methoden des Hundes/Modells und das ist gut (denke ich).

Was ich nicht mag ist, dass das Hund/Modell Verweise auf Methoden in der Besitzer/Ansicht hat. Ich habe unzählige Male gelesen und stimme völlig zu, dass ein Modell nicht eng an seine Ansichten gekoppelt sein sollte, so wie es scheint. Ich bin auch nicht scharf auf alle "Feuer" -Methoden im Hund/Modell, die fast dasselbe machen; Looping über alle seine Listener und nur eine andere Methode für jeden Listener aufrufen.

Ist es möglich, diese Beziehung weiter zu entkoppeln und nicht die Methode Dog/Model spezifische Methoden aufzurufen? Wenn ja, was ist der beste Weg, um Dog/Model-Daten in den Owner/View zu bekommen und damit zu arbeiten?

Dank

+0

Welche Methoden von 'Dog' haben Verweise auf die Ansicht? Ich sehe keine. – SJuan76

+0

Der beste Weg ist, eine Schnittstelle zu verwenden, die das Video-Tutorial vorschlägt. Ist Listener eine Schnittstelle in Ihrer Implementierung? –

+0

SJuan76 - die Referenzen, über die ich spreche, sind die Aufrufe von listener.onDogHungry und listener.onDogNeedsToPee in den Dog 'fire'-Methoden. – whoshotdk

Antwort

3

Sie sollten aus der Kenntnis der spezifischen Implementierung interface entfernt sowohl die Observer und die Observable

public enum EventType { 

    HUNGRY, 
    PEE; 
} 

public interface DogEvent { 

    EventType getType(); 
} 

public interface DogListener { 

    void fireEvent(DogEvent event); 
} 

public class Dog { 

    private final Set<DogListener> listeners = new CopyOnWriteArraySet<DogListener>(); 

    public void register(final DogListener dogListener) { 
     listeners.add(dogListener); 
    } 

    public void unregister(final DogListener dogListener) { 
     listeners.remove(dogListener); 
    } 

    public void firePeeEvent() { 
     fireEvent(new DogEvent() { 
      @Override 
      public EventType getType() { 
       return EventType.PEE; 
      } 
     }); 
    } 

    public void fireHungryEvent() { 
     fireEvent(new DogEvent() { 
      @Override 
      public EventType getType() { 
       return EventType.HUNGRY; 
      } 
     }); 
    } 

    private void fireEvent(final DogEvent dogEvent) { 
     for (final DogListener listener : listeners) { 
      listener.fireEvent(dogEvent); 
     } 
    } 
} 

public class Owner implements DogListener { 

    @Override 
    public void fireEvent(DogEvent event) { 
     switch (event.getType()) { 
      case PEE: 
       System.out.println("Someone take the dog out"); 
       break; 
      case HUNGRY: 
       System.out.println("I can't believe the dog is hungry _again_!"); 
       break; 
     } 
    } 
} 

In diesem Fall wird die Dog weiß nicht, über die Umsetzung der Owner es nur bekannt, dass die Owner ein DogListener ist.

Die Owner auf der anderen Seite weiß nicht über die Dog es weiß nur, dass es eine eingehende hat.

+0

Ich mag diese Antwort für ihre Klarheit, danke Boris. Ich mag nicht besonders die Case-Anweisung, die überprüfen muss, was für ein Event passiert ist, aber ich denke, es geschieht nicht einfach durch Magie: D Ich werde es mit ein paar Observablen/Zuhörern versuchen und sehen, wie es mir geht. Ich werde diese Antwort irgendwann heute akzeptieren, wenn ich etwas vernünftig Effizientes daraus machen kann! – whoshotdk

+0

Ah Ich habe gerade festgestellt, dass dies eine viel bessere Umsetzung von dem ist, was ich zuvor bei meinen Versuchen versucht habe; außer dass ich reflection verwendete, um Methoden basierend auf dem String-Namen anstelle eines Event-Objekts aufzurufen. Ich denke, das wird funktionieren: D – whoshotdk

+0

Die 'enum' war nur ein Vorschlag - Sie könnten ein Besuchermuster verwenden, um den Polymorphismus voll zu nutzen. –

1

Für MVC Zwecke (vor allem für MVP) Ich habe Programmierung gute Erfahrungen mit Event-Basen gesammelt. Sie benötigen also einen EventBus, der von Hund und Besitzer verwendet wird. Der Besitzer wird sich für bestimmte Event-Klassen wie HungerLevelIncrease anmelden und der Hund wird solche Events feuern. Für Ihr Hundebesitzer-Beispiel wäre das ein bisschen seltsam, da der Besitzer seine Hunde nicht kannte, aber für die GUI-Programmierung ist dies eine nette und einfache Lösung, um Dinge besonders zwischen anderen Controllern zu entkoppeln.

Sie könnten einfach einen eigenen Bus erstellen oder den von google guava verwenden.

Dies ist das Beste, was ich weiß, um eine wirklich lose Kopplung zu schaffen.

+0

Das erschien mir zunächst als eine gute Idee, aber nach ein wenig Lesen haben andere bemerkt, dass dieses Muster im Grunde jeden Observablen/Zuhörer dazu bringt, über einen Bus Bescheid zu wissen. Das Hinzufügen neuer Ereignistypen würde auch das Ändern dieses Busses bedeuten. Siehe: http://stackoverflow.com/questions/3987391/why-people-use-message-event-buses-in-the-code. Vielleicht ist das nicht so schlimm wie es scheint, IDK. – whoshotdk

+1

@ user2373021 haben Sie etwas wie [mbassador] (https://github.com/bennidi/mbassador) angeschaut - es ist Annotation basiert, so alles was Sie brauchen ist, Ihre Hörer zu kommentieren und es wird abgeleitet, was sie von der hören wollen Methodenparameter. Es kann auch Nachrichten asynchron senden ... –

+0

@ user2373021 es ist wahr, dass Sie den Bus zu allen Klassen hinzufügen müssen, die ihn brauchen. In den meisten Fällen werden Sie jedoch einen Dependency-Injection-Container zu größeren Anwendungen hinzufügen, die Ihnen eventBus injizieren können. Das Hinzufügen neuer Ereignistypen sollte jedoch keinen Einfluss auf Ihren Bus haben. Wenn Sie nur ein paar Klassen haben, würde ich auch für die einfache Listener-Lösung gehen, aber wenn Sie mehr Dialoge erhalten und mehrere Dialoge miteinander und mit anderen Klassen (z. B. Server) sprechen. Ich würde den EventBus-Mechanismus ausprobieren. – mszalbach