七、观察者模式——关注我,分享旅途最浪漫的瞬间! #和设计模式一起旅行#

最浪漫的事就是在路上,身边有你陪伴!

故事背景

在路上,和设计模式MM,做过飞机,也骑过单车,从大中国到了东京,东京真的很热(知道那啥啥为什么叫东京热了吧)。这一路不断的分享我们走过的路和看过的风景,旅行就是如此的美好,看别人看的厌烦过的风景,走别人走了无数遍的路!只有自己走过了,才算是经历!(就如我的这一场设计模式的学习总计之路,不管有多少人分享过,我依然要自己在过一遍,其中的意义和感受只有经历过才能体会)。

旅行有段时间了,通过我和设计MM在路上的分享,我们还是收获了一点点粉丝,粉丝只要关注了我们,就不会错过一些精彩有趣的事情。 比如我们看见的奇特风景,我们吃过的独特美食,以及两个人一起旅行的感想和体会!

设计模式MM告诉我有一个模式可以帮助想要了解我的对象知道我的现状,只要“关注”了我当我又状态更新的时候,关注 我的对象就可以收到消息。这个里面可以延伸出本篇的主角,那就是观察者模式!

先写一个简单的示范:

//我和性感美丽的MM一起旅行
其中 杨过和小龙女 想知道我们旅行的一些事情

class MeAndMM{

    private YangGuo yangGuo;
    private XiaoLongNv xiaoLongNv;
    private String story; //故事
    private String name;

    public MeAndMM(YangGuo yangGuo,XiaoLongNv xiaoLongNv){
      this.yangGuo = yangGuo;
      this.xiaoLongNv = xiaoLongNv;   
    }

    //get set

    public void updateStory(){

        String story = cteateStory();
        yangGuo.notifyMsg(this.name,story);
        xiaoLongNv.notifyMsg(this.name,story);
    }

    public String cteateStory(){

        return this.story;

    }

} 

class YangGuo{

    public void notifyMsg(String name,String story){
        System.out.println("杨过,你好:" +  name + "有新的故事了,请查看");
        System.out.println(story);
    }

}

class XiaoLongNv{
      public void notifyMsg(String name,String story){
       System.out.println("小龙女,你好:" + name + "有新的故事了,请查看");
       System.out.println(story);
    }

}

public class Client{

    public static void main(String args[]){
        YangGuo y = new YangGuo();
        XiaoLongNv x = new XiaoLongNv();
        MeAndMM mam = new MeAndMM(y,x);
        mam.setName("和设计模式一起 去旅行");

        mam.setStory("泰山看到最美的日出。。。");
        mam.updateStory();

        System.out.println("============================");

        mam.setStory("富士山看到美丽的雪山。。。。");
        mam.updateStory();
    }
}


杨过,你好:和设计模式一起 去旅行有新的故事了,请查看
泰山看到最美的日出。。。
小龙女,你好:和设计模式一起 去旅行有新的故事了,请查看
泰山看到最美的日出。。。
============================
杨过,你好:和设计模式一起 去旅行有新的故事了,请查看
富士山看到美丽的雪山。。。。
小龙女,你好:和设计模式一起 去旅行有新的故事了,请查看
富士山看到美丽的雪山。。。。

分析上面的这一段代码,根据前面的讲过的设计模式原则。
简单说三点问题:

  1. 如果想增加新的观察对象,或许现有得到观察对象不想在继续被通知的话,要修改MeAndMM类,违反了“开闭原则”!
  2. 观察的对象 如YangGuo 和 XiaoLongNv 我们是针对实现编程,不是接口编程。
  3. MeAndMM职责过重,并且无法在运行时候动态的添加和删除观察对象。

故事主角 - 观察者模式

认识本篇故事的主角,观察者模式,在举个例子,方便理解,在传统的报社中,报纸的发布和订阅是怎么回事:
1. 报社出版报纸
2. 想要报纸的人向报社订阅报纸,只要报社发了新的报纸,只要你订阅了,那么你就会收到新出版的报纸
3. 当你有一天不想看报纸的时候,取消订阅,那么就不会再送新报纸给你了
4. 只要报社还在运营中,就会有其他人向他们进行订阅或者取消报纸

