peter_luy
8/7/2017 - 7:42 AM

观察者模式

观察者模式

#观察者模式

##来自博客 单例,工厂,观察者,我认为是设计模式三杰,为何这么说,因为这三种设计模式使用频率最高,变化最多,而且覆盖面最广,当然,也是面试中最容易被问到的。当然,像装饰,建造,策略这几个也是经常会用到的,后面文章中我会一一展开讨论。

从这里开始,我们在介绍设计模式之前,需要强调一下角色。实际上从上篇工厂模式中,就已经有角色的概念。大家想想,工厂模式中有哪些角色?没错,就是工厂角色和产品角色。同样,在观察者模式中,也有两个角色,就是观察者和被观察者。在理解设计模式的时候,首先要有个概念,就是每个角色都对应这一个类,比如观察者模式,观察者肯定对应着一个观察者类,被观察者肯定对应的被观察者类。那么设计模式实际上就是通过面向对象的特性,将这些角色解耦。

观察者模式本质上就是一种订阅/发布的模型,从逻辑上来说就是一对多的依赖关系。什么意思呢?好比是一群守卫盯着一个囚犯,只要囚犯一有异动,守卫就必须马上采取行动(也有可能是更新状态,本质上也是一种行动),那么守卫就是观察者,囚犯就是被观察者。

在一个系统中,实现这种一对多的而且之间有一定关联的逻辑的时候,由于需要保持他们之间的协同关系,所以最简便的方法是采用紧耦合,把这些对象绑定到一起。但是这样一来,一旦有扩展或者修改的时候,开发人员所面对的难度非常大,而且很容易造成Bug。那么观察者模式就解决了这么一个问题,在保持一系列观察者和被观察者对象协同工作的同时,把之间解耦了。

好了,废话不多,撸代码:

//被观察者

public interface IObject {

    IList<IMonitor> ListMonitor { get; set; } //定义观察者集合,因为多个观察者观察一个对象,所以这里用集合
    string SubjectState { get; set; }        //被观察者的状态
 

    void AddMonitor(IMonitor monitor);  //添加一个观察者

    void RemoveMonitor(IMonitor monitor); //移除一个观察者

    void SendMessage(); //向所有观察者发送消息

}
public class Subject : IObject
{


    private IList<IMonitor> listMonitor = new List<IMonitor>();

    public string SubjectState //被观察者的状态
    {
        get;set;
    }


    public IList<IMonitor> ListMonitor //实现具体的观察者列表属性
    {
        get { return listMonitor; }
        set { listMonitor = value; }
    }

    public void AddMonitor(IMonitor monitor)  //实现具体的添加观察者方法
    {
        listMonitor.Add(monitor);
    
    }

    public void RemoveMonitor(IMonitor monitor) //实现具体的移除观察者方法
    {
       
        listMonitor.Remove(monitor);
    }

    public void SendMessage()    //实现具体的发送消息方法
    {
        foreach (IMonitor m in listMonitor)  //发送给所有添加过的观察者,让观察者执行update方法以同步更新自身状态
        {
            m.Update();
        }
    }
}

//观察者

public interface IMonitor //定义观察者接口 { void Update(); }

public class Monitor : IMonitor   //实现具体观察者
{
    private string monitorState="Stop!";    //观察者初始状态,会随着被观察者变化而变化
    private string name;            //观察者名称,用于标记不同观察者
    private IObject subject;        //被观察者对象



    public Monitor (IObject subject, string name)  //在构造观察者时,传入被观察者对象,以及标识该观察者名称
    {
        this.subject = subject;
        this.name = name;
        Console.WriteLine("我是观察者{0},我的初始状态是{1}", name, monitorState);


    }


    public void Update()                           //当被观察者状态改变,观察者需要随之改变
    {
        monitorState = subject.SubjectState;
        Console.WriteLine("我是观察者{0},我的状态是{1}", name, monitorState);

    }
}

//前端调用

static void Main(string[] args) {

        IObject subject = new Subject();
        subject.AddMonitor(new Monitor(subject, "Monitor_1"));
        subject.AddMonitor(new Monitor(subject, "Monitor_2"));
        subject.AddMonitor(new Monitor(subject, "Monitor_3"));

        subject.SubjectState = "Start!";
        subject.SendMessage();

        Console.Read();
    }
}

结果如下:
我是观察者Monitor_1,我的初始状态是Stop!
我是观察者Monitor_2,我的初始状态是Stop!
我是观察者Monitor_3,我的初始状态是Stop!
我是观察者Monitor_1,我的状态是Start!
我是观察者Monitor_2,我的状态是Start!
我是观察者Monitor_3,我的状态是Start!

