设计模式之禅-观察者模式

1.业务背景

知彼知己,百战不殆;不知彼而知己,一胜一负;不知彼,不知己,每战必殆。

李斯和韩非子都是荀子的学生,李斯是师兄,韩非子是师弟,若干年后,李斯成为最强诸侯秦国的上尉,致力于统一全国,于是安插了间谍到各个国家的重要人物的身边,以获取必要的信息,韩非子作为韩国的重量级人物,身边自然没少间谍了。我们先通过程序把这个过程展现一下,看看李斯是怎么监控韩非子,先看类图:

2.先看我们的主角韩非子的接口(类似于韩非子这样的人,被观察者角色):

public interface IHanFeiZi {

    //韩非子也是人,也要吃早饭的

    public void haveBreakfast();

    //韩非之也是人,是人就要娱乐活动

    public void haveFun();

}

韩非子的实现类 HanFeiZi.java:

public class HanFeiZi implements IHanFeiZi{

    //韩非子是否在吃饭,作为监控的判断标准

    private boolean isHaveBreakfast = false;

    //韩非子是否在娱乐

    private boolean isHaveFun = false;

    //韩非子要吃饭了

    public void haveBreakfast(){

        System.out.println("韩非子:开始吃饭了...");

        this.isHaveBreakfast =true;

    }

    //韩非子开始娱乐了,古代人没啥娱乐,你能想到的就那么多

    public void haveFun(){

        System.out.println("韩非子:开始娱乐了...");

        this.isHaveFun = true;

    }

    //以下是bean的基本方法,getter/setter,不多说

    public boolean isHaveBreakfast() {

        return isHaveBreakfast;

    }

    public void setHaveBreakfast(boolean isHaveBreakfast) {

        this.isHaveBreakfast = isHaveBreakfast;

    }

    public boolean isHaveFun() {

        return isHaveFun;

    }

    public void setHaveFun(boolean isHaveFun) {

        this.isHaveFun = isHaveFun;

    }

}

通过 isHaveBreakfast和 isHaveFun 这两个布尔型变量来判断韩非子是否在吃饭或者娱乐

李斯这类人的接口:

public interface ILiSi {

    //一发现别人有动静,自己也要行动起来

    public void update(String context);

}

李斯这类人比较简单,一发现自己观察的对象发生了变化,比如吃饭,娱乐了,自己立刻也要行动起来,那怎么行动呢?看实现类:

public class LiSi implements ILiSi{

    //首先李斯是个观察者,一旦韩非子有活动,他就知道,他就要向老板汇报

    public void update(String str){

        System.out.println("李斯:观察到韩非子活动,开始向老板汇报了...");

        this.reportToQiShiHuang(str);

        System.out.println("李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...\n");

    }

    //汇报给秦始皇

    private void reportToQiShiHuang(String reportContext){

        System.out.println("李斯:报告,秦老板!韩非子有活动了--->"+reportContext);

    }

}

两个重量级的人物都定义出来了,那我们就来看看要怎么监控,先写个监控程序:

class Watch extends Thread{

    private HanFeiZi hanFeiZi;

    private LiSi liSi;

    private String type;

    //通过构造函数传递参数,我要监控的是谁,谁来监控,要监控什么

    public Watch(HanFeiZi _hanFeiZi,LiSi _liSi,String _type){

        this.hanFeiZi =_hanFeiZi;

        this.liSi = _liSi;

        this.type = _type;

    }

    @Override

    public void run(){

        while(true){

            if(this.type.equals("breakfast")){ //监控是否在吃早餐

                //如果发现韩非子在吃饭,就通知李斯

                if(this.hanFeiZi.isHaveBreakfast()){

                    this.liSi.update("韩非子在吃饭");

                    //重置状态,继续监控

                    this.hanFeiZi.setHaveBreakfast(false);

                }

            }else{//监控是否在娱乐

                if(this.hanFeiZi.isHaveFun()){

                    this.liSi.update("韩非子在娱乐");

                    this.hanFeiZi.setHaveFun(false);

                }

            }

        }

    }

}

这个Client就是我们,用我们的视角看待这段历史

public class Client {

