2008-09-29 5 views
9

Wie können Sie zirkuläre Abhängigkeiten vermeiden, wenn Sie zwei Klassen mit einer Erzeuger/Verbraucher-Beziehung entwerfen? Hier benötigt ListenerImpl einen Verweis auf den Broadcaster, um sich selbst zu registrieren oder die Registrierung aufzuheben, und Broadcaster benötigt eine Referenz zurück zu den Listenern, um Nachrichten zu senden. Dieses Beispiel ist in Java, kann aber auf jede OO-Sprache angewendet werden.Wie können zirkuläre Abhängigkeiten vermieden werden, wenn Callbacks verwendet werden?

public interface Listener { 
    void callBack(Object arg); 
} 
public class ListenerImpl implements Listener { 
    public ListenerImpl(Broadcaster b) { b.register(this); } 
    public void callBack(Object arg) { ... } 
    public void shutDown() { b.unregister(this); } 
} 
public class Broadcaster { 
    private final List listeners = new ArrayList(); 
    public void register(Listener lis) { listeners.add(lis); } 
    public void unregister(Listener lis) {listeners.remove(lis); } 
    public void broadcast(Object arg) { for (Listener lis : listeners) { lis.callBack(arg); } } 
} 

Antwort

8

Ich sehe nicht, dass eine zirkuläre Abhängigkeit ist.

Listener hängt von nichts ab.

ListenerImpl hängt von Listener und Broadcaster

Broadcaster auf Listener abhängt.

 Listener 
    ^ ^
    /  \ 
    /   \ 
Broadcaster <-- ListenerImpl 

Alle Pfeile enden am Listener. Es gibt keinen Zyklus. Also, ich denke du bist OK.

+0

Aber es ist immer noch ein Referenzzyklus - Broadcaster hat einen Verweis zu dem konkreten ListenerImpl-Objekt, obwohl der Typ der Referenz der Listener-Schnittstellentyp ist. Bedeutet ein Referenzzyklus keinen Abhängigkeitszyklus? –

+1

Es gibt keine Möglichkeit, einen Referenzzyklus zu entfernen. Es ist irgendwie für diese Art von Sache erforderlich. Referenzzyklen sind heutzutage nicht wirklich ein Problem, da jeder gute Garbage Collector sie gut behandeln sollte. Es sind die Abhängigkeitszyklen, über die Sie sich Sorgen machen müssen. – Herms

+0

@sk: Um 'Zyklus' zu definieren, müssen Sie zuerst 'Abhängigkeit' definieren: Es gibt keinen Zyklus für die Kompilierung. Es ist ein Laufzeitzyklus, wenn alle Referenzen gleich sind und jemand den Zyklus unterbrechen muss, wenn er nicht mehr benötigt wird (Listener entfernen) – xtofl

0

Ich bin kein Java-Entwickler, aber so etwas wie dies:

public class ListenerImpl implements Listener { 
    public Foo() {} 
    public void registerWithBroadcaster(Broadcaster b){ b.register(this); isRegistered = true;} 
    public void callBack(Object arg) { if (!isRegistered) throw ... else ... } 
    public void shutDown() { isRegistered = false; } 
} 

public class Broadcaster { 
    private final List listeners = new ArrayList(); 
    public void register(Listener lis) { listeners.add(lis); } 
    public void unregister(Listener lis) {listeners.remove(lis); } 
    public void broadcast(Object arg) { for (Listener lis : listeners) { if (lis.isRegistered) lis.callBack(arg) else unregister(lis); } } 
} 
7

All OOP-Sprache? OK. Hier ist eine zehnminütige Version in CLOS.

Rundfunk Rahmen