这样就完成了一个观察者模式。我们回过头来看看,在被观察者中,我定义了一个集合用来存放观察者,并且我写了一个Add方法一个Remove方法来添加和移除观察者,这体现了一对多的关系,也提供了可以控制观察者的方式。所以,我们得到第一个关键点:每个观察者需要被保存到被观察者的集合中,并且被观察者提供添加和删除的方式。

然后我么再看一下,观察者和被观察者之间的交互活动。不难发现,我是在添加一个观察者的时候,把被观察者对象以构造函数的形式给传入了观察者。最后我让被观察者执行sendmessage方法,这时会触法所有观察着的update方法以更新状态。所以我们得到第二个关键点,被观察者把自己传给观察者,当状态改变后,通过遍历或循环的方式逐个通知列表中的观察者。

好了,到这里你应该可以把握住观察者模式的关键了。但这里有个问题,被观察者是通过构造函数参数的形式,传给观察者的,而观察者对象时被Add到被观察者的List中。所以,我们得到第三个关键点,虽然解耦了观察者和被观察者的依赖,让各自的变化不大影响另一方的变化,但是这种解耦并不是很彻底,没有完全解除两者之间的耦合。

有很多同学比较怕被问到观察者模式,特别是搞.Net的同学。为什么呢?因为一旦涉及到观察者模式,必然会涉及到2中类型,委托和事件。因为很多人并不理解委托和事件,或者只停留在潜层次。那么,委托,事件,和观察者模式到底有什么关系呢?

首先我们来看委托。委托说白了,就是可以把方法当做另一个方法参数来传递的东东,当然方法签名需要注意一下。委托可以看做是方法的抽象,也就是方法的“类”,一个委托的实例可以是一个或者多个方法。我们可以通过+=或者-=把方法绑定到委托或者从委托移除。

再来看事件,实际上事件是一种特殊的委托。怎么说呢,首先事件也是委托,只是在声明事件的时候,需要加上event,如果你用reflector去看一个事件,你会发现里面就3样东西,一个Add_xxxx方法,一个Remove_xxx方法,一个委托。说道这里,是不是觉得和上面我们定义被观察者时的Add方法,Remove方法有些联系?

没错,实际上.Net的事件机制就是观察者模式的一种体现,并且是利用委托来实现。本质上事件就是一种订阅-发布模型也就是观察者模式,这种机制中包含2个角色,一个是发布者,一个是订阅者。发布者类也就类似于被观察者,发布者类包含事件和委托定义,以及其之间的关系,发布者类的对象调用事件通知其他订阅者。而订阅者类也就类似于观察者,观察者接受事件,并且提供处理的逻辑。

也就是说,订阅者对象(观察者)中的方法会绑定到发布者(被观察者)对象的委托中,一旦发布者(被观察者)中事件被调用,发布者(被观察者)就会调用委托中绑定的订阅者(观察者)的处理逻辑或者说是处理程序,这就是通过观察者模式实现的事件,虽然这段话比较拗口,但是我想理解起来应该还是不太难把。。。

好了,你虽然现在已经对面试官逼逼叨了上面一大通,但是贱贱的面试官仍然想challenge你一下,否则不就是太没面子么。。。

你刚刚说了,在普通的观察者模式中,解耦并不彻底,那么在事件的发布订阅模型中,解耦彻底吗?为什么? 答案是肯定的。因为在事件中,订阅者和发布者之间是通过把事件处理程序绑定到委托,并不是把自身传给对方。所以解决了观察者模式中不完全解耦的问题。这也是关键点之四

  1. 通过委托绑定方法来实现观察者模式,会不会有什么隐患?

有的,通过+=去把方法绑定到委托,很容易忘记-=。如果只绑定不移除,这个方法会一直被引用。我们知道GC去回收的时候,只会处理没有被引用的对象,只要是还被引用的对象时不会被回收掉的。所以如果在长期不关闭的系统中(比如监控系统),大量的代码使用+=而不-=,运行时间长以后有可能会内存溢出。

  1. 事件,委托,观察者模式之间的关系

这个上面已经提到了,委托时一种类型,事件是一种特殊的委托,观察者模式是一种设计模式,事件的机制是观察者模式的一种实现,其中订阅者和发布者通过委托实现协同工作。

好的,最后我们还是来归纳一下观察者模式的关键点

