观察者模式 - 进一步的考虑和广义的C++实现

Izh*_*aki 7 c++ oop design-patterns observer-pattern

我正在编写的C++ MVC框架大量使用了观察者模式.我已经详细阅读了设计模式(GoF,1995)中的相关章节,并了解了文章和现有库(包括Boost)中的大量实现.

但是当我实现这个模式时,我无法理解必须有一个更好的方法 - 我的客户端代码涉及线条和片段,我认为它应该被重构到模式本身,只要我能找到克服的方法一些C++限制.此外,我的语法从未像ExtJs库中那样优雅:

// Subscribing
myGridPanel.on( 'render', this.onRender );

// Firing
this.fireEvent( 'render', null, node );
Run Code Online (Sandbox Code Playgroud)

所以我决定进行进一步的研究,试图达到一个通用的实现,同时优先考虑代码的优点,可读性和性能.我相信我在第5次尝试中获得了累积奖金.

名为的实际实现gxObserver可在GitHub上获得; 它是完整的文档和自述文件拼写出来的优点和缺点.它的语法是:

// Subscribing
mSubject->gxSubscribe( evAge, OnAgeChanged );

// Firing
Fire( evAge, 69 );
Run Code Online (Sandbox Code Playgroud)

做了过度工作后,我觉得只是与SO社区分享我的发现.所以下面我将回答这个问题:

在实现观察者模式时,程序员应该考虑哪些额外的考虑(设计模式中提供的那些)?

虽然专注于C++,但下面的许多内容将适用于任何语言.

请注意:由于SO限制了30000个单词的答案,我的答案必须分为两部分,但有时候第二个答案(首先是"主题"出现的答案).答案的第1部分是从Design Patterns的类图开始的答案.

Izh*_*aki 12

在此输入图像描述

(第一部分的开头)

先决条件

这不是关于国家的全部

设计模式将观察者模式与对象"状态"联系起来.如上面的类图(来自设计模式)所示,可以使用该SetState()方法设置主题的状态; 在国家变更后,主体将通知其所有观察员; 然后观察者可以使用该GetState()方法查询新状态.

但是,GetState()它不是主题基类中的实际方法.相反,每个具体主题都提供自己专门的状态方法.实际代码可能如下所示:

SomeObserver::onUpdate( aScrollManager )
{
    // GetScrollPosition() is a specialised GetState();
    aScrollPosition = aScrollManager->GetScrollPosition();
}
Run Code Online (Sandbox Code Playgroud)

什么是对象状态?我们将其定义为状态变量的集合 - 需要持久化的成员变量(以便稍后恢复).举例来说,无论是BorderWidthFillColour可能是一个图类的状态变量.

我们可以拥有多个状态变量 - 因此一个对象的状态可以以多种方式改变 - 的想法很重要.这意味着受试者可能会发生多种类型的状态变化事件.它还解释了为什么GetState()在主题基类中使用方法毫无意义.

但是只能处理状态变化的观察者模式是不完整的 - 观察者观察无状态通知(即与状态无关的通知)是很常见的.例如,KeyPressMouseMoveOS事件; 或类似的事件BeforeChildRemove,这显然不表示实际的状态变化.这些无状态事件足以证明推送机制的合理性 - 如果观察者无法从主题中检索更改信息,则所有信息都必须与通知一起提供(稍后将详细介绍).

会有很多事件

很容易看出一个主题在"现实生活中"如何引发许多类型的事件; 快速浏览一下ExtJs库,可以看出有些类提供了超过30个事件.因此,广义的主题 - 观察者协议必须集成设计模式所谓的"兴趣" - 允许观察者订阅特定事件,并且主体仅向感兴趣的观察者发起该事件.

// A subscription with no interest.
aScrollManager->Subscribe( this );

// A subscription with an interest.
aScrollManager->Subscribe( this, "ScrollPositionChange" );
Run Code Online (Sandbox Code Playgroud)

它可能是多对多的

单个观察者可以观察来自多个主体的相同事件(使观察者 - 主体关系多对多).例如,属性检查员可以监听许多所选对象的相同属性的更改.如果观察者对发送通知的主体感兴趣,则通知必须包含发件人:

SomeSubject::AdjustBounds( aNewBounds )
{
    ...
    // The subject also sends a pointer to itself.
    Fire( "BoundsChanged", this, aNewBounds );
}

// And the observer receives it.
SomeObserver::OnBoundsChanged( aSender, aNewBounds )
{
}
Run Code Online (Sandbox Code Playgroud)

