浅谈设计模式2——观察模式


我想大家一定都去医院挂过号,大概流程是:挂号,到就诊室门口排队,等着医生叫号看病。今天说的观察者模式就是这样一个道理。很多的病人就是观察者,等待医生状态发生改变——看完病,叫号。然后所有的病人听到号,看看叫的是不是自己,走入诊室看病,或者继续等待。

1、背景叙述
依然感觉还是用书上的例子比较好,但是像“气象观测站”这种东西,除了看天气预报,我绝不会想到。为了不让问题更加复杂,我想还是直接切入主题,用比较形式化的描述方式来阐述,也能更快进入主题。

2、观察者模式
对于某个“对象A”,当它的状态发生变化时——某个属性发生变化,或者调用某个函数。则其他的对象也因为“对象A”的变化做出某些操作。“对象A”为被监听者,其他的对象为监听者。有没有想到Java中的事件?对,这个就是事件的原型。先结合订报纸的示例,用语言描述一下具体实现,然后上代码,最后给大家说下这样做有啥好处。

1)订报纸需要以下步骤
(1)去邮局,请求订报纸,邮局将我们的个人信息注册。——邮局就是“被监听者”,我们是“监听者”。监听者需要到被监听者那里注册个人信息。
(2)当邮局到了新报纸,则就将报纸分发给我们。——邮局从没有报纸的状态,转换到有报纸的状态,状态发生改变,需要告诉我们状态发生改变,“报纸”就是“被监听者”发送给“监听者”信息
(3)我们收到报纸后,有的人读报纸,了解新闻;有的人比较土豪,用来包家具;有的人更土豪,送给邻居。——“监听者”收到消息后,采取不同的动作。

从以上分析举例来看,监听者模式(也就是观察者模式,监听者说的比较顺口)其实是一种通信模式。就是将信息进行广播。然后收到相同信息的不同实体,根据信息和自身特点,做出相应操作。
需要注意的是,因为投递员只认识绿皮邮筒,所以所有的监听者要提供统一的监听接口来接受消息。监听接口其实就是个函数。
但是,为了保证程序面向对象的良好设计,我们使用接口来对各个类进行修饰。

2)程序部分

import java.util.ArrayList;
//测试类
public class Test
{
    public static void main(String args[])
    {
        PostOffice postOffice=new PostOffice();//被观察者
        Observer1 observer1=new Observer1();//观察者1号
        Observer2 observer2=new Observer2();//观察者2号
        
        postOffice.addObserver(observer1);//注册观察者1号
        postOffice.addObserver(observer2);//注册观察者2号
        
        postOffice.setState(true, true);//改变被观察者状态
    }
}
/*
如上面的程序,监听者需要跑到被监听者那里去注册,只有注册了,被监听者才能发消息给监听者。
*/


interface Subject //定义被监听者接口,但是有一个不好点,下面会仔细说。
{
    public void addObserver(Observer o);
    public void deleteObserver(Observer o);
    public void notifyObserver(Object otherArg);
}

interface Observer//定义监听者接口,实际只是定义一个接受信息的接口 
{
    public void  receive(Subject subject, Object otherArg );
}

//实现被被观察者接口
class PostOffice implements Subject
{
    private boolean newsPaper=false;
    private boolean gift=false;
    
    private ArrayList<Observer> arrayList;//存放观察者的列表
    
    public PostOffice()//构造函数
    {
        this.newsPaper=false;
        this.gift=false;
        this.arrayList=new ArrayList<Observer>(); //注意一定要初始化对象实体
    }
    
      
    public void stateChanged()//用户自定义
    {
        this.notifyObserver(null);
    }
    
    public void setState(boolean newspaper, boolean gift) //用户自定义
    {
        this.newsPaper=newspaper;
        this.gift=gift;
        
        this.stateChanged();
    }
    
    //以下为实现的Subject接口
    public void addObserver(Observer o)
    {
        this.arrayList.add(o);
    }
    public void deleteObserver(Observer o)
    {
        int index=this.arrayList.indexOf(o);
        if(index>0)
            this.arrayList.remove(index);
    }
    public void notifyObserver(Object otherArg) 
    {
        for (Observer observer : this.arrayList) 
            observer.receive(this, otherArg);
    }
}

class Observer1 implements Observer
{
    @Override
    public void receive(Subject subject, Object otherArg) 
    {
        System.out.println("this is Observer1");        
    }
}

class Observer2 implements Observer
{

    @Override
    public void receive(Subject subject, Object otherArg) {
        System.out.println("this is Observer2");
        
    }
}

3)程序分析部分
希望上面的程序大家能看懂,下面主要针对程序需要说明几点注意事项。
(1)Java类库中,被监听者Subject实际上是一个类,而此处是一个接口。那么到底哪个比较好呢?
如果是接口,就必须维护一个ArrayList列表(接口中都是public成员,这样就暴露了列表成员),来存储申请注册的监听者。而如果是类,则没有这么多麻烦,直接调用addObserver(),添加监听者即可。也就是说,接口破坏了封装性。
但是,如果是类,由于Java只支持单继承,因此这样做子类就不能继承其他类,也不好用。

(2)Observer接口中,receive()函数传递的参数可以根据需要自己设定,但是需要考虑两个方面:
a、参数不能太多,否则会增加通信量,严重影响程序的执行效率。
b、参数不能太少,因为这个函数是所有类接收信息的统一接口,一旦未来功能要扩展,就需要修改很多的东西——凡是实现receive()方法的类全都需要修改。
所以此处我把接口设计成接收两个参数,一个是被监听者,一个是其他类。如果需要被监听者的成员变量,可以采用getter 和 setter方法。如果需要获取其他信息,可以将这些信息全部打包在一个容器类里,通过otherArg来传递。

(3)对于PostOffice类,有三个函数:setState()函数来改变当前对象的状态,stateChanged()函数由用户自定义,来执行当对象状态改变时,需要作出的动作。最后一个notifyObserver()才是真正通知监听者,通知他们。为什么要这么麻烦,直接将notifyObserver()放到setState()中不是更好?
这样做将:改变对象状态,改变对象状态后应该做的操作,通知监听者,三个操作进行了分离。将操作的粒度变小,从而让修改更加容易。

同时,还要注意,对于邮局这个模型,报纸来了通知一下就好。但是对于温度检测等模型,变化速度太快,而响应速度又不太高(每秒变化一度,但是变化十度才做出反应)这样的情况,设置上面的三个函数就很有效。

顺便说下,上面的函数依然有文章可做。可以在Subject接口中再增加一个setchanged()函数。下面只是说下增加到PostOffice中的代码。增加了setChanged(),好处是可以根据当前状态,来决定到底通不通知监听者。

       private boolean changed=false;
       public void stateChanged()//用户自定义
    {
        this.setChanged(false);//注意这行,通知完后,就表明没有改变了
        this.notifyObserver(null);
    }
    
    public void setState(boolean newspaper, boolean gift) //用户自定义
    {
        this.newsPaper=newspaper;
        this.gift=gift;
        
        this.setChanged(true);//注意这行,发生了改变
        
        this.stateChanged();
    }
       public void  setChanged(boolean changed)
    {
        this.changed=changed;
    }

至此,观察模式结束。码了这么多字,好累。

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

推荐阅读更多精彩内容