观察者模式及实现(转)
一、 观察者模式
目的
概述
原理
二、 C#中的观察者模式
概述
模型与观察者基类
优点
三、 C#事例及实现
题目:猫大叫,两只老鼠开始逃跑,主人醒来,宝宝也醒来了并且哭了起来
解决方案:
1. 建立模型(目标基类)
2. 建立观察者基类(单行为,多行为)
3. 建立具体目标
4. 建立具体观察者
5. 运行测试
四、C++实现
一、 观察者模式
目的
我们都知道解决一个问题有N种解决方式,但在面向对象的设计中如何能做到“高内聚,低耦合”,设计可重用的对象才是我们追求的。在设计过程中,我们经常会接触到一种情况:一个对象的行为引发其它多个对象相应的行为。这时我们便可以通过观察者模式的设计思想来设计对象模型。
概述
观察者模式(Observer Pattern)是设计模式中行为模式的一种,它解决了上述具有一对多依赖关系的对象的重用问题。此模式的参与者分为两大类,一类是被观察的目标,另一类是观察该目标的观察者们。正因为该模式是基于“一对多”的关系,所以该模式一般是应用于由一个目标对象和N个观察者对象组成(当然也可以扩展为有多个目标对象,但我们现在只讨论前者)的场合。当目标对象的状态发生改变或做出某种行为时,正在观察该目标对象的观察者们将自动地、连锁地作出相应的响应行为。
原理
我们可以把观察目标理解为主动方、发布方、主体等;把观察者理解为被动方、订阅方、观察器等。目标是整个行为链的源头,其它观察者都依赖于它的变化而作出响应。为了实现低耦合,我们不能使用“直接调用”的方式而需要利用“订阅(清单)-通知”的机制去完成设计。通俗地说就是观察者向目标“订阅”它的改变,而目标发生改变后就“通知”所有已经“订阅”了它的改变的观察者,从而执行“订阅”的内容。这种机制的好处在于降低耦合度,分工明确,目标只负责在自身状态发生改变或做出某种行为时向自身的订阅清单发出“通知”,而不是直接调用观察者的行为(方法);观察者只负责向目标“订阅”它的变化,以及定义自身在收到目标“通知”后所需要做出的具体行为(也就是订阅的内容)。就像我们向出版社订阅报刊一样,出版社有新一期报刊发行时并不是直接跟每位订阅者联系,而是“通知”订阅者名单按顺序给每位订阅者发送所订报刊。
二、 C#中的观察者模式
概述
每种编程架构及程序语言,对观察者模式都有不通的具体实现。在.NET框架中,C#语言使用委托以及事件,可以很好的实现观察者模式。委托相当于“订阅清单”的角色,当目标中关联了该委托的事件被触发时,则委托将自动按序执行观察者注册于委托中的方法。
模型与观察者基类
我们把观察者模式的参与者都描述为派生自模型及观察者二个抽象基类的类。模型规划了事件,而观察者则规划了订阅及行为。
模型需要做的只是声明委托以及声明委托类型的事件。当然,还可以附加上封装了触发委托事件的方法。所有派生自模型的类都是具体目标,它们所要做的只是在适当的场合触发事件。(即发出“通知”)。
在观察者基类中,我们通过构造器将抽象的响应方法注册(订阅)于委托事件中。所有派生自观察者基类的类都是具体观察者。因为订阅行为已经在抽象基类完成,具体观察者需要做的只是通过覆盖观察者基类的方法去定义具体需要响应的行为,和通过构造器把需要观察的具体目标传递给基类构造器。
优点
通过对模型与观察者基类的分析可知,委托与事件的机制几乎消除了这两个模块之间的耦合,灵活性提高了很多。如果需要增加观察者,则只需要覆盖基类抽象方法及把观察目标传递给基类。
三、 C#事例及实现
题目:猫大叫,两只老鼠开始逃跑,主人醒来,宝宝也醒来了并且哭了起来.
解决方案:
namespace DelegateEvent
{
/// <summary>
/// 建立模型(目标基类)
/// 在Observer Pattern(观察者模式)中,此类作为所有Subject(目标)的抽象基类,所有要充当Subject的类(在此事例中为"猫")都继承于此类.
/// 我们说此类作为模型,用于规划目标(即发布方)所产生的事件,及提供触发事件的方法.
/// 此抽象类无抽象方法,主要是为了不能实例化该类对象,确保模式完整性.
/// 具体实施:
/// 1.声明委托
/// 2.声明委托类型事件
/// 3.提供触发事件的方法
/// </summary>
public abstract class ModelBase
{
public ModelBase()
{
}
public delegate void SubEventHandler();
public event SubEventHandler SubEvent;
protected void Notify()
{
//提高执行效率及安全性
if(this.SubEvent!=null)
this.SubEvent();
}
}
/// <summary>
/// 具体目标
/// </summary>
public class Cat:ModelBase
{
public void Cat()
{}
public void Cry()
{
this.Notify();
}
}
/// <summary>
/// 在Observer Pattern(观察者模式)中,此类作为所有Observer(观察者)的抽象基类,所有要充当观察者的类(在此事例中为"老鼠"和"人")都继承于此类.
/// 我们说此类作为观察者基类,用于规划所有观察者(即订阅方)订阅行为.
/// 1.指定观察者所观察的对象(即发布方).(通过构造器传递)
/// 2.规划观察者自身需要作出响应方法列表
/// 3.注册需要委托执行的方法.(通过构造器实现)
///</summary>
public abstract class Observer
{
/// 构造时通过传入模型对象,把观察者与模型关联,并完成订阅.
public Observer(ModelBase childModel)
{
childModel.SubEvent+=new ModelBase.SubEventHandler(Response);
}
public abstract void Response();
}
/// <summary>
/// 具体观察者--老鼠
/// </summary>
public class Mouse:Observer
{
private string sName;
public Mouse(string name,ModelBase childModel):base(childModel)
{
this.sName = name;
}
public override void Response()
{
System.Console.WriteLine(this.name+"开始逃跑");
}
}
/// <summary>
/// 具体观察者--主人
/// </summary>
public class Master:Observer
{
private string sName;
public Master(ModelBase childModel):base(childModel)
{
}
public override void Response()
{
System.Console.WriteLine("主人醒来");
}
}
/// <summary>
/// 在Observer Pattern(观察者模式)中,此类作为所有Observer(观察者)的抽象基类,所有要充当观察者的类(在此事例中为"老鼠"和"人")都继承于此类.
/// 我们说此类作为观察者基类,用于规划所有观察者(即订阅方)订阅行为.
/// 1.指定观察者所观察的对象(即发布方).(通过构造器传递)
/// 2.规划观察者自身需要作出响应方法列表
/// 3.注册需要委托执行的方法.(通过构造器实现)
///</summary>
public abstract class Observer2
{
/// 构造时通过传入模型对象,把观察者与模型关联,并完成订阅.
public Observer2(ModelBase childModel)
{
childModel.SubEvent+=new ModelBase.SubEventHandler(Response);
childModel.SubEvent+=new ModelBase.SubEventHandler(Response2);
}
/// <summary>
/// 规划了观察者的一种行为(方法),所有派生于该观察者基类的具体观察者都通过覆盖该方法来实现作出响应的行为.
/// </summary>
public abstract void Response();
public abstract void Response2();
}
/// <summary>
/// 具体观察者--宝宝
/// </summary>
public class Baby:Observer2
{
public Master(ModelBase childModel):base(childModel)
{
}
public override void Response()
{
System.Console.WriteLine("宝宝醒来");
}
public override void Response2()
{
System.Console.WriteLine("开始哭闹");
}
}
/// <summary>
/// 运行测试
/// </summary>
public class SubMain
{
public SubMain()
{}
public static void Main()
{
Cat myCat=new Cat();
Mouse myMouse1=new Mouse("mouse1",myCat);
Mouse myMouse2=new Mouse("mouse2",myCat);
Master myMaster=new Master(myCat);
Bagy myBaby = new Baby(myCat);
myCat.Cry();
}
}
}
四、C++实现
如果用C++实现,ATL则提供了IConnectionPointContainer、IConnectionPoint、IEnumConnectionPoints、IEnumConnections等以支持所谓的连接点及可连接对象等,而在MFC中,一个文档可以对应多个视图,而当文档发生更新后,可以通过UpdateAllViews来同步更新所有视图,这虽然并非严格意义上的Observer模式的应用,但其本质是相似的。
#include <iostream>
using namespace std;
class ChatRoom;
struct Observer
{
virtual void SetChatRoom(ChatRoom* pChatRoom) = 0;
virtual void Notify(const string& from, const string& to, const string& msg) = 0;
};
class ChatRoom // Subject
{
private:
vector<Observer*> vo;
public:
virtual ~ChatRoom()
{
vector<Observer*>::iterator vi = vo.begin();
for (; vi != vo.end(); vi++)
{
delete *vi;
}
}
void Login(Observer* po)
{
vo.push_back(po);
po->SetChatRoom(this);
}
void Logout(Observer* po)
{
vector<Observer*>::iterator vi = vo.begin();
for (; vi != vo.end(); vi++)
{
if (*vi == po)
vo.erase(vi);
}
}
void Notify(const string& from, const string& to, const string& msg)
{
vector<Observer*>::iterator vi = vo.begin();
for (; vi != vo.end(); vi++)
{
(*vi)->Notify(from, to, msg);
}
}
};
class Chater : public Observer // ConcreteObserver
{
private:
string name;
ChatRoom* pChatRoom;
public:
Chater(const string& name) : name(name) {}
void SetChatRoom(ChatRoom* pChatRoom)
{
this->pChatRoom = pChatRoom;
}
void Send(const string& to, const string& msg)
{
pChatRoom->Notify(name, to, msg);
}
void Notify(const string& from, const string& to, const string& msg)
{
if (0 == to.compare(name))
{
cout << "[" << from.c_str() << "] to [" << to.c_str() << "]: "
<< msg.c_str() << endl;
}
}
};
int main()
{
ChatRoom cr;
Chater* c1 = new Chater("David");
Chater* c2 = new Chater("Jordan");
Chater* c3 = new Chater("O'Neal");
cr.Login(c1);
cr.Login(c2);
cr.Login(c3);
c1->Send("Jordan", "You are a great basketball player"); // Send message to change subject state
c2->Send("O'Neal", "Work hard, you are great!");
cr.Logout(c1);
cr.Logout(c2);
cr.Logout(c3);
}
在上述示例中,引起Subject(即ChatRoom)发生变化的是Observer(即Chater),而接收Subject发出的Notify消息的同样是Observer,这在有些应用中可能是两个/类不同的实体。此外,在上述示例中,当ChatRoom接收到消息时,不会对消息的内容进行解析,而是直接通过Notify通知所有Chater,由所有Chater自己负责检查该消息是否是发送给自己的(实际的ChatRoom不可能采用这种形式),这在一定程度上与广播非常相似,所以有时候,我们可以采用Observer模式来模拟软件广播。
以上示例中,Subject不对消息的内容进行检查,而是盲目地进行广播,在具体应用中,我们可以结合使用Mediator模式和Observer模式,将Subject设计成一个Mediator,对消息内容进行检查,进而根据消息内容进行消息的Publish。