如果你了解上面报纸的订阅是怎么回事,其实就知道观察者模式是怎么回事了。

出版者(主题) + 订阅者(观察者) = 观察者模式

观察者模式: 定义了对象之间的一对多依赖,这样一来当一个对象改变状态时,它的所有依赖都会收到通知并自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式等。

观察者模式示意图

简单类图如下:

观察者模式类图

  • Subject(主题):主题是被观察的对象,在主题中定义一个观察者的集合,一个主题可以接受任意数量的观察者来观察,主题中提供一系列方法增加和删除观察者对象,并使用notify()方法进行通知观察者。主题可以是接口也可以抽象类!
  • ConcreteSubjcet(具体主题):具体主题是主题的子类,当它的状态发生改变时,向各个观察者发出通知。
  • Observer(观察者):观察者将对观察的主题改变时候做出反应,观察者一般为接口。
  • ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标主题的一个引用,并实现抽象观察者的update方法。

当两个对象之间松耦合,它们依然可以进行交互,但是不太清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。改变主题或观察者其他一方,并不会影响另一方

下面用示意代码进行描述:

abstract class Subject{

    //定义个存储观察者的集合
    public List<Observer> observers = new ArrayList<Observer>();

    //添加观察者
    public void registerObserver(Observer observer){
        observers.add(observer);
    }
    //移除观察者
    public void removeObserver(Observer observer{
        observers.remove(observer);
    }
    //通知观察者
    pbulic abstract void notify();
}


class ConcreteSubject extends Subject{

    //实现通知的方法
    public void notify(){
        for(Observer observer : obervsers){
            oberver.update();
        }
    }
}

interface Observer{
    void update();
}

class ConcreteObserver implements Observer{
    pbulic void update(){
        //具体业务代码
    }
}

在有些更加复杂的情况下,具体观察者类ConcreteObserver的update()方法在执行时需要使用到具体目标类ConcreteSubject中的状态(属性),因此在ConcreteObserver与ConcreteSubject之间有时候还存在关联或依赖关系,在ConcreteObserver中定义一个ConcreteSubject实例,通过该实例获取存储在ConcreteSubject中的状态。

下面就开始正式的修炼了!

武功修炼

通过上面对观察者模式的学习,将之前故事背景的简单示例进行修改!

class abstract Subject{
    //定义个存储观察者的集合
    public List<Observer> observers = new ArrayList<Observer>();

    //添加观察者
    public void registerObserver(Observer observer){
        observers.add(observer);
    }
    //移除观察者
    public void removeObserver(Observer observer){
        observers.remove(observer);
    }
    //通知观察者
    public abstract void notifyObserver();
}



class MeAndMMSubject extends Subject{
    private String story; //故事
    private String name;

    //get set

   //实现通知的方法
    public void notifyObserver(){
        for(Observer observer : obervsers){
            oberver.updateMsg(name,story);
        }
    }

     public void cteateStory(String story){
         this.story = story;
        notifyObserver();

    }
}

interface Observer{
    void updateMsg(String name,String story);
}

class YangGuoObserver implements Observer{
   @Override
    public void updateMsg(String name,String story){
        System.out.println("杨过,你好:" +  name + "有新的故事了,请查看");
        System.out.println(story);
    }
}

class XiaoLongNvbserver implements Observer{
    @Override
    public void updateMsg(String name,String story){
        System.out.println("小龙女,你好:" +  name + "有新的故事了,请查看");
        System.out.println(story);
    }
}


public class Client{

    public static void main(String args[]){
        MeAndMMSubject mamSubject = new MeAndMMSubject("和设计模式一起去旅行");

        Observer obserber;
        obserber = new YangGuoObserver();
        //将杨过设置为观察者
        mamSubject.registerObserver(obserber);
        obserber = new XiaoLongNvbserver();
        //将小龙女设置为观察者
        mamSubject.registerObserver(obserber);

        mamSubject.cteateStory("使用观察者模式了,泰山看到最美的日出。。。");

        System.out.println("============================");

        mamSubject.cteateStory("使用观察者模式了,富士山看到美丽的雪山。。。。");

    }
}

杨过,你好:和设计模式一起去旅行有新的故事了,请查看
使用观察者模式了,泰山看到最美的日出。。。
小龙女,你好:和设计模式一起去旅行有新的故事了,请查看
使用观察者模式了,泰山看到最美的日出。。。
============================
杨过,你好:和设计模式一起去旅行有新的故事了,请查看
使用观察者模式了,富士山看到美丽的雪山。。。。
小龙女,你好:和设计模式一起去旅行有新的故事了,请查看
使用观察者模式了,富士山看到美丽的雪山。。。。

上面这种方式是推的方式获取数据!

还有拉的方式获取数据!并且由观察者自行决定观察主题或者取消观察主题!

public abstract class Subject {
    //定义个存储观察者的集合
    public List<Observer> observers = new ArrayList<Observer>();

