观察者模式;知道*什么*改变了吗?


10

我创建了两个抽象类Subject和Observer,它们定义了经典的Observer模式接口。我从中派生出实现Observer模式的方法。观察者可能看起来像这样:

void MyClass::Update(Subject *subject)
{
    if(subject == myService_)
    {
        DoSomething();
    }
    else if(subject == myOtherService_)
    {
        DoSomethingElse();
    }
}

很好,它告诉我更改了某些内容。但是,它没有告诉我发生了什么变化。有时这是可以的,因为我只是要查询主题以获取最新数据,但是其他时候我需要知道主题上发生了什么变化。我注意到在Java中,它们同时具有notifyObservers()方法和notifyObservers(Object arg)方法,以大概指定有关更改内容的详细信息。

就我而言,我需要知道是否有几个不同的动作中的一个发生在主体上,如果是特定动作,则需要知道与该动作相关的整数。

所以我的问题是:

  1. 传递通用参数的C ++方法是什么(就像Java一样)?
  2. 观察者甚至是最好的模式吗?也许某种事件系统?

更新

我找到了这篇文章,该文章讨论了如何对观察者模式进行模板化:使用模板实现主题/观察者模式。这让我想知道是否可以模板化参数。

我发现了这个关于模板参数模板化的堆栈溢出问题:基于模板的主题观察者模式-我应该使用static_cast还是dynamic_cast。但是,OP似乎有一个没有人回答的问题。

我可以做的另一件事是将Update方法更改为采用EventArg对象,如下所示:

void MyClass::Update(Subject *subject, EventArg arg)
{
  ...

然后为特定的参数数据创建EventArg的子类,然后将其投射回update方法中的特定子类。

更新2

还发现了一篇关于创建基于异步消息的c ++框架的文章;第2部分将具有主题讨论交流有关细节什么改变。

我现在正在认真考虑使用Boost.Signals。简单的时候使用我自己的观察者模式很有意义,但是模板化类型和参数开始变得复杂。我可能需要Boost.Signals2的线程安全性。

更新3

我还发现了一些关于观察者模式的有趣文章:

赫伯·萨特的观察家概论

在C ++中实现观察者模式-第1部分

实施观察者设计模式的经验(第2部分)

实施观察者设计模式的经验(第3部分)

但是,我已将实现切换为使用Boost.Signals,尽管出于我的目的可能有点blo肿,但它可以成功运行。可能有关膨胀或速度的任何问题都是无关紧要的。


正文中的问题似乎与您的标题不符。真正的核心是哪个?
妮可(Nicole)

@Renesis:我目前正在使用Observer模式,如文章开头的代码示例中所示。对于我当前正在处理的代码,事实证明我需要具体了解更改的内容,以便做出相应的反应。我当前的观察者模式实现(这是标准模式)不提供此信息。我的问题是如何最好地获取有关更改的信息。
用户

@Renesis:这是一个论坛主题,询问与我类似的问题:gamedev.net/topic/497105-observer-pattern
用户,

广告更新2:我绝对支持使用Boost.Signals。比滚动自己的要方便得多。还有一个libsigc ++,如果您只是想为这项任务提供更轻巧的东西(Boost.Signals使用Boost其余部分中的很多代码);它不是线程安全的。
Jan Hudec

关于速度问题:我不知道具体怎么快速Boost.Signals的,但这只是一个问题,当你有一个巨大的事件量飞来飞去...
马克斯

Answers:


4

无论是C ++或Java中,一个通知观察者可以用的信息一起走什么改变。相同的方法notifyObservers(Object arg)也可以在C ++中使用。

通常,问题仍然存在,可能有多个主题分派给一个或多个观察者,因此class arg不能进行硬编码。

通常,最好的方法是使arg以通用消息/令牌的形式出现,该消息/令牌对于各种类别形成相同的数据类型,但是对于不同的观察到的类别,其值会有所不同。替代地,如果所有这样的通知值都是从某个对所有人都通用的基于基类的类中派生出来的。

对于观察者模式,这非常重要的精氨酸数据类型不硬observee与观察者之间编码-否则它是使事情难以发展的耦合。

编辑
如果您希望观察者不仅观察到而且还需要根据已更改内容执行许多其他任务,则还可以签出Visitor模式。在访问者模式中,观察者/访问者调用已修改的对象,因此不仅可以知道修改的内容,还可以对其进行实际操作


5
如果观察者解释的说法,也就是耦合,无论你怎么隐藏类型。事实上,我会说这是难以发展,如果你通过Objectvoid *boost::any或一些类似的通用)比,如果你通过特定类型的,因为你会在那件事改变了编译的时候看具体的类型,而与通用类型它将编译并停止工作,因为观察者将无法使用传递的实际数据。
1月Hudec

@JanHudec:我同意这一点,但这是否意味着您为每个参数(即,每个用例)设置了一个特定的一次性Observer / Subject子类集?
用户

@JanHudec:耦合也是唯一的方法。受试者对观察者一无所知。是的,观察者知道该主题,但不是观察者模式的工作方式吗?
用户

1
@User:是的,我为每个主题都创建了一个特定的接口,每个观察者都实现了需要观察的主题的接口。好吧,我使用的所有语言在语言或框架中都有绑定的方法指针(C#委托,C ++ 11 std::functionBoost boost::function,Gtk + GClosure,python绑定的方法等),因此我只定义了具有适当签名的方法,并要求系统创建实际的方法观察者。耦合确实只是一种方式,主题为观察者定义了接口,但是对实现没有任何想法。
Jan Hudec

0

有几种方法可以在C ++中发送“类似于Java”的通用事件参数。

1)将事件参数声明为void *,并将其强制转换为事件处理程序中的正确类。

2)将事件参数声明为指向新类/接口的指针/ ref,例如(根据您的示例)

class GenericEventArgument
{
  virtual bool didAction1Happen() = 0;
  virtual int getActionInteger() = 0;
};

并让实际事件参数类从此类派生。

至于您有关基于模板的观察者的问题,请查看

http://www.codeproject.com/Articles/3267/Implementing-a-Subject-Observer-pattern-with-templ


-1

我不知道这是否是规范名称,但是从我以前的Smalltalk时代起,我就记得用“方面”这个词来标识可观察对象发生了什么变化。

它不像您对EventArg(及其子类化)的想法那样复杂。它只是将字符串(甚至可以是整数常量)从可观察对象传递给观察者。

另外:只有两种简单的方法(update(observable)updateAspect(observable, aspect)

减:观察者可能不得不向可观察者询问进一步的信息(即您的“整数”)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.