但值得注意的是,在许多情况下,观察者并不关心发送者的身份.例如,当主体是单身时,或者当观察者对事件的处理不依赖于主体时.因此,不是强迫发送者成为协议的一部分,我们应该允许它,将其留给程序员是否拼写发送者.

观察员

事件处理程序

处理事件的观察者方法(即事件处理程序)可以有两种形式:覆盖或任意.在观察员的实施中提供关键和复杂的部分,本节将讨论这两者.

重写的处理程序

被覆盖的处理程序是Design Patterns提供的解决方案.基类Subject类定义一个虚OnEvent()方法,子类重写它:

class Observer
{
public:
    virtual void OnEvent( EventType aEventType, Subject* aSubject ) = 0;
};

class ConcreteObserver
{
    virtual void OnEvent( EventType aEventType, Subject* aSubject )
    {
    }
};
Run Code Online (Sandbox Code Playgroud)

请注意,我们已经解释了主题通常会触发多种类型的事件.但是在方法中处理所有事件(特别是如果有几十个事件)OnEvent是不实用的 - 如果每个事件都在自己的处理程序中处理,我们可以编写更好的代码; 实际上,这使得OnEvent事件路由器成为其他处理程序:

void ConcreteObserver::OnEvent( EventType aEventType, Subject* aSubject )
{
    switch( aEventType )
    {
        case evSizeChanged:
            OnSizeChanged( aSubject );
            break;
        case evPositionChanged:
            OnPositionChanged( aSubject );
            break;
    }
}

void ConcreteObserver::OnSizeChanged( Subject* aSubject )
{
}

void ConcreteObserver::OnPositionChanged( Subject* aSubject )
{
}
Run Code Online (Sandbox Code Playgroud)

具有重写(基类)处理程序的优点是它易于实现.订阅主题的观察者可以通过提供对自己的引用来做到这一点:

void ConcreteObserver::Hook()
{
    aSubject->Subscribe( evSizeChanged, this );
}
Run Code Online (Sandbox Code Playgroud)

然后主题只保留一个Observer对象列表,并且触发代码可能如下所示:

void Subject::Fire( aEventType )
{
    for ( /* each observer as aObserver */)
    {
        aObserver->OnEvent( aEventType, this );
    }
}
Run Code Online (Sandbox Code Playgroud)

重写处理程序的缺点是它的签名是固定的,这使得额外参数(在推模型中)的传递变得棘手.此外,对于每个事件,程序员必须维护两位代码:router(OnEvent)和实际的handler(OnSizeChanged).

任意处理程序

克服被覆盖的OnEvent处理程序的不足的第一步是......没有全部!如果我们可以告诉主题哪个方法来处理每个事件,那将是很好的.像这样的东西:

void SomeClass::Hook()
{
    // A readable Subscribe( evSizeChanged, OnSizeChanged ) has to be written like this:
    aSubject->Subscribe( evSizeChanged, this, &ConcreteObserver::OnSizeChanged );
}

void SomeClass::OnSizeChanged( Subject* aSubject )
{
}
Run Code Online (Sandbox Code Playgroud)

请注意,通过此实现,我们不再需要我们的类继承Observer该类; 实际上,我们根本不需要Observer类.这个想法并不是一个新概念,在Herb Sutter的2003年Dobbs博士的文章"Generalizing Observer"中有详细描述.但是,在C++中实现任意回调并不是一件简单的事情.赫伯function在他的文章中使用了这个设施,但不幸的是,他的提案中的一个关键问题尚未完全解决.该问题及其解决方案如下所述.

由于C++不提供本机委托,因此我们需要使用成员函数指针(MFP).C++中的MFP是类函数指针而不是对象函数指针,因此我们必须为(MFP)和(对象实例)提供Subscribe方法.我们将这个组合称为代表.&ConcreteObserver::OnSizeChangedthis

成员函数指针+对象实例=委托

Subject该类的实现可能依赖于比较代理的能力.例如,在我们希望向特定代表发起事件的情况下,或者当我们想要取消订阅特定代表时.如果处理程序不是虚拟处理程序并且属于订阅类(与基类中声明的处理程序相对),则委托可能具有可比性.但在大多数其他情况下,编译器或继承树(虚拟或多重继承)的复杂性将使它们无法比拟.Don Clugston撰写了一篇关于这个问题的精彩深入的文章,其中他还提供了一个克服这个问题的C++库; 虽然不符合标准,但该库几乎适用于所有编译器.