    //添加观察者
    public void registerObserver(Observer observer){
        observers.add(observer);
    }
    //移除观察者
    public void removeObserver(Observer observer){
        observers.remove(observer);
    }
    //通知观察者
    public abstract void notifyObserver();
}

public class MeAndMMSubject extends Subject {
    private String story; //故事
    private String name;

    public MeAndMMSubject(String name){
        this.name = name;
    }
    public String getStory() {
        return story;
    }

    public void setStory(String story) {
        this.story = story;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //实现通知的方法
    @Override
    public void notifyObserver(){
        for(Observer observer : observers){
            observer.updateMsg();
        }
    }

    public void cteateStory(String story){
        this.story = story;
        notifyObserver();

    }
}

public interface Observer {
    void updateMsg();
}

public class XiaoLongNvbserver implements Observer {
    private Subject subject;
    public XiaoLongNvbserver(Subject subject){
        this.subject = subject;
        subject.registerObserver(this);
    }
    @Override
    public void updateMsg(){
        if(subject instanceof MeAndMMSubject){
            MeAndMMSubject meAndMMSubject = (MeAndMMSubject) subject;
            System.out.println("小龙女,你好:" +  meAndMMSubject.getName() + "有新的故事了,请查看");
            System.out.println(meAndMMSubject.getStory());
        }
    }

    public void unsubscribe(){
        subject.removeObserver(this);
    }

}

public class YangGuoObserver implements Observer {
    private Subject subject;
    public YangGuoObserver(Subject subject){
        this.subject = subject;
        subject.registerObserver(this);
    }
    @Override
    public void updateMsg(){
        if(subject instanceof MeAndMMSubject){
            MeAndMMSubject meAndMMSubject = (MeAndMMSubject) subject;
            System.out.println("杨过,你好:" +  meAndMMSubject.getName() + "有新的故事了,请查看");
            System.out.println(meAndMMSubject.getStory());
        }
    }

