2008-09-29 3 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 зависит от Слушателя и Broadcaster

Broadcaster зависит от прослушивателя.

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

Все стрелки заканчиваются при прослушивании. Нет никакого цикла. Итак, я думаю, что все в порядке.

+0

Но есть еще ссылка цикла - вещатель имеет ссылку к конкретному объекту ListenerImpl, хотя тип ссылки - тип интерфейса Listener. Не указывает ли цикл ссылок на цикл зависимостей? – 2008-09-29 16:55:31

+1

На самом деле нет способа удалить ссылочный цикл. Это отчасти требуется для этого типа вещей. В эти дни эталонные циклы не являются проблемой, учитывая, что любой хороший сборщик мусора должен прекрасно их обрабатывать. Это циклы зависимостей, о которых вам нужно беспокоиться. – Herms 2008-09-29 17:05:56

0

Я не ява DEV, но что-то вроде этого:

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

Любой язык ООП? ОК. Вот десятиминутная версия в 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 

К сожалению, Лисп решает большую часть проблем шаблона проектирования (например, как ваши), устраняя необходимость для них.

4

В отличие от ответа Гермса, I do см. Петлю. Это не цикл зависимостей, это ссылочный цикл: LI содержит объект B, объект объекта B (массив) объектов (ов). Они не освобождаются легко, и необходимо позаботиться о том, чтобы они были свободны, когда это возможно.

Обходное решение - просто иметь объект LI, удерживающий WeakReference для вещателя. Теоретически, если вещатель ушел, в любом случае вам нечего отвлекаться на регистрацию, поэтому ваша дерегистрация будет просто проверять, есть ли вещательная компания, чтобы отменить регистрацию, и сделать это, если есть.

0

Вот пример в Lua (я использую свой собственный Oop lib здесь, см. Ссылки на «Объект» в коде).

Как в примере CLOS Mikael Jansson, вы можете использовать функции напрямую, устраняя необходимость определения слушателей (обратите внимание на использование «...», Это переменные аргументы 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