1. 概述
有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
2. 解决的问题
将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。
3. 模式中的角色
3.1 抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
3.2 具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
3.3 抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
3.4 具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。
4. 模式解读
4.1 观察者模式的类图
4.2 观察者模式的代码
/// <summary>
/// 抽象主题类
/// </summary>
public abstract class Subject
{
private IList<Observer> observers = new List<Observer>();
/// <summary>
/// 增加观察者
/// </summary>
/// <param name="observer"></param>
public void Attach(Observer observer)
{
observers.Add(observer);
}
/// <summary>
/// 移除观察者
/// </summary>
/// <param name="observer"></param>
public void Detach(Observer observer)
{
observers.Remove(observer);
}
/// <summary>
/// 向观察者(们)发出通知
/// </summary>
public void Notify()
{
foreach (Observer o in observers)
{
o.Update();
}
}
}
/// <summary>
/// 抽象观察者类,为所有具体观察者定义一个接口,在得到通知时更新自己
/// </summary>
public abstract class Observer
{
public abstract void Update();
}
/// <summary>
/// 具体观察者或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
/// </summary>
public class ConcreteSubject : Subject
{
private string subjectState;
/// <summary>
/// 具体观察者的状态
/// </summary>
public string SubjectState
{
get { return subjectState; }
set { subjectState = value; }
}
}
/// <summary>
/// 具体观察者,实现抽象观察者角色所要求的更新接口,已是本身状态与主题状态相协调
/// </summary>
public class ConcreteObserver : Observer
{
private string observerState;
private string name;
private ConcreteSubject subject;
/// <summary>
/// 具体观察者用一个具体主题来实现
/// </summary>
public ConcreteSubject Subject
{
get { return subject; }
set { subject = value; }
}
public ConcreteObserver(ConcreteSubject subject, string name)
{
this.subject = subject;
this.name = name;
}
/// <summary>
/// 实现抽象观察者中的更新操作
/// </summary>
public override void Update()
{
observerState = subject.SubjectState;
Console.WriteLine("The observer's state of {0} is {1}", name, observerState);
}
}
4.3 客户端代码
class Program
{
static void Main(string[] args)
{
// 具体主题角色通常用具体自来来实现
ConcreteSubject subject = new ConcreteSubject();
subject.Attach(new ConcreteObserver(subject, "Observer A"));
subject.Attach(new ConcreteObserver(subject, "Observer B"));
subject.Attach(new ConcreteObserver(subject, "Observer C"));
subject.SubjectState = "Ready";
subject.Notify();
Console.Read();
}
}
运行结果
5. 模式总结
5.1 优点
5.1.1 观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
5.2 缺点
5.2.1 依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。
5.3 适用场景
5.3.1 当一个对象的改变需要给变其它对象时,而且它不知道具体有多少个对象有待改变时。
5.3.2 一个抽象某型有两个方面,当其中一个方面依赖于另一个方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。
估计有些朋友在看前面的内容的时候,心里就嘀咕上了,Java里面不是已经有了观察者模式的部分实现吗,为何还要全部自己从头做呢?
主要是为了让大家更好的理解观察者模式本身,而不用受Java语言实现的限制。
好了,下面就来看看如何利用Java中已有的功能来实现观察者模式。在java.util包里面有一个类Observable,它实现了大部分我们需要的目标的功能;还有一个接口Observer,它里面定义了update的方法,就是观察者的接口。
因此,利用Java中已有的功能来实现观察者模式非常简单,跟前面完全由自己来实现观察者模式相比有如下改变:
- 不需要再定义观察者和目标的接口了,JDK帮忙定义了
- 具体的目标实现里面不需要再维护观察者的注册信息了,这个在Java中的Observable类里面,已经帮忙实现好了
- 触发通知的方式有一点变化,要先调用setChanged方法,这个是Java为了帮助实现更精确的触发控制而提供的功能
- 具体观察者的实现里面,update方法其实能同时支持推模型和拉模型,这个是Java在定义的时候,就已经考虑进去了
好了,说了这么多,还是看看例子会比较直观。
(1)新的目标的实现,不再需要自己来实现Subject定义,在具体实现的时候,也不是继承Subject了,而是改成继承Java中定义的Observable,示例代码如下:
/**
* 报纸对象,具体的目标实现
*/
public class NewsPaper extends java.util.Observable {
/**
* 报纸的具体内容
*/
private String content;
/**
* 获取报纸的具体内容
* @return 报纸的具体内容
*/
public String getContent() {
return content;
}
/**
* 示意,设置报纸的具体内容,相当于要出版报纸了
* @param content 报纸的具体内容
*/
public void setContent(String content) {
this.content = content;
//内容有了,说明又出新报纸了,那就通知所有的读者
//注意在用Java中的Observer模式的时候,下面这句话不可少
this.setChanged();
//然后主动通知,这里用的是推的方式
this.notifyObservers(this.content);
//如果用拉的方式,这么调用
//this.notifyObservers();
}
}
(2)再看看新的观察者的实现,不是实现自己定义的观察者接口,而是实现由Java提供的Observer接口,示例代码如下:
/**
* 真正的读者,为了简单就描述一下姓名
*/
public class Reader implements java.util.Observer {
/**
* 读者的姓名
*/
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void update(Observable o, Object obj) {
//这是采用推的方式
System.out.println(name+"收到报纸了,阅读先。目标推过来的内容是==="+obj);
//这是获取拉的数据
System.out.println(name+"收到报纸了,阅读先。主动到目标对象去拉的内容是==="+((NewsPaper)o).getContent());
}
}
(3)客户端使用
客户端跟前面的写法没有太大改变,主要在注册阅读者的时候,调用的方法跟以前不一样了,示例代码如下:
public class Client {
public static void main(String[] args) {
//创建一个报纸,作为被观察者
NewsPaper subject = new NewsPaper();
//创建阅读者,也就是观察者
Reader reader1 = new Reader();
reader1.setName("张三");
Reader reader2 = new Reader();
reader2.setName("李四");
Reader reader3 = new Reader();
reader3.setName("王五");
//注册阅读者
subject.addObserver(reader1);
subject.addObserver(reader2);
subject.addObserver(reader3);
//要出报纸啦
subject.setContent("本期内容是观察者模式");
}
}
赶紧测试一下,运行运行,看看结果,运行结果如下所示:
王五收到报纸了,阅读先。目标推过来的内容是===本期内容是观察者模式
王五收到报纸了,阅读先。主动到目标对象去拉的内容是===本期内容是观察者模式
李四收到报纸了,阅读先。目标推过来的内容是===本期内容是观察者模式
李四收到报纸了,阅读先。主动到目标对象去拉的内容是===本期内容是观察者模式
张三收到报纸了,阅读先。目标推过来的内容是===本期内容是观察者模式
张三收到报纸了,阅读先。主动到目标对象去拉的内容是===本期内容是观察者模式
然后好好对比自己实现观察者模式和使用Java已有的功能来实现观察者模式,看看有什么不同,有什么相同,好好体会一下。
Swing中的观察者模式
Java的Swing中到处都是观察者模式的身影,比如大家熟悉的事件处理,就是典型的观察者模式的应用。(说明一下:早期的Swing事件处理用的是职责链)
Swing组件是被观察的目标,而每个实现监听器的类就是观察者,监听器的接口就是观察者的接口,在调用addXXXListener方法的时候就相当于注册观察者。
当组件被点击,状态发生改变的时候,就会产生相应的通知,会调用注册的观察者的方法,就是我们所实现的监听器的方法。
从这里还可以学一招:如何处理一个观察者观察多个目标对象?
你看一个Swing的应用程序,作为一个观察者,经常会注册观察多个不同的目标对象,也就是同一类,既实现了按钮组件的事件处理,又实现了文本框组件的事件处理,是怎么做到的呢?
答 案就在监听器接口上,这些监听器接口就相当于观察者接口,也就是说一个观察者要观察多个目标对象,只要不同的目标对象使用不同的观察者接口就好了,当然, 这些接口里面的方法也不相同,不再都是update方法了。这样一来,不同的目标对象通知观察者所调用的方法也就不同了,这样在具体实现观察者的时候,也 就实现成不同的方法,自然就区分开了
参考链接: http://canann.iteye.com/blog/1814653 http://www.cnblogs.com/wangjq/archive/2012/07/12/2587966.html