问题:
在软件系统开发中经常需要为某些对象建立一些依赖关系,而这些依赖于该对象的依赖者会根据该对象的状态变化,触发某些事件或方法也做出相应的改变,我们怎么样建立这种依赖关系,并做到当对象状态发生变化时对依赖对象的通知?
定义:
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
意图:
提供一个目标(Subject)对象,他提供依赖于它的观察者Observer的注册(Attach:将Observer注册到Subject中,Subject将Observer一个Container中。)和注销(Detach:Observer告诉Subject要撤销观察,被观察者从Container中将观察者去除。)操作,并且提供了使得依赖于它的所有观察者的通知操作(Notify),当Subject目标状态发生改变后Subject对象发出Notify通知所有Observer进行修改(调用Update)。
参与者:
•抽象主题(Subject)角色:
抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
•具体主题(ConcreteSubject)角色:
维护对所有具体观察者的引用的列表,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
•抽象观察者(Observer)角色:
为所有的具体观察者定义一个接口;定义了一个update方法,在得到主题的通知时被调用,这个接口叫做更新接口。
•具体观察者(ConcreteObserver)角色:
实现抽象观察者角色所要求的更新接口,通过update()方法接收ConcreteSubject的通知,并作出具体的处理。如果需要,具体观察者角色可以保持一个指向ConcreteSubject对象的引用(用于ConcreteSubject状态信息的传递)。
UML:
实例说明:
诺基亚手机工厂
工厂在接到生产任务后,通知工厂的各个零部件生产组,先生产手机需要的零部件
uml图如下:
代码:
/// 主题对象接口
/// </summary>
public interface INokiaSubject
{
void Attach(IPhoneComponentObserver phoneComponentObserver);
void Detach(IPhoneComponentObserver phoneComponentObserver);
void NodifyCreateComponent();
}
/// <summary>
/// 观察者接口
/// </summary>
public interface IPhoneComponentObserver
{
/// <summary>
/// 观察者方法,接收通知生产手机部件
/// </summary>
void CreateComponent(string type, int count);
}
/// <summary>
/// 手机cpu 具体观察者接口
/// </summary>
public class CpuObserver : IPhoneComponentObserver
{
public void CreateComponent(string type, int count)
{
Console.WriteLine("生产了{0}个{1}手机的CPU.", count, type);
}
}
/// <summary>
/// 手机主板 具体观察者接口
/// </summary>
public class MbObserver : IPhoneComponentObserver
{
public void CreateComponent(string type, int count)
{
Console.WriteLine("生产了{0}个{1}手机的主板.", count, type);
}
}
/// <summary>
/// 手机生产工厂, 具体主题对象类
/// </summary>
public class PhoneFactoryObserver : INokiaSubject
{
string type;
int count;
List<IPhoneComponentObserver> container = new List<IPhoneComponentObserver>();
public PhoneFactoryObserver(string _type, int _count)
{
this.type = _type;
this.count = _count;
}
/// <summary>
/// 注册
/// </summary>
/// <param name="phoneComponentObserver"></param>
public void Attach(IPhoneComponentObserver phoneComponentObserver)
{
container.Add(phoneComponentObserver);
}
/// <summary>
/// 移除
/// </summary>
/// <param name="phoneComponentObserver"></param>
public void Detach(IPhoneComponentObserver phoneComponentObserver)
{
container.Remove(phoneComponentObserver);
}
/// <summary>
/// 通知
/// </summary>
public void NodifyCreateComponent()
{
foreach (IPhoneComponentObserver obj in container)
{
obj.CreateComponent(type, count);
}
}
public void ProductionPhone()
{
System.Console.WriteLine("生产了{0}台,{1}手机", this.count, this.type);
}
}
/// <summary>
/// 客户端测试
/// </summary>
public void ObserverTest()
{
//工厂准备生产 20 部 N8
PhoneFactoryObserver ns = new PhoneFactoryObserver("N8", 20);
//注册生产部cpu生产组
ns.Attach(new CpuObserver());
//注册生产部主板生产组
ns.Attach(new MbObserver());
//通知注册的 手机 部件生产组
ns.NodifyCreateComponent();
//生产手机
ns.ProductionPhone();
}
.NET中的Observer模式:
委托可以充当抽象的Observer接口,而提供事件的对象充当了目标对象,使用委托,只需向Subject注册,通知时,调用的Observer的update()即可,不需要注册Observer到Subject中,因此委托是比抽象Observer接口更耦合更低,更为灵活。
/// 定义一个委托,用于注册需要通知的方法
/// </summary>
/// <param name="type"></param>
/// <param name="count"></param>
public delegate void NotifyComponentObserverEventHandler(string type, int count);
/// <summary>
/// 主题对象接口
/// </summary>
public interface INokiaSubject
{
NotifyComponentObserverEventHandler NotifyComponentObserverEvent { get; set; }
void NodifyCreateComponent();
}
/// <summary>
/// 手机cpu 具体观察者接口
/// </summary>
public class CpuObserver
{
public void CreateCpuComponent(string type, int count)
{
Console.WriteLine("生产了{0}个{1}手机的CPU.", count, type);
}
}
/// <summary>
/// 手机主板 具体观察者接口
/// </summary>
public class MbObserver
{
public void CreateComponent(string type, int count)
{
Console.WriteLine("生产了{0}个{1}手机的主板.", count, type);
}
}
/// <summary>
/// 手机生产工厂, 具体主题对象类
/// </summary>
public class PhoneFactoryObserver : INokiaSubject
{
string type;
int count;
public NotifyComponentObserverEventHandler NotifyComponentObserverEvent { get; set; }
public PhoneFactoryObserver(string _type, int _count)
{
this.type = _type;
this.count = _count;
}
/// <summary>
/// 通知方法~
/// </summary>
public void NodifyCreateComponent()
{
if (NotifyComponentObserverEvent != null)
{
NotifyComponentObserverEvent(type, count);
}
}
public void ProductionPhone()
{
System.Console.WriteLine("生产了{0}台,{1}手机", this.count, this.type);
}
}
/// <summary>
/// 客户端测试
/// </summary>
public void ObserverTest()
{
//工厂准备生产 20 部 N8
PhoneFactoryObserver ns = new PhoneFactoryObserver("N8", 20);
//注册生产部cpu生产组
CpuObserver cpuObserver = new CpuObserver();
ns.NotifyComponentObserverEvent += new NotifyComponentObserverEventHandler(cpuObserver.CreateCpuComponent);
//注册生产部主板生产组
MbObserver mbObserver = new MbObserver();
ns.NotifyComponentObserverEvent += new NotifyComponentObserverEventHandler(mbObserver.CreateComponent);
//通知 手机 部件生产组
ns.NodifyCreateComponent();
//生产手机
ns.ProductionPhone();
}
优点:
•Subject和Observer之间是松偶合的,所以观察者模式把一对多对象之间的通知依赖关系的变得更为松散,大大提高了程序的可维护性和可扩展性,也很好的符合了开放-封闭原则
•Subject在发送广播通知的时候,无需太多的知道Observer的细节,只需调用更新接口即可。
缺点:
•如果一个Subject有大量Observer订阅的,广播通知是以顺序循环遍历的方式通知的,会存在一定效率问题。如果前一个Observer通讯产生异常,后面的通知就需要等待或不被执行
•所有对于一个具体的Subject,所有的Observer,得到的通知都是相同的,不能根据需要为其特殊定制。
应用情景:
•对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。
•对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。
观察者模式两种方式:
•推模型:
主题对象向观察者推送主题的详细信息(参数),不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
•拉模型:
主题对象在通知观察者的时候,传递一个主题对象自身的引用,观察者根据需要从主题对象引用中“拉取”需要的数据。
•区别:
推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,观察者自己去按需要取值。
推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者重构观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。