コールバックが使用されている場合、循環依存関係をどのように回避できますか?
-
02-07-2019 - |
質問
プロデューサー/コンシューマー関係を持つ2つのクラスを設計するときに、循環依存関係を回避するにはどうすればよいですか?ここで、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); } }
}
解決
循環依存であることはわかりません。
リスナーは何にも依存しません。
ListenerImplはリスナーとブロードキャスターに依存しています
ブロードキャスターはリスナーに依存しています。
Listener
^ ^
/ \
/ \
Broadcaster <-- ListenerImpl
すべての矢印はリスナーで終わります。サイクルはありません。だから、あなたは大丈夫だと思う。
他のヒント
OOP言語はありますか? OK。これが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は(あなたのような)デザインパターンの問題のほとんどをそれらの必要性を排除することで解決します。
ハームスの答えとは対照的に、私はループをします。依存関係ループではなく、参照ループです。LIはBオブジェクトを保持し、BオブジェクトはLIオブジェクト(の配列)を保持します。簡単には解放されないため、可能な場合は解放されるように注意する必要があります。
1つの回避策は、LIオブジェクトにWeakReferenceをブロードキャスターに保持させることです。理論的には、ブロードキャスターがなくなった場合は、とにかく登録解除するものは何もないので、登録解除は登録解除するブロードキャスターがあるかどうかを確認し、ある場合は登録します。
私は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); } }
}
弱参照を使用して、サイクルを中断します。
この回答。
Luaの例を次に示します(私は自分の Oop lib を使用しています。コード内のオブジェクト」)。
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