9

プロデューサー/コンシューマーの関係を持つ 2 つのクラスを設計しているときに、循環依存関係を回避するにはどうすればよいでしょうか? ここで、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); } }
}
4

6 に答える 6

9

それが循環依存関係であるとは思いません。

リスナーは何にも依存しません。

ListenerImpl は Listener と Broadcaster に依存します

ブロードキャスターはリスナーに依存します。

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

すべての矢印はリスナーで終了します。サイクルはありません。だから、私はあなたが大丈夫だと思います。

于 2008-09-29T16:23:50.900 に答える
7

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 は (あなたのような) 設計パターンの問題のほとんどを、それらの必要性を排除することによって解決します。

于 2008-09-29T17:09:13.773 に答える
4

Hermsの答えとは対照的に、ループが見られます。これは依存関係ループではなく、参照ループです。LIはBオブジェクトを保持し、BオブジェクトはLIオブジェクト(の配列)を保持します。それらは簡単には解放されないので、可能な場合は解放されるように注意する必要があります。

回避策の1つは、LIオブジェクトにブロードキャスターへのWeakReferenceを保持させることです。理論的には、放送局がなくなった場合、とにかく登録を解除するものは何もないので、登録解除は、登録を解除する放送局があるかどうかを確認し、ある場合はそうします。

于 2008-09-29T17:57:33.827 に答える
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); } }
}
于 2008-09-29T16:28:27.317 に答える
0

弱い参照を使用して、サイクルを断ち切ります。

この回答を参照してください。

于 2008-10-02T09:35:41.247 に答える
0

これは Lua の例です (ここでは独自のOop ライブラリを使用しています。コード内の「オブジェクト」への参照を参照してください)。

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
于 2008-11-28T09:50:32.323 に答える