使用回调时如何避免循环依赖关系?

How can circular dependencies be avoided when callbacks are used?

提问人:sk. 提问时间:9/30/2008 最后编辑:Brad Cupitsk. 更新时间:4/28/2017 访问量:4176

问:

在设计两个具有生产者/消费者关系的类时,如何避免循环依赖关系?这里 ListenerImpl 需要对 Broadcaster 的引用才能注册/注销自身,而 Broadcaster 需要对 Listener 的引用才能发送消息。这个例子是用 Java 编写的,但它可以应用于任何面向对象语言。

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); } }
}
OOP 反转控制 回调

评论


答:

9赞 Herms 9/30/2008 #1

我不认为这是一种循环依赖。

侦听器不依赖于任何东西。

ListenerImpl 依赖于 Listener 和 Broadcaster

Broadcaster 依赖于 Listener。

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

所有箭头都以 Listener 结束。没有周期。所以,我认为你没事。

评论

0赞 sk. 9/30/2008
但是仍然有一个引用周期 - 广播者具有对具体 ListenerImpl 对象的引用,即使引用的类型是 Listener 接口类型。引用周期不意味着依赖周期吗?
1赞 Herms 9/30/2008
实际上没有办法删除参考周期。对于这种事情来说,这是必需的。如今,参考周期并不是真正的问题,因为任何好的垃圾收集器都应该很好地处理它们。您需要担心的是依赖关系周期。
0赞 xtofl 12/23/2008
@sk:为了定义“周期”,必须先定义“依赖关系”:没有编译时周期。如果所有引用都相等,则存在一个运行时循环,并且有人需要在不再需要时打破该循环(删除侦听器)
0赞 Sunny Milenov 9/30/2008 #2

我不是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); } }
}
6赞 Mikael Jansson 9/30/2008 #3

任何 OOP 语言?还行。这是 CLOS 中的 10 分钟版本。

广播框架

(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通过消除对它们的需求来解决大多数设计模式问题(例如您的问题)。

5赞 Tanktalus 9/30/2008 #4

与Herms的回答相反,我确实看到了一个循环。它不是一个依赖循环,而是一个引用循环:LI 保存 B 对象,B 对象保存 LI 对象的(数组)。它们不容易释放,需要注意确保它们在可能的情况下释放。

一种解决方法是简单地让 LI 对象保存对广播者的 WeakReference。从理论上讲,如果广播公司已经离开,无论如何都没有什么可以注销的,因此您的注销将简单地检查是否有广播公司要注销,如果有,则这样做。

评论

0赞 Herms 9/30/2008
引用循环并不是真正的问题。AFAIK java 的垃圾收集器应该可以很好地处理它。我不认为你真的会从使用弱引用中获得任何好处。
0赞 xtofl 12/23/2008
@Herms:你被自动垃圾回收:)宠坏了我发现这是一个非常好的问题:永远不要不假思索地假设所有权没问题!+1
0赞 janm 10/2/2008 #5

使用弱引用来打破循环。

请看这个答案

0赞 Sébastien RoccaSerra 11/28/2008 #6

这是 Lua 中的一个示例(我在这里使用我自己的 Oop 库,请参阅代码中对“Object”的引用)。

就像在 Mikael Jansson 的 CLOS 示例中一样,你可以直接使用函数,无需定义侦听器(注意使用“...”,这是 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

坚持你的实现,这里有一个可以用任何动态语言编写的例子,我猜:

--# 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