值得一提的是,虚拟事件处理程序是否是我们真正需要的东西; 也就是说,我们是否可能有一个观察者子类想要覆盖(或扩展)其(具体观察者)基类的事件处理行为的场景.可悲的是,答案是这很可能.因此,广义的观察者实现应该允许虚拟处理程序,我们很快就会看到一个这样的例子.

更新协议

Design Patterns的实现要点7描述了pull vs push模型.本节扩展了讨论范围.

使用拉模型,主体发送最少的通知数据,然后观察者需要从主题中检索更多信息.

我们已经确定拉模型不适用于无状态事件,例如BeforeChildRemove.也许值得一提的是,使用pull模型,程序员需要为每个事件处理程序添加代码行,这些代码行不会与推送模型一起存在:

// Pull model
void SomeClass::OnSizeChanged( Subject* aSubject )
{
    // Annoying - I wish I didn't had to write this line.
    Size iSize = aSubject->GetSize();
}

// Push model
void SomeClass::OnSizeChanged( Subject* aSubject, Size aSize )
{
    // Nice! We already have the size.
}
Run Code Online (Sandbox Code Playgroud)

值得记住的另一件事是我们可以使用推模型实现拉模型,但不是相反.虽然推模型为观察者提供了所需的所有信息,但程序员可能希望不发送特定事件的信息,并让观察者向主体询问更多信息.

固定推动

使用固定推送模型,通知所携带的信息通过约定的数量和类型的参数传递给处理程序.这很容易实现,但由于不同的事件将具有不同数量的参数,因此必须找到一些解决方法.在这种情况下,唯一的解决方法是将事件信息打包到一个结构(或类)中,然后传递给处理程序:

// The event base class
struct evEvent
{
};

// A concrete event
struct evSizeChanged : public evEvent
{
    // A constructor with all parameters specified.
    evSizeChanged( Figure *aSender, Size &aSize )
      : mSender( aSender ), mSize( aSize ) {}

    // A shorter constructor with only sender specified.
    evSizeChanged( Figure *aSender )
      : mSender( aSender )
    {
        mSize = aSender->GetSize();
    }

    Figure *mSender;
    Size    mSize;
};

// The observer's event handler, it uses the event base class.
void SomeObserver::OnSizeChanged( evEvent *aEvent )
{
    // We need to cast the event parameter to our derived event type.
    evSizeChanged *iEvent = static_cast<evSizeChanged*>(aEvent);

    // Now we can get the size.
    Size iSize  = iEvent->mSize;
}
Run Code Online (Sandbox Code Playgroud)

现在虽然主题及其观察者之间的协议很简单,但实际实施相当漫长.有一些缺点要考虑:

首先,我们需要evSizeChanged为每个事件编写相当多的代码(请参阅参考资料).很多代码都很糟糕.

其次,涉及的一些设计问题并不容易回答:我们是否应该evSizeChangedSize课堂上声明,或者与发射它的主题一起声明?如果你考虑一下,两者都不理想.那么,尺寸变化通知是否总是带有相同的参数,还是依赖于主体?(答案:后者是可能的.)

第三,有人需要在触发前创建事件的实例,然后将其删除.所以主题代码看起来像这样:

// Argh! 3 lines of code to fire an event.
evSizeChanged *iEvent = new evSizeChanged( this );
Fire( iEvent );
delete iEvent;
Run Code Online (Sandbox Code Playgroud)

或者我们这样做:

// If you are a programmer looking at this line than just relax!
// Although you can't see it, the Fire method will delete this 
// event when it exits, so no memory leak!
// Yes, yes... I know, it's a bad programming practice, but it works.
// Oh.. and I'm not going to put such comment on every call to Fire(),
// I just hope this is the first Fire() you'll look at and just 
// remember.
Fire( new evSizeChanged( this ) );
Run Code Online (Sandbox Code Playgroud)

第四,有一个铸造业务正在进行中.我们在处理程序中完成了转换,但也可以在主题的Fire()方法中完成.但是这将涉及动态转换(性能代价高昂),或者我们进行静态转换,如果事件被触发并且处理程序期望的事件不匹配,则可能导致灾难.

第五,处理程序arity几乎没有可读性:

// What's in aEvent? A programmer will have to look at the event class 
// itself to work this one out.
void SomeObserver::OnSizeChanged( evSizeChanged *aEvent )
{
}
Run Code Online (Sandbox Code Playgroud)

与此相反:

void SomeObserver::OnSizeChanged( ZoomManager* aManager, Size aSize )
{
}
Run Code Online (Sandbox Code Playgroud)

