举个栗子
问题描述
几个同事上班期间看股市行情,让前台MM帮忙看着老板什么时候过来查岗,老板进门的时候MM就拨电话给其中一个同事,于是所有同事都知道了,再继续工作。。。
简单实现
前台秘书MM
/**
* 前台秘书MM
* Created by callmeDevil on 2019/7/27.
*/
public class Secretary {
// 同事列表
private List<StockObserver> observers = new ArrayList<>();
private String action;
// 增加
public void attach(StockObserver observer){
// 有几个同事请前台帮忙,于是就给集合增加几个对象
observers.add(observer);
}
// 通知
public void call(){
// 待老板来了,就给所有登记的同事们发通知
for (StockObserver observer : observers) {
observer.update();
}
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
看股票同事
/**
* 看股票同事
* Created by callmeDevil on 2019/7/27.
*/
public class StockObserver {
private String name;
private Secretary sub;
public StockObserver(String name, Secretary sub){
this.name = name;
this.sub = sub;
}
public void update(){
System.out.println(String.format("%s %s 关闭股票行情,继续工作!", sub.getAction(), name));
}
}
测试
public class Test {
public static void main(String[] args) {
// 前台妹子
Secretary mm = new Secretary();
// 看股票的同事
StockObserver observer1 = new StockObserver("哪路托", mm);
StockObserver observer2 = new StockObserver("啥是gay", mm);
// 前台妹子记下两位同事
mm.attach(observer1);
mm.attach(observer2);
// 发现老板
mm.setAction("老板回来了!");
// 通知两个同事
mm.call();
}
}
测试结果
老板回来了! 哪路托 关闭股票行情,继续工作!
老板回来了! 啥是gay 关闭股票行情,继续工作!
存在问题
- “前台MM”和“看股票同事”互相耦合
- 如果还有人想看NBA直播,那只能改动“前台MM”,不符合开放-封闭原则
- 其次应该遵循依赖倒转原则,让两者之间不相互依赖
简单实现2
抽象观察者
/**
* 抽象观察者
* Created by callmeDevil on 2019/7/27.
*/
public abstract class Observer {
protected String name;
protected Secretary sub;
public Observer(String name, Secretary sub) {
this.name = name;
this.sub = sub;
}
public abstract void update();
}
前台秘书MM
/**
* 前台秘书MM
* Created by callmeDevil on 2019/7/27.
*/
public class Secretary {
// 同事列表
private List<Observer> observers = new ArrayList<>();
private String action;
// 增加
public void attach(Observer observer) { //针对抽象编程,减少了与具体类的耦合
observers.add(observer);
}
// 减少
public void detach(Observer observer) { //针对抽象编程,减少了与具体类的耦合
observers.remove(observer);
}
// 通知
public void call() {
for (Observer observer : observers) {
observer.update();
}
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
看股票的同事
/**
* 看股票的同事
* Created by callmeDevil on 2019/7/27.
*/
public class StockObserver extends Observer {
public StockObserver(String name, Secretary sub) {
super(name, sub);
}
@Override
public void update(){
System.out.println(String.format("%s %s 关闭股票行情,继续工作!", sub.getAction(), name));
}
}
看NBA的同事
/**
* 看 NBA 的同事
* Created by callmeDevil on 2019/7/27.
*/
public class NBAObserver extends Observer {
public NBAObserver(String name, Secretary sub) {
super(name, sub);
}
@Override
public void update() {
System.out.println(String.format("%s %s 关闭NBA 直播,继续工作!", sub.getAction(), name));
}
}
测试代码同上
存在问题
其实“前台MM”也应该抽象出来,如果老板回来时,MM来不及电话了,于是通知大家的任务变成谁来做?是的,这时候是老板本人变成了通知者。
观察者模式
定义
又叫做发布-订阅模式。定义了一种一对多的依赖关系,让多个观察者对象同事监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
UML图
代码实现
通知者接口
/**
* 通知者接口
* Created by callmeDevil on 2019/7/27.
*/
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void call();
String getAction();
void setAction(String action);
}
老板
/**
* 老板
* Created by callmeDevil on 2019/7/27.
*/
public class Boss implements Subject {
// 同事列表
private List<Observer> observers = new ArrayList<>();
private String action;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void call() {
for (Observer observer : observers) {
observer.update();
}
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
抽象观察者
/**
* 抽象观察者
* Created by callmeDevil on 2019/7/27.
*/
public abstract class Observer {
protected String name;
protected Subject sub;
public Observer(String name, Subject sub) { // 原来是“前台MM”,现改成“抽象通知者”
this.name = name;
this.sub = sub;
}
public abstract void update();
}
看股票的同事
/**
* 看股票的同事
* Created by callmeDevil on 2019/7/27.
*/
public class StockObserver extends Observer {
public StockObserver(String name, Subject sub) { // 原来是“前台MM”,现改成“抽象通知者”
super(name, sub);
}
@Override
public void update(){
System.out.println(String.format("%s %s 关闭股票行情,继续工作!", sub.getAction(), name));
}
}
“看NBA的同事”实现与“看股票的同事”类似,此处省略
测试
public class Test {
public static void main(String[] args) {
// 老板
Boss boss = new Boss();
// 看股票的同事
StockObserver observer1 = new StockObserver("哪路托", boss);
// 看NBA的同事
NBAObserver observer2 = new NBAObserver("啥事gay", boss);
boss.attach(observer1);
boss.attach(observer2);
boss.detach(observer1); // 主角光环!斑怎么样都打不过哪路托!所以减去
// 斑复活了!
boss.setAction("我宇智波斑复活了!");
// 发出通知
boss.call();
}
}
测试结果
我宇智波斑复活了! 啥事gay 关闭NBA 直播,继续工作!
总结
- 动机是什么?将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象之间的一致性,我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。
- 什么时候使用?当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体有多少对象待改变;一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立的改变和复用。
- 总的来讲,观察者模式所在的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一边的改变。
- 缺点?举个栗子,比如 VS2005,当你点击运行程序时,与运行程序以后,处理弹出一个控制台的程序窗体以外,工具栏发生了变化,工具箱不见了...。仅仅是点击了一个“运行”按钮,就发生这么多变化,而各个变化都涉及到不同的控件,并且没办法让每个控件都去实现一个“Observer”接口,也就是说不可能用接口的方式且实现观察模式。再回过头来看上面的栗子,尽管已经使用了依赖倒转原则,但是“抽象通知者”还是依赖“抽象观察者”,也就是说,万一没有了抽象观察者这样的接口,这通知的功能就完成不了了。另外就是每个具体观察者,他不一定是“更新”的方法要调用,就像刚才说的,我希望的是“工具箱”是隐藏,“自动窗口”是打开,这根本就不是同名的方法,这就是观察者模式不足的地方。在 .NET 中,可以用事件委托较好的解决该缺点,有兴趣的可以查看他人文章,此处不再叙述。