Observer Pattern は大きく以下の観点があると仮説をたて,それが妥当なのか,あるいは過不足があるのかを考える.
1..*
を実現しつつ操作同士を疎結合に保つ
例にもれず実装はする.
$ 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
$ scons --version
SCons by Steven Knight et al.:
SCons: v4.0.1.c289977f8b34786ab6c334311e232886da7e8df1, 2020-07-17 01:50:03, by bdbaddog on ProDog2020
SCons path: ['/usr/lib/python3/dist-packages/SCons']
Copyright (c) 2001 - 2020 The SCons Foundation
関連するファイル数が多いため,重要と思われるファイルをデフォルトで展開するようにした.
design_pattern/observer_pattern.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 "observer_pattern/observer_pattern.hpp"
using design_pattern::observer_pattern::Display;
using design_pattern::observer_pattern::ObservableLine;
using design_pattern::observer_pattern::ObservableTriangle;
/**
* 画面上に図形を配置するようなケースを考える.
* ObservableDiagram がモデル,Display はモデルを利用するプレゼンテーションのイメージである.
* モデルの状態が変化すると Display に通知され,モデルが自動的に操作 (画面更新,ここでは標準出力) される.
*/
int main() {
// モデルを利用するオブジェクト (Observer)
auto display = Display();
// 描画するモデルを生成する.
auto line = std::make_shared<ObservableLine>();
auto triangle = std::make_shared<ObservableTriangle>();
// 画面に図形を配置する.実態は Observer が Obsavable なオブジェクトの購読を始める.
display.Register(line);
display.Register(triangle);
// diagram の状態を変化させるごとに自動的に dump される.
// モデルは自身がどう扱われるか気にしないが Observer が状態を監視しており,状態が変化した後になされる操作が
// 自動的に実行される.プログラマもモデルの状態を変化させるだけでよく,そのあと行われる処理は別の文脈で考えればよい.
line->Resize(100, 100);
line->Resize(0, 0);
line->Resize(101, 101);
triangle->Resize(100, 100);
triangle->Resize(0, 0);
triangle->Resize(101, 101);
return 0;
}
design_pattern/observer_pattern/observer_pattern.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_OBSERVER_PATTERN_OBSERVER_PATTERN_HPP_
#define DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVER_PATTERN_HPP_
#include "observer_pattern/Display.hpp"
#include "observer_pattern/Observable.hpp"
#include "observer_pattern/ObservableDiagram.hpp"
#include "observer_pattern/ObservableLine.hpp"
#include "observer_pattern/ObservableTriangle.hpp"
#include "observer_pattern/Observer.hpp"
#endif // DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVER_PATTERN_HPP_
design_pattern/observer_pattern/Display.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_OBSERVER_PATTERN_DISPLAY_HPP_
#define DESIGN_PATTERN_OBSERVER_PATTERN_DISPLAY_HPP_
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "../../join.hpp"
#include "Observable.hpp"
#include "ObservableDiagram.hpp"
#include "Observer.hpp"
namespace design_pattern::observer_pattern {
/**
* 簡易的にプレゼンテーションを表現する.モデルが変更されるたびに画面を更新する.
*/
class Display : public Observer<ObservableDiagram> {
protected:
void OnPublished(std::shared_ptr<Observable> sender) override {
std::cout << std::static_pointer_cast<ObservableDiagram>(sender)->ToString() << std::endl;
}
private:
std::vector<std::shared_ptr<ObservableDiagram>> objects_;
std::string ToString() {
auto messages = std::vector<std::string>();
std::transform(this->objects_.begin(), this->objects_.end(), std::back_inserter(messages),
[](std::shared_ptr<ObservableDiagram> &subject) { return subject->ToString(); });
return Join("\n", messages);
}
void Dump() { std::cout << this->ToString() << std::endl; }
};
} // namespace design_pattern::observer_pattern
#endif // DESIGN_PATTERN_OBSERVER_PATTERN_DISPLAY_HPP_
design_pattern/observer_pattern/Observable.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_OBSERVER_PATTERN_OBSERVABLE_HPP_
#define DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVABLE_HPP_
#include <functional>
#include <memory>
#include <vector>
namespace design_pattern::observer_pattern {
/**
* 監視可能なオブジェクトを表現する
*/
class Observable : public std::enable_shared_from_this<Observable> {
public:
void Subscribe(std::function<void(std::shared_ptr<Observable>)> &&func) { subscribers_.push_back(func); }
protected:
void Notify() {
std::for_each(this->subscribers_.begin(), this->subscribers_.end(),
[this](auto &func) { func(shared_from_this()); });
}
private:
std::vector<std::function<void(std::shared_ptr<Observable>)>> subscribers_;
};
} // namespace design_pattern::observer_pattern
#endif // DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVABLE_HPP_
design_pattern/observer_pattern/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_OBSERVER_PATTERN_OBSERVABLEDIAGRAM_HPP_
#define DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVABLEDIAGRAM_HPP_
#include <memory>
#include <string>
#include <utility>
#include "../component/Diagram.hpp"
#include "Observable.hpp"
using design_pattern::component::Diagram;
namespace design_pattern::observer_pattern {
/**
* 監視可能な図形を表現する.
*/
class ObservableDiagram : public Observable {
public:
virtual void Resize(int height, int width) {
if (diagram()->height() == height && diagram()->width() == width) return;
diagram()->Resize(height, width);
this->Notify();
}
std::string ToString() { return diagram()->ToString(); }
protected:
explicit ObservableDiagram(std::unique_ptr<Diagram> diagram) : diagram_(std::move(diagram)) {}
const std::unique_ptr<Diagram> &diagram() { return this->diagram_; }
private:
const std::unique_ptr<Diagram> diagram_;
};
} // namespace design_pattern::observer_pattern
#endif // DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVABLEDIAGRAM_HPP_
design_pattern/observer_pattern/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_OBSERVER_PATTERN_OBSERVABLELINE_HPP_
#define DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVABLELINE_HPP_
#include <memory>
#include "../component/Line.hpp"
#include "observer_pattern/ObservableDiagram.hpp"
using design_pattern::component::Line;
namespace design_pattern::observer_pattern {
/**
* 監視可能な線分を表現する.
*/
class ObservableLine : public ObservableDiagram {
public:
ObservableLine() : ObservableDiagram(std::make_unique<Line>()) {}
};
} // namespace design_pattern::observer_pattern
#endif // DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVABLELINE_HPP_
design_pattern/observer_pattern/ObservableTriangle.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_OBSERVER_PATTERN_OBSERVABLETRIANGLE_HPP_
#define DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVABLETRIANGLE_HPP_
#include <memory>
#include "../component/Triangle.hpp"
#include "ObservableDiagram.hpp"
using design_pattern::component::Triangle;
namespace design_pattern::observer_pattern {
/**
* 監視可能な三角形を表現する.
*/
class ObservableTriangle : public ObservableDiagram {
public:
ObservableTriangle() : ObservableDiagram(std::make_unique<Triangle>()) {}
};
} // namespace design_pattern::observer_pattern
#endif // DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVABLETRIANGLE_HPP_
design_pattern/observer_pattern/Observer.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_OBSERVER_PATTERN_OBSERVER_HPP_
#define DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVER_HPP_
#include <memory>
#include <vector>
namespace design_pattern::observer_pattern {
/**
* 監視する責務を負う抽象クラスである.
* テンプレートの型は Observable を継承したクラスでなければならない.
*/
template <typename T>
class Observer {
static_assert(std::is_base_of<Observable, T>::value, "T must inherit from Observable");
public:
// NOLINTNEXTLINE(readability/inheritance)
virtual void Register(std::shared_ptr<T> subject) final {
subject->Subscribe([this](std::shared_ptr<Observable> sender) { this->OnPublished(sender); });
this->objects_.push_back(subject);
}
virtual void OnPublished(std::shared_ptr<Observable> sender) = 0;
virtual ~Observer() {}
protected:
std::vector<std::weak_ptr<T>> objects_; // Weak pointers to avoid circular references
};
} // namespace design_pattern::observer_pattern
#endif // DESIGN_PATTERN_OBSERVER_PATTERN_OBSERVER_HPP_
design_pattern/component/Canvas.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_COMPONENT_CANVAS_HPP_
#define DESIGN_PATTERN_COMPONENT_CANVAS_HPP_
#include <algorithm>
#include <iostream>
#include <iterator>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "../demangle.hpp"
#include "../join.hpp"
#include "Diagram.hpp"
namespace design_pattern::component {
/**
* 図形が描画されるキャンバス
*/
class Canvas {
public:
void AddDiagram(std::shared_ptr<Diagram> diagram) {
this->diagram_sets.push_back(std::make_shared<DiagramSet>(diagram));
}
void Dump() { std::cout << (this->diagram_sets.size() != 0 ? this->ToString() : std::string("empty")) << std::endl; }
std::string ToString() {
auto messages = std::vector<std::string>();
std::transform(diagram_sets.begin(), diagram_sets.end(), std::back_inserter(messages),
[](std::shared_ptr<DiagramSet> diagram_set) { return diagram_set->ToString(); });
return Join("\n", messages);
}
void Move(size_t index, int offset_x, int offset_y) { this->diagram_sets.at(index)->Move(offset_x, offset_y); }
std::vector<std::shared_ptr<Diagram>> diagrams() {
auto result = std::vector<std::shared_ptr<Diagram>>();
std::transform(this->diagram_sets.begin(), this->diagram_sets.end(), std::back_inserter(result),
[](std::shared_ptr<DiagramSet> diagram_set_) { return diagram_set_->diagram(); });
return result;
}
private:
/**
* Canvas が管理する Diagram のそれぞれがどの位置に存在するかを管理する
*/
class DiagramSet {
public:
explicit DiagramSet(std::shared_ptr<Diagram> diagram, int x = 0, int y = 0) : diagram_(diagram), x_(x), y_(y) {}
void Move(int offset_x, int offset_y) {
this->x_ += offset_x;
this->y_ += offset_y;
}
std::shared_ptr<Diagram> diagram() { return this->diagram_; }
std::string ToString() {
char buffer[0xFF];
sprintf(buffer, "x: %d, y: %d - %s", this->x_, this->y_, this->diagram_->ToString().c_str());
return buffer;
}
private:
std::shared_ptr<Diagram> diagram_;
int x_;
int y_;
};
std::vector<std::shared_ptr<DiagramSet>> diagram_sets;
};
} // namespace design_pattern::component
#endif // DESIGN_PATTERN_COMPONENT_CANVAS_HPP_
design_pattern/component/Diagram.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_COMPONENT_DIAGRAM_HPP_
#define DESIGN_PATTERN_COMPONENT_DIAGRAM_HPP_
#include <sstream>
#include <string>
#include "../demangle.hpp"
namespace design_pattern::component {
/**
* 描画される図形の抽象クラス
*/
class Diagram {
public:
Diagram() : height_(0), width_(0) {}
virtual ~Diagram() = 0;
/**
* 自身の型と大きさを文字列で表現する.
*/
std::string ToString() {
std::stringstream ss;
ss << Demangle(typeid(*this)) << " "
<< "(" << this->height() << "," << this->width() << ")";
return ss.str();
}
/**
* 自身の高さを返す.
*/
int height() { return this->height_; }
/**
* 自身の幅を返す.
*/
int width() { return this->width_; }
/**
* リサイズする.
*/
virtual void Resize(int height, int width) {
this->height_ = height;
this->width_ = width;
}
private:
int height_;
int width_;
};
Diagram::~Diagram() {}
} // namespace design_pattern::component
#endif // DESIGN_PATTERN_COMPONENT_DIAGRAM_HPP_
design_pattern/component/Line.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_COMPONENT_LINE_HPP_
#define DESIGN_PATTERN_COMPONENT_LINE_HPP_
#include "Diagram.hpp"
namespace design_pattern::component {
/**
* 線分を表現するクラス
*/
class Line : public Diagram {};
} // namespace design_pattern::component
#endif // DESIGN_PATTERN_COMPONENT_LINE_HPP_
design_pattern/component/Triangle.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_COMPONENT_TRIANGLE_HPP_
#define DESIGN_PATTERN_COMPONENT_TRIANGLE_HPP_
#include "Diagram.hpp"
namespace design_pattern::component {
/**
* 三角形を表現するクラス
*/
class Triangle : public Diagram {};
} // namespace design_pattern::component
#endif // DESIGN_PATTERN_COMPONENT_TRIANGLE_HPP_
demangle.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 DEMANGLE_HPP_
#define DEMANGLE_HPP_
#include <cxxabi.h>
#include <string>
/**
* @brief 型名をデマングル化する.
*
* @param id デマングル化する型のtype_infoオブジェクト
* @return std::string デマングル化された型名を表す文字列
* @throws std::exception デマングル化が失敗した場合に投げられます
*
* @details
* gcc および clang コンパイラでマングル化された型名をデマングル化します.
*/
std::string Demangle(const std::type_info& id) {
int status;
char* demangled_name = abi::__cxa_demangle(id.name(), 0, 0, &status);
if (status == 0) {
auto result = std::string(demangled_name);
free(demangled_name);
return result;
}
throw std::exception();
}
#endif // DEMANGLE_HPP_
join.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 JOIN_HPP_
#define JOIN_HPP_
#include <numeric>
#include <string>
#include <vector>
/**
* @brief 文字列の配列を指定したデリミタで結合する.
*
* @param delimitor 文字列を連結する際の区切り文字
* @param messages 連結する文字列の配列
* @return std::string 連結された文字列
*
* @details
* 指定したデリミタで文字列の配列を連結する.
* デリミタに "," を指定し,文字列の配列として {"Hello", * "World"}
* を指定した場合,"Hello,World"を返します.
*/
std::string Join(const std::string &delimitor,
const std::vector<std::string> &messages) {
return std::accumulate(
std::next(messages.begin()), messages.end(), messages[0],
[delimitor](std::string a, std::string b) { return a + delimitor + b; });
}
#endif // JOIN_HPP_
$ ./content/build/design_pattern/observer_pattern
design_pattern::component::Line (100,100)
design_pattern::component::Line (0,0)
design_pattern::component::Line (101,101)
design_pattern::component::Triangle (100,100)
design_pattern::component::Triangle (0,0)
design_pattern::component::Triangle (101,101)