    public static void main(String[] args) throws InterruptedException {

        //定义出韩非子和李斯

        LiSi liSi = new LiSi();

        HanFeiZi hanFeiZi = new HanFeiZi();

        //观察早餐

        Watch watchBreakfast = new Watch(hanFeiZi,liSi,"breakfast");

        //开始启动线程,监控

        watchBreakfast.start();

        //观察娱乐情况

        Watch watchFun = new Watch(hanFeiZi,liSi,"fun");

        watchFun.start();

        //然后这里我们看看韩非子在干什么

        Thread.sleep(1000); //主线程等待1秒后后再往下执行

        hanFeiZi.haveBreakfast();

        //韩非子娱乐了

        Thread.sleep(1000);

        hanFeiZi.haveFun();

    }

}

运行结果如下:

韩非子:开始吃饭了...

李斯:观察到李斯活动,开始向老板汇报了...

李斯:报告,秦老板!韩非子有活动了--->韩非子在吃饭

李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...

韩非子:开始娱乐了...

李斯:观察到李斯活动,开始向老板汇报了...

李斯:报告,秦老板!韩非子有活动了--->韩非子在娱乐

李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...

上面的程序,使用了一个 while(true)这样一个死循环来做监听,一台服务器就跑你这一个程序就完事了,错,绝对的错!

既然韩非子一吃饭李斯就知道了,那我们为什么不把李斯这个类聚集到韩非子这里类上呢


public class HanFeiZi implements IHanFeiZi{

    //把李斯声明出来

    private ILiSi liSi =new LiSi();

    //韩非子要吃饭了

    public void haveBreakfast(){

        System.out.println("韩非子:开始吃饭了...");

        //通知李斯

        this.liSi.update("韩非子在吃饭");

    }

    //韩非子开始娱乐了,古代人没啥娱乐,你能想到的就那么多

    public void haveFun(){

        System.out.println("韩非子:开始娱乐了...");

        this.liSi.update("韩非子在娱乐");

    }

}

韩非子 HanFeiZi 实现类就把接口的两个方法实现就可以了,在每个方法中调用 LiSi.update()方法,完成李斯观察韩非子任务,李斯的接口和实现类都没有任何改变

public class Client {

    public static void main(String[] args) {

        //定义出韩非子

        HanFeiZi hanFeiZi = new HanFeiZi();

        //然后这里我们看看韩非子在干什么

        hanFeiZi.haveBreakfast();

        //韩非子娱乐了

        hanFeiZi.haveFun();

    }

}

李斯都不用在 Client 中定义了,非常简单。

韩非子这么有名望(法家代表)、有实力(韩国的公子,他老爹参与过争夺韩国王位)的人,就只有秦国一个国家关心他吗?想想也不可能呀,肯定有一大帮的各国的类似李斯这样的人在看着他,监视着一举一动。再者,李斯只观察韩非子的吃饭,娱乐吗?政治倾向不关心吗?思维倾向不关心吗?

Observable 是被观察者,就是类似韩非子这样的人,Observer 接口是观察者,类似李斯这样的,在 Observable 接口中有三个比较重要的方法,分别是 addObserver 增加观察者,deleteObserver 删除观察者,notifyObservers通知所有的观察者。

所有被观察者者,通用接口

public interface Observable {

    //增加一个观察者

    public void addObserver(Observer observer);

    //删除一个观察者,——我不想让你看了

    public void deleteObserver(Observer observer);

    //既然要观察,我发生改变了他也应该用所动作——通知观察者

    public void notifyObservers(String context);

}

看韩非子的实现类:

public class HanFeiZi implements Observable{

    //定义个变长数组,存放所有的观察者

    private ArrayListobserverList = new ArrayList();

    //增加观察者

    public void addObserver(Observer observer){

        this.observerList.add(observer);

    }

    //删除观察者

    public void deleteObserver(Observer observer){

        this.observerList.remove(observer);

    }

    //通知所有的观察者

    public void notifyObservers(String context){

        for(Observer observer:observerList){

            observer.update(context);

        }

    }

    //韩非子要吃饭了

    public void haveBreakfast(){

        System.out.println("韩非子:开始吃饭了...");

        //通知所有的观察者

        this.notifyObservers("韩非子在吃饭");

    }

    //韩非子开始娱乐了,古代人没啥娱乐,你能想到的就那么多

    public void haveFun(){

        System.out.println("韩非子:开始娱乐了...");

        this.notifyObservers("韩非子在娱乐");

    }

}

再来看观察者接口 Observer.java:

public interface Observer {

    //一发现别人有动静,自己也要行动起来

    public void update(String context);

}

李斯这个人,是个观察者,只要韩非子一有动静,这边就知道

public class LiSi implements Observer{

    public void update(String str){

        System.out.println("李斯:观察到李斯活动,开始向老板汇报了...");

        this.reportToQiShiHuang(str);

        System.out.println("李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...\n");

    }