(defclass broadcaster() 
    ((listeners :accessor listeners 
       :initform '()))) 

(defgeneric add-listener (broadcaster listener) 
    (:documentation "Add a listener (a function taking one argument) 
    to a broadcast's list of interested parties")) 

(defgeneric remove-listener (broadcaster listener) 
    (:documentation "Reverse of add-listener")) 

(defgeneric broadcast (broadcaster object) 
    (:documentation "Broadcast an object to all registered listeners")) 

(defmethod add-listener (broadcaster listener) 
    (pushnew listener (listeners broadcaster))) 

(defmethod remove-listener (broadcaster listener) 
    (let ((listeners (listeners broadcaster))) 
    (setf listeners (remove listener listeners)))) 

(defmethod broadcast (broadcaster object) 
    (dolist (listener (listeners broadcaster)) 
    (funcall listener object))) 

Beispiel Unterklasse

(defclass direct-broadcaster (broadcaster) 
    ((latest-broadcast :accessor latest-broadcast) 
    (latest-broadcast-p :initform nil)) 
    (:documentation "I broadcast the latest broadcasted object when a new listener is added")) 

(defmethod add-listener :after ((broadcaster direct-broadcaster) listener) 
    (when (slot-value broadcaster 'latest-broadcast-p) 
    (funcall listener (latest-broadcast broadcaster)))) 

(defmethod broadcast :after ((broadcaster direct-broadcaster) object) 
    (setf (slot-value broadcaster 'latest-broadcast-p) t) 
    (setf (latest-broadcast broadcaster) object)) 

Beispielcode

Lisp> (let ((broadcaster (make-instance 'broadcaster))) 
     (add-listener broadcaster 
         #'(lambda (obj) (format t "I got myself a ~A object!~%" obj))) 
     (add-listener broadcaster 
         #'(lambda (obj) (format t "I has object: ~A~%" obj))) 
     (broadcast broadcaster 'cheezburger)) 

I has object: CHEEZBURGER 
I got myself a CHEEZBURGER object! 

Lisp> (defparameter *direct-broadcaster* (make-instance 'direct-broadcaster)) 
     (add-listener *direct-broadcaster* 
        #'(lambda (obj) (format t "I got myself a ~A object!~%" obj))) 
     (broadcast *direct-broadcaster* 'kitty) 

I got myself a KITTY object! 

Lisp> (add-listener *direct-broadcaster* 
        #'(lambda (obj) (format t "I has object: ~A~%" obj))) 

I has object: KITTY 
Leider

, Lisp die meisten der Design-Muster Probleme löst (wie Sie) die Notwendigkeit entfällt, für Sie.

4

Im Gegensatz zu Herms Antwort, ich tun sehe eine Schleife. Es ist keine Abhängigkeitsschleife, es ist eine Referenzschleife: LI hält das B-Objekt, das B-Objekt enthält (ein Array von) LI-Objekt (en). Sie befreien sich nicht so leicht und es muss darauf geachtet werden, dass sie nach Möglichkeit frei sind.

Eine Problemumgehung besteht einfach darin, dass das LI-Objekt eine WeakReference für den Broadcaster enthält. Theoretisch, wenn der Sender weg ist, gibt es sowieso nichts, mit dem man sich abmelden kann, also wird Ihre Abmeldung einfach prüfen, ob es einen Sender gibt, von dem man sich abmeldet, und wenn ja, dann tut es das auch.

+0

Referenzen Schleifen sind nicht wirklich ein Problem. Der Garbage Collector von AFAIK Java sollte es gut behandeln. Ich glaube nicht, dass Sie wirklich etwas davon bekommen würden, wenn Sie eine schwache Referenz verwenden. – Herms

+0

@Herms: Sie werden durch automatische Müllsammlung verwöhnt :) Ich finde es ein _sehr_ gutes Problem: Nimm niemals einfach Besitz, um in Ordnung zu sein, ohne es zu überlegen! +1 – xtofl

0

Verwenden Sie schwache Referenzen, um den Zyklus zu unterbrechen.

Siehe this answer.

0

Hier ist ein Beispiel in Lua (ich benutze meine eigenen Oop lib hier, siehe Verweise auf 'Objekt' im Code).

Wie in Mikael Janssons CLOS-Beispiel können Sie Funktionen direkt verwenden, wodurch die Definition von Listenern entfällt (beachten Sie die Verwendung von ...‘, Es ist Lua varargs):

Broadcaster = Object:subclass() 

function Broadcaster:initialize() 
    self._listeners = {} 
end 

function Broadcaster:register(listener) 
    self._listeners[listener] = true 
end 

function Broadcaster:unregister(listener) 
    self._listeners[listener] = nil 
end 
function Broadcaster:broadcast(...) 
    for listener in pairs(self._listeners) do 
     listener(...) 
    end 
end 

zu Ihrer Implementierung Das Festhalten, hier ein Beispiel ist, die in jeder dynamischen Sprache geschrieben werden könnte ich denke:

--# Listener 
Listener = Object:subclass() 
function Listener:callback(arg) 
    self:subclassResponsibility() 
end 

--# ListenerImpl 
function ListenerImpl:initialize(broadcaster) 
    self._broadcaster = broadcaster 
    broadcaster:register(this) 
end 
function ListenerImpl:callback(arg) 
    --# ... 
end 
function ListenerImpl:shutdown() 
    self._broadcaster:unregister(self) 
end 

--# Broadcaster 
function Broadcaster:initialize() 
    self._listeners = {} 
end 
function Broadcaster:register(listener) 
    self._listeners[listener] = true 
end 
function Broadcaster:unregister(listener) 
    self._listeners[listener] = nil 
end 
function Broadcaster:broadcast(arg) 
    for listener in pairs(self._listeners) do 
     listener:callback(arg) 
    end 
end 
Verwandte Themen