设计模式之观察者模式

设计模式中的观察者(Observer)模式我个人觉得是蛮好玩的。原因是原本这个模式并不难,道理也较容易懂,但就是这样我也是看了好几次才明白这个模式的用意是什么。其实模式本质真的是可以跨语言,很多时候模式是种思想:"重用,解耦合"。这种思想在GOF提出的23种模式中无处不在。

Observer,观察者模式。它还有个别名叫:发布--订阅.这个别名比较形象。时常上网的朋友都会在网上订一些网络杂志/消费记录通知等等。比如我就订了移动公司的每月话费清单,还订了天极网的每周杂志。正是这种服务商提供订阅,而消费者订阅相应的服务构成了观察者的雏形。

作为构建软件的你,或许更关心这些发布--订阅是怎么实现的。发布--订阅的真正好处是:可重用,松耦合。我们先来看看可重用。

可重用,无论对发布者(移动公司)或是消费者(订阅话费清单的人)来说。从软件角度来说他们都是可重用的,这体现在。发布者可以稍稍改动一下把话费清单改为积分通知,重用了原来的话费清单模块。订阅者也稍稍改动一下订阅联通公司的话费清单。这样在软件中,你重用发布部分也好,重用订阅人也好,随你.

松散耦合,移动公司新增了积分通知——你并不知道,也不需要知道。你订阅了联通公司的话费清单——移动公司并不知道,也不需要知道。

现在可以对号入座了,我们消费者就是观察者(Observer).我们观察我们每个月的消费记录。移动公司提供订阅的主题(Subject).我们先来看看Observer以及Subject都能做哪些事情。

Observer只需(被通知)话费清单已到.

class Observer
{
public:
    virtual void Received()=0; //收到通知后动作,阅读话费清单
};

Subject(移动公司),需提供

  1. 一个接口让用户订阅话费清单(添加已订阅用户列表);
  2. 一个接口让用户取消订阅话费清单(更新已订阅用户列表);
  3. 发出通知告诉订阅用户话费清单已到.
class Subject
{
public:
    //Support Subscribe/Unsubscrible
    virtual void Attach(Observer* o);
    virtual void Detach(Observer* o);
    virtual void Notify();   //发送通知
private:
    list<Observer*> observers;  //已订阅用户列表
};

函数部分实现部分也很简单,提供上面所说的三种功能。

void Subject::Attach(Observer* o)
{
    observers.push_back(o);   //加至订阅用户列表
}
void Subject::Detach(Observer* o)
{
    observers.remove(o);     //从订阅用户列表中移除
}
void Subject::Notify()
{
     //通知所有订阅者
    list<Observer*>::iterator i;
    for(i=observers.begin(); i!=observers.end(); i++)      {
        static_cast<Observer*>(*i)->Received(this);
    }
}

现在我们定义出具体的一个Subject和具体的订阅人:

//定义一个具体Subject类(移动公司)
class Mobile:public Subject
{
public:
    Mobile(){ _checklist = "";};
    string GetChecklist();
    void SimulateChklst();
private:
    string _checklist;
};
void Mobile::SimulateChklst()
{
    cout<<"---------话费清单(Mobile.Suzhou)---------"<<endl;
    _checklist = "市话话费:17.52\t\t长途话费:57.00\n短信:5.00(包月)\t\t彩铃:2.00(包月)\n";
    Notify();
}
string Mobile::GetChecklist()
{
    return _checklist;
}
//定义一个具体观察者:Jerry --找移动公司订阅(到移动大厅让移动服务人员订阅)
class Jerry:public Observer
{
public:
    void Received(Subject* sender);
};
void Jerry::Received(Subject* sender)
{
    cout<<"Jerry收到话费通知:"<<endl
        <<static_cast<Mobile*>(sender)->GetChecklist()
        <<endl;
}
//定义另一个具体观察者:Anco --由观察者自已订阅(上网自助订阅)
class Anco:public Observer
{
public:
    void Subscribe(Subject *subject);
    void Unsubscrible(Subject *subject);
    void Received(Subject* sender);
private:
    Subject _subject;
};
void Anco::Received(Subject* sender)
{
    cout<<"Anco收到话费通知:"<<endl
        <<static_cast<Mobile*>(sender)->GetChecklist()
        <<endl;
}
void Anco::Subscribe(Subject *subject)    //自助订阅
{
    subject->Attach(this);
}
void Anco::Unsubscrible(Subject *subject)  //自助取消订阅
{
    subject->Detach(this);
}

