【问题标题】:Loosely coupled observer pattern松散耦合的观察者模式
【发布时间】:2013-05-06 02:03:34
【问题描述】:

我意识到这个话题已经被掩盖了,但我仍然在挣扎,并且可以通过一些具体的帮助来做。

我的目标是在某种可观察对象(比如说狗)和某种侦听器(比如说所有者)之间实现一个简单的观察者模式。

最终,在 MVC 范例中,所有者将是一个“视图”,而狗是一个“模型”。我使用 Dog 和 Owner 只是为了尝试简化这里的事情。

我尝试使用 Java 内置的 Observer/Observable 类,但已经意识到 Observers update() 方法有多糟糕 - 它接收一个 POJO,我需要在 update() 方法中检查/强制转换该 POJO。我更希望我的 'update()' 方法收到它可以期待的东西。

所以,我遵循了一些教程,包括这个以 Dog/Owner 为例的教程:

http://www.youtube.com/watch?v=qw0zZAte66A

这里已经向我展示了如何滚动我自己的 Observer/Observed 类。在伪代码中,我现在拥有的是:

Dog/Model {

    List listeners;

    public fireDogHungryEvent() {

        foreach listener {
            listener.onDogHungry(this);
        }
    }

    public fireDogPeeEvent() {

        foreach listener {
            listener.onDogNeedsToPee(this);
        }
    }

    public getHungerLevel() { return hungerLevel; }
    public getBladderCapacity() { return bladderCapacity; }
}

Owner/View {

    public onDogHungry(model) {
        println(model.getHungerLevel());
    }

    public onDogNeedsToPee(model) {
        println(model.getBladderCapacity());
    }
}

所以现在我有处理特定事件的方法,而不是一个 update() 方法。杰出的。我目前对 Owner/view 类很满意。它知道 Dog/model 的方法,这很好(我认为)。

我不喜欢的是 Dog/model 引用了 Owner/view 中的方法。我已经阅读了无数次并且完全同意模型不应该与其视图紧密耦合,例如它似乎在上面。我也不热衷于 Dog/model 中的所有“火”方法做几乎相同的事情;循环遍历它的所有监听器,并在每个监听器上调用不同的方法。

是否可以进一步解耦这种关系并且不让 Dog/model 调用特定的方法?如果是这样,将狗/模型数据接收到所有者/视图并适当使用它的最佳方法是什么?

谢谢

【问题讨论】:

  • Dog 的哪些方法引用了视图?我没有看到。
  • 最好的方法是使用视频教程建议的界面。侦听器是您实现中的接口吗?
  • SJuan76 - 我所说的引用是在 Dog 'fire' 方法中对 listener.onDogHungry 和 listener.onDogNeedsToPee 的调用。
  • Kevin - 监听器实际上是一个接口,正如教程所建议的那样。然而,在教程结束时,我仍然需要从可观察方法调用侦听器方法。

标签: java model-view-controller observer-pattern loose-coupling


【解决方案1】:

您应该interface 远离ObserverObservable 的具体实现知识

public enum EventType {

    HUNGRY,
    PEE;
}

public interface DogEvent {

    EventType getType();
}

public interface DogListener {

    void fireEvent(DogEvent event);
}

public class Dog {

    private final Set<DogListener> listeners = new CopyOnWriteArraySet<DogListener>();

    public void register(final DogListener dogListener) {
        listeners.add(dogListener);
    }

    public void unregister(final DogListener dogListener) {
        listeners.remove(dogListener);
    }

    public void firePeeEvent() {
        fireEvent(new DogEvent() {
            @Override
            public EventType getType() {
                return EventType.PEE;
            }
        });
    }

    public void fireHungryEvent() {
        fireEvent(new DogEvent() {
            @Override
            public EventType getType() {
                return EventType.HUNGRY;
            }
        });
    }

    private void fireEvent(final DogEvent dogEvent) {
        for (final DogListener listener : listeners) {
            listener.fireEvent(dogEvent);
        }
    }
}

public class Owner implements DogListener {

    @Override
    public void fireEvent(DogEvent event) {
        switch (event.getType()) {
            case PEE:
                System.out.println("Someone take the dog out");
                break;
            case HUNGRY:
                System.out.println("I can't believe the dog is hungry _again_!");
                break;
        }
    }
}

在这种情况下,Dog 不知道Owner 的实现,它只知道OwnerDogListener

另一方面,Owner 不知道Dog,它只知道它有一个传入的DogEvent

【讨论】:

  • 我喜欢这个答案的清晰性,谢谢鲍里斯。我特别不喜欢那里的 case 语句,它需要检查发生了什么样的事件,但我想它不会只是通过魔法发生:DI 将与几个可观察对象/侦听器一起尝试一下,看看我怎么过。如果我能用它解决一些合理有效的问题,我会在今天的某个时候接受这个答案!
  • 啊,我刚刚意识到这是我之前尝试过的更好的实现;除了我使用反射来调用基于字符串名称而不是事件对象的方法。我认为这会奏效:D
  • enum 只是一个建议 - 您可以使用访问者模式来充分利用多态性。
【解决方案2】:

出于 MVC 目的(尤其是 MVP),我收集了有关事件库编程的良好经验。因此,您需要一个供 Dog 和 Owner 使用的 EventBus。所有者将订阅某些事件类,例如 HungerLevelIncrease,而狗将触发此类事件。对于您的 Dog Owner 示例,这会有点奇怪,因为 Owner 不认识他的狗,但对于 GUI 编程,这是一个很好且简单的解决方案,尤其是在其他控制器之间解耦。

您可以轻松地自己创建总线,也可以使用来自google guava 的总线。

这是我所知道的创建真正松散耦合的最佳方法。

【讨论】:

  • 这最初对我来说似乎是个好主意,但经过一些阅读后,其他人注意到这种模式基本上使每个可观察/听众都需要了解一辆公共汽车。添加新的事件类型也意味着更改该总线。请参阅:stackoverflow.com/questions/3987391/…。也许这并不像看起来那么糟糕,idk。
  • @user2373021 你看过像mbassador 这样的东西吗?它是基于注释的,所以你只需要注释你的听众,它就会从方法参数中推断出他们想听什么。它还可以异步发送消息...
  • @user2373021 确实,您需要将总线添加到所有需要他的类中。然而,大多数时候,无论如何您都会将依赖注入容器添加到可以为您注入 eventBus 的更大应用程序中。但是添加新的事件类型根本不应该影响您的总线。如果您只有几个类,我也会选择简单的侦听器解决方案,但是如果您获得更多对话并且有多个对话相互对话并与其他类(例如服务器)对话。我会尝试一下 EventBus 机制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-05
  • 2011-01-20
  • 1970-01-01
  • 2011-01-01
  • 2020-10-26
  • 2011-02-22
  • 1970-01-01
相关资源
最近更新 更多