设计模式(21):观察者模式OBSERVER

观察者模式

观察者模式(Observer)又可以称为发布-订阅模式(Publish-Subscribe)

意图

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。


使用场景

在以下任一情况下可以使用观察者模式:

  • 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  • 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。

结构

观察者模式结构如下
ShowImage

  • Subject(目标)

    – 目标知道它的观察者。可以有任意多个观察者观察同一个目标。

    – 提供注册和删除观察者对象的接口。

  • Observer(观察者)

    – 为那些在目标发生改变时需获得通知的对象定义一个更新接口。

  • ConcreteSubject(具体目标)

    – 将有关状态存入各ConcreteObserver对象。

    – 当它的状态发生改变时, 向它的各个观察者发出通知。

  • ConcreteObserver(具体观察者)

    – 维护一个指向ConcreteSubject对象的引用。

    – 存储有关状态,这些状态应与目标的状态保持一致。

    – 实现Observer的更新接口以使自身状态与目标的状态保持一致。

协作

  • 当ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者。
  • 在得到一个具体目标的改变通知后, ConcreteObserver对象可向目标对象查询信息。ConcreteObserver使用这些信息以使它的状态与目标对象的状态一致。

效果

Observer模式允许你独立的改变目标和观察者。你可以单独复用目标对象而无需同时复用其观察者, 反之亦然。它也使你可以在不改动目标和其他的观察者的前提下增加观察者。
下面是观察者模式其它一些优缺点:

  • 目标和观察者间的抽象耦合

    一个目标所知道的仅仅是它有一系列观察者, 每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。
  • 支持广播通信

    不像通常的请求, 目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象并不关心到底有多少对象对自己感兴趣;它唯一的责任就是通知它的各观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。
  • 意外的更新

    因为一个观察者并不知道其它观察者的存在, 它可能对改变目标的最终代价一无所知。在目标上一个看似无害的的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外, 如果依赖准则的定义或维护不当,常常会引起错误的更新, 这种错误通常很难捕捉。

观察者模式实现(Implement)

案例

几个朋友约着一起去必胜客吃披萨,点完餐以后只需要等待柜台来告诉自己披萨是否做好了,而不用自己去询问柜台,这里就用到了观察者模式的思想。

代码实现

柜台,对应模式中的Subject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 柜台,Subject in pattern */
public interface Counter {
//增加一个观察者
void add(Customer customer);
//去掉一个观察者
void delete(Customer customer);
//通知所有的观察者
void notifyAllCustomers();
//通知指定的观察者
void notifyCustomer(Customer customer);
}

顾客消费者,对应模式中的Observer

1
2
3
4
5
/* 等待取餐的顾客, Observer in pattern */
public interface Customer {
//取餐
void pickUpFood();
}

必胜客柜台,对应模式中的ConcreteSubject

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
/* 必胜客柜台,ConcreteSubject in pattern */
public class PizzaHutCounter implements Counter {
private List<Customer> customerList = new LinkedList<>();
@Override
public void add(Customer customer) {
customerList.add(customer);
}
@Override
public void delete(Customer customer) {
customerList.remove(customer);
}
@Override
public void notifyAllCustomers() {
for (Customer customer : customerList) {
customer.pickUpFood();
}
customerList.clear();
}
@Override
public void notifyCustomer(Customer customer) {
customer.pickUpFood();
customerList.remove(customer);
}
}

顾客A,对应ConcreteObserver

1
2
3
4
5
6
7
/* 等待取餐的顾客A, ConcreteObserver in pattern */
public class CustomerA implements Customer {
@Override
public void pickUpFood() {
System.out.println("顾客A取餐...");
}
}

Client调用

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
public class ObserverDemoTest {
@Test
public void orderFoodInPizzaHut() {
//实例化必胜客柜台,对应模式中的Subject
Counter counter = new PizzaHutCounter();
//店里面来了5个顾客ABCDE
Customer customerA = new CustomerA();
Customer customerB = new CustomerB();
Customer customerC = new CustomerC();
Customer customerD = new CustomerD();
Customer customerE = new CustomerE();
//顾客点餐,顾客D点的是培根披萨,顾客ABCE点的都是鸡肉披萨
System.out.println("顾客点餐中...");
counter.add(customerA);
counter.add(customerB);
counter.add(customerC);
counter.add(customerD);
counter.add(customerE);
//顾客C因为有事临时取消了订单
counter.delete(customerC);
//顾客D的披萨已经做好了,柜台通知顾客D取餐
System.out.println("顾客D的披萨已经做好了...");
counter.notifyCustomer(customerD);
System.out.println("-----顾客D开始用餐-----");
//顾客ABE的披萨也好了,柜台通知顾客A,B,E取餐
System.out.println("顾客A的披萨已经做好了...");
System.out.println("顾客B的披萨已经做好了...");
System.out.println("顾客E的披萨已经做好了...");
counter.notifyAllCustomers();
System.out.println("-----顾客A开始用餐-----");
System.out.println("-----顾客B开始用餐-----");
System.out.println("-----顾客E开始用餐-----");
}
}

总结

观察者模式最核心的就是将观察者与被观察者解耦,可以提供一种一对多的依赖关系。RxJava是目前使用十分广泛的异步的基于事件和流的框架,该框架就用到了观察者模式的思想。

参考代码

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!