要写好代码,设计模式(Design Pattern)是必不可少的基本功,设计模式是对面向对象设计(Object Oriented Design)中重复呈现的问题的一种无效解决方案,本次从比拟常见的观察者模式动手(Observer Pattern)。在观察者模式中,存在多个观察者对象依赖(Observer)都依赖同一个指标对象(Subject),当被依赖的指标对象发生变化的时候,会告诉所有依赖它的观察者对象,而后各个观察者对象依据本人的须要做出对应的响应。
其次要长处如下:
- 升高了指标与观察者之间的耦合关系
- 建设了指标与观察者之间的变动触发机制
其次要毛病如下:
- 指标与观察者之间的依赖关系并没有齐全解除,而且有可能呈现循环援用
- 当观察者对象很多时,告诉的发布会破费很多工夫,影响程序的效率
比拟形象不好了解?咱们来参考日常功能设计中几个常见的场景。
01观察者模式在天气预报场景的利用
关注天气预报是咱们日常生活中一个比拟重要的习惯,不同的角色对于天气的变动由有着不同的反馈。例如今天特大暴雨,气象部门思考的是评估并公布正当的政策领导,教育部门须要评估是否须要复课,应急部门思考的是如何提前准备应急救济,环卫部门须要筹备暴雨后的大量环卫工作,打工人须要思考如何上下班通勤,也存在一些不受此次暴雨影响地区的人什么也不须要思考。联合上述观察者模式的介绍,在此场景中,天气属于被各个角色依赖的指标对象(Subject),气象/教育/应急/环卫等部门,打工人,其余地区的人都属于观察者对象(Observer)。指标对象发生变化后,各个观察者对象收到音讯后都会作出对应的响应措施。基于以上场景实现的简化版观察者模式类图如下:
- 指标对象(WeatherSubject):即被察看的指标对象,即本例中的天气,持有一个天气状态的属性state,以及绑定观察者的办法attach(),观察者的办法notifyAllObservers()。当状态产生变更后,调用notifyAllObservers()办法告诉所有观察者。
- 形象观察者对象(Observer):用于定义各个观察者的行为规范,也能够采纳接口的形式实现
- 理论观察者对象:MeteorologicalDepartment、RescueDepartment、OfficeWorker、Other等,理论须要关注指标对象并作出响应的角色。继承自形象观察者对象,并基于各自的关注点实现了对于的响应办法update()。
基于以上示例的Java版代码demo试下:
package com;import java.util.ArrayList;import java.util.List;// 基于天气预报场景的观察者模式实现Demopublic class ObserverPatternDemo { // 天气对象 static class WeatherSubject { private int state; List<Observer> observers = new ArrayList<>(); void attach(Observer observer) { observers.add(observer); } // 告诉观察者 void notifyAllObservers() { for (Observer o : observers ) { o.update(getState()); } } public int getState() { return state; } // 状态变更 public void setState(int state) { this.state = state; if (state == 1) { System.out.println("=====今天要下大暴雨====="); } else { System.out.println("=====今天天气很好呀====="); } // 变更后告诉观察者 notifyAllObservers(); } } // 形象观察者对象 static abstract class Observer { abstract void update(int state); } // 政府气象部门 static class MeteorologicalDepartment extends Observer { @Override void update(int state) { if (state == 1) { System.out.println("【气象部门】收回预警"); } } } // 政府应急救济部门 static class ResumeDepartment extends Observer { @Override void update(int state) { if (state == 1) { System.out.println("【救济部门】筹备应急预案"); } } } // 打工人 static class OfficeWorker extends Observer { @Override void update(int state) { if (state == 1) { System.out.println("【打工人】思考今天怎么上下班通勤"); } else { System.out.println("【打工人】致力搬砖"); } } } // 其余无影响的人 static class Other extends Observer { @Override void update(int state) { if (state == 1) { System.out.println("【其他人】下雨啊,对我影响不大"); } else { System.out.println("【其他人】今天天气不错,进来玩玩"); } } } public static void main(String[] args) { // 初始化指标对象 WeatherSubject subject = new WeatherSubject(); // 初始化观察者对象,并关注指标对象 Observer ob1 = new MeteorologicalDepartment(); Observer ob2 = new ResumeDepartment(); Observer ob3 = new OfficeWorker(); Observer ob4 = new Other(); subject.attach(ob1); subject.attach(ob2); subject.attach(ob3); subject.attach(ob4); // 状态变动: 大暴雨 subject.setState(1); // 状态变动: 好天气 subject.setState(2); //执行后果如下 =====今天要下大暴雨===== 【气象部门】收回预警 【救济部门】筹备应急预案 【打工人】思考今天怎么上下班通勤 【其他人】下雨啊,对我影响不大 =====今天天气很好呀===== 【打工人】致力搬砖 【其他人】今天天气不错,进来玩玩 }}
02 观察者模式在领取场景中的利用
在领取业务场景下,用户购买一件商品,当领取胜利之后三方会回调本身,在这个时候零碎可能会有很多须要执行的逻辑(如:更新订单状态,发送短信告诉,告诉物流零碎开始备货,赠送礼品…)。
通常最直观的解决形式,会创立对应领取零碎须要依赖的类(Order、SMS、Express...)以及领取类Pay,在领取主逻辑中实例化各依赖类,当用户领取胜利后,一一调用各依赖类解决逻辑。但这样领取类要理解须要告诉哪些类,且领取主逻辑臃肿耦合也较重,不便于扩大和保护。
观察者模式则能够更好的解决这种领取场景,这些领取零碎所依赖的类逻辑之间并没有强耦合,因而适宜应用观察者模式去实现这些性能,对与领取类来说不关怀须要告诉哪些类,只须要提供告诉列表,当有更多的操作时,只须要向告诉列表中增加新的观察者即可,用户领取胜利告诉所有注册的观察者。实现了对批改敞开,对扩大凋谢的开闭准则。
具体实现上通常包含以下几局部:
- 形象出被观察者Observable类,形象出共有的属性和办法;
- 创立具体被观察者Pay类,只有用户领取胜利,它就要去告诉所有注册的观察者;
- 形象出观察者接口Observer,它蕴含了一个更新本人的形象办法update,当接到具体主题Pay的更改告诉时被调用;
- 创立订单、短信、物流等具体观察者类,它们要察看领取状态,失去Pay的更改告诉时更新本身的状态;
// 形象主题(Subject)角色public abstract class Observable { // 观察者列表 private List<Observer> observers = new ArrayList<>(); // 增加观察者 public void add(Observer observer){ observers.add(observer); } // 移除观察者 public void remove(Observer observer){ observers.remove(observer); } // 告诉观察者 protected void notifyObservers(){ for (Observer observer : observers) { observer.update(); } }}// 具体主题(Concrete Subject)角色public class Pay extends Observable{ public void pay(){ System.out.println("领取实现."); // 告诉观察者 super.notifyObservers(); }}// 形象观察者(Observer)角色public interface Observer { // 告诉 void update();}// 具体观察者(Concrete Observer)角色// 订单:批改订单状态public class Order implements Observer{ @Override public void update() { System.out.println("批改订单状态..."); }}// 短信:发送扣款短信到用户public class SMS implements Observer{ @Override public void update() { System.out.println("账户扣款短信告诉..."); }}//物流:告诉物流零碎开始备货public class Express implements Observer{ @Override public void update() { System.out.println("物流开始备货..."); }}// 客户端调用public class Client { public static void main(String[] args) { Pay pay = new Pay(); pay.add(new Order()); pay.add(new SMS()); pay.add(new Express()); pay.pay(); }}
03 观察者模式在数据订阅场景的利用
在理论利用中,数据推送的场景下,常常会用到观察者模式。以小说资源为例,新的章节生产实现后,须要该数据的业务很多,如搜寻、网盘、贴吧、小度等,各方接收数据的形式各异。
对于每个订阅方,会实现截然不同的订阅办法,可能是推送http接口、kafka队列、afs文件系统等。能够别离实现这些订阅办法,并进行注册,当呈现音讯公布时,顺次触发订阅者办法。
以下是PHP语言的实现demo:
interface Observer { public function push($data);}class Publish { private $observers = array(); public function register(Observer $observer) { $this->observers[] = $observer; } public function delete(Observer $observer) { $index = array_search($observer, $this->observers); if ($index !== FALSE && array_key_exists($index, $this->observers)) { unset($this->_observers[$index]); } } public function push($data) { foreach ($this->observers as $observer) { $observer->push($data); } }}class Search implements Observer { public function push($data) { //推送afs }}class Cloud implements Observer { public function push($data) { //推送kafaka }}$publish = new Publish();$publish->register(new Search());$publish->register(new Cloud());$publish->push($data);
04 总结
通过对以上三个理论案例的解说和具体的代码实现浏览,大家对观察者模式的利用场景和具体实现计划应该有了更加深刻的理解了。联合以上三个案例的的剖析,适宜观察者模式的场景都有以下典型特色:
- 存在多对一的依赖关系:即多个观察者依赖同一个指标对象
- 存在指标变更触发机制:指标变更后,须要触发一系列的其余工作
通过观察者机制来实现以上场景,能够实现目标类和观察者类的解耦,即指标对象无需晓得须要告诉哪些观察者,不便后续的扩大与保护。
---------- END ----------
举荐浏览【技术加油站】系列:
百度工程师教你玩转设计模式(单例模式)
百度工程师教你疾速晋升研发效率小技巧