0%

java-设计模式(六)

二十一、观察者模式(observer)

定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。

主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。

img

  • 主题(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。

  • 观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法,观察者为所有的具体观察者定义一个接口,在得到主题的通知时更新自己

  • ConcreteSubject类是具体主题,将有关状态存入具体观察者对象,在具体主题内部状态改变时,给所有登记过的观察者发出通知;

  • ConcreteObserver是具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协同。

Implementation

主题Subject

  首先定义一个观察者数组,并实现增、删及通知操作。它的职责很简单,就是定义谁能观察,谁不能观察,用Vector是线程同步的,比较安全,也可以使用ArrayList,是线程异步的,但不安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Subject {

//观察者数组
private Vector<Observer> oVector = new Vector<>();

//增加一个观察者
public void addObserver(Observer observer) {
this.oVector.add(observer);
}

//删除一个观察者
public void deleteObserver(Observer observer) {
this.oVector.remove(observer);
}

//通知所有观察者
public void notifyObserver() {
for(Observer observer : this.oVector) {
observer.update();
}
}

}

抽象观察者Observer

  观察者一般是一个接口,每一个实现该接口的实现类都是具体观察者。

1
2
3
4
public interface Observer {
//更新
public void update();
}

具体主题

  继承Subject类,在这里实现具体业务,在具体项目中,该类会有很多变种。

1
2
3
4
5
6
7
8
9
public class ConcreteSubject extends Subject {

//具体业务
public void doSomething() {
//...
super.notifyObserver();
}

}

具体观察者

  实现Observer接口。

1
2
3
4
5
6
7
8
public class ConcreteObserver implements Observer {

@Override
public void update() {
System.out.println("收到消息,进行处理");
}

}

Client客户端

  首先创建一个被观察者,然后定义一个观察者,将该被观察者添加到该观察者的观察者数组中,进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client {

public static void main(String[] args) {
//创建一个主题
ConcreteSubject subject = new ConcreteSubject();
//定义一个观察者
Observer observer = new ConcreteObserver();
//观察
subject.addObserver(observer);
//开始活动
subject.doSomething();
}

}

开发中常见的场景

  • 聊天室程序的,服务器转发给所有客户端

  • 网络游戏(多人联机对战)场景中,服务器将客户端的状态进行分发

  • 邮件订阅

  • Servlet中,监听器的实现

  • Android中,广播机制

  • JDK的AWT中事件处理模型,基于观察者模式的委派事件模型(Delegation Event Model)

    • 事件源—————-目标对象

    • 事件监听器————观察者

  • 京东商城中,群发某商品打折信息

观察者模式的应用

  1. 何时使用

  • 一个对象状态改变,所有的依赖对象都将得到通知

 2. 方法

  • 使用面向对象技术

 3. 优点

  • 观察者和被观察者是抽象耦合的
  • 建立了一套触发机制

 4. 缺点

  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
  • 如果观察者和观察目标间有循环依赖,可能导致系统崩溃
  • 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的

 5. 使用场景

  • 关联行为场景
  • 事件多级触发场景
  • 跨系统的消息变换场景,如消息队列的处理机制

  6. 应用实例

  • 手机丢了,委托别人给其他人发消息通知
  • 通知老师/老板来了
  • 拍卖,拍卖师观察最高标价,然后通知给其它竞价者竞价
  • 在一个目录下建立一个文件,会同时通知目录管理器增加目录,并通知磁盘减少空间,文件是被观察者,目录管理器和磁盘管理器是观察者
  • 猫叫了一声,吓着了老鼠,也惊到了主人,猫是被观察者,老鼠和人是观察者

  7. 注意事项

  • 避免循环引用
  • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式

二十二、备忘录模式(Memento)

在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。

img

  • Originator:原始对象
  • Caretaker:负责保存好备忘录
  • Memento:备忘录,存储原始对象的状态。备忘录实际上有两个接口,一个是提供给 Caretaker 的窄接口:它只能将备忘录传递给其它对象;一个是提供给 Originator 的宽接口,允许它访问到先前状态所需的所有数据。理想情况是只允许 Originator 访问本备忘录的内部状态。

Implementation

发起人角色

  记录当前时刻的内部状态,并负责创建和恢复备忘录数据,允许访问返回到先前状态所需的所有数据。

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
public class Originator {

private String state;

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public Memento createMento() {
return (new Memento(state));
}

public void setMemento(Memento memento) {
state = memento.getState();
}

public void show() {
System.out.println("state = " + state);
}

}

备忘录角色

  负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Memento {

private String state;

public Memento(String state) {
this.state = state;
}

public String getState() {
return state;
}

}

备忘录管理员角色

  对备忘录进行管理、保存和提供备忘录,只能将备忘录传递给其他角色。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Caretaker {

private Memento memento;

public Memento getMemento() {
return memento;
}

public void setMemento(Memento memento) {
this.memento = memento;
}

}

Client客户端

  下面编写一小段代码测试一下,即先将状态置为On,保存后再将状态置为Off,然后通过备忘录管理员角色恢复初始状态。

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) {
Originator originator = new Originator();
originator.setState("On"); //Originator初始状态
originator.show();

Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMento());

originator.setState("Off"); //Originator状态变为Off
originator.show();

originator.setMemento(caretaker.getMemento()); //回复初始状态
originator.show();
}

}

备忘录模式的应用

  1. 何时使用

  • 需要记录一个对象的内部状态时,为了允许用户取消不确定或者错误的操作,能够恢复到原先的状态

  2. 方法

  • 通过一个备忘录类专门存储对象状态

  3. 优点

  • 给用户提供了一种可以恢复状态的机制,可以使用能够比较方便地回到某个历史的状态
  • 实现了信息的封装,使得用户不需要关心状态的保存细节

  4. 缺点

  • 消耗资源

  5. 使用场景

  • 需要保存和恢复数据的相关场景
  • 提供一个可回滚的操作,如ctrl+z、浏览器回退按钮、Backspace键等
  • 需要监控的副本场景

  6. 应用实例

  • 游戏存档
  • ctrl+z键、浏览器回退键等(撤销/还原)
  • 棋盘类游戏的悔棋
  • 数据库事务的回滚

  7. 注意事项

  • 为了符合迪米特法则,需要有一个管理备忘录的类
  • 不要在频繁建立备份的场景中使用备忘录模式。为了节约内存,可使用原型模式+备忘录模式