Java-通用ChangeListener

Java-通用ChangeListener,java,Java,情景: 我有一个容器对象,它包含一个混合的包,所有对象都继承自MyContainedObject类。容器类的使用者不能直接访问所包含的对象,但我希望知道它们何时更改 设计决策: 侦听特定类类型上的ChangeEvents的最佳方法是什么?我最初的想法是使用泛型。比如说, private TreeMap<Class, ChangeListener> listeners; public <T extends MyContainedObject> addChangeListe

情景:

我有一个容器对象,它包含一个混合的包,所有对象都继承自MyContainedObject类。容器类的使用者不能直接访问所包含的对象,但我希望知道它们何时更改

设计决策:

侦听特定类类型上的ChangeEvents的最佳方法是什么?我最初的想法是使用泛型。比如说,

private TreeMap<Class, ChangeListener> listeners;

public <T extends MyContainedObject> addChangeListenerForObjectsOfType(Class<T> className, ChangeListener listener)
{
   listeners.put(className, listener);
}
私有树映射侦听器;
public addChangeListenerForObjectSoftType(类类名,ChangeListener侦听器)
{
put(className,listener);
}
当检测到更改时,容器类将遍历列表并仅通知为该类类型注册的侦听器

其他建议


谢谢。

您也可以简单地让您的容器代理调用所包含的相关对象的
addChangeListener
。这将允许他们维护其侦听器列表并根据需要触发调用,而不会增加侦听器继承权中另一个级别的复杂性。

这种特定于类型的通知方法的一个问题是,当特定接口发生更改时,客户端可能会注册感兴趣的侦听器;e、 g.
addChangeListenerForObjectSoftType(Iterable.class)
。这意味着您的通知算法无法在侦听器映射中进行简单的查找,除非您明确禁止针对接口注册侦听器,否则需要更复杂(效率更低)

我可能会采取不同的方法,而不是让您的实现完全通用。例如,如果您可以确定您感兴趣的少数顶级子类,那么您可以提供一个更明确的侦听器接口:

public interface Listener {
    void classAChanged(ChangeEvent e);
    void classBChanged(ChangeEvent e);
    void classCChanged(ChangeEvent e);
}
我个人更喜欢这种方式,因为它对于实现接口的程序员来说更为明确,使代码更具可读性。显然,如果您在地图中潜在地存储了数百个不同的子类,那么这可能是不合适的


您可以更进一步,提供一个通用的
ChangeEvent
实现,以避免在侦听器回调方法中进行向下转换。

根据您想要编写的侦听器类型,您建议的设计有一些限制可能会导致问题

  • 侦听器一次只能注册一个原始类型。如果一个监听器对许多类型感兴趣,或者它想使用其他标准来决定它感兴趣的对象,那么它必须多次注册。而且,侦听器可能不知道存在哪些类型
  • 目前还不清楚如何处理注册MyContainedObject的子类,并且该子类有子类的情况
  • 容器不知道的MyContainedObject实例可能会发生更改
  • 我建议为容器设置一个侦听器,并让每个MyContainedObject实例也支持侦听器:

    public interface ContainerListener {
      void itemAdded(MyContainedObject item);
      void itemRemoved(MyContainedObject item);
    }
    
    public interface MyContainedObjectListener {
      void itemChanged(MyContainedObject item);
    }
    
    正如Adamski所建议的,如果MyContainedObject的子类数量有限,那么您可以使方法更加具体:

    public interface ContainerListener {
      void circleAdded(Circle item);
      void squareAdded(Square item);
      void shapeRemoved(Shape item);
    }
    
    或者,您可以在MyContainedObjectListener中创建特定方法

    如果你决定一个类型的注册满足你的需求,那么考虑让侦听器泛型:

    public <T extends MyContainedObject> addChangeListenerFor(Class<T> className, ChangeListener<? super T> listener)
    

    public addChangeListenerFor(Class className,ChangeListener我假设树映射上的键类型是一个类,而不是MyContainedObject

    如果您确实需要侦听特定类类型上的ChangeEvents,并且希望即使在设置侦听器之后也能够向集合中添加元素,这似乎是非常合理的。您可能希望为同一类型支持多个侦听器,因此您应该使用Multimap类(有一些)或使用集合(可能是一个IdentityHashSet)用于映射中的值

    您可能还希望向ChangeListener添加一个类型参数,以便侦听器可以将触发事件的对象转换为适当的类型

    interface ChangeListener<T> {
        void changed(T obj, /* whatever */);
    }
    
    接口更改侦听器{
    无效变更(T obj,/*无论什么*/);
    }
    
    您必须在容器内部执行未经检查的强制转换才能使其工作,但只要您的侦听器添加方法做正确的事情,它应该是安全的。例如:

    public <T extends MyContainedObject> addChangeListener(Class<T> klass,
                                                           ChangeListener<? super T> listener) {
        ...
    }    
    
    private <T extends MyContainedObject> Set<ChangeListener<? super T>> getChangeListeners(T obj) {
        Set<ChangeListener<? super T>> result = new IdentityHashSet<ChangeListener<? super T>>();
        for (Map.Entry<Class<? extends MyContainedObject>, Set<ChangeListener<?>>> entry : listeners.entrySet()) {
            if (entry.getKey().isInstance(obj)) {
                // safe because signature of addChangeListener guarantees type match
                @SuppressWarnings("unchecked")
                Set<ChangeListener<? super T>> listeners =
                    (Set<ChangeListener<? super T>>) entry.getValue();
                result.addAll(listeners);
            }
        }
        return result;
    }
    
    publicAddChangeListener(类klass,
    ChangeListener>>条目:listeners.entrySet()){
    if(entry.getKey().isInstance(obj)){
    //安全,因为addChangeListener的签名保证类型匹配
    @抑制警告(“未选中”)
    
    SetThis是一个很好的建议,但只有在添加侦听器后从不修改包含的对象集,或者如果您对包含的对象集和侦听器随时间而变化的对象集没有意见,它才有效。谢谢。这正是我的想法。另外,您是对的,我的意思是类而不是类MyContainedObject。