¿Cómo se pueden evitar las dependencias circulares cuando se usan devoluciones de llamada?

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

Pregunta

¿Cómo puede evitar las dependencias circulares cuando diseña dos clases con una relación productor / consumidor? Aquí ListenerImpl necesita una referencia a Broadcaster para registrarse / cancelar el registro, y Broadcaster necesita una referencia a los Listeners para enviar mensajes. Este ejemplo está en Java, pero puede aplicarse a cualquier lenguaje 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); } }
}
¿Fue útil?

Solución

No veo que sea una dependencia circular.

El oyente no depende de nada.

ListenerImpl depende de Listener y Broadcaster

La emisora ??depende del oyente.

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

Todas las flechas terminan en Listener. No hay ciclo Entonces, creo que estás bien.

Otros consejos

¿Algún lenguaje OOP? DE ACUERDO. Aquí hay una versión de diez minutos en CLOS.

Marco de difusión

(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)))

Ejemplo de subclase

(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))

Código de ejemplo

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

Desafortunadamente, Lisp resuelve la mayoría de los problemas de patrones de diseño (como el suyo) al eliminar la necesidad de ellos.

En contraste con la respuesta de Herms, do veo un bucle. No es un bucle de dependencia, es un bucle de referencia: LI contiene el objeto B, el objeto B contiene (una matriz de) objeto (s) LI. No se liberan fácilmente, y se debe tener cuidado para garantizar que se liberen cuando sea posible.

Una solución alternativa es simplemente hacer que el objeto LI mantenga una WeakReference a la emisora. Teóricamente, si la emisora ??se ha ido, no hay nada con lo que desregistrarse de todos modos, por lo que su cancelación de registro simplemente verificará si hay una emisora ??para desregistrarse, y lo hará si la hay.

No soy un desarrollador de Java, pero algo como esto:

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); } }
}

Use referencias débiles para romper el ciclo.

Ver esta respuesta .

Aquí hay un ejemplo en Lua (uso mi propio Oop lib aquí, vea las referencias a ' Objeto 'en el código).

Al igual que en el ejemplo CLOS de Mikael Jansson, puede usar funciones directamente, eliminando la necesidad de definir oyentes (tenga en cuenta el uso de '...', son los varargs de 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

Respetando su implementación, aquí hay un ejemplo que podría escribirse en cualquier lenguaje dinámico, supongo:

--# 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top