一、每个观察者需要被保存到被观察者的集合中,并且被观察者提供添加和删除的方式。

二、被观察者把自己传给观察者,当状态改变后,通过遍历或循环的方式逐个通知列表中的观察者。

三、虽然解耦了观察者和被观察者的依赖,让各自的变化不大影响另一方的变化,但是这种解耦并不是很彻底,没有完全解除两者之间的耦合。

四、在事件中,订阅者和发布者之间是通过把事件处理程序绑定到委托,并不是把自身传给对方。所以解决了观察者模式中不完全解耦的问题

注意点:在使用委托绑定方法时,需要注意移除方法,否则可能会造成内存溢出。

建议:不能理解事件和委托的同学,好好地把事件和委托看一看,并且自己写点代码加深印象。


##来自我的经验案例: 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己。

using namespace std;

template< class T > class CListener;

template< class T > class CNotifyProcessor { public: CNotifyProcessor(void){} virtual ~CNotifyProcessor(void){} /************************************ >@ Method: Notify >@ FullName: CNotifyProcessor::Notify >@ Brief: 接收通知数据接口 >@ Access: virtual public >@ Returns: void >@ Qualifier:
>@ Parameter: const NotifyData & notifyData ************************************/ virtual void Notify(const T& notifyData) = 0; bool RegisterListener(CListener& listener) { return listener.RegisterProcessor(this); } bool UnRegisterListener(CListener& listener) { return listener.UnRegisterProcessor(this); } protected: CNotifyProcessor(const CNotifyProcessor&); CNotifyProcessor& operator = (const CNotifyProcessor&); }; //>群发方式,目前只会有一个处理者,先释放处理者,再释放监听者 template< class T > class CListener { public: CListener(void){} virtual ~CListener(void) { try { ClearProcessor(); } catch(...) {

    }
}
/************************************
>@ Method:    RegisterProcessor
>@ FullName:  CListener::RegisterProcessor
>@ Brief:          添加处理者
>@ Access:      virtual public 
>@ Returns:     bool
>@ Qualifier:   
>@ Parameter: CNotifyProcessor * notifyProcessor
************************************/
virtual bool RegisterProcessor(CNotifyProcessor<T>* notifyProcessor)
{
    if (NULL == notifyProcessor)
    {
        return false;
    }
    HPR_Guard guard(&m_notifyProcessorMutex);
    map<int,CNotifyProcessor<T>*>::iterator iter = m_notifyProcessor.find((int)notifyProcessor);
    if (iter == m_notifyProcessor.end())
    {
        m_notifyProcessor.insert(make_pair<int,CNotifyProcessor<T>*>((int)notifyProcessor,notifyProcessor));
        return true;
    }
    return false;
}
/************************************
>@ Method:    UnRegisterProcessor
>@ FullName:  CListener::UnRegisterProcessor
>@ Brief:          删除处理者
>@ Access:      virtual public 
>@ Returns:     bool
>@ Qualifier:   
>@ Parameter: CNotifyProcessor * notifyProcessor
************************************/
virtual bool UnRegisterProcessor(CNotifyProcessor<T>* notifyProcessor)
{
    if (NULL == notifyProcessor)
    {
        return false;
    }
    HPR_Guard guard(&m_notifyProcessorMutex);
    map<int,CNotifyProcessor<T>*>::iterator iter = m_notifyProcessor.find((int)notifyProcessor);
    if (iter != m_notifyProcessor.end())
    {
        m_notifyProcessor.erase(iter);
        return true;
    }
    return false;
}
/************************************
>@ Method:    Notify
>@ FullName:  CListener::Notify
>@ Brief:          下发监听者收到的通知(群发方式)
>@ Access:      virtual public 
>@ Returns:     void
>@ Qualifier:   
>@ Parameter: const NotifyData& notifyData
************************************/
virtual void NotifyProcessor(const T& notifyData)
{
    HPR_Guard guard(&m_notifyProcessorMutex);

    for (map<int,CNotifyProcessor<T>*>::iterator iter = m_notifyProcessor.begin();iter != m_notifyProcessor.end();iter++)
    {
        iter->second->Notify(notifyData);
    }
}
/************************************
>@ Method:    ClearProcessor
>@ FullName:  CListener::ClearProcessor
>@ Brief:          
>@ Access:      public 
>@ Returns:     void
>@ Qualifier:   
************************************/
void ClearProcessor()
{
    HPR_Guard guard(&m_notifyProcessorMutex);
    m_notifyProcessor.clear();
}

