了解C ++中的基本概念之外的观察者模式

问题描述 投票:5回答:2

[我正在研究Head First Design Patterns中的设计模式,为了变得自信,我计划在学习相应的章节之后,用C ++实现每个模式。

关于观察者模式,我确实在努力超越语言所主导的主要思想。

我一直在浏览以下内容:

但是,当我开始使用C ++进行编码时,某些特定于语言的困难就暴露了我对整个主题的误解,而我无法通过上面的方式解决。但是,我已经在这里发布了,因为我有一个(貌似)工作代码。

示例代码如下,之后我列出了一些我对这种模式的理解和使用的关注。

#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <unordered_set>

class Observer {
 public:
  virtual void update() = 0;
};

class Observable {
 protected:
  std::unordered_set<Observer*> observers;
 public:
  virtual void addObserver(Observer&) = 0;
  virtual void removeObserver(Observer&) = 0;
  virtual void notifyObservers() = 0;
};

class Virus : public Observable {
 public:
  void addObserver(Observer& o) {
    observers.insert(&o);
  };
  void removeObserver(Observer& o) {
    std::erase_if(observers, [&o](auto const& io){ return &o == io; });
  };
  void notifyObservers() {
    for (auto& o : observers) o->update();
  };
  void operator++() {
    ++spread;
    std::cout << "\nLevel: " << spread
              << "\nSending notifications:\n";
    notifyObservers();
  }
  int getSpread() { return spread; };
 private:
   int spread = 0;
};

class NormalCountry final : public Observer {
 private:
  void update() override {
    if (obs.getSpread() < 2)
      std::cout << "NormalCountry: What!? Coronavirus?\n";
    else
      std::cout << "NormalCountry: Ok, let's quarantine...\n";
  };
 private:
  Virus& obs;
 public:
  void selfUnsubscribe() {
    obs.removeObserver(*this);
  };
  void selfSubscribe() {
    obs.addObserver(*this);
  };
  NormalCountry() = delete;
  NormalCountry(Virus& o) : obs(o) {
    selfSubscribe();
  }
};

class BraveCountry final : public Observer {
 public:
  void update() override {
    std::cout << "BraveCountry: No worries, people, our antibodies are cooler!\n";
  };
};

int main() {
  Virus cv;
  NormalCountry it(cv);
  BraveCountry uk;
  ++cv;
  cv.addObserver(uk);
  ++cv;
  it.selfUnsubscribe();
  ++cv;
}

关于模式本身的疑问:

  • 我的理解是,观察者仅需通过观察到的Observable即可知道其中的某些更改,这很容易将Observer视为一个接口, C ++表示一个抽象类,仅定义纯虚拟update方法,该方法迫使派生类使用特定的signature重载该方法(在Java中似乎差不多);但是,这并没有说明观察者是否应该了解比“二进制”信息更多的信息(什么/什么都没有改变);确实,这是牵拉或推挤设计的决定,一方面,对我而言,这看起来像是一个实现细节。另一方面,该决定会影响应如何声明update纯虚拟方法(选择参数列表),以及具体的观察者是否应持有指向被观察对象的指针/引用。这就是说,如果给了我两个抽象类ObserverObservable,则已经选择了实现方式,并且我不能更改。

  • 关于Observable接口,该书说它只需要提供addObserverremoveObservernotifyObservers的声明。但是,Observer的集合必须是具体观察者类的成员。我了解选择集合(std::vectorstd::list,...)是实现细节,但是集合some kind必须在Observable中的事实看起来并不像详情。但是,一旦尝试对上述三个成员函数进行编码,就必须以一种可观察的具体方式选择一个集合。也许就够了,我不知道。

  • Observer::update纯虚拟方法应该是公共的,以便具体的可观察对象实现notifyObservers。但是具体的观察者可以将其update的实现私有化。这样做对我来说很有意义:如果updatenotifyObservers是每个观察者可观察关系的两端,那么如果观察者的update具有以下条件,为什么调用者代码应该能够调用观察者的notifyObservers不决定这样做吗?好吧,也许出于同样的原因,观察者可以处理自己的(取消)订阅(如果它持有被观察对象的句柄)。

特定于C ++的疑问:

  • 可观察类(抽象基或派生的具体对象)是否应具有原始指针或智能指针的集合?这种选择的含义可能是什么?

  • addObserverremoveObserver应该采用Observer参数。我认为不应通过值传递来避免复制;也许甚至都不用指针,否则在呼叫站点,我们必须传递&obj而不是obj。然后通过引用;但是哪个呢?一个const左值引用将允许传递临时观察者,但这有意义吗?如果是的话,那么对rvalues和lvalues来说应该有两个重载,因为虚函数不允许具有通用引用的一个模板函数。

  • 在我的示例代码中,我已经在具体的onservers类之一中存储了对具体可观察对象的引用,以便可以在getSpread实现中使用特定于具体可观察对象(update)的成员。我担心这可能不好。

c++ observer-pattern
2个回答
1
投票
  1. 关于观察者界面

Obesever应该具有update(或onChange或类似的东西)方法。 Obesever几乎总是需要有关更改的上下文来自我更新。因此,此处出现“推与拉”问题。在推送中,可观察必须将上下文信息有效负载传递给观察者。但是现在出现了一个问题,满足所有观察者的有效载荷结构应该是什么?当添加新的观察者时,有效载荷结构将如何演变,该观察者需要一些上下文参数,而该上下文参数不是当前有效载荷结构的一部分?另一方面,在拉动设计中,观察者需要查询可观察到的新状态。这意味着可以观察到的需要向观察者公开适当的接口。推与拉之间的选择取决于用例。推送提供了更好的解耦,但是更新上下文信息应该足够简单。在拉方法中,观察者可以查询可观察对象并获取更复杂和自定义的状态信息,即使不同的观察者也可以以不同的方式查询可观察对象。但是拉法在观察者和可观察者之间具有斯托格纳耦合。

  1. 关于可观察者的收集

观察者集合的三个主要用例是addObserver,removeObserver和notifyObservers。因此,任何无序集合都可以。

  1. 关于观察者更新为公开

更新是一种通知方式,因此它必须在公共界面中可用。

特定于C ++

  1. 可观察者应保持弱引用(不延长观察者生命时间)观察者。
  2. 保持对观察者中可观察对象的引用:这不是一个好主意。可以将其视为pull方法的隐式实现。但是保留界面会比保留具体对象更好。

0
投票

很多问题,但这是我的一些观点

我更喜欢将它们视为客户端和服务器,但这只是我想的。

观察者模式的基本思想是,客户(观察者)对服务器中的某些内容感兴趣(可观察),请订阅该内容事件。服务器为客户端提供客户端必须实现的接口然后客户端将该实现传递给服务器。这基本上是当您执行addObserver时会做什么。然后服务器将调用该方法(或方法)在界面中描述。这是服务器的责任决定观察者应该执行什么]

服务器具有另一个接口来处理本身实现的订阅这些是您称为addObserver和removeObserver的函数。

接口可以写成简单的结构

struct IClient
{
  virtual void update(/*some argumet*/) = 0;
  virtual ~IClient() = default;
}

class Observer : IClient 
{
...
}

在客户端/服务器内部,如何表示数据取决于实现者,但通常是智能指针是WTG。

关于引用,我会改用shared_ptr并传递weak_ptr到服务器,或者将对象移动到服务器。

© www.soinside.com 2019 - 2024. All rights reserved.