1. 需求
假如公司里的员工都在偷偷炒股,为了防止老板发现,现在需要开发一个摸鱼系统,要求前台小姐发现老板回来之后要及时通知大家。
2. 代码版本1.0(双向耦合版)
2.1 UML类图
2.2 前台小姐类
//前台秘书类
public class Secretary {
protected String name;
public Secretary(String name){
this.name = name;
}
//同事列表
private ArrayList<StockObserver> list = new ArrayList<>();
private String action;
//增加同事(有几个同事需要前台通知,就新增几个对象)
public void attach(StockObserver observer){
list.add(observer);
}
//通知
public void notifyEmployee(){
//待老板来了,就给所有登记过的同事发通知
for (StockObserver item : list) {
item.update();
}
}
//得到状态
public String getAction(){
return this.action;
}
//设置状态(就是设置具体通知的话)
public void setAction(String value){
this.action = value;
}
}
2.3 看股票类
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(this.sub.name+":"+this.sub.getAction()+"!"+this.name+",请关闭股票行情,赶紧工作。");
}
}
2.4 客户端类
//前台小姐
Secretary secretary = new Secretary("前台小姐");
//看股票的同事
StockObserver employee1 = new StockObserver("同事1", secretary);
StockObserver employee2 = new StockObserver("同事2", secretary);
//前台登记下两个同事
secretary.attach(employee1);
secretary.attach(employee2);
//当发现老板回来时
secretary.setAction("老板回来了");
//通知两个同事
secretary.notifyEmployee();
2.5 输出结果
2.6 弊端
由此看出,前台小姐类和看股票类的耦合度过高,而且实际情况中所有同事都不一定在同一件事上摸鱼。
为了遵循开放–封闭 和 依赖倒置原则,应该让程序都依赖抽象,而不是相互依赖。
3. 代码版本2.0(解耦版1)
3.1 UML 类图
3.2 抽象观察者类
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();
}
3.3 具体观察者类
public class StockObserver extends Observer{
public StockObserver(String name, Secretary sub) {
super(name, sub);
}
@Override
public void update() {
System.out.println(super.sub.name+":"+super.sub.getAction()+"!"+super.name+",请关闭股票行情,赶紧工作!");
}
}
public class NBAObserver extends Observer{
public NBAObserver(String name, Secretary sub) {
super(name, sub);
}
@Override
public void update() {
System.out.println(super.sub.name+":"+super.sub.getAction()+"!"+super.name+",请关闭NBA直播,赶紧工作!");
}
}
3.4 前台小姐类
//前台秘书类
public class Secretary {
protected String name;
public Secretary(String name){
this.name = name;
}
//同事列表
private ArrayList<Observer> list = new ArrayList<>();
private String action;
//增加同事(有几个同事需要前台通知,就新增几个对象)
public void attach(Observer observer){
list.add(observer);
}
//通知
public void notifyEmployee(){
//待老板来了,就给所有登记过的同事发通知
for (Observer item : list) {
item.update();
}
}
//得到状态
public String getAction(){
return this.action;
}
//设置状态(就是设置具体通知的话)
public void setAction(String value){
this.action = value;
}
}
3.5 弊端
在具体观察者类中,与前台小姐类耦合了,应该把“前台小姐类”抽象出来。
4. 代码版本3.0(解耦版2,观察者模式实现)
4.1 UML类图
4.2 抽象通知者类
抽象通知者,可以是接口,也可以是抽象类。
public abstract class Subject {
protected String name;
public Subject(String name){
this.name = name;
}
//同事列表
private ArrayList<Observer> list = new ArrayList<>();
private String action;
//增加同事(有几个同事需要前台通知,就新增几个对象)
public void attach(Observer observer){
list.add(observer);
}
//减少同事
public void detach(Observer observer){
list.remove(observer);
}
//通知
public void notifyEmployee(){
//待老板来了,就给所有登记过的同事发通知
for (Observer item : list) {
item.update();
}
}
//得到状态
public String getAction(){
return this.action;
}
//设置状态(就是设置具体通知的话)
public void setAction(String value){
this.action = value;
}
}
4.3 具体通知者类
具体的通知者类可能是前台秘书,也可能是老板,它们也许有各自的一些方法,但对于通知者来说,它们是一样的,所以它们都去继承这个抽象类Subject。
public class Boss extends Subject{
public Boss(String name) {
super(name);
}
//拥有自己的属性和方法
}
public class Secretary extends Subject{
public Secretary(String name) {
super(name);
}
//拥有自己的属性和方法
}
4.4 抽象观察者类
public abstract class Observer {
protected String name;
protected Subject sub;
public Observer(String name, Subject sub){
this.name = name;
this.sub = sub;
}
public abstract void update();
}
4.5 具体观察者类
public class NBAObserver extends Observer {
public NBAObserver(String name, Subject sub) {
super(name, sub);
}
@Override
public void update() {
System.out.println(super.sub.name+":"+super.sub.getAction()+"!"+super.name+",请关闭NBA直播,赶紧工作!");
}
}
public class StockObserver extends Observer {
public StockObserver(String name, Subject sub) {
super(name, sub);
}
@Override
public void update() {
System.out.println(super.sub.name+":"+super.sub.getAction()+"!"+super.name+",请关闭股票行情,赶紧工作!");
}
}
4.6 客户端类
Subject boss1 = new Boss("老板1");
//看股票的同事
Observer employee1 = new StockObserver("同事1",boss1);
Observer employee2 = new StockObserver("同事2",boss1);
//看NBA的同事
Observer employee3 = new NBAObserver("同事3",boss1);
//老板登记下三个同事
boss1.attach(employee1);
boss1.attach(employee2);
boss1.attach(employee3);
boss1.detach(employee1);//同事1其实没有被通知到,所以减去
//老板回来
boss1.setAction("我是老板,我回来了");
//通知两个同事
boss1.notifyEmployee();
4.7 输出结果
老板1:我是老板,我回来了!同事2,请关闭股票行情,赶紧工作!
老板1:我是老板,我回来了!同事3,请关闭NBA直播,赶紧工作!
5. 观察者模式
5.1 概念
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
5.2 使用场景
将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。
而观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦Subject的状态发生了改变,所有的Observer都可以得到通知。Subject发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。
总结得出观察者模式的使用场景如下:
- 当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象有待改变
- 解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。
5.3 案例代码
5.3.1 UML 类图
5.3.2 抽象通知者类
Subject类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对
象。
public abstract class Subject {
private ArrayList<Observer> list = new ArrayList<Observer>();//针对抽象的Observer编程
//增加观察者
public void attach(Observer observer){
list.add(observer);
}
//减少观察者
public void detach(Observer observer){
list.remove(observer);
}
//通知观察者
public void notifyObserver(){
for (Observer item : list) {
item.update();
}
}
protected String subjectState;
public String getSubjectState(){
return this.subjectState;
}
public void setSubjectState(String val){
this.subjectState = val;
}
}
5.3.3 抽象观察者类
Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫作更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个update方法,这个方法叫作更新方法。
public abstract class Observer {
public abstract void update();
}
5.3.4 具体通知者类
ConcreteSubject类,叫作具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
public class ConcreteSubject extends Subject{
//具体的通知者方法
}
5.3.5 具体观察者类
ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
public class ConcreteObserver extends Observer{
private String name;
private Subject sub;
public ConcreteObserver(String name,Subject sub){
this.name = name;
this.sub = sub;
}
@Override
public void update() {
System.out.println("观察者"+this.name+"的新状态"+this.sub.getSubjectState());
}
}
5.3.6 客户端类
Subject subject = new ConcreteSubject();
subject.attach(new ConcreteObserver("NameX",subject));
subject.attach(new ConcreteObserver("NameY",subject));
subject.attach(new ConcreteObserver("NameZ",subject));
subject.setSubjectState("ABC");
subject.notifyObserver();
6. Java内置接口实现
现实编程中,具体的观察者完全有可能是风马牛不相及的类,但它们都需要根据通知者的通知来做出update的操作,所以让它们都实现一个接口就可以实现这个想法了。
正好,Java已经为观察者模式准备好了相关的接口和抽象类了 有 了 这 些 Java 内 置 代 码 的 支 持 , 你 只 需 要 扩 展 或 继 承Observable,并告诉它什么时候应该通知观察者,就OK了,剩下的事Java会帮你做。
6.1 代码版本4.0(Java内置接口实现)
6.1.1 UML类图
6.1.2 具体通知者板类
由 于 已 经 有 了 Observable 实 现 的 各 种 方 法 , 比 如 加 观 察 者( addObserver ) 、 减 观 察 者 ( deleteObserver ) 、 通 知 观 察 者(notifyObservers)等。所以Boss类继承了Observable,已经无须再实现这些代码了。
Boss继承Observable类,当addObserver添加一些观察者后,它在setAction里是这样工作的:调用setChanged方法,标记状态已经改变,然后调用notifyObservers方法来通知观察者。
public class Boss extends Observable {
protected String name;
private String action;
public Boss(String name){
this.name = name;
}
//得到状态
public String getAction(){
return this.action;
}
//设置状态(就是设置具体通知的话)
public void setAction(String val){
this.action = val;
super.clearChanged();
super.notifyObservers();
}
}
6.1.3 具体观察者类
public class StockObserver implements Observer {
protected String name;
public StockObserver(String name){
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
Boss b = (Boss)o;//需要拆箱将Observable对象转换成Boss
System.out.println(b.name+":"+b.getAction()+"!"+this.name+",请关闭股票行情,赶紧工作");
}
}
6.1.4 客户端
Boss boss1 = new Boss("老板1");
//看股票的同事
Observer employee1 = new StockObserver("同事1");
Observer employee2 = new StockObserver("同事2");
Observer employee3 = new StockObserver("同事3");
//老板登记两个同事
boss1.addObserver(employee1);
boss1.addObserver(employee2);
boss1.deleteObserver(employee3);//同事3其实没有通知到,所以减去
//老板回来
boss1.setAction("我是老板。我回来了");
6.1.5 不足
这个版本的代码仍然存在不足,在StockObserver类中,竟然出现了Boss,具体类中耦合了具体类了,这就没有针对接口编程了。下面给出改良版
6.1.6 改良版UML类图
6.1.7 改良版抽象通知者类和具体通知者类
public class Subject extends Observable {
protected String name;
private String action;
public Subject(String name){
this.name = name;
}
//得到状态
public String getAction(){
return this.action;
}
//设置状态(就是设置具体通知的话)
public void setAction(String val){
this.action = val;
super.clearChanged();
super.notifyObservers();
}
}
public class Boss extends Subject {
public Boss(String name){
super(name);
}
}
6.1.8 改良版具体观察者类
public class StockObserver implements Observer {
protected String name;
public StockObserver(String name){
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
Subject b = (Subject) o;//需要拆箱将Observable对象转换成Boss
System.out.println(b.name+":"+b.getAction()+"!"+this.name+",请关闭股票行情,赶紧工作");
}
}