    public void unsubscribe(){
        subject.removeObserver(this);
    }

}

public class Client {
    public static void main(String args[]){
        MeAndMMSubject mamSubject = new MeAndMMSubject("和设计模式一起去旅行");
         //将杨过设置为观察者 
        YangGuoObserver ygObserber = new YangGuoObserver(mamSubject);
        //将小龙女设置为观察者
        XiaoLongNvbserver xlnObserber = new XiaoLongNvbserver(mamSubject);

        mamSubject.cteateStory("使用观察者模式了,泰山看到最美的日出。。。");

        System.out.println("============================");
        //小龙女取消订阅
        xlnObserber.unsubscribe();
        mamSubject.cteateStory("使用观察者模式了,富士山看到美丽的雪山。。。。");

    }
}

杨过,你好:和设计模式一起去旅行有新的故事了,请查看
使用观察者模式了,泰山看到最美的日出。。。
小龙女,你好:和设计模式一起去旅行有新的故事了,请查看
使用观察者模式了,泰山看到最美的日出。。。
============================
杨过,你好:和设计模式一起去旅行有新的故事了,请查看
使用观察者模式了,富士山看到美丽的雪山。。。。

其实在拉的方式中是需要有个一个状态标识的,我上面只是简单说明。

JDK观察者模式

观察者模式是JDK使用比较多的模式之一,非常有用。下面就简单看一下JDK对观察者模式的支持吧!

//JDK中的主题,被观察者
/**
Observable类充当观察目标类,在Observable中定义了一个向量Vector来存储观察者对象。

*/
public class Observable {
    private boolean changed = false;
    private Vector obs = new Vector();
    //构造方法,实例化Vector向量。
    public Observable() {
    }
    //用于注册新的观察者对象到向量中。
    public synchronized void addObserver(Observer var1) {
        if (var1 == null) {
            throw new NullPointerException();
        } else {
            if (!this.obs.contains(var1)) {
                this.obs.addElement(var1);
            }

        }
    }
    //用于删除向量中的某一个观察者对象。
    public synchronized void deleteObserver(Observer var1) {
        this.obs.removeElement(var1);
    }
    //通知方法,用于在方法内部循环调用向量中每一个观察者的update()方法。 拉的方式
    public void notifyObservers() {
        this.notifyObservers((Object)null);
    }
    //通知方法,用于在方法内部循环调用向量中每一个观察者的update()方法。 推的方式
    public void notifyObservers(Object var1) {
        Object[] var2;
        synchronized(this) {
            if (!this.changed) {
                return;
            }

            var2 = this.obs.toArray();
            this.clearChanged();
        }

        for(int var3 = var2.length - 1; var3 >= 0; --var3) {
            ((Observer)var2[var3]).update(this, var1);
        }

    }
    //用于清空向量,即删除向量中所有观察者对象。
    public synchronized void deleteObservers() {
        this.obs.removeAllElements();
    }
    //该方法被调用后会设置一个boolean类型的内部标记变量changed的值为true,表示观察目标对象的状态发生了变化。
    protected synchronized void setChanged() {
        this.changed = true;
    }
    //用于将changed变量的值设为false,表示对象状态不再发生改变或者已经通知了所有的观察者对象,
    //调用了它们的update()方法。
    protected synchronized void clearChanged() {
        this.changed = false;
    }
    //用于测试对象状态是否改变。
    public synchronized boolean hasChanged() {
        return this.changed;
    }
    //用于返回向量中观察者的数量。
    public synchronized int countObservers() {
        return this.obs.size();
    }
}
//JDK中的观察者
/*
Observer接口:在java.util.Observer接口中只声明一个方法,它充当抽象观察者
当观察目标的状态发生变化时,该方法将会被调用,在Observer的子类中将实现update()方法,
即具体观察者可以根据需要具有不同的更新行为。当调用观察目标类Observable的notifyObservers()方法时,
将执行观察者类中的update()方法
*/
public interface Observer {

    void update(Observable o, Object arg);
}

直接使用JDk的Observer接口和Observable类来作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类。下面使用JDK的观察者模式实现上面的例子。


// 使用 JDK的 Observable
public class MeAndMMSubject extends Observable {

    private String story; //故事
    private String name;

    public MeAndMMSubject(String name){
        this.name = name;
    }
    public String getStory() {
        return story;
    }

    public void setStory(String story) {
        this.story = story;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void cteateStory(String story){
        this.story = story;
        //设置状态改变,只有状态改变了才通知观察者
        setChanged();
        //调用实现通知的方法
        notifyObservers();

    }


}

//使用 JDK 的 Observer
public class XiaoLongNvbserver implements Observer {

    private  Observable observable;
    public XiaoLongNvbserver(Observable observable){
        this.observable = observable;
        observable.addObserver(this);
    }
    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof MeAndMMSubject){
            MeAndMMSubject meAndMMSubject = (MeAndMMSubject) o;
            System.out.println("小龙女,你好:" +  meAndMMSubject.getName() + "有新的故事了,请查看");
            System.out.println(meAndMMSubject.getStory());
        }

    }

    public void unsubscribe(){
        observable.deleteObserver(this);
    }
}

//使用 JDK 的 Observer
public class YangGuoObserver implements Observer{
    private Observable observable;
    public YangGuoObserver(Observable observable){
        this.observable = observable;
        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof MeAndMMSubject){
            MeAndMMSubject meAndMMSubject = (MeAndMMSubject) o;
            System.out.println("杨过,你好:" +  meAndMMSubject.getName() + "有新的故事了,请查看");
            System.out.println(meAndMMSubject.getStory());
        }
    }

    public void unsubscribe(){
        observable.deleteObserver(this);
    }

}

