观察者模式
观察者模式属于行为型模式。在程序设计中,观察者模式通常由两个对象组成:观察者和被观察者。当被观察者状态发生改变时,它会通知所有的观察者对象,使他们能够及时做出响应,所以也被称作“发布-订阅模式”。
组成
- 抽象主题(Subject):定义了一个接口,包含了注册观察者、删除观察者、通知观察者等方法。
- 具体主题(ConcreteSubject):实现了抽象被观察者接口,维护了一个观察者列表,并在状态发生改变时通知所有注册的观察者。
- 抽象观察者(Observer):定义了一个接口,包含了更新状态的方法。
- 具体观察者(ConcreteObserver):实现了抽象观察者接口,存储了需要观察的被观察者对象,并在被观察者状态发生改变时进行相应的处理。
例子
股票充当主题,投资者充当观察者,但股票价格发生变动时,通知所有的观察者
抽象主题
1 2 3 4 5 6
| public abstract class Subject { public abstract void addObserver(Observer o); public abstract void delObserver(Observer o); public abstract void notifyObserver(); }
|
股票:具体主题
持有一个观察者的集合,当股票价格变动时,调用notifyobserver方法通知所有观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class Stock extends Subject{
private ArrayList<Observer> list = new ArrayList<>(); private String name; private double price;
public Stock(String name,double price){ this.name = name; this.price = price; }
@Override public void addObserver(Observer o) { list.add(o); }
@Override public void delObserver(Observer o) { list.remove(o); }
@Override public void notifyObserver() { for(Observer o : list){ o.respond(this); } }
public void setPrice(double price){ double rate = Math.abs(price-this.price)/this.price; this.price = price; if(rate >= 0.05){ notifyObserver(); } }
public String getName(){ return this.name; }
public double getPrice(){ return this.price; }
}
|
抽象观察者
1 2 3
| public interface Observer { public void respond(Stock stock); }
|
投资者:具体观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Investor implements Observer{ private String name; public Investor(String name){ this.name = name; }
@Override public void respond(Stock stock) { System.out.println("提示股民:" + this.name); System.out.println("股票"+ stock.getName()+"价格变化超过5%"); System.out.println("当前的价格为:"+ stock.getPrice()); } }
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Client { public static void main(String[] args) { Stock stock1 = new Stock("苹果",100); Stock stock2 = new Stock("华为",80);
Investor investor1 = new Investor("盖伦"); Investor investor2 = new Investor("赵信");
stock1.addObserver(investor1); stock2.addObserver(investor1); stock1.addObserver(investor2); stock2.addObserver(investor2);
stock1.setPrice(102); stock2.setPrice(100); } }
|
优点:
- 被观察者和观察者对象之间不需要知道对方的具体实现,只需要知道对方的接口,避免了紧耦合的关系。
- 由于被观察者对象并不关心具体的观察者是谁,所以在程序运行的过程中,可以动态地增加或者删除观察者对象,增加了灵活性。
- 符合开闭原则,当需要添加新的观察者时,只需要添加一个实现观察者接口的类,而不需要修改被观察者对象的代码。
缺点:
当观察者没有被正确移除时,可能会导致内存泄漏的问题。
实现观察者模式,需要定义多个接口和类,增加了程序的复杂度。
在某些情况下,被观察者和观察者对象之间可能出现循环依赖的问题。
推和拉
主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
应用场景
生活场景
拍卖的时候,拍卖师是观察者,价格是被观察者。拍卖师观察最高标价,然后通知给其他竞价者竞价。
共享单车:共享单车是被观察者对象,用户是观察者对象。当有新的单车被放置或被租用时,系统会发送给用户通知。
微信公众号:微信公众号是被观察者对象,粉丝是观察者对象。当公众号发布了新的文章或消息时,系统会发送消息给关注该公众号的粉丝。
程序场景
当一个对象的状态发生改变时,需要通知多个对象做出相应的响应。例如,王者荣耀更新前,会通知所有用户要更新的时间。
当很多对象同时对某一个主题感兴趣时,可以采用观察者模式实现发布-订阅模式。例如,生产者发送消息到消息队列中,并通知所有订阅此队列的消费者进行消费。
数据库开发中,当数据库表中的数据发生变化时,需要通知相关的模块进行更新或其他操作。例如,当用户更新了数据库中的某个记录时,就可以通过观察者模式通知所有注册的监听器进行响应。