Observable Member

このエントリの目指すところ

C# ライクなイベントハンドリングを実装する.パターンの名称がわからないので,とりあえず Observable Member という名称で記載する.

調査

語られる像

整理

疑問

要点

実例

設計

環境

$ cat /etc/os-release 
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
$ clang++ --version
Ubuntu clang version 14.0.0-1ubuntu1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

実装

design_pattern/observable_member.cpp
// Copyright (c) 2023, Kumazawa (sparrow-blue)
// This source code is licensed under the BSD 3-Clause License.
// See https://github.com/sparrow-blue/blog/blob/main/LICENSE for details.

#include "observable_member/observable_member.hpp"

#include <iostream>

using design_pattern::observable_member::EventHandler;
using design_pattern::observable_member::EventArgs;
using design_pattern::observable_member::ObservableLine;

int main() {
  auto observable_line = ObservableLine(10, 10);
  observable_line.HeightChanged += [](void* sender, EventArgs<int> e) {
    std::cout << "Height Changed: " << e.old_value << " -> " << e.new_value << std::endl;
  };
  observable_line.WidthChanged += [](void* sender, EventArgs<int> e) {
    std::cout << "Width Changed: " << e.old_value << " -> " << e.new_value << std::endl;
  };

  // 値の変更をトリガにイベントを発火する.
  observable_line.height(9);
  observable_line.width(0);
  observable_line.width(3);
  observable_line.height(7);
  return 0;
}
design_pattern/observable_member/observable_member.hpp
// Copyright (c) 2023, Kumazawa (sparrow-blue)
// This source code is licensed under the BSD 3-Clause License.
// See https://github.com/sparrow-blue/blog/blob/main/LICENSE for details.

#ifndef DESIGN_PATTERN_OBSERVABLE_MEMBER_OBSERVABLE_MEMBER_HPP_
#define DESIGN_PATTERN_OBSERVABLE_MEMBER_OBSERVABLE_MEMBER_HPP_

#include "ObservableDiagram.hpp"
#include "ObservableLine.hpp"
#include "EventHandler.hpp"

#endif  // DESIGN_PATTERN_OBSERVABLE_MEMBER_OBSERVABLE_MEMBER_HPP_
design_pattern/observable_member/ObservableDiagram.hpp
// Copyright (c) 2023, Kumazawa (sparrow-blue)
// This source code is licensed under the BSD 3-Clause License.
// See https://github.com/sparrow-blue/blog/blob/main/LICENSE for details.

#ifndef DESIGN_PATTERN_OBSERVABLE_MEMBER_OBSERVABLEDIAGRAM_HPP_
#define DESIGN_PATTERN_OBSERVABLE_MEMBER_OBSERVABLEDIAGRAM_HPP_

#include "EventHandler.hpp"

namespace design_pattern::observable_member {

class ObservableDiagram {
 public:
  ObservableDiagram(int width, int height) : width_(width), height_(height) {}

#pragma region width

  EventHandler<int> WidthChanged;

  void width(int width) {
    auto event_args = EventArgs<int>(width, width_);
    this->width_ = width;
    WidthChanged.Invoke(this, event_args);
  }

  int width() { return this->width_; }

#pragma endregion

#pragma region height

  EventHandler<int> HeightChanged;

  void height(int height) {
    auto event_args = EventArgs<int>(height, height_);
    this->height_ = height;
    HeightChanged.Invoke(this, event_args);
  }

  int height() { return this->height_; }

#pragma endregion

 private:
  int width_;
  int height_;
};

}  // namespace design_pattern::observable_member

#endif  // DESIGN_PATTERN_OBSERVABLE_MEMBER_OBSERVABLEDIAGRAM_HPP_
design_pattern/observable_member/ObservableLine.hpp
// Copyright (c) 2023, Kumazawa (sparrow-blue)
// This source code is licensed under the BSD 3-Clause License.
// See https://github.com/sparrow-blue/blog/blob/main/LICENSE for details.

#ifndef DESIGN_PATTERN_OBSERVABLE_MEMBER_OBSERVABLELINE_HPP_
#define DESIGN_PATTERN_OBSERVABLE_MEMBER_OBSERVABLELINE_HPP_

#include "ObservableDiagram.hpp"

namespace design_pattern::observable_member {

class ObservableLine : public ObservableDiagram {
 public:
  ObservableLine(int width, int height) : ObservableDiagram(width, height) {}
};

}  // namespace design_pattern::observable_member

#endif  // DESIGN_PATTERN_OBSERVABLE_MEMBER_OBSERVABLELINE_HPP_
design_pattern/observable_member/EventHandler.hpp
// Copyright (c) 2023, Kumazawa (sparrow-blue)
// This source code is licensed under the BSD 3-Clause License.
// See https://github.com/sparrow-blue/blog/blob/main/LICENSE for details.

#ifndef DESIGN_PATTERN_OBSERVABLE_MEMBER_EVENTHANDLER_HPP_
#define DESIGN_PATTERN_OBSERVABLE_MEMBER_EVENTHANDLER_HPP_

#include <functional>
#include <memory>
#include <vector>

namespace design_pattern::observable_member {

template <typename T>
class EventArgs {
 public:
  EventArgs(T after, T before) : new_value(after), old_value(before) {}
  T new_value;
  T old_value;
};

template <typename T>
class EventHandler {
  using Event = std::function<void(void*, EventArgs<T>)>;

 public:
  EventHandler() : subscribers_(std::make_unique<std::vector<Event>>()) {}
  void Invoke(void* sender, EventArgs<T> event_args) {
    std::for_each(subscribers_->begin(), subscribers_->end(),
                  [&sender, &event_args](Event& subscriber) { subscriber(sender, event_args); });
  }
  void Subscribe(Event event) { subscribers_->push_back(event); }
  void operator+=(Event&& event) { Subscribe(event); }

 private:
  const std::unique_ptr<std::vector<Event>> subscribers_;
};

}  // namespace design_pattern::observable_member

#endif  // DESIGN_PATTERN_OBSERVABLE_MEMBER_EVENTHANDLER_HPP_

実行結果

$ date && ./content/build/design_pattern/observable_member
Sun Jun 18 00:29:26 JST 2023
Height Changed: 10 -> 9
Width Changed: 10 -> 0
Width Changed: 0 -> 3
Height Changed: 9 -> 7