Question

Comment éviter les dépendances circulaires lorsque vous concevez deux classes avec une relation producteur / consommateur? Ici, ListenerImpl a besoin d'une référence à Broadcaster pour pouvoir s'enregistrer / se désenregistrer, et Broadcaster a besoin d'une référence aux auditeurs pour pouvoir envoyer des messages. Cet exemple est en Java mais il peut s’appliquer à n’importe quel langage 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); } }
}
Était-ce utile?

La solution

Je ne vois pas cela comme une dépendance circulaire.

L'auditeur ne dépend de rien.

ListenerImpl dépend de Listener et Broadcaster

Le diffuseur dépend de Listener.

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

Toutes les flèches se terminent à Listener. Il n'y a pas de cycle. Donc, je pense que tu vas bien.

Autres conseils

Un langage POO? D'ACCORD. Voici une version de dix minutes dans CLOS.

Cadre de diffusion

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

Exemple de sous-classe

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

Exemple de code

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

Malheureusement, Lisp résout la plupart des problèmes de modèle de conception (tels que le vôtre) en éliminant le besoin de les résoudre.

Contrairement à la réponse de Herms, je vois en boucle. Ce n'est pas une boucle de dépendance, c'est une boucle de référence: LI contient l'objet B, l'objet B contient (un tableau de) objet (s) LI. Ils ne libèrent pas facilement et il faut veiller à les libérer autant que possible.

Une solution de contournement consiste simplement à demander à l'objet LI de conserver une référence faible au diffuseur. Théoriquement, si le radiodiffuseur est parti, il n’ya rien à désinscrire de toute façon. Votre désinscription vérifiera donc simplement s’il existe un diffuseur sur lequel se désinscrire, et le fera s’il en existe un.

Je ne suis pas un développeur Java, mais quelque chose comme ceci:

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

Utilisez des références faibles pour rompre le cycle.

Voir cette réponse .

Voici un exemple dans Lua (j'utilise mon propre Oop lib ici, voir les références à ' Objet 'dans le code).

Comme dans l'exemple CLOS de Mikael Jansson, vous pouvez utiliser des fonctions directement, ce qui vous évite de définir des écouteurs (notez l'utilisation de "...", ce sont les variables 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

En ce qui concerne votre implémentation, voici un exemple qui pourrait être écrit dans n’importe quel langage dynamique, je suppose:

--# 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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top