protected: CListener(const CListener& ); CListener& operator = (const CListener& ); private: map<int,CNotifyProcessor*> m_notifyProcessor; HPR_Mutex m_notifyProcessorMutex; };

观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。
最后我们还是来归纳一下观察者模式的关键点:

  • 每个观察者需要被保存到被观察者的集合中,并且被观察者提供添加和删除的方式。
  • 被观察者把自己传给观察者,当状态改变后,通过遍历或循环的方式逐个通知列表中的观察者。

class Observer;

//Subject,抽象通知者或者主题
class Subject
{
protected:
std::string SubjectState;
public:
virtual void Attach(Observer* observer)=0;
virtual void Detach(Observer* observer)=0;
virtual void Notify()=0;
std::string GetSubjectState();
void SetSubjectState(std::string state);
};

//ConcreteSubject,具体通知者或者具体主题。
class Boss:public Subject
{
private:
std::list<Observer> observers;
std::string action;
public:
void Attach(Observer
observer);
void Detach(Observer* observer);
void Notify();
};

std::string Subject::GetSubjectState()
{
return SubjectState;
}
void Subject::SetSubjectState(std::string state)
{
this->SubjectState=state;
}

void Boss::Attach(Observer* observer)
{
observers.push_back(observer);
}
void Boss::Detach(Observer* observer)
{
std::list<Observer>::iterator it;
for(it=observers.begin();it!=observers.end();it++)
{
if(
it==observer)
{
observers.erase(it);
break;
}
}
}
void Boss::Notify()
{
std::list<Observer>::iterator it;
for(it=observers.begin();it!=observers.end();it++)
{
(
*it).Update();
}
}

//Observer,抽象观察者
class Observer
{
protected:
std::string name;
Subject* sub;
public:
Observer();
Observer(std::string name,Subject* sub);
virtual void Update();
bool operator==(const Observer&)const;
};

//ConcreteObserver,具体观察者,股票观察者
class StockObserver:public Observer
{
public:
StockObserver();
StockObserver(std::string name,Subject* sub);
void Update();
};

//ConcreteObserver,具体观察者,NBA观察者
class NBAObserver:public Observer
{
public:
NBAObserver();
NBAObserver(std::string name,Subject* sub);
void Update();
};

Observer::Observer(){}

Observer::Observer(std::string name,Subject* sub)
{
this->name=name;
this->sub=sub;
}

void Observer::Update()
{
std::cout<<"Observer.Update()"<<std::endl;
}

bool Observer::operator(const Observer& observer)const
{
return (this->nameobserver.name)&&(this->sub==observer.sub);
}

StockObserver::StockObserver(){}

StockObserver::StockObserver(std::string name,Subject* sub)
{
this->name=name;
this->sub=sub;
}

void StockObserver::Update()
{
std::cout<<sub->GetSubjectState()<<" "<<name<<" "<<"关闭股市行情,继续工作!"<<std::endl;
}

NBAObserver::NBAObserver(){}

NBAObserver::NBAObserver(std::string name,Subject* sub)
{
this->name=name;
this->sub=sub;
}

void NBAObserver::Update()
{
std::cout<<sub->GetSubjectState()<<" "<<name<<" "<<"关闭NBA直播,继续工作!"<<std::endl;
}

demo:

void main()
{
//通知者
Subject* huhansan=new Boss();

//4个观察者实例  
Observer* tongshi1=new StockObserver("魏关姹",huhansan);  
Observer* tongshi2=new StockObserver("易管察",huhansan);  
Observer* tongshi3=new NBAObserver("霍华德",huhansan);  
Observer* tongshi4=new NBAObserver("林书豪",huhansan);  

//将4个观察者都加入到通知者的通知队列中  
huhansan->Attach(tongshi1);  
huhansan->Attach(tongshi2);  
huhansan->Attach(tongshi3);  
huhansan->Attach(tongshi4);  
  
//魏关姹没有被老板通知到,减去。  
huhansan->Detach(tongshi1);  

huhansan->SetSubjectState("我胡汉三回来了!");  

//通知  
huhansan->Notify();  

delete huhansan;  
delete tongshi1;  
delete tongshi2;  
delete tongshi3;  
delete tongshi4;  

std::cout<<""<<std::endl;  
system("pause");  

}

这个例子中,subject是作为主题的基类去工作的,具体的主题还是需要再去派生出来,同样的,观察者也是这样,需要派生出来。这样做,确实不如上面利用虚基类来处理得巧妙,后续尽量使用上面虚基类的实现方式吧。