设计模式之 观察者模式

观察者模式(Observer Pattern),又称为发布/订阅模式。

观察者模式定义了对象之间的一对多的关系,当一个对象发生了变化,其他对象能够得到通知,并做相应处理。

举例:比如我们现在的微信朋友圈,我发布一个朋友圈,那么我的好友都能收到这个新的朋友圈信息,这就就是一个发布/订阅的应用场景。我们试想一下,这个场景应该怎么实现呢?

有两种方式,一种是我的账户每隔一定时间(比如5秒)取一下我当前所有好友的朋友圈状态,看看有没有人发布更新,如果有就拉取。这种方式的一个问题就是,如果用户量巨大,那么每个用户相当于都有一个定时任务,12亿人就是12亿个定时任务。这不是几十台上百台服务器能搞定的。

有的同学会说,可以合并一些用户的定时任务,比如1万个用户作为一个定时任务,在某个时间点启动,检查是否有新的朋友圈状态,这种确实可以,但这也仅仅是减少了定时任务的数量,还是有个问题没有解决,那就是这种定时任务内部还有大量资源浪费,因为这1万个用户里面,还是可能有很多用户压根没有发朋友圈,所以这种方案,在这种场景下并不适用。

那么方案二就是发布订阅了,我所有的好友相当于订阅了我的朋友圈,当我发了一个朋友圈之后,我发布朋友圈这个动作就相当于出发了一个通知事件,这是程序将我的所有好友取出,依次通知每个好友,这样是基于一个事件驱动的,有事件才会触发通知,整体上实现就简单了很多。

我们来看代码:

1、新建抽象观察者

package com.itzhimei.study.design.observer;

/**
 * www.itzhimei.com
 * 观察者抽象
 */
public interface IObserver {

    /**
     * 定义通知观察者的方法
     * @param message
     */
    void update(String message);
}

2、定义具体观察者

package com.itzhimei.study.design.observer;

/**
 * www.itzhimei.com
 * 订阅者实现
 */
public class ObserverA implements IObserver {
    @Override
    public void update(String message) {
        System.out.println("ObserverA阅读文章内容:"+message);
    }
}
package com.itzhimei.study.design.observer;

/**
 * www.itzhimei.com
 * 订阅者实现
 */
public class ObserverB implements IObserver {
    @Override
    public void update(String message) {
        System.out.println("ObserverB阅读文章内容:"+message);
    }
}
package com.itzhimei.study.design.observer;

/**
 * www.itzhimei.com
 * 订阅者实现
 */
public class ObserverC implements IObserver {
    @Override
    public void update(String message) {
        System.out.println("ObserverC阅读文章内容:"+message);
    }
}

3、定义抽象发布者

package com.itzhimei.study.design.observer;

/**
 * www.itzhimei.com
 * 发布者抽象
 */
public interface ISubject {

    /**
     * 添加订阅对象
     * @param observer
     */
    void add(IObserver observer);

    /**
     * 发布朋友圈
     * @param article
     */
    void publishArticles(String article);

    /**
     * 通知方法
     * @param article
     */
    void notifyAllObserver(String article);
}

4、发布者实现

package com.itzhimei.study.design.observer;

import java.util.ArrayList;
import java.util.List;

/**
 * www.itzhimei.com
 * 发布者实现
 */
public class Subject implements ISubject {

    List<IObserver> observerList = new ArrayList<>();

    @Override
    public synchronized void add(IObserver observer) {
        observerList.add(observer);
    }

    @Override
    public void publishArticles(String article) {
        this.notifyAllObserver(article);
    }

    @Override
    public void notifyAllObserver(String article) {
        for(IObserver ob: observerList) {
            ob.update(article);
        }
    }
}

5、测试

package com.itzhimei.study.design.observer;

/**
 * www.itzhimei.com
 * 观察者测试
 */
public class Client {

    public static void main(String[] args) {
        IObserver oa = new ObserverA();
        IObserver ob = new ObserverB();
        IObserver oc = new ObserverC();

        ISubject subject = new Subject();
        subject.add(oa);
        subject.add(ob);
        subject.add(oc);

        subject.publishArticles("今天心情美美哒!");
    }
}

输出结果:

ObserverA阅读文章内容:今天心情美美哒!
ObserverB阅读文章内容:今天心情美美哒!
ObserverC阅读文章内容:今天心情美美哒!

发布/订阅模式UML关系图:

发布/订阅模式UML关系图

发布/订阅涉及角色:

1、抽象主题角色:定义发布者行为

2、具体主题角色:实现消息、状态通知、持有订阅者集合

3、抽象订阅角色:定义订阅者行为

4、具体订阅角色:实现订阅者行为

总结:

发布/订阅模式,从上例可以看出,就是数据是推数据还是拉数据,我们举的反面例子是拉数据,用定时任务不停的扫描,拉去最新数据;我们用的发布/订阅模式,则是通过事件驱动的推数据,简化了整体代码逻辑,让系统更加易于维护。