23种设计模式之观察者模式
参考资料
- Java设计模式:23种设计模式全面解析(超级详细)
- 韩顺平老师的Java设计模式(图解+框架源码剖析)
- 秦小波老师的《设计模式之禅》
下文如有错漏之处,敬请指正
一、简介
定义
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。(当被观察者的内容发生改变时会通知所有的观察者)
特点
- 观察者模式是一种行为型模式
通用类图
观察者模式的主要角色:
-
Subject
被观察者角色
提供了一个用于保存观察者对象的集合,可以动态增加、删除观察者。用于管理观察者并通知观察者。
-
ConcreteSubject
具体被观察者角色
定义自己的业务逻辑,实现抽象被观察者中的抽象方法,当具体被观察者的内部状态发生改变时,会通知所有注册过的观察者对象。
-
Observer
抽象观察者角色
它是一个抽象类或接口,包含一个update的抽象方法,当收到被观察者的消息时被调用。
-
ConcreteObserver
具体观察者角色
实现抽象观察者中定义的抽象方法,各个观察者有自己的处理逻辑。
优点
- 降低了被观察者与观察者之间的耦合关系,两者之间是抽象耦合关系,如此设计,则不管是增加观察者还是被观察者都非常容易扩展。
- 被观察者与观察者之间建立了一套触发机制,被观察者设置触发条件,一旦触发便通知所有的观察者。
缺点
-
被观察者与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用,即观察者可能也是被观察者。
-
当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
应用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 跨系统的消息交换场景,如消息队列的处理机制。
二、观察者模式
需求:
现在有一支股票,股民可以设置该股票上涨时的提醒。使用观察者模式模拟这一功能。
抽象被观察者:
package observer.standard;
import java.util.ArrayList;
import java.util.List;
public abstract class Subject {
// 定义一个观察者集合
protected List<Observer> observers = new ArrayList<>();
// 增加一个观察者
public void add(Observer observer) {
observers.add(observer);
}
// 删除一个观察者
public void remove(Observer observer) {
observers.remove(observer);
}
// 通知所有的观察者
public abstract void notifyObservers();
}
具体被观察者:
package observer.standard;
public class Stock extends Subject {
private double money = 1.0;
// 自定义通知方法的逻辑
public void setMoney(double money) {
// 只有当前股价上涨了,才通知所有的观察者
if (money > this.money) {
this.money = money;
notifyObservers();
}
this.money = money;
}
// 实现通知方法
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(money);
}
}
}
抽象观察者:
package observer.standard;
public interface Observer {
// 更新方法
public void update(double money);
}
具体观察者:
package observer.standard;
public class Investor implements Observer {
private String name;
public Investor(String name) {
this.name = name;
}
// 实现更新方法
@Override
public void update(double money) {
System.out.println(name + "您关注的股票上涨了,现在的价格是每股" + money + "元");
}
}
Client:
package observer.standard;
public class Client {
public static void main(String[] args) {
// 模拟三个用户订阅了同一种股票的涨时提醒
Observer user1 = new Investor("Space");
Observer user2 = new Investor("Spencer");
Observer user3 = new Investor("Alice");
// 添加观察者
Stock stock = new Stock();
stock.add(user1);
stock.add(user2);
stock.add(user3);
// 被观察者更新状态,并通知所有的观察者
stock.setMoney(1.12);
System.out.println("===================");
stock.setMoney(1.2);
System.out.println("===================");
// 由于股价没有上涨,因此没有通知观察者
stock.setMoney(1.1);
/**
* 输出结果:
* Space您关注的股票上涨了,现在的价格是每股1.12元
* Spencer您关注的股票上涨了,现在的价格是每股1.12元
* Alice您关注的股票上涨了,现在的价格是每股1.12元
* ===================
* Space您关注的股票上涨了,现在的价格是每股1.2元
* Spencer您关注的股票上涨了,现在的价格是每股1.2元
* Alice您关注的股票上涨了,现在的价格是每股1.2元
* ===================
*/
}
}
当有新的股民要设置股票的涨时提醒,只需要将其加入到观察者集合中即可。
当有新的股票时,需要增加新的具体主体类(被观察者),当新的股票发生变化时会通知所有的观察者,但实际我们只希望关注了该股票的股民收到消息,此时可以使用双分派指定对象的类型(被观察者的类型)进而选择更新方法是否执行。好在JDK已经为我们实现了具有双分派的观察者功能。
JDK内置的观察者模式
具体被观察者:
package observer.jdk;
import java.util.Observable;
public class StockA extends Observable {
private double money = 1.0;
// 自定义通知方法的逻辑
public void setMoney(double money) {
// 只有当前股价上涨了,才通知所有的观察者
if (money > this.money) {
// 更新股价
this.money = money;
// 设置被观察者的内容已被更改
this.setChanged();
// 通知所有的观察者更新内容
this.notifyObservers(money);
}
// 更新股价
this.money = money;
}
}
package observer.jdk;
import java.util.Observable;
public class StockB extends Observable {
private double money = 1.0;
// 自定义通知方法的逻辑
public void setMoney(double money) {
// 只有当前股价上涨了,才通知所有的观察者
if (money > this.money) {
// 更新股价
this.money = money;
// 设置被观察者的内容已被更改
this.setChanged();
// 通知所有的观察者更新内容
this.notifyObservers(money);
}
// 更新股价
this.money = money;
}
}
观察者:
package observer.jdk;
import java.util.Observable;
import java.util.Observer;
public class Investor implements Observer {
private String name;
public Investor(String name) {
this.name = name;
}
// 实现更新方法
@Overrideaaa
public void update(Observable o, Object arg) {
// 判断是否是(特定)关注该股票的被观察者,只观察StockA的变化
if(o instanceof StockA){
System.out.println(name+"您关注的股票A上涨了,现在的价格是每股"+arg+"元");
}
}
}
Client:
package observer.jdk;
public class Client {
public static void main(String[] args) {
// 模拟三个用户订阅了同一种股票的涨时提醒
Investor user1 = new Investor("Space");
Investor user2 = new Investor("Spencer");
Investor user3 = new Investor("Alice");
// 为StockA添加观察者
StockA stockA = new StockA();
stockA.addObserver(user1);
stockA.addObserver(user2);
stockA.addObserver(user3);
// 为StockB添加观察者
StockB stockB = new StockB();
stockB.addObserver(user1);
stockB.addObserver(user2);
stockB.addObserver(user3);
// 更新StockA的股价
System.out.println("========StockA===========");
stockA.setMoney(1.12);
System.out.println("===================");
stockA.setMoney(1.2);
// 更新StockB的股价,由于不管查StockB的变化,因此StockB没有任何输出
System.out.println("========StockB===========");
stockB.setMoney(1.12);
System.out.println("===================");
// 被观察者更新状态,并通知所有的观察者
stockB.setMoney(1.2);
/**
* 输出结果:
* ========StockA===========
* Alice您关注的股票A上涨了,现在的价格是每股1.12元
* Spencer您关注的股票A上涨了,现在的价格是每股1.12元
* Space您关注的股票A上涨了,现在的价格是每股1.12元
* ===================
* Alice您关注的股票A上涨了,现在的价格是每股1.2元
* Spencer您关注的股票A上涨了,现在的价格是每股1.2元
* Space您关注的股票A上涨了,现在的价格是每股1.2元
* ========StockB===========
* ===================
*/
}
}
三、总结
观察者模式可以为一对多的依赖关系建立一套触发机制,当被被观察者触发机制后,会通知所有的观察者进行更新操作。并且通过JDK内置的java.util.Observable实现类和java.util.Observer接口可以十分便捷的实现观察者模式,且可以对特定的观察者进行处理。