一、场景问题
设计一套报纸发布/订阅的程序设计
- 报亭:发布内容,并分发到各个订阅者
- 读者:订阅报纸,也可取消订阅
二、传统解决方案
一种解决方式是报刊只负责生产报纸,发布内容。由各个订阅者自己去报亭拿自己的报纸。这种方式会导致各个订阅者需要不间断地轮询报亭取报纸,造成资源的浪费。所以这类问题一般会采用观察者模式来实现。
三、模式剖析
1、模式定义
观察者模式(Observer Pattern):指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式。
2、模式结构
观察者模式一般包含如下角色
-
Subject
:抽象主题角色提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
//抽象目标 abstract class Subject { protected List<Observer> observers=new ArrayList<Observer>(); //增加观察者方法 public void add(Observer observer) { observers.add(observer); } //删除观察者方法 public void remove(Observer observer) { observers.remove(observer); } //通知观察者方法 public abstract void notifyObserver(); }
-
Concrete Subject
:具体主题角色实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
//具体目标 class ConcreteSubject extends Subject { public void notifyObserver() { System.out.println("具体目标发生改变..."); System.out.println("--------------"); for(Object obs:observers) { ((Observer)obs).response(); } } }
这个
notifyObserver
的具体实现也可以放到父类中(如果各个具体的主题通知方法类似时) -
Observer
:抽象观察角色抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用
//抽象观察者 interface Observer { //反应 void response(); }
-
Concrete Observer
:具体观察者角色实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态
//具体观察者1 class ConcreteObserver1 implements Observer { public void response() { System.out.println("具体观察者1作出反应!"); } } //具体观察者2 class ConcreteObserver2 implements Observer { public void response() { System.out.println("具体观察者2作出反应!"); } }
3、模式结构图
4、使用观察者模式改进案例
4.1、代码展示
-
主题类:这里是报亭的父类,没有使用抽象类和接口,通知观察者的方法也在此类实现
public class Subject { /** * 订阅者 **/ private List<Observer> observers = new ArrayList<>(); /** * 注册观察者 * @param observer 1 * @return void **/ public void attach(Observer observer) { observers.add(observer); } /** * 取消注册 * @param observer 1 * @return void **/ public void detach(Observer observer) { observers.remove(observer); } /** * 通知订阅者 * @return void **/ protected void notifyObservers() { for (Observer observer : observers) { observer.update(this); } } }
-
报亭类
public class Newspaper extends Subject { /** * 报纸内容 */ private String content; public String getContent() { return content; } /** * 发布内容,并通知订阅者 * @param content 1 * @return void **/ public void publish(String content) { //发布新报纸 this.content = content; //通知观察者 notifyObservers(); } }
-
抽象观察者
public interface Observer { /** * 被通知的方法 * * @param subject 1 * @return void **/ void update(Subject subject); }
-
读者(具体观察者)
public class Reader implements Observer { /** * 读者姓名 */ private String name; public Reader(String name) { this.name = name; } @Override public void update(Subject subject) { System.out.println(name + "收到报纸,本期内容为:" + ((Newspaper) subject).getContent()); } }
4.2、客户端测试
public static void main(String[] args) {
//创建报亭
Newspaper subject = new Newspaper();
//注册订阅者
Reader reader1 = new Reader("张三");
Reader reader2 = new Reader("李四");
Reader reader3 = new Reader("王五");
subject.attach(reader1);
subject.attach(reader2);
subject.attach(reader3);
//报亭发布内容
subject.publish("观察者模式");
}
输出
张三收到报纸,本期内容为:观察者模式
李四收到报纸,本期内容为:观察者模式
王五收到报纸,本期内容为:观察者模式
5、优缺点
- 优点
- 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
- 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
- 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
- 观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。
- 缺点
- 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
6、使用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制