    //汇报给秦始皇

    private void reportToQiShiHuang(String reportContext){

        System.out.println("李斯:报告,秦老板!韩非子有活动了--->"+reportContext);

    }

}

王斯,也是观察者

public class WangSi implements Observer{

    //王斯,看到韩非子有活动,自己就受不了

    public void update(String str){

        System.out.println("王斯:观察到韩非子活动,自己也开始活动了...");

        this.cry(str);

        System.out.println("王斯:真真的哭死了...\n");

    }

    //一看李斯有活动,就哭,痛哭

    private void cry(String context){

        System.out.println("王斯:因为"+context+",——所以我悲伤呀!");

    }

}

刘斯这个人,是个观察者

public class LiuSi implements Observer{

    //刘斯,观察到韩非子活动后,自己也做一定得事情

    public void update(String str){

        System.out.println("刘斯:观察到韩非子活动,开始动作了...");

        this.happy(str);

        System.out.println("刘斯:真被乐死了\n");

    }

    //一看韩非子有变化,他就快乐

    private void happy(String context){

        System.out.println("刘斯:因为" +context+",——所以我快乐呀!" );

    }

}

public class Client {

    public static void main(String[] args) {

        //三个观察者产生出来

        Observer liSi = new LiSi();

        Observer wangSi = new WangSi();

        Observer liuSi = new LiuSi();

        //定义出韩非子

        HanFeiZi hanFeiZi = new HanFeiZi();

        //我们后人根据历史,描述这个场景,有三个人在观察韩非子

        hanFeiZi.addObserver(liSi);

        hanFeiZi.addObserver(wangSi);

        hanFeiZi.addObserver(liuSi);

        //然后这里我们看看韩非子在干什么

        hanFeiZi.haveBreakfast();

    }

}

运行结果如下:

韩非子:开始吃饭了...

李斯:观察到李斯活动,开始向老板汇报了...

李斯:报告,秦老板!韩非子有活动了--->韩非子在吃饭

李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...

王斯:观察到韩非子活动,自己也开始活动了...

王斯:因为韩非子在吃饭,——所以我悲伤呀!

王斯:真真的哭死了...

刘斯:观察到韩非子活动,开始动作了...

刘斯:因为韩非子在吃饭,——所以我快乐呀!

刘斯:真被乐死了

HanFeiZi 这个实现类中应该抽象出一个父类,父类完全实现接口,HanFeiZi这个类只实现两个方法 haveBreakfast 和 haveFun 就可以了

HanFeiZi 的实现类:

public class HanFeiZi extends Observable{

    //韩非子要吃饭了

    public void haveBreakfast(){

        System.out.println("韩非子:开始吃饭了...");

        //通知所有的观察者

        super.setChanged();

        super.notifyObservers("韩非子在吃饭");

    }

    //韩非子开始娱乐了,古代人没啥娱乐,你能想到的就那么多

    public void haveFun(){

        System.out.println("韩非子:开始娱乐了...");

        super.setChanged();

        this.notifyObservers("韩非子在娱乐");

    }

}

改变的不多,引入了一个 java.util.Observable 对象,删除了增加、删除观察者的方法

我们再来看观察者的实现类:

public class LiSi implements Observer{

    //首先李斯是个观察者,一旦韩非子有活动,他就知道,他就要向老板汇报

    public void update(Observable observable,Object obj){

        System.out.println("李斯:观察到李斯活动,开始向老板汇报了...");

        this.reportToQiShiHuang(obj.toString());

        System.out.println("李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...\n");

    }

    //汇报给秦始皇

    private void reportToQiShiHuang(String reportContext){

        System.out.println("李斯:报告,秦老板!韩非子有活动了--->"+reportContext);

    }

}

然后再来看 Client 程序:

public class Client {

    public static void main(String[] args) {

        //三个观察者产生出来

        Observer liSi = new LiSi();

        Observer wangSi = new WangSi();

        Observer liuSi = new LiuSi();

        //定义出韩非子

        HanFeiZi hanFeiZi = new HanFeiZi();

        //我们后人根据历史,描述这个场景,有三个人在观察韩非子

        hanFeiZi.addObserver(liSi);

        hanFeiZi.addObserver(wangSi);

        hanFeiZi.addObserver(liuSi);

        //然后这里我们看看韩非子在干什么

        hanFeiZi.haveBreakfast();

    }

}

程序体内没有任何变更,只是引入了一个接口而已

观察者模式,这个模式的通用类图如下:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容