小帅的公司最近在开发一套销售系统。
老板想把上个月,这个月和下个月预计的销售数据分别在PC,平板电脑,手机端显示,以便多维度激励销售人员拼搏努力
小帅心想,这有何难?刷刷刷,立马设计了一个类。
项目组的老王凑过来看了看:”你的设计有一个明显的问题,不同平台的代码都强耦合在一个类里。
导致以后增加或减少显示平台的时候都要修改主程序。
这是典型的针对具体的实现编程,而不是针对接口编程。“
认识观察者模式
那你有什么办法解决上面的问题呢?
你行你上啊?小帅不服气的说。
老王嘿嘿的笑着:我还真有办法,用观察者模式再合适不过了。
观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
观察者可以动态的添加和删除,不会影响主题类。
https://mmbiz.qpic.cn/mmbiz_gif/EvyAfa0Ws5qIia8c6Tib41zNiaSbqtB8Pq39hc9zthcJotwSQ5vricZSQ7Hqndubklvibp86B5yoa0AlbrAgAH8stPA/640?wx_fmt=gif&wxfrom=5&wx_lazy=1
这里是标准的观察者模式类图(图片参考:《设计模式》)
我们来实现一下,但是我们这个例子里稍有变化,代码如下:
主题
代码语言:javascript复制public interface Subject {
void attachObserver(Observer observer);
void detachObserver(Observer observer);
void notifyObserver();
}
代码语言:javascript复制import java.util.ArrayList;
import java.util.List;
public class SalesDataSubject implements Subject{
private List<Observer> observers = new ArrayList<Observer>();
// 上个月销售额
private double lastMonthSales = 90;
// 这个月销售额
private double thisMonthSales = 100;
// 下个月销售额预计
private double nextMonthSales = 105;
public void attachObserver(Observer observer) {
observers.add(observer);
}
public void detachObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObserver() {
for (Observer observer : observers) {
observer.update(lastMonthSales, thisMonthSales, nextMonthSales);
}
}
}
观察者
代码语言:javascript复制public interface Observer {
void update(double lastMonthSales, double thisMonthSales, double nextMonthSales);
}
代码语言:javascript复制public class PcObserver implements Observer{
public void update(double lastMonthSales, double thisMonthSales, double nextMonthSales) {
System.out.println("PC上显示报表,上个月销售金额:" lastMonthSales "万,这个月销售金额:"
thisMonthSales "万,下个月预计销售金额:" nextMonthSales "万");
}
}
代码语言:javascript复制public class IpadObserver implements Observer{
public void update(double lastMonthSales, double thisMonthSales, double nextMonthSales) {
System.out.println("ipad上显示报表,上个月销售金额:" lastMonthSales "万,这个月销售金额:"
thisMonthSales "万,下个月预计销售金额:" nextMonthSales "万");
}
}
代码语言:javascript复制public class MobileObserver implements Observer{
public void update(double lastMonthSales, double thisMonthSales, double nextMonthSales) {
System.out.println("手机上显示报表,上个月销售金额:" lastMonthSales "万,这个月销售金额:"
thisMonthSales "万,下个月预计销售金额:" nextMonthSales "万");
}
}
测试
代码语言:javascript复制public class TestObserver {
public static void main(String[] args) {
SalesDataSubject SalesDataSubject = new SalesDataSubject();
PcObserver pcObserver = new PcObserver();
IpadObserver ipadObserver = new IpadObserver();
MobileObserver mobileObserver = new MobileObserver();
// 添加观察者
SalesDataSubject.attachObserver(pcObserver);
SalesDataSubject.attachObserver(ipadObserver);
SalesDataSubject.attachObserver(mobileObserver);
// 通知观察者
SalesDataSubject.notifyObserver();
// 删除ipad观察者
SalesDataSubject.detachObserver(ipadObserver);
System.out.println("删除ipad端显示--------");
// 通知观察者
SalesDataSubject.notifyObserver();
}
}
运行结果
代码语言:javascript复制PC上显示报表,上个月销售金额:90.0万,这个月销售金额:100.0万,下个月预计销售金额:105.0万
ipad上显示报表,上个月销售金额:90.0万,这个月销售金额:100.0万,下个月预计销售金额:105.0万
手机上显示报表,上个月销售金额:90.0万,这个月销售金额:100.0万,下个月预计销售金额:105.0万
删除ipad端显示--------
PC上显示报表,上个月销售金额:90.0万,这个月销售金额:100.0万,下个月预计销售金额:105.0万
手机上显示报表,上个月销售金额:90.0万,这个月销售金额:100.0万,下个月预计销售金额:105.0万
总结
这样Subject(主题)和Observer(观察者)之间就实现了松耦合,主题不知道也不用关心一共有多少观察者,主题只要调用notifyObserver()方法就可以通知所有的观察者了。
观察者的新增和减少,完全不会影响到主题,对主题来说无感知,实现了解耦。
”上面的例子使用的是推模型(push model),主题主动把具体的数据推给观察者;
还有一种是拉模型(pull model) 主题把自己的对象作为参数传给观察者,观察者自己要什么数据通过主题对象直接取就行了。“
老王补充道。
”观察者模式有么多优点难道就没有缺点吗?“
小帅显得有点不服气。
老王,想了想,说道:
缺点还是有的,毕竟没有完美的设计嘛。
比如主题不知道观察者的具体的实现,对更新观察者状态的代价一无所知,可能一个很小的操作都能引起一系列观察者以及依赖这些观察者对象的更新。
多少频率更新一次才好?
主题也很慌的好嘛。。。
还有,虽然观察者可以随时知道主题发生了变化,但是观察者模式没有相应的机制使观察者知道主题对象具体发生了什么变化。
主题到底改了什么?
观察者一脸懵逼。。。
”虽然有一些缺点,但是优势更加明显,观察者模式有点意思"。
小帅若有所思。
”观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子。
比如,邮件订阅、RSS Feeds,本质上都是观察者模式。
不同的应用场景和需求下,这个模式也有截然不同的实现方式,有同步阻塞的实现方式,也有异步非阻塞的实现方式;
有进程内的实现方式,也有跨进程的实现方式。。。“
老王越讲越有劲,回头一看,小帅人都不知道跑哪里去了。。。