2008-09-29 91 views
9

如何在設計兩個生產者/消費者關係的類時避免循環依賴?這裏ListenerImpl需要一個對Broadcaster的引用來註冊/註銷自己,而Broadcaster需要引用回Listeners才能發送消息。這個例子在Java中,但它可以應用於任何OO語言。使用回調函數時如何避免循環依賴?

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

回答

8

我不認爲這是一個循環依賴。

監聽器依賴於什麼。

ListenerImpl取決於聽衆和播音員

轉播取決於監聽器。

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

所有箭頭結束於監聽器。沒有循環。所以,我認爲你沒問題。

+0

但還是有參考週期 - 播音員具有參考到具體的ListenerImpl對象,即使引用的類型是Listener接口類型。參考循環是否意味着依賴循環? – 2008-09-29 16:55:31

+1

沒有真正的方法去除參考週期。這是這種類型的東西需要的。鑑於任何好的垃圾收集器都可以處理它們,所以引用週期現在不是真正的問題。它是你需要擔心的依賴循環。 – Herms 2008-09-29 17:05:56

0

我不是一個Java開發,但這樣的事情:

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

任何OOP語言?好。這是CLOS的十分鐘版本。

廣播框架

(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))) 

實施例的子類

(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)) 

實施例代碼

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 

不幸的是,Lisp中通過消除需要解決了大多數的設計圖案的問題(如你的)爲他們。

4

與Herms的回答相反,我看到一個循環。它不是一個依賴循環,它是一個參考循環:LI保存B對象,B對象保存(一個LI對象的數組)。他們不能輕鬆自由,需要注意確保他們儘可能自由。

一種解決方法是簡單地讓LI對象擁有對廣播者的弱引用。從理論上講,如果廣播公司已經離開,那麼無論如何都不需要註銷,那麼你的註銷將只是檢查是否有廣播公司註銷,如果有的話。

0

下面是Lua中的一個例子(我在這裏使用我自己的Oop lib,參見代碼中'Object'的引用)。

就像在Mikael Jansson的CLOS示例中,您可以直接使用函數,而不需要定義監聽器(注意使用'...」,這是Lua的可變參數):

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 

堅持您的實現,下面是可以寫在任何動態語言,我想一個例子:

--# 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 
相關問題