这引导我们进入下一部分.

变异推动

就查看代码而言,许多程序员希望看到这个主题代码:

void Figure::AdjustBounds( Size &aSize )
{
     // Do something here.

     // Now fire
     Fire( evSizeChanged, this, aSize );
}

void Figure::Hide()
{
     // Do something here.

     // Now fire
     Fire( evVisibilityChanged, false );
}
Run Code Online (Sandbox Code Playgroud)

这个观察者代码:

void SomeObserver::OnSizeChanged( Figure* aFigure, Size aSize )
{
}

void SomeObserver::OnVisibilityChanged( aIsVisible )
{
}
Run Code Online (Sandbox Code Playgroud)

主题的Fire()方法和观察者处理程序每​​个事件都有不同的arity.代码是可读的,并且尽可能短.

这个实现涉及一个非常干净的客户端代码,但会带来相当复杂的Subject代码(具有大量的功能模板和可能的其他好东西).这是大多数程序员将要采取的权衡 - 最好在一个地方(Subject类)拥有复杂的代码,而不是许多(客户端代码); 如果主题类完美地工作,程序员可能只是把它视为一个黑盒子,很少关注它是如何实现的.

值得考虑的是如何以及何时确保Firearity和处理程序arity匹配.我们可以在运行时完成它,如果两者不匹配,我们会提出一个断言.但是如果我们在编译期间遇到错误会非常好,为此我们必须明确地声明每个事件的arity,就像这样:

class Figure : public Composite, 
               public virtual Subject
{
public:
    // The DeclareEvent macro will store the arity somehow, which will
    // then be used by Subscribe() and Fire() to ensure arity match 
    // during compile time.
    DeclareEvent( evSizeChanged, Figure*, Size )
    DeclareEvent( evVisibilityChanged, bool )
};
Run Code Online (Sandbox Code Playgroud)

稍后我们将看到这些事件声明如何发挥另一个重要作用.

(第一部分结束)


Izh*_*aki 7

(start of part II)

Subjects

The Subscription Process

What is being stored?

Depending on the specific implementation, subjects may store the following data when observers subscribe:

  • Event Id – The interest, or what event the observer subscribes to.
  • The Observer Instance – most commonly in the form of an object pointer.
  • A member function pointer – if using an arbitrary handler.

This data will form the parameters of the subscribe method:

// Subscription with an overridden handler (where the observer class has a base class handler method).
aSubject->Subscribe( "SizeChanged", this );

// Subscription with an arbitrary handler.
aSubject->Subscribe( "SizeChanged", this, &ThisObserverClass::OnSizeChanged );
Run Code Online (Sandbox Code Playgroud)

It is worth noting that if arbitrary handler are used, member function pointers are likely to be packed together with the observer instance in a class or a struct to form a delegate. And so the Subscribe() method might have the following signature:

// Delegate = object pointer + member function pointer.
void Subject::Subscribe( EventId aEventId, Delegate aDelegate )
{
   //...
}
Run Code Online (Sandbox Code Playgroud)

实际存储(可能在a内std::map)将涉及事件id作为键,代理作为值.

实现事件ID

在触发它们的主题类之外定义事件ID可以简化对这些ID的访问.但总的来说,一个主题发起的事件对于该主题是独一无二的.因此,在大多数情况下,在主题类中声明事件ID是合乎逻辑的.

虽然声明事件ID的方法不止一种,但这里只讨论了3个最感兴趣的方法:

从表面上看,Enums似乎是最合乎逻辑的选择:

class FigureSubject : public Subject
{
public:
    enum {
        evSizeChanged,
        evPositionChanged
    };
};
Run Code Online (Sandbox Code Playgroud)

枚举(在订阅和解雇时会发生)的比较很快.也许这种策略唯一的不便是观察者需要在订阅时指定类:

// 'FigureSubject::' is the annoying bit.
aSubject->Subscribe( FigureSubject::evSizeChanged, this );
Run Code Online (Sandbox Code Playgroud)

Strings provide a 'looser' option to enums, as typically the subject class will not declare them like enums; instead, clients will just use:

// Observer code
aFigure->Subscribe( "evSizeChanged", this );
Run Code Online (Sandbox Code Playgroud)

The nice thing about strings is that most compilers colour code them differently from other parameters, which somehow improves the readability of the code:

// Within a concrete subject
Fire( "evSizeChanged", mSize, iOldSize );
Run Code Online (Sandbox Code Playgroud)

But the issue with strings is that we cannot tell at runtime if we have misspelled an event name. Also, string comparison takes longer than enum comparison, as strings have to be compared character by character.

