作为一个编程菜鸟,过去在学习设计模式的时候,老师给推荐了一本《大话设计模式》。阅读以后受益匪浅,可惜当初没有坚持看完。
最近有时间了,又重新捡起来学习了一遍,整理了一下笔记,由于本人能力有限,欢迎大家批评指正。
1.观察者模式 Observer Pattern
- 又叫做发布-订阅模式、模型-视图模式、源-收听者模式或从属者模式。
- 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
- 或者说一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。
- 此种模式通常被用来实现事件处理系统。
2.基本简介
- 观察者模式完美的将观察者和被观察者的对象分离开。举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。
- 面向对象设计的一个原则是:系统中的每个类将重点常在某一个功能上,而不是其他方面。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
- 观察者模式定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
3.实现方式
观察者模式有很多实现方式,从根本上说,该模式必须包含两个角色:观察者和被观察者对象。
在刚才的例子中,业务数据是被观察对象,用户界面是观察者。观察者和被观察者之间存在“观察”的逻辑关联,当被观察者发生改变的时候,观察者就会观察到这样的变化,并且做出相应的响应。如果在用户界面、业务数据之间使用这样的观察过程,可以确保界面和数据之间划清界限,假定应用程序的需求发生变化,需要修改界面的表现,只需要重新构建一个用户界面,业务数据不需要发生变化。
实现观察者模式的时候要注意,观察者和被观察者对象之间的互动关系不能体现成类之间的直接调用,否则就将使观察者和被观察者之间紧密的耦合起来,从根本上违反面向对象的设计的原则。无论是观察者“观察“对象,还是被观察者将自己的改变”通知“观察者,都不应该直接调用。
4.uml类图
5.组成
(1)subject类
可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
(2)Observer类
抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现,更新接口通常包含一个Update()方法,这个方法叫更新方法。
(3)ConcreteSubject类
叫做具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
(4)ConreteObserver类
具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
6.实例
(1)需求
当老板来时,前台通知不务正业的同事回到工作岗位上来。
(2)实例结构图
(3)代码
a.抽象通知者接口
package com.longinus.op;
public interface Subject {
public void Attach(Observer obs);
public void Detach(Observer obs);
public void Notify();
public String getSubState();
public void setSubState(String str);
}
b.具体通知者前台类
package com.longinus.op;
import java.util.ArrayList;
import java.util.List;
public class Foreground implements Subject {
private List<Observer> obslist = new ArrayList<>();
private String state;
@Override
public void Attach(Observer obs) {
// TODO Auto-generated method stub
obslist.add(obs);
}
@Override
public void Detach(Observer obs) {
// TODO Auto-generated method stub
obslist.remove(obs);
}
@Override
public void Notify() {
// TODO Auto-generated method stub
for(Observer obs : obslist){
obs.upDate();
}
}
@Override
public String getSubState() {
// TODO Auto-generated method stub
return state;
}
@Override
public void setSubState(String state) {
// TODO Auto-generated method stub
this.state = state;
}
}
c.抽象观察者接口
package com.longinus.op;
public abstract class Observer {
public abstract void upDate();
}
d.看股票的同事
package com.longinus.op;
public class StockWorkMate extends Observer {
private String name;
private Subject subj;
public StockWorkMate(String name,Subject subj) {
// TODO Auto-generated constructor stub
this.name = name;
this.subj = subj;
}
@Override
public void upDate() {
// TODO Auto-generated method stub
System.out.println(subj.getSubState() +","+ name + ",关闭股票行情,继续工作!");
}
}
e.看NBA的同事
package com.longinus.op;
public class NBAWorkMate extends Observer {
private String name;
private Subject subj;
public NBAWorkMate(String name,Subject subj) {
// TODO Auto-generated constructor stub
this.name = name;
this.subj = subj;
}
@Override
public void upDate() {
// TODO Auto-generated method stub
System.out.println(subj.getSubState() +","+ name + ",关闭篮球比赛,继续工作!");
}
}
f.测试类
package com.longinus.op;
public class Test {
public static void main(String[] args) {
Foreground fg = new Foreground();
StockWorkMate swm = new StockWorkMate("李雷", fg);
NBAWorkMate nwm = new NBAWorkMate("韩梅梅", fg);
fg.Attach(nwm);
fg.Attach(swm);
fg.setSubState("老板来了");
fg.Notify();
}
}
g.输出结果
老板来了,韩梅梅,关闭篮球比赛,继续工作!
老板来了,李雷,关闭股票行情,继续工作!
7.观察者模式特点
- 将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使给类紧密耦合。这样会给维护、扩展和重用都带来不便。Subject发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。
- 何时使用?当一个对象的改变需要同时改变其他对象时,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。
8.拓展
- 尽管已经用了依赖倒转原则,但是“抽象通知者”还是依赖“抽象观察者”,当没有抽象观察者这样的接口时,通知的功能就完成不了了。此时如果通知者和观察者之间根本就互相不知道,通过事件委托由客户端来决定通知谁。
- 委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。委托可以看作是对函数的抽象,是函数的‘类’,委托的实例将代表一个具体的函数。
- 一个委托可以搭载多个方法,所有方法被一次唤起。更重要的是,它可以使得委托对象所搭载的方法并不需要属于同一个类。委托对象所搭载的所有方法必须具有相同的原形和形式,也就是拥有相同的参数列表和返回值类型。