我们的类,看起来是下面这个样子的:

其中需要注意那一段注释:

for(all o in observers){
   o->Received();
}

这段注释道出了Observer的另一个重点:由基类Notify统一维护各观察者的状态,这样可以确保观察者都可以得到通知。这给解耦合带来了绝佳的效果。
我们再也不用担心,谁去维护这个状态,状态怎么样保持一致。

现在我给出完整的代码:

//Platform: WinXP + VC6.0
#include <iostream>
#include <string>
#include <list>
using namespace std;
//--------------------------------------------------
//An abstract class define the Observer's interface
class Subject;
class Observer
{
public:
    virtual ~Observer(){};
    virtual void Received(Subject* sender)=0; //传入sender,支持多主题Subscribe
protected:
    Observer(){};
};
//An abstract class define the Subject's interface
class Subject
{
public:
    virtual ~Subject(){};

    //Support subcribe
    virtual void Attach(Observer* o);
    virtual void Detach(Observer* o);
    virtual void Notify();
public:
    Subject(){};
private:
    list<Observer*> observers;
};

void Subject::Attach(Observer* o)
{
    observers.push_back(o);
}
void Subject::Detach(Observer* o)
{
    observers.remove(o);
}
void Subject::Notify()
{
    list<Observer*>::iterator i;
    for(i=observers.begin(); i!=observers.end(); i++)
    {
        static_cast<Observer*>(*i)->Received(this);
    }
}
//--------------------------------------
//定义一个具体Subject类
class Mobile:public Subject
{
public:
    Mobile(){ _checklist = "";};
    string GetChecklist();
    void SimulateChklst();
private:
    string _checklist;
};
void Mobile::SimulateChklst()
{
    cout<<"---------话费清单(Mobile.Suzhou)---------"<<endl;
    _checklist = "市话话费:17.52\t\t长途话费:57.00\n短信:5.00(包月)\t\t彩铃:2.00(包月)\n";
    Notify();
}
string Mobile::GetChecklist()
{
    return _checklist;
}
//定义一个具体观察者:Jerry --找移动公司订阅(到移动大厅让移动服务人员订阅)
class Jerry:public Observer
{
public:
    void Received(Subject* sender);
};
void Jerry::Received(Subject* sender)
{
    cout<<"Jerry收到话费通知:"<<endl
        <<static_cast<Mobile*>(sender)->GetChecklist()
        <<endl;
}
//定义另一个具体观察者:Anco --由观察者自已订阅(上网自助订阅)
class Anco:public Observer
{
public:
    void Subscribe(Subject *subject);
    void Unsubscrible(Subject *subject);
    void Received(Subject* sender);
private:
    Subject _subject;
};
void Anco::Received(Subject* sender)
{
    cout<<"Anco收到话费通知:"<<endl
        <<static_cast<Mobile*>(sender)->GetChecklist()
        <<endl;
}
void Anco::Subscribe(Subject *subject)
{
    subject->Attach(this);
}
void Anco::Unsubscrible(Subject *subject)
{
    subject->Detach(this);
}
//------------Main函数------------------
int main()
{
    Mobile mobile;
    Jerry jerry;
    Anco anco;
    //订阅
    mobile.Attach(&jerry); //Subject添加Observer
    anco.Subscribe(&mobile); //Observer subscrible Subject;
     //本质就是向Subject维护的observers链表中加一条记录

    mobile.SimulateChklst();
    cout<<"\t[月底了...移动又该送话费清单啦:-)大家赶快看啊!]"<<endl;
    mobile.SimulateChklst();

    cout<<endl<<"\t[喏,Anco,取消订阅了...]"<<endl;
    anco.Unsubscrible(&mobile);
    mobile.SimulateChklst ();

    system("PAUSE");
    return 0;
}

