最佳实践:Class实现了几个观察者

时间:2015-08-06 15:06:24

标签: java

有几位听众L1, L2, ...都很相似:

interface L1 { void onL1(); }
interface L2 { void onL2(); }
...

所有这些都在课程EventMaker中使用,如下所示:

class EventMaker {
  ...
  List<L1> l1s = new LinkedList<>();
  List<L2> l2s = new LinkedList<>();
  ...
  void addL1(L1 l1) {...; l1s.add(l1); ...;}
  void removeL1(L1 l1) {...; l1s.remove(l1); ...;}
  void callL1() {...}

  void addL2(L2 l2) {l2s.add(l2);}
  void removeL2(L2 l2) {l2s.remove(l2);}
  void callL2() {...}
  ...

}

我该怎么做才能减少重复代码?我试图使用泛型方法,但它在很多方面都没有解决方案。

// ********************************************* ***************************

我的解决方案:

abstract class Caller<Listener> {

    List<Listener> listeners;

    public Caller() {
        listeners = new LinkedList<>();
    }

    public void add(Listener listener) {
        listeners.add(listener);
    }

    public void remove(Listener listener) {
        listeners.remove(listener);
    }

    public void call() {
        for (Listener listener : listeners) {
            onCall(listener);
        }
    }

    public abstract void onCall(Listener listener);

}

清空侦听器界面:

interface Listener {    
}

For Every-Listener:

class L1Caller extends Caller<L1> {

    @Override
    public void onCall(L1 listener) {
        listener.l1(); // unique name
    }

}

最后,我为每个listern类型添加一个调用者到我的主类。

...
L1Caller l1Caller;
L2Caller l2Caller;
...

并使用它:

l1Caller.add(...);
l2Caller.call();

3 个答案:

答案 0 :(得分:0)

我不知道这是否是最佳做法,但这当然是可能的。

public class Caller {

    public static final Token<L1> L1 = new Token<>();
    public static final Token<L2> L2 = new Token<>();

    public static class Token<T> {
        // Making the constructor private means that no other Tokens can be made.
        private Token() {}
    }

    private final Map<Token<?>, List<Object>> lists;

    public Caller() {
        lists = new HashMap<>();
        Token<?>[] tokens = {L1, L2};
        for (Token<?> token : tokens)
            lists.put(token, new LinkedList<>());
    }

    public <T> void add(Token<T> token, T t) {
        lists.get(token).add(t);
    }

    public <T> void remove(Token<T> token, T t) {
        lists.get(token).remove(t);
    }
}

尽管使用了通配符和Object,但它完全是类型安全且易于使用:

caller.add(Caller.L1, () -> {
    // do stuff
});

答案 1 :(得分:0)

将现有的ChangeListener与其感兴趣的源所在的ChangeEvent一起使用。改变使用泛型类型。

然后创建一个类来保存一个监听器列表,比如说

public class ChangeManager {
    private final List listeners = ...
    public void addChangeListener(ChangeListener listener) { ... }
    public void notifyChange(ChangeEvent event) { ... }
}

这可以以垃圾收集和并发安全的方式处理侦听器。

现在你的班级代表了几个ChangeManagers。

private final ChangeManager l1 = new ChangeManager();
private final ChangeManager l2 = new ChangeManager();

private final ChangeEvent L1_EVENT = new ChangeEvent(this);

...
    l1.addChangeListener(changeEvent -> {
        ...
    });

    l1.notifyChange(L1_EVENT);

答案 2 :(得分:0)

您应该将单独的add*方法保留在API中,因为它们是有意义的,并且可以提供更清晰的API。改变API的一个选择是拥有一个add(type, listener)并通过它路由所有调用。但是,您必须公开该类型,并且客户端必须协调将正确的侦听器添加到正确的类型。

不改变API但在内部集中管理侦听器的一种方法是这样的模式:

class EventMaker {
  ...
  static String TYPE_L1 = "L1";
  static String TYPE_L2 = "L2";
  Map<String, List<Object>> listeners;
  ...
  void addInternal(String t, Object listener) {
     List<Object> list = listeners.get(t);
     if (list == null) { list = new CopyOnWriteArrayList<>(); listeners.put(t, list); }
     list.add(listener);
  }
  void removeInternal(String t, Object listener) {
     List<Object> list = listeners.get(t);
     if (list != null) { list.remove(listener; }
     // optional
     if (list != null && list.isEmpty) listeners.remove(t);
  }
  <ListenerType> List<ListenerType> getInternal(String t, Class<ListenerType> cls) {
     List<Object> list = listeners.get(t);
     if (list == null) return Collections.emptyList();
     return (List<ListenerType>)list;
  }
  void removeInternal(String t, Object listener) {...}
  void addL1(L1 l1) { addInternal(TYPE_L1, l1);}
  void removeL1(L1 l1) { removeInternal(TYPE_L1, l1);}
  void callL1() {
     List<L1> list = getInternal(TYPE_L1, L1.class);
     for (L1 ears : list) ears.onL1();
  }

  void addL2(L2 l2) {addInternal(TYPE_L2, l2);}
  void removeL2(L2 l2) {removeInternal(TYPE_L2, l2);}
  void callL2() {...}
  ...
}

在地图中忽略该类型,因为您要保护所有内容,并在访问其中一个侦听器列表时安全地转换。虽然通知你的听众没有共同的基础,但仍然需要这种类型。并发安全CopyOnWriteArrayList也有一些性能影响,值得研究一下监听器列表。

我还建议返回句柄,而不是要求removeListener调用。这允许调用者在清理时不必查找事件源,但是具有执行工作的OO句柄。 java.lang.AutoCloseable(如果使用Java 7或更高版本,则为java.lang.Runnable)是一个不错的选择:

AutoCloseable addInternal(String t, final Object listener) {
     List<Object> list = listeners.get(t);
     if (list == null) { list = new ArrayList<>(); listeners.put(t, list); }
     list.add(listener);
     final List<Object> flist = list; // for final reference below
     return new AutoCloseable() {
          public void close() { flist.remove(listener); }
     };
}

现在调用者只需在返回的句柄上调用.close()即可取消注册。

如果您使用的是Java 8,则更简单:

AutoCloseable addInternal(String t, final Object listener) {
     List<Object> list = listeners.get(t);
     if (list == null) { list = new ArrayList<>(); listeners.put(t, list); }
     list.add(listener);
     return () -> list.remove(listener);
}

您还应该考虑正确同步,以确保线程安全,如果它是一个问题,使您的字段为私有和最终适当,以及我没有在这些示例中解决的其他良好做法清理。