观察者模式

观察者模式也称为订阅者模式,实际上我觉得订阅者更容易理解。这种设计模式在生活中很常见。比如订阅期刊杂志、定牛奶等等。我们使用的软件中也很常见。比如说微博,你关注了某位明星,其实你就是他的观察者。每当你关注的明星发了新的动态,你就会接收到通知。观察者模式基于发布订阅的方式。订阅者订阅目标对象,目标对象维护订阅者的集合。一旦目标对象状态变化,需要通知所有订阅者,从而触发订阅者的某个行为。

1. 实现观察者模式

实现观察者模式,在目标对象中需要维护所有他的观察者引用。观察者可以观察多个不同目标对象的,所以需要让观察者知道是哪个目标对象发送的通知。下面我们通过一个简单的例子来看看如何实现观察者模式。

这个例子叫老师点名了。上大学时候,经常有同学旷课在宿舍打游戏,并且嘱咐去上课的同学,老师要是点名了给我打电话。还好宿舍离教学楼近,接到通知的同学赶紧跑去教室也能赶上。有的胆子大点的同学,接到通知后也不去上课,而是找个关系好的同学帮忙喊声到。

去上课的同学是通知者(目标对象),他持有所有需要他通知老师点名的同学(观察者)的引用,才能在老师点名的时候通知到每个人。程序中我们一般用容器存储观察者。当通知的时候循环调用所有观察者暴露出的更新方法。

“老师点名了” 目标对象代码如下:

public class TeacherRollCallSubject {
    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer){
        observers.add(observer);
    }

    public void removeObserver(Observer observer){
        observers.remove(observer);
    }

    public void notifyObservers (){
        observers.forEach(Observer::update);
    }
}

观察者只需要实现一个方法,供通知者做通知的时候调用。我们先定义观察者的接口。

public interface Observer {
    void update();
}

我们定义第一类观察者的实现,他接到通知后,会马上去教室。


public class GotoClassObserver implements Observer {
    @Override
    public void update() {
        System.out.println("老师点名了!");
        System.out.println("我要马上赶到教室去!");
    }
}

第二类观察者,接到通知后,会通知自己的好朋友帮自己答到。

public class AskForHelpObserver implements Observer {
    @Override
    public void update() {
        System.out.println("老师点名了!");
        System.out.println("赶紧给XX发信息,让他替我答到!");
    }
}

客户端代码中,分别声明两个不同的观察者,然后让这两个观察者都观察老师点名了目标对象。最后出发目标对象的通知方法。客户端代码如:

public class Client {
    public static void main(String[] args) {
        Observer studentOne = new GotoClassObserver();
        Observer studentTwo = new AskForHelpObserver();

        TeacherRollCallSubject subject = new TeacherRollCallSubject();
        subject.addObserver(studentOne);
        subject.addObserver(studentTwo);

        subject.notifyObservers();
    }
}

运行后输出如下:

老师点名了!
我要马上赶到教室去!
老师点名了!
赶紧给XX发信息,让他替我答到!

可以看到每个观察者都接到了通知,并且按照自己实现的响应方式作出不同的逻辑处理。第一个同学会赶到教室。第二个同学则是给好朋友发信息,让其替他答到。

2. 观察者模式优缺点

2.1 优点

1、目标对象状态的变化,不需要观察者真的一直观察。当存在大量观察者时,如果所有的观察者都去轮询状态,那么系统资源的消耗极大。而观察者模式避免了这种情况;

2、观察者模式支持广播,状态变化时,目标对象的所有观察者都会得到通知;

3、符合开闭原则,目标对象依赖的是观察者的接口,可以很方便的对观察者进行扩展,而不需要修改已有观察者。反过来观察者也是依赖的目标接口。

2.2 缺点

1、观察者模式中,观察者接口限定了方法签名。有一定的局限性。

3. 观察者模式适用场景

1、抽象模型可以分为两个部分,一部分行为取决于另外一部分状态的变化。并且你想让这两部分各自独立。每部分都可以独自使用和复用;
2、一个对象的变化,需要通知其他对象,并且有多少对象需要通知并不清楚
3、你想让通知与被通知双方松耦合。

4. 小结

观察者模式最大的优点就是把目标和观察者解耦,观察者根据目标对象状态的变化作出响应。而目标者可以把自己状态变化广播给所有注册的观察者。实际使用中有推/拉两种模型。

  • 在推模型中,目标对象会把状态改变相关的所有信息推送出去,信息的量有可能会很大。
  • 在拉模型中,目标对象只推送出最核心的信息,比如变化的数据 id。

观察者收到消息后再决定如何处理,比如查询与变化相关的自己感兴趣的数据。推模型,目标对象需要知道所有观察者对数据的需求。而拉模型效率会比较差,观察者收到消息后,还需要自己再去获取改变的内容。关于推拉模型总结如下:
图片描述