interface AListener { public void aChanged(); public void bChanged(); }
class AEventSource { private List<AListener> listeners = new ArrayList<AListener>(); public void add(AListener listener) { listeners.add(listener); } private void aChanged() { for (AListener listener : listeners) { listener.aChanged(); } } private void bChanged() { for (AListener listener : listeners) { listener.bChanged(); } } public void f() { ... aChanged(); ... bChanged(); ... } ... }
aChanged()
and bChanged()
methods, all listeners are notified that this event has occurred. And in the f()
method at the right moment these methods are invoked.AEventSource
class by adding different notification methods and some code that will choose one way or another. Secondly, everything that relates to the notification of listeners can be taken out in a separate class. Then the desired notification method can be set, for example, by passing the desired object as a parameter to the constructor.AEventSource
, but two (or more) classes AEventSource1
, AEventSource2
, and most likely it will be, then choosing the first method will have to duplicate the new complex code for notification in two classes, and this is already copy -paste, that is, evil. The second reason is that the second approach respects the principle of shared responsibility (the one that SRP). In fact, now AEventSource
two reasons for changing the AEventSource
class: if we want to change the notification order, and if we want to change the reason for the notification, that is, the f()
function from the example above. class ACompositeListener implements AListener { private List<AListener> listeners = new ArrayList<AListener>(); public void add(AListener listener) { listeners.add(listener); } @Override public void aChanged() { for (AListener listener : listeners) { listener.aChanged(); } } @Override public void bChanged() { for (AListener listener : listeners) { listener.bChanged(); } } }
AEventSource
will look something like this: class AEventSource { private AListener listener; AEventSource(AListener listener) { this.listener = listener; } public void f() { ... listener.aChanged(); ... listener.bChanged(); ... } }
ACompositeListener compositeListener1 = new ACompositeListener(); compositeListener1.add(new AConcreteListener1()); ACompositeListener compositeListener2 = new ACompositeListener(); compositeListener1.add(compositeListener2); compositeListener2.add(new AConcreteListener2()); compositeListener2.add(new AConcreteListener3()); AEventSource source = new AEventSource(compositeListener1);
A
to the class names everywhere), that you also need to know how to work with listeners who have one more method besides the AListener
interface methods, let's call it dChanged()
That is, there is a second interface for DListener
listeners: interface DListener extends AListener { public void dChanged(); }
DListener
interface extends the AListener
interface. But really, as soon as we write class DCompositeListener extends ACompositeListener { ... }
add(AListener listener)
method, since it allows you to add an object that can implement the AListener
interface, but not the DListener
. If we want to override this method as follows: @Override public void add(DListener listener) { ... }
method does not override or implement a method from a supertype
(thanks to the @Override
annotations). @Override public void add(final AListener listener) { if (listener instanceof DListener) { listeners.add(listener); } else { throw new IllegalArgumentException(); } }
void g(ACompositeListener composite) { composite.add(new AConcreteListener1()); }
composite
. But here we call the g()
function like this: g(new DCompositeListener());
AListener
interface, and to all listeners who did not have the dChanged()
method add an empty or throwing an exception to the implementation of the dChanged()
method, we discard it, because this would violate the interface separation principle (also known as ISP).ACompositeListener
class ACompositeListener
all the code responsible for adding, deleting and iterating listeners will be moved to the new ListenerList
class. And since we have listeners of different types, it will be a parameterized class. class ListenerList<T> implements Iterable<T> { protected List<T> listeners = new ArrayList<T>(); public void add(T listener) { listeners.add(listener); } public void remove(T listener) { listeners.remove(listener); } public Iterator<T> iterator() { return listeners.iterator(); } }
class ATranslator<T extends AListener> { protected ListenerList<T> listeners; public ATranslator(ListenerList<T> listeners) { this.listeners = listeners; } public void aChanged() { for (AListener listener : listeners) { listener.aChanged(); } } public void bChanged() { for (AListener listener : listeners) { listener.bChanged(); } } } class DTranslator<T extends DListener> extends ATranslator<T> { public DTranslator(ListenerList<T> listeners) { super(listeners); } public void dChanged() { for (DListener listener : listeners) { listener.dChanged(); } } }
<T extends XListener>
), so that only those listeners that implement the corresponding interface can be added to the list. Attempting to add a listener that does not implement the desired interface will result in a compilation error. What we sought.ACompositeListener
and ACompositeListener
written simply: class ACompositeListener extends ListenerList<AListener> implements AListener { private ATranslator<AListener> aTranslator = new ATranslator<AListener>(this); @Override public void aChanged() { aTranslator.aChanged(); } @Override public void bChanged() { aTranslator.bChanged(); } }
class DCompositeListener extends ListenerList<DListener> implements DListener { private DTranslator<DListener> dTranslator = new DTranslator<DListener>(this); @Override public void aChanged() { dTranslator.aChanged(); } @Override public void bChanged() { dTranslator.bChanged(); } @Override public void dChanged() { dTranslator.dChanged(); } }
Source: https://habr.com/ru/post/128827/
All Articles