Types is the last option discussed here:

class FigureSubject : public Subject
{
public:
    // Declaring the events this subject supports.
    class SizeChangedEventType     : public Event {} SizeChangedEvent;
    class PositionChangedEventType : public Event {} PositionChangedEvent;
};
Run Code Online (Sandbox Code Playgroud)

The benefit of using types is that they allow the overloading of methods like Subscribe() (which we'll soon see can solve a common problem with observers):

// This particular method will be called only if the event type is SizeChangedType
FigureSubject::Subscribe( SizeChangedType aEvent, void *aObserver )
{
    Subject::Subscribe( aEvent, aObserver );

    Fire( aEvent, GetSize(), aObserver );
}
Run Code Online (Sandbox Code Playgroud)

But again, observers need a bit of extra code to subscribe:

// Observer code
aFigure->Subscribe( aFigure->SizeChangedEvent, this );
Run Code Online (Sandbox Code Playgroud)

Where to Store Observers?

设计模式中的实施点1处理每个主题的观察者应该存储在何处.本节增加了该讨论,提供了3个选项:

  • 全球哈希
  • 每科目
  • 每场比赛

As suggested in Design Patterns, one place to store the subject-observer map is in a global hash table. The table will include the subject, the event and the observer (or delegate). Of all methods, this one is the most memory efficient as subjects don’t consume a member variable to store the list of observers in – there is only one global list. This could be useful if the pattern is implemented in javascript frameworks due to the limited memory offered by browsers. The main disadvantage of this method is that it is also the slowest – for every event being fired, we first have to filter the requested subject from the global hash, then filter the requested event, and only then iterate through all observers.

Also suggested in Design Patterns is that every subject keeps a list of its observers. This will consume slightly more memory (in the form of an std::map member variable per subject), but it provides better performance than a global hash as the subject only need to filter the requested event, then iterate through all the observers of this event. The code may look like so:

class Subject
{
protected:    
    // A callback is represented by the event id and the delegate.
    typedef std::pair< EventId, Delegate > Callback;

    // A map type to store callbacks
    typedef std::multimap< EventId, Delegate > Callbacks;

    // A callbacks iterator
    typedef Callbacks::iterator CallbackIterator;

    // A range of iterators for use when retrieving the range of callbacks
    // of a specific event.
    typedef std::pair< CallbackIterator, CallbackIterator> CallbacksRange;

    // The actual callback list
    Callbacks mCallbacks;
public:
    void Fire( EventId aEventId )
    {
        CallbacksRange   iEventCallbacks;
        CallbackIterator iIterator;

        // Get the callbacks for the request event.
        iEventCallbacks = mCallbacks.equal_range( aEventId );

        for ( iIterator = iEventCallbacks.first; iIterator != iEventCallbacks.second; ++iIterator  )
        {
            // Do the firing.
        }
    }
};
Run Code Online (Sandbox Code Playgroud)

Not suggested in Design Patterns is the option of having each event as a member variable, then store observers within the event itself. This is the most memory consuming strategy as not only each event consumes a member variable, but there’s also an std::vector storing observers per event. However, this strategy provides best performance as there’s no filtering to be done and we can just iterate through the attached observers. This strategy will also involve the most simplistic code compared to the other two. To implement it, an event will have to offer subscription and fire methods:

class Event
{
public:
    void Subscribe( void *aDelegate );
    void Unsubscribe( void *aDelegate );

    void Fire();
};
Run Code Online (Sandbox Code Playgroud)

The subject may look something like so:

class ConcreteSubject : public Subject
{
public:
    // Declaring the events this subject supports.
    class SizeChangedEventType     : public Event {} SizeChangedEvent;
    class PositionChangedEventType : public Event {} PositionChangedEvent;
};
Run Code Online (Sandbox Code Playgroud)

Although observers could theoretically subscribe to the events directly, we’ll see that it pays to go through the subject instead:

// Subscribing to the event directly - possible but will limit features.
aSubject->SizeChangedEvent.Subscribe( this );

// Subscribing via the subject.
aSubject->Subscribe( aSubject->SizeChangedEvent, this );
Run Code Online (Sandbox Code Playgroud)

The 3 strategies provide a clear case of the store-vs-compute tradeoff. And can be compared using the following table:

enter image description here

The approach taken should account for the following:

  • The subjects/observers ratio – The memory penalty will be higher in systems with few observers and many subjects, especially where the typical subject will have no or just one observer.
  • The frequency of notifications – Where the more frequent notifications are, the higher the performance penalty will be.

When the observer pattern is used to notify MouseMove events, one may want to consider more the performance of the implementation. As far as memory penalties go, the following calculation may help. Given:

  • Using the per-event strategy
  • A typical 64-bit system
  • Each subject has an average of 8 events

8 million subject instances will consume just below 1GB of RAM (events memory only).

Same observer, same event?

One key question in the implementation of the observer pattern is whether or not we allow the same observer to subscribe more than once to the same event (of the same subject).

To begin with, if we do allow it we are likely to use std::multimap instead of std::map. In addition, the following line will be problematic:

aSubject->Unsubscribe( evSizeChanged, this );
Run Code Online (Sandbox Code Playgroud)

Since there is no way for the subject to know which of the previous subscriptions (there can be more than one!) to unsubscribe from. So Subscribe() will have to return a token that Unsubscribe() will use, and the whole implementation gets far more complex.

On the face of it, it seems rather idiotic – why would the same object like to subscribe to the same event more than once? But consider the following code:

class Figure
{
public:
    Figure( Subject *aSubject )
    {
        // We subscribe to the subject on size events
        aSubject->Subscribe( evSizeChanged, this, &Figure::OnSizeChanged );
    }

    void OnSizeChanged( Size aSize )
    {
    }
};

class Circle : public Figure
{
public:
    Circle( Subject *aSubject )
      : Figure( aSubject) 
    {
        // We subscribe to the subject on size events
        aSubject->Subscribe( evSizeChanged, this, &Circle::OnSizeChanged );
    }

    void OnSizeChanged( Size aSize )
    {
    }
};
Run Code Online (Sandbox Code Playgroud)

This particular code will lead to the same object subscribing to the same event twice. It is also worth noticing that since the OnSizeChanged() method is not virtual, the member function pointer will be different between the two subscription calls. So in this particular case the subject could also compare the member function pointer, and the unsubscribe signature will be:

aSubject->Unsubscribe( evSizeChanged, this, &Circle::OnSizeChanged );
Run Code Online (Sandbox Code Playgroud)

But if the OnSizeChanged() is virtual, there is no way to distinguish between the two subscription calls without a token.

Truth to be told, if OnSizeChanged() is virtual, there is no reason for the Circle class to subscribe to the event again since it is its own handler that will be called and not that of the base class:

class Figure
{
public:
    // Constructor
    Figure( Subject *aSubject )
    {
        // We subscribe to the subject on size events
        aSubject->Subscribe( evSizeChanged, this, &Figure::OnSizeChanged );
    }

    virtual void OnSizeChanged( Size aSize )
    {
    }
};

class Circle : public Figure
{
public:
    // Constructor
    Circle( Subject *aSubject )
      : Figure( aSubject) { }

    // This handler will be called first when evSizeChanged is fired.
    virtual void OnSizeChanged( Size aSize )
    {
        // And we can call the base class handler if we want.
        Figure::OnSizeChanged( aSize );
    }
};
Run Code Online (Sandbox Code Playgroud)

This code probably represents the best compromise when it comes to both the base class and its subclass having to respond to the same event. But it requires the handlers to be virtual and the programmer to know which events the base class subscribes to.

Disallowing the same observer to subscribe more than once to the same event greatly simplifies the implementation of the pattern. It saves the need to compare member function pointers (a tricky business) and it allows Unsubscribe() to be as short as this (even if an MFP was provided with Subscribe()):

aSubject->Unsubscribe( evSizeChanged, this );
Run Code Online (Sandbox Code Playgroud)

Post Subscription Consistency

One of the prime aims of the observer pattern is to keep observers consistent with their subject state – and we have already seen that state change events do exactly that.

It is somewhat surprising that it went amiss to the authors of Design Patterns to assert that when an observer subscribes to a subject, the state of the former is not yet consistent with the state of the latter. Consider this code:

class Figure
{
public:
    // Constructor
    Figure( FigureSubject *aSubject )
    {
        // We subscribe to the subject on size events
        aSubject->Subscribe( evSizeChanged, this, &Figure::OnSizeChanged );
    }

    virtual void OnSizeChanged( Size aSize )
    {
        mSize = aSize;

        // Refresh the view.
        Refresh();
    }
private:
    Size mSize;
};
Run Code Online (Sandbox Code Playgroud)

Upon creation, the Figure class does subscribe with its subject, but its size is not consistent with that of the subject, nor it will refresh the view to display what should be its correct size.

When the observer pattern is used to fire state change event, one will often find the need to update the observers manually after subscription. One way to achieve this is within the observer:

class Figure
{
public:
    Figure( FigureSubject *aSubject )
    {
        // We subscribe to the subject on size events
        aSubject->Subscribe( evSizeChanged, this, &Figure::OnSizeChanged );

        // Now make sure we're consistent with the subject.
        OnSizeChanged( aSubject->GetSize() );
    }

    // ...
};
Run Code Online (Sandbox Code Playgroud)

But imagine a subject with 12 state change events. It would be nice if the whole thing would happen automatically, where upon subscription the subject will fire the correct event back to the observer.

One way to achieve this requires an overloaded Subscribe() method in the concrete subject:

// This method assumes that each event has its own unique class, so the method
// can be overloaded.
FigureSubject::Subscribe( evSizeChanged aEvent, Delegate aDelegate )
{
    Subject::Subscribe( aEvent, aDelegate );

    // Notice the last argument in this call.
    Fire( aEvent, GetSize(), aDelegate );
}
Run Code Online (Sandbox Code Playgroud)

Then the observer code:

class Figure
{
public:
    Figure( FigureSubject *aSubject )
    {
        // We subscribe to the subject on size events.
        // The subject will fire the event upon subscription
        aSubject->Subscribe( evSizeChanged, MAKEDELEGATE( this, &Figure::OnSizeChanged ) );
    }

    // ...
};
Run Code Online (Sandbox Code Playgroud)

Notice that the Fire call now takes an extra parameter (aDelegate) so it can only update that specific observer and not observers already subscribed.

gxObserver deals with this scenario by defining bound events. These are events whose only parameter (other than an optional sender) is bound to a getter or a member variable:

class Subject : virtual public gxSubject
{
public:
    gxDefineBoundEvent( evAge, int, GetAge() )

    int GetAge() { return mAge; }
private:
    int mAge;    
}
Run Code Online (Sandbox Code Playgroud)

Which also allows subjects to fire an event providing only the event type:

// Same as Fire( evAge, GetAge() );
Fire( evAge );
Run Code Online (Sandbox Code Playgroud)

Regardless of the mechanism used, it is worth remembering:

  • There need to be a way to ensure observers are consistent with their subject right after state event subscription.
  • It is better if this is implemented in the subject class, not in the observer code.
  • The Fire() method may need an extra optional parameter so it can fire to a single observer (the one that just subscribed).

The Firing Process

Fire From the Base Class

The following code snippet shows the implementation of event firing in JUCE:

void Button::sendClickMessage (const ModifierKeys& modifiers)
{
    for (int i = buttonListeners.size(); --i >= 0;)
    {
        ButtonListener* const bl = (ButtonListener*) buttonListeners[i];
        bl->buttonClicked (this);
    }
}
Run Code Online (Sandbox Code Playgroud)

There are a few issues with this approach:

  • It is apparent from the code that the class maintains its own list of buttonListeners, which would imply it also has its own AddListener and RemoveListener methods.
  • The concrete subject is the one looping through the list of observers.
  • The subject is highly coupled to its observer, as it knows both its class (ButtonListener) and the actual callback method within it (buttonClicked).

All these points mean that there is no base subject class. If taking this approach, any firing/subscription mechanism will have to be re-implemented per each concrete subject. This is counter object oriented programming.

It would be sensible to have the observers’ management, their traversal, and the actual notification done in a subject base class; this way, any changes to the underlining mechanism (introducing thread-safety, for instance) would not require a change in each concrete subject. This will leave our concrete subjects with a well-encapsulated and simple interface, and firing is reduced to one line:

// In a concreate subject
Fire( evSize, GetSize() );
Run Code Online (Sandbox Code Playgroud)

Events Suspension and Resumption

Many applications and frameworks will find the need to suspend the firing of events for a specific subject. Sometimes we’d like the suspended events to queue up and be fired when we resume firing, sometimes we just want to ignore them. As far as the subject interface goes:

class Subject
{
public:
    void SuspendEvents( bool aQueueSuspended );
    void ResumeEvents();
};
Run Code Online (Sandbox Code Playgroud)

One example to where event suspension is useful is during the destruction of composite objects. When a composite object is being destroyed, it first destroys all of its children, which first destroy all of their children, and so on. Now if these composite objects reside in the model layer, they’ll need to notify their corresponding objects in the view layer (say using an evBeforeDestroy event):

enter image description here

Now in this specific case there is no need for each object to fire an evBeforeDestroy event – it will do if only the top-level model object will (deleting the top-level view object will also delete all its children). So whenever a composite as such is destroyed, it would like to suspend the events of its children (without queuing them).

Another example would be the loading of document involving many objects, some observe others. While a subject may load first and have its size set based on the file data, its observers may not yet be loaded and thus would not get the size change notification. In this case, we’d like to suspend events before load, but queue them until the document was loaded in full. Firing all queued events then will ensure all observers are consistent with their subjects.

Lastly, an optimised queue will not queue the same event for the same subject more than once. When notifications resume, there is no point notifying observers of a size change to (10,10) if a later queued event will notify (20,20). So the most recent version of each event is the one the queue should keep.

How to add subject capabilities to a class?

A typical subject interface would look something along these lines:

class Subject
{
public:
    virtual void Subscribe( aEventId, aDelegate );
    virtual void Unsubscribe( aEventId, aDelegate );
    virtual void Fire( aEventId );
}
Run Code Online (Sandbox Code Playgroud)

The question is how do we add this interface to various classes. There are three options to consider:

  • Inheritance
  • Composition
  • Multiple Inheritance

Inheritance

In Design Patterns, a ConcreteSubject class inherits from a Subject class.

class ScrollManager: public Subject
{
}
Run Code Online (Sandbox Code Playgroud)

Both the class diagrams and sample code in Design Pattern could easily leave one thinking that this is how to go about it. But the very same book warns against inheritance and recommends favouring composition over it. This is sensible: consider an application with many composites where only some are subjects; shall the Composite class inherit from the Subject class? If so, many composites will have subject capabilities they don’t need, and there may a memory penalty in the form of an observer list variable that is always empty.

Composition

Most applications and frameworks will find the need to 'plug’ the subject capabilities only into selected classes, which are not necessarily base classes. Composition allows exactly that. In practice, a class will have a member mSubject providing interface to all the subject methods, like so:

class ScrollManager: public SomeObject
{
public:
    Subject mSubject;
}
Run Code Online (Sandbox Code Playgroud)

One issue with this strategy is that it carries memory penalty (a member variable) for each subject-supported class. The other is that it makes accessing the subject protocol somewhat cumbersome:

// Notification within a class composed with the subject protocol.
mSubject.Fire( ... );

// Or the registration from an observer.
aScrollManager.mSubject.Subscribe( ... );
Run Code Online (Sandbox Code Playgroud)

Multiple Inheritance

Multiple Inheritance allows us to compose the subject protocol into a class at will, but without the pitfalls of member composition:

class ScrollManager: public SomeObject,
                     public virtual Subject
{
}
Run Code Online (Sandbox Code Playgroud)

This way, we are getting rid of mSubject from the previous example, so we’re left with:

// Notification within a subject class.
Fire( ... );

// Or the registration from an observer.
aScrollManager.Subscribe( ... );
Run Code Online (Sandbox Code Playgroud)

Notice that we use public virtual for the subject inheritance, so if subclasses of ScrollManager decide to re-inherit the protocol, we don’t get the interface twice. But it is fair to assume that programmers will notice that a base class is already a subject, thus there’s no reason to re-inherit it.

While multiple inheritance is generally not encouraged and not all languages support it, it is well worth considering for this purpose. ExtJs, which being based on Javascript does not support multiple inheritance, uses mixins to achieve the same thing:

Ext.define('Employee', {
    mixins: {
        observable: 'Ext.util.Observable'
    },

    constructor: function (config) {
        this.mixins.observable.constructor.call(this, config);
    }
});
Run Code Online (Sandbox Code Playgroud)

Conclusion

To conclude this article, generalised implementations of the observer pattern should account to these key points:

  • Subjects fire both state and stateless events – the latter can only be realised with a push model.
  • Subjects typically fire more than one type of event.
  • Observers may subscribe to the same event with a multitude of subjects. Meaning the subject-observer protocol should allow the sender to be spelt.
  • Arbitrary event handlers greatly simplify client code and facilitate the preferred vari-arity push model; but their implementation is not straight forward and will result in a more complex subject code.
  • Event handlers may need to be virtual.
  • Observers can be stored in a global hash, per subject, or per event. The choice forms a tradeoff between memory, performance and code simplicity.
  • Ideally an observer only subscribes once with the same subject to the same event.
  • Keeping observers consistent with their subject’s state straight after subscription is something to keep in mind.
  • The firing of events may need to be suspended with a queuing option, and then resumed.
  • Multiple inheritance is worth considering when adding subject capabilities to a class.

(end of part II)