//client 还是之前的代码
public class Client {
    public static void main(String args[]){
        MeAndMMSubject mamSubject = new MeAndMMSubject("和设计模式一起去旅行");

        YangGuoObserver ygObserber = new YangGuoObserver(mamSubject);
        //将杨过设置为观察者
        XiaoLongNvbserver xlnObserber = new XiaoLongNvbserver(mamSubject);
        //将小龙女设置为观察者
        mamSubject.cteateStory("使用观察者模式了,泰山看到最美的日出。。。");

        System.out.println("============================");
        //小龙女取消订阅
        xlnObserber.unsubscribe();
        mamSubject.cteateStory("使用观察者模式了,富士山看到美丽的雪山。。。。");

    }
}

JDk实现的观察者模式存在的问题分析;

  • 首先被观察者Observables 是一个类,而不是一个接口,这样现在了它的使用和复用,因为java不支持多重继承
  • Observable将关键的方法保护起来,看Observable的源码发现setChange()方法被保护了,意味着只能用继承Observable来使用,不能用组合的方式将Observable实例加入到你自己的对象中。这样的设计违反“多用组合,少用继承”。

故事结局

看了这个多,对观察者模式进行小小的总结
- 观察者模式定义了对象之间一对多的关系
- 主题(也就是被观察者)用一个共同的接口来更新观察者
- 观察者和被观察者之间松耦合组合,被观察者不知道观察者的细节,只知道观察者实现了观察者接口
- 使用观察者模式可以时候用推或者拉的方法,一般情况推的方式被认为更“正确”

优点
- 观察者模式在观察的主题和观察者之间建立一个抽象的耦合,观察者只需要维持一个抽象观察者的集合,无须了解具体的观察者。
- 观察者支持广播通信,一对多系统设计。
- 观察者满足“开闭原则”,当新增观察者时候,不需要修改系统原有代码!

缺点
- 如果一个观察主题有很多观察者时候,通过所有的观察者会花费很多时间。
- 观察者不知道观察主题是怎么发生变化的,仅仅知道观察主题发生了变化。
- 观察者和观察主题存在循环依赖,可能会触发循环调用,导致系统崩溃。

适用场景
- 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

Next 期待下一篇吧!我们到了巴厘岛了,在岛上准备开一个奶茶店,遇到了一些问题,给我们带来了困扰,下一篇 装饰者模式!

参考

本专栏文章列表

一、设计模式-开篇—为什么我要去旅行? #和设计模式一起旅行#
二、设计模式-必要的基础知识—旅行前的准备 #和设计模式一起旅行#
三、设计模式介绍—她是谁,我们要去哪里? #和设计模式一起旅行#
四、单例模式—不要冒充我,我只有一个! #和设计模式一起旅行#
五、工厂模式—旅行的钱怎么来 #和设计模式一起旅行#
六、策略模式—旅行的交通工具 #和设计模式一起旅行#
七、观察者模式——关注我,分享旅途最浪漫的瞬间! #和设计模式一起旅行#
八、装饰者模式—巴厘岛,奶茶店的困扰! #和设计模式一起旅行#
九、命令模式—使用命令控制奶茶店中酷炫的灯 #和设计模式一起旅行#
十、模板方法模式—制作更多好喝的饮品! #和设计模式一起旅行#
十一、代理模式 —专注,做最好的自己!#和设计模式一起旅行#
十二、适配器模式——解决充电的烦恼 #和设计模式一起旅行#
十三、外观模式—— 简化接口 #和设计模式一起旅行#
十四、迭代器模式—— 一个一个的遍历 #和设计模式一起旅行#
十五、组合模式—— 容器与内容的一致性 #和设计模式一起旅行#
十六、状态模式—用类表示状态 #和设计模式一起旅行#
十七、访问者模式-访问数据结构并处理数据 #和设计模式一起旅行#
十八、职责链模式-推卸责任,不关我的事,我不管!#和设计模式一起旅行#
十九、原型模式—通过复制生产实例 #和设计模式一起旅行#
二十、设计模式总结—后会有期 #和设计模式一起旅行#


如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!

如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!


欢迎访问我的csdn博客,我们一同成长!

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 : http://blog.csdn.net/u010648555

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 成长之路 设计师:Amelia_0503 返回首页