كيف يمكن تجنب التبعيات الدائرية عند استخدام عمليات الاسترجاعات؟

StackOverflow https://stackoverflow.com/questions/149439

سؤال

كيف يمكنك تجنب التبعيات الدائرية عندما تقوم بتصميم فئتين مع علاقة المنتج/المستهلك؟هنا يحتاج برنامج ListerImpl إلى مرجع إلى المذيع من أجل التسجيل/إلغاء تسجيل نفسه، ويحتاج المذيع إلى مرجع إلى المستمعين من أجل إرسال الرسائل.هذا المثال موجود بلغة 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); } }
}
هل كانت مفيدة؟

المحلول

لا أرى أن هذا تبعية دائرية.

المستمع لا يعتمد على شيء.

يعتمد برنامج ListerImpl على المستمع والمذيع

المذيع يعتمد على المستمع.

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

تنتهي جميع الأسهم عند المستمع.ليس هناك دورة.لذا، أعتقد أنك بخير.

نصائح أخرى

أي لغة 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 بحل معظم مشاكل نمط التصميم (مثل مشكلتك) عن طريق إلغاء الحاجة إليها.

وعلى النقيض من إجابة هيرمس، أنا يفعل رؤية حلقة.إنها ليست حلقة تبعية، بل حلقة مرجعية:LI يحمل الكائن B، والكائن B يحمل (صفيف) كائن (كائنات) LI.إنهم لا يتحررون بسهولة، ويجب توخي الحذر لضمان تحررهم عندما يكون ذلك ممكنًا.

أحد الحلول هو ببساطة جعل كائن LI يحمل مرجعًا ضعيفًا إلى جهة البث.من الناحية النظرية، إذا اختفت جهة البث، فلا يوجد شيء يمكن إلغاء التسجيل به على أي حال، لذا فإن إلغاء تسجيلك سيتحقق ببساطة مما إذا كان هناك جهة بث لإلغاء التسجيل منها، ويفعل ذلك إذا كان هناك.

أنا لست مطور جافا، ولكن شيء من هذا القبيل:

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 (أستخدم ملفي الخاص عفوًا ليب هنا، راجع الإشارات إلى "الكائن" في الكود).

كما هو الحال في مثال CLOS الخاص بـ Mikael Jansson، يمكنك استخدام الوظائف مباشرةً، مما يلغي الحاجة إلى تعريف المستمعين (لاحظ استخدام "..."، إنها varargs الخاصة بـ 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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top