序
我想大家一定都去医院挂过号,大概流程是:挂号,到就诊室门口排队,等着医生叫号看病。今天说的观察者模式就是这样一个道理。很多的病人就是观察者,等待医生状态发生改变——看完病,叫号。然后所有的病人听到号,看看叫的是不是自己,走入诊室看病,或者继续等待。
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;
}
至此,观察模式结束。码了这么多字,好累。