设计模式记--Observer Pattern观察者模式
观察者模式——定义了对象之间的一对多依赖,这样一来,当一个对像改变状态时,它的所有依赖者都会收到通知并自动更新.
从定义可以看出,OBSERVER(观察者)模式逻辑上需要两组对象来实现.首先它必需要有发布者(Publish),也可称为被观察的目标 (Subject)(习惯上都称它为目标Subject,后面我们都称它作目标Subject),另外就是订阅者(Subscribe),习惯上称为观察 者(Observer).一个目标对象对应多个观察者对象,目标对象发生变化时,所有在目标对象中注册的观察者对象会得到通知,自动更新自己.
观察者模式UML图如下:
观察者模式的相关角色:
1、抽象主体(Subject)角色:也 就是被关注的对象,是一对多关系中的那个“一”。它的相关信息的变化将会通知给订阅这个变化的观察者。主体角色把所有对观察考对象的引用保存在一个集合 (List,ArrayList.....)里,每个主体可能管理若干数量的观察者。抽象主体提供一个接口,可以增加和删除观察者对象,主体角色又叫做抽 象被观察者(Observable)角色,一般用一个抽象类或者一个接口实现。
2、抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主体的通知时更新自己。这个接口叫做更新接口(Update)。抽象观察者角色一般用一个抽象类或者一个接口实现。在这个示意性的实现中,更新接口只包含一个方法(即Update()方法),这个方法叫做更新方法。
3、具体主体(ConcreteSubject)角色:将有关状态存入具体现察者对象;在具体主体的内部状态改变时,给所有登记过的观察者发出通知。具体主体角色又叫做具体被观察者角色(Concrete Observable)。具体主题角色通常用一个具体子类实现。
4、具体观察者(ConcreteObserver)角色:存储与主体的状态自恰的状态。具体现察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主体的状态相协调。如果需要,具体现察者角色可以保存一个指向具体主体对象的引用。具体观察者角色通常用一个具体子类实现。
下面,我们用代码来示例观察者模式。
程序如下图:
一、观察者模式的基本思路
1、抽象主体Subject
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace MyObserver
{
//定义Subject抽象类,它是'ConcreteSubject'具体目标对象的基类
//要实现Observer模式时,通常将数据对象作为目标(Subject),各个显示数据的对象作为观察者Observer
//每一个观察者(Observer)通过调用目标(Subject)中的一个公有(public)方法,在他所感兴趣的数据中注册(registers)自己。
//这样,当数据改变时,每一个目标(Subject)通过观察者(Observer)的接口发送更新通知。
abstract class Subject
{
#region 定义一个List来装盛所有与此数据对象相联系的观察者Observer
private List<Observer> _observers = new List<Observer>();
#endregion
#region 附加或解除Observer功能(注册或注销功能)
public void Attach(Observer observer)
{
_observers.Add(observer); //observer观察者在此数据对象中注册(registers)自己。
}
public void Detach(Observer observer)
{
_observers.Remove(observer);//在此数据对象中取消注册,也即让对象变更时不用再通知此observer观察者
}
#endregion
#region 通知在_observers列表中的所有观察者
public void Nofity()
{
//遍历观察者列表,按列表的名录逐一通知
foreach (Observer o in _observers)
{
o.Update();
}
}
#endregion
}
}
2、具体主体ConcreteSubject
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyObserver
{
//定义具体数据对象类,它继承自Subject抽象类
class ConcreteSubject:Subject
{
#region SubjectState属性
private string _subjectState;
public string SubjectState
{
get { return _subjectState; }
set { _subjectState = value; }
}
#endregion
}
}
3、抽象观察者Observer
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyObserver
{
#region 定义Observer抽象类,它是ConcreteObserver的基类
abstract class Observer
{
//定义一个用于发送更新通知的接口
//这样,当数据改变时,每一个目标(Subject)通过观察者(Observer)的接口发送更新通知。
public abstract void Update();
}
#endregion
}
4、具体观察者ConcreteObserver
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyObserver
{
class ConcreteObserver:Observer
{
private string _name;
private string _observerState;
private ConcreteSubject _subject;
public ConcreteSubject Subject
{
get { return _subject; }
set { _subject = value; }
}
#region 构造函数
public ConcreteObserver(ConcreteSubject subject, string name)
{
this._subject = subject;
this._name = name;
}
#endregion
#region 实现目标数据更新通知接口
public override void Update()
{
_observerState = _subject.SubjectState;
Console.WriteLine("观察者 {0} 收到的数据对象的新状态值是 {1}",_name,_observerState);
}
#endregion
}
}
5、客户端代码
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
#region 基本思路示例
Console.WriteLine("----------观察者模式基本思路示例--------");
ConcreteSubject s = new ConcreteSubject(); //首先创建一个数据对象
s.Attach(new ConcreteObserver(s, "X")); //向这个数据对象内注册三个观察者X,Y,Z
s.Attach(new ConcreteObserver(s, "Y"));
s.Attach(new ConcreteObserver(s, "Z"));
s.SubjectState = "ABC"; //改变数据对象的状态值
s.Nofity(); //调用数据对象的通知功能来依次通知已经注册的观察者
Console.ReadKey();
#endregion
二、我的团长我的团使用观察者模式
这里,我们让孟烦了在前哨望风,当他发现敌情时,他马上通知所有兄弟们准备战斗。这里,孟烦了就是具体主体ConcreteSubject,他的那些兄弟:要麻,迷龙,豆饼....等等都是具体观察者(ConcreteObserver)
1、抽象主体Subject:Guard
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace MyObserver
{
abstract class Guard
{
#region 定义一个List来收集所有需要通知到的其它士兵
private List<Soldier> _soldiers = new List<Soldier>();
#endregion
#region 附加或解除Observer功能(注册或注销功能)
public void Attach(Soldier observer)
{
_soldiers.Add(observer); //observer观察者在此数据对象中注册(registers)自己。
}
public void Detach(Soldier observer)
{
_soldiers.Remove(observer);//在此数据对象中取消注册,也即让对象变更时不用再通知此observer观察者
}
#endregion
#region 通知在_observers列表中的所有观察者
public void Nofity()
{
//遍历观察者列表,按列表的名录逐一通知
foreach (Soldier o in _soldiers)
{
o.Update();
}
}
#endregion
}
}
2、具体主体ConcreteSubject:ConcreteGurad
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyObserver
{
class ConcreteGurad:Guard
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
#region FightInfo属性
private string _fightInfo;
public string FightInfo
{
get { return _fightInfo; }
set { _fightInfo = value; }
}
#endregion
public ConcreteGurad(string name)
{
this._name = name;
}
}
}
3、抽象观察者Observer:Soldier
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyObserver
{
abstract class Soldier
{
public abstract void Update();
}
}
4、具体观察者ConcreteObserver:ConcreteSoldier
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyObserver
{
class ConcreteSoldier:Soldier
{
private string _observerState;
private string _name;
private ConcreteGurad _subject;
public ConcreteGurad Subject
{
get { return _subject; }
set { _subject = value; }
}
#region 构造函数
public ConcreteSoldier(ConcreteGurad subject, string name)
{
this._subject = subject;
this._name = name;
}
#endregion
#region 实现目标数据更新通知接口
public override void Update()
{
_observerState = _subject.FightInfo ;
Console.WriteLine("士兵 '{0}' 收到哨兵 '{1}' 的信号:{2}",_name,_subject.Name ,_observerState);
}
#endregion
}
}
5、客户端代码
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
#region 我的团长我的团
Console.WriteLine("----------我的团长我的团观察者模式示例--------");
ConcreteGurad cg = new ConcreteGurad("孟烦了");
ConcreteSoldier yaoma = new ConcreteSoldier(cg, "要麻");
ConcreteSoldier sepigu = new ConcreteSoldier(cg, "蛇屁股");
ConcreteSoldier doubing = new ConcreteSoldier(cg, "豆饼");
ConcreteSoldier kangya = new ConcreteSoldier(cg, "康丫");
ConcreteSoldier milong = new ConcreteSoldier(cg, "迷龙");
ConcreteSoldier bula = new ConcreteSoldier(cg, "不辣");
cg.Attach(yaoma);
cg.Attach(sepigu);
cg.Attach(doubing);
cg.Attach(kangya);
cg.Attach(milong);
cg.Attach(bula);
cg.FightInfo = "鬼子从右边摸上来了,大家准备歼灭他们
![](https://www.cnblogs.com/Images/dot.gif)
cg.Nofity();
Console.ReadKey();
#endregion
程序运行后效果如下:
总结:
1、要点
(1)抽象主体角色公开了自身的事件,可以给任意观察者订阅。
(2)象观察者角色定义了统一的处理行为,在C#中使用事件-代理模式的话,统一的处理行为并不这么重要,有的时候甚至还会限制灵活性。
(3)观察者往往只需要实现响应方法即可。
(4)有多个主体角色、多个观察者角色交错,也可以一个类型是两个角色,主体也可以提供多个事件。从应用上来说观察者模式变化是非常多的。
2、优缺点
观察者模式的优缺点
Observer模式的优点是实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,类别清晰,并抽象了更新接口,使得可以有各种各样不同的表示层(观察者)。
但是其缺点是每个外观对象必须继承这个抽像出来的接口类,这样就造成了一些不方便,比如有一个别人写的外观对象,并没有继承该抽象类,或者接口不对,我们 又希望不修改该类直接使用它。虽然可以再应用Adapter模式来一定程度上解决这个问题,但是会造成更加复杂烦琐的设计,增加出错几率。
观察者模式的效果有以下几个优点:
(1)观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体现察者聚集,每一个具体现察者都符合一个抽象观察者的接 口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层 次。
(2)观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知。
观察者模式有下面的一些缺点:
(1)如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
(2)如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察考模式时要特别注意这一点。
(3)如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
(4)虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。