C#为观察者模式提供的便利:

C#中的事件机制为实现观察者(Observer)提供了很好的支持,在C#中若一类提供了事件,则这个类就相当于发布了一个主题。

在C#中若在一个类中定义了一个事件,则等于定义了一个委托+两个方法,对应于上面的Subject类就是定义了ObserverList成员用来存用户列表,两个方法分别对应Attach()和Detach().

在C#中实现以上的观察者模式就要简单的多了,现给出代码:

//Platform: WinXP + C# in vs2003
using System;
namespace Class1
{
    //-------------定义委托及事件传送的数据-------------
    internal class ChkLstEventArgs:EventArgs
    {
        private readonly string _checklist;
        public ChkLstEventArgs(string checklist)
        {
            _checklist = checklist;
        }
        public string Checklist
        {
            get{return _checklist;}
        }
    }
    internal delegate void PublishEventHandler(object sender, ChkLstEventArgs args);
   //--------------------定义抽象类---------------------
    internal class Subject
    {
        public event PublishEventHandler PublishChkLst;
        public void Notify(ChkLstEventArgs args)
        {
            if(PublishChkLst != null)
                PublishChkLst(this,args);
        }
    }
    internal abstract class Observer
    {
        public abstract void Received(object sender, ChkLstEventArgs args);
    }
    //------------------定义实体类----------------------
    class Mobile:Subject
    {
        public void SimulateChklst()
        {
            Console.WriteLine("---------话费清单(Mobile.Suzhou)---------");
            ChkLstEventArgs args = new ChkLstEventArgs("市话话费:17.52\t\t长途话费:57.00\n短信:5.00(包月)\t\t彩铃:2.00(包月)\n");
            base.Notify(args);
        }
    }
    class Jerry:Observer
    {
        public override void Received(object sender, ChkLstEventArgs args)
        {
            Console.WriteLine("Jerry收到话费通知:\n{0}",args.Checklist);
        }
    }
    class Anco:Observer
    {
        public void Subscribe(Subject sub)
        {
            sub.PublishChkLst += new PublishEventHandler(Received);
        }
        public void Unsubscrible(Subject sub)
        {
            sub.PublishChkLst -= new PublishEventHandler(Received);
        }
        public override void Received(object sender, ChkLstEventArgs args)
        {
            Console.WriteLine("Anco收到话费通知:\n{0}",args.Checklist);
        }
    }
    class ExcelProgram
    {
        [STAThread]
        static void Main(string[] args)
        {
            Mobile mobile = new Mobile();
            Jerry jerry = new Jerry();
            Anco anco = new Anco();

            //订阅
            mobile.PublishChkLst += new PublishEventHandler(jerry.Received); //Subject添加Observer
            anco.Subscribe(mobile); //Observer subscrible Subject;
                     //本质就是向Subject维护的observers链表中加一条记录

            mobile.SimulateChklst();
            Console.WriteLine("\t[月底了...移动又该送话费清单啦:-)大家赶快看啊]");
            mobile.SimulateChklst();
            Console.WriteLine("\t[喏,Anco,取消订阅了...]");
            anco.Unsubscrible(mobile);
            mobile.SimulateChklst ();

            Console.ReadLine();
        }
    }
}

观察者模式应用相当广泛,因为他非常好用。当你看MVC模式时,你可以看到观察者模式。当你用VB,DELPHI编程时,对类似按钮的事件的处理也可以看到观察者的影子。

引用GOF设计模式中的两段话作为总结:

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。

Observer模式描述了如何建立这种关系。这一模式中的关键对象是目标(Subject)和观察者(Observer)。一个目标可以有任意数目的依赖它的观察者。一旦目标的状态发生改变, 所有的观察者都得到通知。作为对这个通知的响应,每个观察者都将查询目标以使其状态与目标的状态同步。

@ 2007-12-05 08:00

Comments:

Sharing your thoughts: