简说设计模式——观察者模式
一、什么是观察者模式
观察者一般可以看做是第三者,比如在学校上自习的时候,大家肯定都有过交头接耳、各种玩耍的经历,这时总会有一个“放风”的小伙伴,当老师即将出现时及时“通知”大家老师来了。再比如,拍卖会的时候,大家相互叫价,拍卖师会观察最高标价,然后通知给其它竞价者竞价,这就是一个观察者模式。
对于观察者模式而言,肯定有观察者和被观察者之分。比如在一个目录下建立一个文件,这时系统会通知目录管理器增加目录,并通知磁盘减少空间,在这里,文件就是观察者,目录管理器和磁盘就是被观察者。
观察者模式(observer),又叫发布-订阅模式(publish/subscribe),定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。uml结构图如下:
其中,subject类是主题,它把所有对观察者对象的引用文件存在了一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供了一个接口,可以增加和删除观察者对象;observer类是抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己;concretesubject类是具体主题,将有关状态存入具体观察者对象,在具体主题内部状态改变时,给所有登记过的观察者发出通知;concreteobserver是具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协同。
1. 主题subject
首先定义一个观察者数组,并实现增、删及通知操作。它的职责很简单,就是定义谁能观察,谁不能观察,用vector是线程同步的,比较安全,也可以使用arraylist,是线程异步的,但不安全。
1 public class subject { 2 3 //观察者数组 4 private vector<observer> ovector = new vector<>(); 5 6 //增加一个观察者 7 public void addobserver(observer observer) { 8 this.ovector.add(observer); 9 } 10 11 //删除一个观察者 12 public void deleteobserver(observer observer) { 13 this.ovector.remove(observer); 14 } 15 16 //通知所有观察者 17 public void notifyobserver() { 18 for(observer observer : this.ovector) { 19 observer.update(); 20 } 21 } 22 23 }
2. 抽象观察者observer
观察者一般是一个接口,每一个实现该接口的实现类都是具体观察者。
1 public interface observer { 2 //更新 3 public void update(); 4 }
3. 具体主题
继承subject类,在这里实现具体业务,在具体项目中,该类会有很多变种。
1 public class concretesubject extends subject { 2 3 //具体业务 4 public void dosomething() { 5 //... 6 super.notifyobserver(); 7 } 8 9 }
4. 具体观察者
实现observer接口。
1 public class concreteobserver implements observer { 2 3 @override 4 public void update() { 5 system.out.println("收到消息,进行处理"); 6 } 7 8 }
5. client客户端
首先创建一个被观察者,然后定义一个观察者,将该被观察者添加到该观察者的观察者数组中,进行测试。
1 public class client { 2 3 public static void main(string[] args) { 4 //创建一个主题 5 concretesubject subject = new concretesubject(); 6 //定义一个观察者 7 observer observer = new concreteobserver(); 8 //观察 9 subject.addobserver(observer); 10 //开始活动 11 subject.dosomething(); 12 } 13 14 }
运行结果如下:
二、观察者模式的应用
1. 何时使用
- 一个对象状态改变,所有的依赖对象都将得到通知
2. 方法
- 使用面向对象技术
3. 优点
- 观察者和被观察者是抽象耦合的
- 建立了一套触发机制
4. 缺点
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
- 如果观察者和观察目标间有循环依赖,可能导致系统崩溃
- 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的
5. 使用场景
- 关联行为场景
- 事件多级触发场景
- 跨系统的消息变换场景,如消息队列的处理机制
6. 应用实例
- 手机丢了,委托别人给其他人发消息通知
- 通知老师/老板来了
- 拍卖,拍卖师观察最高标价,然后通知给其它竞价者竞价
- 在一个目录下建立一个文件,会同时通知目录管理器增加目录,并通知磁盘减少空间,文件是被观察者,目录管理器和磁盘管理器是观察者
- 猫叫了一声,吓着了老鼠,也惊到了主人,猫是被观察者,老鼠和人是观察者
7. 注意事项
- 避免循环引用
- 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式
三、观察者模式的实现
下面举一个具体实例,假设上班时间有一部分同事在看股票,一部分同事在看nba,这时老板回来了,前台通知了部分同事老板回来了,这些同事及时关闭了网页没被发现,而没被通知到的同事被抓了个现行,被老板亲自“通知”关闭网页,uml图如下:
1. 通知者接口
1 public interface subject { 2 3 //增加 4 public void attach(observer observer); 5 //删除 6 public void detach(observer observer); 7 //通知 8 public void notifyobservers(); 9 10 //状态 11 public void setaction(string action); 12 public string getaction(); 13 14 }
2. 观察者
1 public abstract class observer { 2 3 protected string name; 4 protected subject subject; 5 6 public observer(string name, subject subject) { 7 this.name = name; 8 this.subject = subject; 9 } 10 11 public abstract void update(); 12 13 }
3. 具体通知者
前台secretary和老板boss作为具体通知者,实现subject接口。这里只给出secretary类的代码,boss类与之类似。
1 public class secretary implements subject { 2 3 //同事列表 4 private list<observer> observers = new linkedlist<>(); 5 private string action; 6 7 //添加 8 @override 9 public void attach(observer observer) { 10 observers.add(observer); 11 } 12 13 //删除 14 @override 15 public void detach(observer observer) { 16 observers.remove(observer); 17 } 18 19 //通知 20 @override 21 public void notifyobservers() { 22 for(observer observer : observers) { 23 observer.update(); 24 } 25 } 26 27 //前台状态 28 @override 29 public string getaction() { 30 return action; 31 } 32 33 @override 34 public void setaction(string action) { 35 this.action = action; 36 } 37 38 }
4. 具体观察者
stockobserver是看股票的同事,nbaobserver是看nba的同事,作为具体观察者,继承observer类。这里只给出stockobserver类的代码,nbaobserver类与之类似。
1 public class stockobserver extends observer { 2 3 public stockobserver(string name, subject subject) { 4 super(name, subject); 5 } 6 7 @override 8 public void update() { 9 system.out.println(subject.getaction() + "\n" + name + "关闭股票行情,继续工作"); 10 } 11 12 }
5. 前台作为通知者进行通知(client)
前台作为通知者,通知观察者。这里添加adam和tom到通知列表,并从通知列表中删除了adam,测试没在通知列表中的对象不会收到通知。
public class client { public static void main(string[] args) { //前台为通知者 secretary secretary = new secretary(); stockobserver observer = new stockobserver("adam", secretary); nbaobserver observer2 = new nbaobserver("tom", secretary); //前台通知 secretary.attach(observer); secretary.attach(observer2); //adam没被前台通知到,所以被老板抓了个现行 secretary.detach(observer); //老板回来了 secretary.setaction("小心!boss回来了!"); //发通知 secretary.notifyobservers(); } }
运行结果如下,只有tom接收到了通知:
6. 老板作为通知者进行通知(client)
老板作为通知者,通知观察者。这里将tom从老板的通知列表中移除,老板只通知到了adam。
1 public class client { 2 3 public static void main(string[] args) { 4 //老板为通知者 5 boss boss = new boss(); 6 7 stockobserver observer = new stockobserver("adam", boss); 8 nbaobserver observer2 = new nbaobserver("tom", boss); 9 10 //老板通知 11 boss.attach(observer); 12 boss.attach(observer2); 13 14 //tom没被老板通知到,所以不用挨骂 15 boss.detach(observer2); 16 17 //老板回来了 18 boss.setaction("咳咳,我大boss回来了!"); 19 //发通知 20 boss.notifyobservers(); 21 } 22 23 }
运行结果如下,只有adam挨骂了:
当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象有待改变的时候,应该考虑使用观察者模式。
而使用观察者模式的动机在于:将一个系统分割成一系列相互协作的类有一个很不好的副作用,就是需要维护相关对象间的一致性,我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便,而观察者模式所做的工作就是在解除耦合。
源码地址:https://gitee.com/adamjiangwh/gof