Domanda

Come evitare le dipendenze circolari quando si progettano due classi con una relazione produttore / consumatore? Qui ListenerImpl ha bisogno di un riferimento a Broadcaster per registrarsi / annullare la registrazione, e Broadcaster ha bisogno di un riferimento ai listener per poter inviare messaggi. Questo esempio è in Java ma può essere applicato a qualsiasi linguaggio 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); } }
}
È stato utile?

Soluzione

Non vedo che si tratti di una dipendenza circolare.

Il listener non dipende da nulla.

ListenerImpl dipende da Listener e Broadcaster

L'emittente dipende da Listener.

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

Tutte le frecce finiscono in Listener. Non c'è ciclo. Quindi, penso che tu stia bene.

Altri suggerimenti

Qualche lingua OOP? OK. Ecco una versione di dieci minuti in CLOS.

Quadro di radiodiffusione

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

Sottoclasse di esempio

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

Codice di esempio

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

Sfortunatamente, Lisp risolve la maggior parte dei problemi del modello di progettazione (come il tuo) eliminandone la necessità.

Contrariamente alla risposta di Herms, faccio vedere un ciclo. Non è un ciclo di dipendenza, è un ciclo di riferimento: LI contiene l'oggetto B, l'oggetto B contiene (un array di) oggetti LI. Non si liberano facilmente e occorre prestare attenzione per assicurarsi che siano liberi quando possibile.

Una soluzione alternativa è semplicemente che l'oggetto LI contenga un WeakReference sull'emittente. Teoricamente, se l'emittente è andata via, non c'è nulla con cui annullare la registrazione, quindi la tua cancellazione controllerà semplicemente se c'è un'emittente da cui annullare la registrazione, e se lo è,

Non sono uno sviluppatore Java, ma qualcosa del genere:

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

Usa riferimenti deboli per interrompere il ciclo.

Vedi questa risposta .

Ecco un esempio in Lua (uso il mio Oop lib qui, vedi riferimenti a " Oggetto "nel codice).

Come nell'esempio CLOS di Mikael Jansson, puoi usare direttamente le funzioni, eliminando la necessità di definire gli ascoltatori (nota l'uso di '...', sono i vararg di 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

Attenendosi alla tua implementazione, ecco un esempio che potrebbe essere scritto in qualsiasi linguaggio dinamico immagino:

--# 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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top