2008-09-29 12 views
9

¿Cómo se pueden evitar las dependencias circulares cuando se diseñan dos clases con una relación productor/consumidor? Aquí ListenerImpl necesita una referencia a Broadcaster para registrarse/anular el registro, y Broadcaster necesita una referencia de vuelta a los Listeners para enviar mensajes. Este ejemplo está en Java, pero puede aplicarse a cualquier lenguaje OO.¿Cómo se pueden evitar las dependencias circulares cuando se utilizan devoluciones de llamada?

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

Respuesta

8

No veo que se trate de una dependencia circular.

El oyente depende de nada.

ListenerImpl depende del oyente y locutor

Transmisor depende del oyente.

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

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

+0

Pero todavía hay un ciclo de referencia - radiodifusor tenga una referencia al objeto concreto ListenerImpl aunque el tipo de la referencia sea el tipo de interfaz de Listener. ¿No implica un ciclo de referencia un ciclo de dependencia? –

+1

No hay realmente una manera de eliminar un ciclo de referencia. Es algo necesario para este tipo de cosas. Los ciclos de referencia no son realmente un problema en estos días, dado que cualquier buen recolector de basura debería manejarlos bien. Son ciclos de dependencia de los que debe preocuparse. – Herms

+0

@sk: para definir 'ciclo' primero debe definir 'dependencia': no ​​hay ciclo de tiempo de compilación. Existe un ciclo de ejecución si todas las referencias son iguales, y alguien necesita interrumpir el ciclo cuando ya no es necesario (eliminar oyentes) – xtofl

0

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

Cualquier lenguaje de programación orientada a objetos? DE ACUERDO. Aquí hay una versión de diez minutos en CLOS.

Radiodifusión marco

(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 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 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 diseño de patrones (como la suya) al eliminar la necesidad para ellos.

4

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

Una solución consiste simplemente en que el objeto LI tenga una WeakReference para la emisora. Teóricamente, si la emisora ​​se ha ido, no hay nada que anular el registro de todos modos, por lo que su cancelación de registro simplemente comprobará si hay una emisora ​​para anular el registro, y lo hará si existe.

+0

Los bucles de referencia no son realmente un problema. El recolector de basura de AFAIK java debería manejarlo bien. No creo que realmente ganes nada con el uso de una referencia débil. – Herms

+0

@Herms: está siendo mimado por la recolección automática de basura :) Encuentro un tema muy bueno: ¡nunca asuma simplemente que la propiedad es lo más acertada sin pensarlo dos veces! +1 – xtofl

0

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

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

pegar a su aplicación, aquí está un ejemplo que podría ser escrito 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 
Cuestiones relacionadas