设计模式-观察者模式(高效的发声)

观察者模式

2016-12-8 11:08:06

观察者模式又叫发布-订阅模式,它定义了一种一对多的依赖关系,多个观察者对象可同时监听某一主题对象,当该主题对象状态发生变化时,相应的所有观察者对象都可收到通知。这是一个非常好理解的模式,简单介绍如下:

场景

假设我们有这样一个需求,我们的新闻博客写的不错,有几个读者向订阅我们的新闻。这样以便于我们的博客一有新闻更新,就可以推送到用户了。简单的我们可以这么写

1
2
3
4
5
6
7
public class DisgraceNewsPublisher {
public void newsPublished() {
News news = getLatestNews();
jack.update(news);
amy.update(news);
}
}

但是我们发现如果我们的用户Amy对新闻内容不满意,想退订新闻的话,我们的发布新闻代码得更改,同时我们的新用户Eric想要订阅,代码又得重新改,这显然违背了开闭原则。这样做的问题有:

  • 我们是针对具体实现编程,而非针对接口。
  • 对于每个新的用户,我们都得修改代码。
  • 我们无法再运行时动态地增加(或删除)用户。
  • 用户没有实现一个公共的接口。所有用户都是update方法
  • 我们尚未封装改变的部分。

观察者模式

这个时候我们的观察者模式就登场了,观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。观察者模式一般会有如下角色:

抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。

抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。

具体主题角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。

具体观察者角色:该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。 它的类图如下:

松耦合的威力

观察者模式实现了松耦合,就如上文的例子,新闻的发布者和用户之间会有交互,但他们不需要清楚彼此的细节。 当两个对象之间松耦合,他们依然可以互交,但是不太清楚彼此的细节。 观察者模式提供了一种对象设计,让主题和观察者之间松耦合。 关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁,做了些什么或者其他任何细节。任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可以用心的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。有新类型的观察者出现时,主题的代码不需要修改,假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,人后注册为观察者即可,主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。

新闻发布代码实现

Subject.java 主题的抽象接口

1
2
3
4
5
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}

Observer.java 观察者的抽象接口

1
2
3
public interface Observer {
void update(String args);
}

NewsPublisher.java 新闻发布者--主题的具体类

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
public class NewsPublisher implements Subject {
private ArrayList<Observer> observers;
public NewsPublisher() {
observers = new ArrayList<Observer>();
}
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (Observer o : observers) {
o.update(getLatestNews());
}
}
private String getLatestNews() {
return "this is latest news";
}
}

NewsRegisterJack.java注册用户jack

1
2
3
4
5
public class NewsRegisterJack implements Observer {
public void update(String latestNews) {
System.out.println("Jack is getting the news ..." +latestNews);
}
}

NewsRegisterAmy.java注册用户amy

1
2
3
4
5
public class NewsRegisterAmy implements Observer {
public void update(String latestNews) {
System.out.println("Amy is getting the news ..." +latestNews);
}
}

我们来做一下测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ObserverTest {
public static void main(String[] args) {
Subject subject = new NewsPublisher();
Observer jack = new NewsRegisterJack();
Observer amy = new NewsRegisterAmy();
// notify when nobody is subscribed.
subject.notifyObservers();
// notify when both user is subscribed.
subject.registerObserver(jack);
subject.registerObserver(amy);
subject.notifyObservers();
// notify when amy is unsubscribed.
subject.removeObserver(amy);
subject.notifyObservers();
}
}

输出如下

1
2
3
4
5
Jack is getting the news ...this is latest news
Amy is getting the news ...this is latest news
Jack is getting the news ...this is latest news
Process finished with exit code 0

观察者模式优缺点

观察者模式优点

  • 抽象主题只依赖于抽象观察者
  • 观察者模式支持广播通信
  • 观察者模式使信息产生层和响应层分离

观察者模式缺点

如一个主题被大量观察者注册,则通知所有观察者会花费较高代价 如果某些观察者的响应方法被阻塞,整个通知过程即被阻塞,其它观察者不能及时被通知

观察者模式与OOP原则

已遵循的原则

  • 依赖倒置原则(主题类依赖于抽象观察者而非具体观察者)
  • 迪米特法则
  • 里氏替换原则
  • 接口隔离原则
  • 单一职责原则
  • 开闭原则

参考

Head First设计模式读书总结——观察者模式 设计模式——观察者模式:天气推送的两种实现 观察者模式中,消息采用推和拉方式来传递的比较

请我喝杯咖啡吧!