【原创】自己动手循序渐进实现观察者模式

引言

自上一篇《自己动手实现牛逼的单例模式》问世之后,得到了不错的评价。于是博主在五一放弃出去游玩机会,赶制了这篇《自己动手循序渐进实现观察者模式》,依然还是一步一步推导出最终版的观察者模式。

观察者模式

定义:在许多设计中,经常涉及多个对象都对一个特殊对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化,在这样的情况下就可以使用观察者模式。 在这里,我们以母亲观察宝宝为例子,宝宝正在睡觉,醒来后,母亲做出相应的喂食行为。

1、观察者V1

假设我们用下图所示的情景

先定义一个Baby类,baby先睡五秒,五秒后醒来,代码如下所示

package rjzheng.observer1;public class Baby implements Runnable {    // 默认是睡着
    private boolean wakeup = false;    // 醒来的行为
    public void wakeUp() {        this.wakeup = true;
    }    public boolean isWakeup() {        return wakeup;
    }    public void setWakeup(boolean wakeup) {        this.wakeup = wakeup;
    }    @Override
    public void run() {        // TODO Auto-generated method stub
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        wakeUp();
    }
}

而母亲类即Mother类,则一直监听宝宝的状态,宝宝一醒来就喂食,代码如下所示

package rjzheng.observer1;public class Mother implements Runnable {    private Baby baby;    public Mother(Baby baby) {        this.baby = baby;
    }    public void feed(Baby baby){
        System.out.println("已经给宝贝喂食");
    }    @Override
    public void run(){        while(!baby.isWakeup()){            for(int i=0;i<5;i++){                try {
                    Thread.sleep(1000);
                    System.out.println("宝宝还有"+(5-i)+"秒醒来");
                } catch (InterruptedException e) {                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }        this.feed(baby);
    }
}

然后写一个ObserverTest1测试一下,代码如下所示

package rjzheng.observer1;public class ObserverTest1 {    public static void main(String[] args) {
        Baby baby =new Baby();        new Thread(baby).start();        new Thread(new Mother(baby)).start();
        
    }
}

输出如下

宝宝还有5秒醒来
宝宝还有4秒醒来
宝宝还有3秒醒来
宝宝还有2秒醒来
宝宝还有1秒醒来
已经给宝贝喂食

2、观察者V2

这时情景变为下图

可是上一版,母亲要一直看着宝宝,母亲觉得太累了,母亲要是想中间聊个微信啥的,多不方便,于是V2版出现了。我们能不能孩子醒来后,主动告诉给母亲呢。于是修改Baby的wakeUp方法,即醒来状态改变后,增加一个通知母亲的方法。 综上所述,Baby类做如下几点修改

  1. 持有Mother的引用,修改构造函数
  2. 在wakeUp方法中增加,通知母亲的逻辑
  3. 在run方法中,自己五秒后醒来,自己修改自己的状态

package rjzheng.observer2;public class Baby implements Runnable {    // 默认是睡着
    private boolean wakeup = false;    
    private Mother mother;    //1. 持有Mother的引用,修改构造函数
    public Baby(Mother mother){        this.mother = mother;
    }    
    //2. 在wakeUp方法中增加,通知母亲的逻辑
    public void wakeUp() {
        wakeup = true;        this.mother.feed(this);
    }    public boolean isWakeup() {        return wakeup;
    }    public void setWakeup(boolean wakeup) {        this.wakeup = wakeup;
    }    //3. 在run方法中,自己五秒后醒来,自己修改自己的状态
    @Override
    public void run() {        while(!this.isWakeup()){            for(int i=0;i<5;i++){                try {
                    Thread.sleep(1000);
                    System.out.println("宝宝还有"+(5-i)+"秒醒来");
                } catch (InterruptedException e) {                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }            this.wakeUp();
        }
    }
}

而Mother类,则不需要做线程操作,Baby类通知Mother类后,做出相应处理即可。因此,Mother类修改如下

  1. 删除run方法等线程相关操作
  2. 增加一个无参构造方法(因为Mother类不需要持有Baby类的引用了) 综上所述,Mother类的代码如下

package rjzheng.observer2;public class Mother{    private Baby baby;    public Mother(){
        
    }    public Mother(Baby baby) {        this.baby = baby;
    }    public void feed(Baby baby){
        System.out.println("已经给宝贝喂食");
    }
}

测试类ObserverTest2的代码如下

package rjzheng.observer2;public class ObserverTest2 {    public static void main(String[] args) {
        Baby baby =new Baby(new Mother());        new Thread(baby).start();
        
    }
}

输出如下所示

宝宝还有5秒醒来
宝宝还有4秒醒来
宝宝还有3秒醒来
宝宝还有2秒醒来
宝宝还有1秒醒来
已经给宝贝喂食

3、观察者V3

这个时候,宝宝的父亲回来了。父亲觉得,在宝宝醒来后,应该带宝宝出去玩。于是就和孩子他妈约定,根据孩子醒来的时间决定,带孩子做什么。如果醒来的时间是饭点,就给孩子喂食;如果醒来的时间不是饭点,就带孩子出去玩。 那么重点来了,醒来的时间是否为饭点这个属性,放在父亲类里,不大合适;放在宝宝类里也不大合适;放母亲类里也不大合适。根据面向对象的设计原则,我们定义一个醒来的事件类WakeUpEvent,将醒来的时间是否为饭点放在WakeUpEvent里。除此之外,再定义一个事件源source属性,那么WakeUpEvent类如下所示

package rjzheng.observer3;/**
 * 醒来的事件对象
 * @author zhengrongjun
 *
 */public class WakeUpEvent {    //醒来时间是否为饭点
    private boolean isFoodTime;    //事件源
    private Baby source;    public WakeUpEvent(boolean isFoodTime,Baby source){        this.isFoodTime = isFoodTime;        this.source = source;
    }    public boolean isFoodTime() {        return isFoodTime;
    }    public void setFoodTime(boolean isFoodTime) {        this.isFoodTime = isFoodTime;
    }    public Baby getSource() {        return source;
    }    public void setSource(Baby source) {        this.source = source;
    }
}

那么,接下来,Father类拥有一个带宝贝出去玩的方法,如下所示

package rjzheng.observer3;public class Father {    
    public void play(WakeUpEvent wakeUpEvent){        if(!wakeUpEvent.isFoodTime()){
            System.out.println("抱宝贝出去玩");
        }
    }
}

自然,Mother类拥有一个给宝贝喂食的方法,如下所示

package rjzheng.observer3;public class Mother{    public void feed(WakeUpEvent wakeUpEvent){        if(wakeUpEvent.isFoodTime()){
            System.out.println("给宝贝喂食");
        }
    }
}

那么在这种情况下Baby类修改为如下所示

package rjzheng.observer3;public class Baby implements Runnable {    private Mother mother;    private Father father;    public Baby(Mother mother,Father father) {        this.mother = mother;        this.father = father;
    }    public void wakeUp() {        this.mother.feed(new WakeUpEvent(true, this));        this.father.play(new WakeUpEvent(true, this));
    }    @Override
    public void run() {        boolean flag = true;        while (flag) {            for (int i = 0; i < 5; i++) {                try {
                    Thread.sleep(1000);
                    System.out.println("宝宝还有" + (5 - i) + "秒醒来");
                } catch (InterruptedException e) {                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }            this.wakeUp();
            flag=false;
        }
    }
}

测试类ObserverTest3如下所示

package rjzheng.observer3;public class ObserverTest3 {    public static void main(String[] args) {
        Baby baby =new Baby(new Mother(),new Father());        new Thread(baby).start();
        
    }
}

运行结果如下

宝宝还有5秒醒来
宝宝还有4秒醒来
宝宝还有3秒醒来
宝宝还有2秒醒来
宝宝还有1秒醒来
给宝贝喂食

4、观察者V4

这时情景变为下图

又过了几天,孩子不乐意了。。。。孩子觉得,我每次还要去记爸爸能带我干嘛,妈妈能带我干嘛,太麻烦了。以后,爷爷奶奶来来了,我难道还要去记你们能干嘛,再通知相应的人嘛。不如这样,我把你们全部叫来,你们自己商量好,该带我干嘛吧。

OK。。这也是观察者V3版的缺点,代码复用性太差。如果多一个爷爷GrandFather类,那Baby类里头还要多一个Grandfather类的引用,并且在wakeup里增加通知逻辑,太麻烦。索性,定义一个WakeUpListener接口,继承这个接口的人,代表都具有处理WakeUpEvent事件的能力

那么WakeUpListener接口的源码如下

package rjzheng.observer4;public interface WakeUpListener {    public void actiontoWakenUp(WakeUpEvent wakeUpEvent);
}

这时Father类和Mother类代码如下,都实现了WakeUpListener接口

package rjzheng.observer4;public class Father implements WakeUpListener {    @Override
    public void actiontoWakenUp(WakeUpEvent wakeUpEvent) {        // TODO Auto-generated method stub
        if(!wakeUpEvent.isFoodTime()){
            System.out.println("抱宝贝出去玩");
        } 
    }
}
package rjzheng.observer4;public class Mother implements WakeUpListener{    @Override
    public void actiontoWakenUp(WakeUpEvent wakeUpEvent) {        if(wakeUpEvent.isFoodTime()){
            System.out.println("给宝贝喂食");
        }
    }
}

这时的Baby类如下所示

package rjzheng.observer4;import java.util.ArrayList;import java.util.List;public class Baby implements Runnable {    private List<WakeUpListener> wakeUpListeners = new ArrayList(); 

    public void addListeners(WakeUpListener wakeUpListener){        this.wakeUpListeners.add(wakeUpListener);
    }    public void wakeUp() {        for(WakeUpListener listener : wakeUpListeners)
            listener.actiontoWakenUp(new WakeUpEvent(true, this));
    }    @Override
    public void run() {        boolean flag = true;        while (flag) {            for (int i = 0; i < 5; i++) {                try {
                    Thread.sleep(1000);
                    System.out.println("宝宝还有" + (5 - i) + "秒醒来");
                } catch (InterruptedException e) {                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }            this.wakeUp();
            flag=false;
        }
    }
}

测试类ObserverTest4为

package rjzheng.observer4;public class ObserverTest4 {    public static void main(String[] args) {
        Baby baby =new Baby();
        baby.addListeners(new Father());
        baby.addListeners(new Mother());        new Thread(baby).start();
    }
}

最后结果如下

宝宝还有5秒醒来
宝宝还有4秒醒来
宝宝还有3秒醒来
宝宝还有2秒醒来
宝宝还有1秒醒来
给宝贝喂食

总结

本篇文章给出了四种观察者模式,一般都是用V4版啦。。前面V1,V2,V3都只是铺垫,为了引出最后一版的观察者模式,希望大家有所收获。

原文发布于微信公众号 - JAVA高级架构(gaojijiagou)

原文发表时间:2018-06-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

重构 改善既有代码的设计--笔记

查看一个类是否“过大”,这里有一个小技巧分享给大家。就是看两点:1)这个类实例变量太多,必然会有Duplicated Code(重复代码) 2)类内如果有太多代...

1044
来自专栏ACM小冰成长之路

KWIC-C/C++实现

吐槽 最近我们 JavaJava 老师不知道为啥非要我用 C/C++C/C++ 来实现 KWICKWIC,但是因为没有上过课,不知道这个东西是干嘛的,所以想网上...

23610
来自专栏java工会

深度思考编程的艺术

1758
来自专栏Java技术栈

跟我学 Java 8 新特性之 Stream 流(四)并行流

在开始讨论并行流之前,我先引发一下大家的思考,就你看到这篇文章的时间,你们是不是经常听到,Intel i7 CPU什么8核16线程,什么Android手机8核4...

852
来自专栏测试开发架构之路

C语言程序设计50例(二)(经典收藏)

【程序11】 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月    后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总...

44210
来自专栏Java大联盟

23种设计模式详解(三)

951
来自专栏Java帮帮-微信公众号-技术文章全总结

Java基础-day05-超市收银系统案例题

Java基础-day05-超市收银系统案例题 案例描述 将超市购物小票案例中,键盘录入部分封装为方法。 将switch语句完成的三个分支代码逻辑封装为3个方法 ...

5104
来自专栏程序员互动联盟

【答疑解惑】为什么这么多人不喜欢用goto?

有网友在群里面问了一个问题:goto语句如何使用?这个问题引发了一系列争论。如下: ? ? ? ? ? 上面的讨论总结下来就是两点,有人建议尽量不要使用got...

3075
来自专栏Phoenix的Android之旅

重构-为什么 if-else 不是好代码

平时开发中if-else用的多吗? 其实这是个再正常不过的coding习惯,当我们代码量小的时候用来做条件判断是再简单不过的了。 但对于优秀程序员来说,这并不是...

1481
来自专栏陈纪庚

使用装饰者模式做有趣的事情

装饰者模式是一种为函数或类增添特性的技术,它可以让我们在不修改原来对象的基础上,为其增添新的能力和行为。它本质上也是一个函数(在javascipt中,类也只是函...

742

扫码关注云+社区