Compare commits
3 Commits
bd8245c9aa
...
dbb86258e1
Author | SHA1 | Date | |
---|---|---|---|
dbb86258e1 | |||
096cd189d6 | |||
d6e62f1ed6 |
@ -2,7 +2,9 @@ cmake_minimum_required(VERSION 3.20)
|
||||
project(nf7 C CXX)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
|
||||
add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
|
||||
include(cmake/git_hash.cmake)
|
||||
|
||||
add_subdirectory(iface)
|
||||
|
@ -1,10 +1,25 @@
|
||||
add_library(nf7_iface)
|
||||
target_include_directories(nf7_iface PUBLIC ${PROJECT_SOURCE_DIR})
|
||||
target_link_libraries(nf7_iface PRIVATE git_hash)
|
||||
|
||||
target_sources(nf7_iface
|
||||
PRIVATE
|
||||
version.cc
|
||||
PUBLIC
|
||||
observer.hh
|
||||
version.hh
|
||||
)
|
||||
|
||||
add_executable(nf7_iface_test)
|
||||
target_sources(nf7_iface_test
|
||||
PRIVATE
|
||||
observer_test.hh
|
||||
observer_test.cc
|
||||
)
|
||||
target_link_libraries(nf7_iface_test
|
||||
PRIVATE
|
||||
nf7_iface
|
||||
GTest::gmock_main
|
||||
GTest::gtest_main
|
||||
)
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(nf7_iface_test)
|
||||
|
86
iface/observer.hh
Normal file
86
iface/observer.hh
Normal file
@ -0,0 +1,86 @@
|
||||
// No copyright
|
||||
// ---- this header provides a way to implement Observer pattern easily
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace nf7::iface {
|
||||
|
||||
// T is notified to Observer<T> by Observer<T>::Target.
|
||||
// All observers should be based on this.
|
||||
template <typename T>
|
||||
class Observer {
|
||||
public:
|
||||
class Target;
|
||||
|
||||
inline explicit Observer(Target&);
|
||||
inline virtual ~Observer() noexcept;
|
||||
|
||||
protected:
|
||||
virtual void Notify(const T&) {}
|
||||
virtual void NotifyWithMove(T&& v) { Notify(v); }
|
||||
virtual void NotifyDestruction(const T* = nullptr) {}
|
||||
|
||||
private:
|
||||
Target& target_;
|
||||
};
|
||||
|
||||
|
||||
// All objects which can be observed by Observer<T> must be based on this.
|
||||
template <typename T>
|
||||
class Observer<T>::Target {
|
||||
public:
|
||||
friend class Observer<T>;
|
||||
|
||||
Target() = default;
|
||||
virtual ~Target() noexcept {
|
||||
for (auto obs : obs_) {
|
||||
obs->NotifyDestruction();
|
||||
}
|
||||
}
|
||||
|
||||
Target(const Target&) = delete;
|
||||
Target(Target&&) = delete;
|
||||
Target& operator=(const Target&) = delete;
|
||||
Target& operator=(Target&&) = delete;
|
||||
|
||||
protected:
|
||||
void Notify(T&& v) const noexcept {
|
||||
if (1 == obs_.size()) {
|
||||
auto first = *obs_.begin();
|
||||
first->NotifyWithMove(std::move(v));
|
||||
return;
|
||||
}
|
||||
for (auto obs : obs_) {
|
||||
obs->Notify(v);
|
||||
}
|
||||
}
|
||||
|
||||
bool observed(const T* = nullptr) const noexcept { return !obs_.empty(); }
|
||||
|
||||
private:
|
||||
void Register(Observer<T>& obs) {
|
||||
obs_.insert(&obs);
|
||||
}
|
||||
void Unregister(const Observer<T>& obs) noexcept {
|
||||
obs_.erase(&const_cast<Observer<T>&>(obs));
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_set<Observer<T>*> obs_;
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
Observer<T>::Observer(Target& target) : target_(target) {
|
||||
target_.Register(*this);
|
||||
}
|
||||
template <typename T>
|
||||
Observer<T>::~Observer() noexcept {
|
||||
target_.Unregister(*this);
|
||||
}
|
||||
|
||||
} // namespace nf7::iface
|
41
iface/observer_test.cc
Normal file
41
iface/observer_test.cc
Normal file
@ -0,0 +1,41 @@
|
||||
// No copyright
|
||||
#include "iface/observer.hh"
|
||||
#include "iface/observer_test.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
using namespace nf7::iface;
|
||||
using namespace nf7::iface::test;
|
||||
|
||||
TEST(Observer, NotifyWithMove) {
|
||||
ObserverTargetMock<int32_t> target;
|
||||
ObserverMock<int32_t> sut {target};
|
||||
|
||||
EXPECT_CALL(sut, NotifyWithMove(111)).Times(1);
|
||||
|
||||
target.Notify(int32_t {111});
|
||||
}
|
||||
|
||||
TEST(Observer, NotifyWithRef) {
|
||||
ObserverTargetMock<int32_t> target;
|
||||
ObserverMock<int32_t> sut1 {target};
|
||||
ObserverMock<int32_t> sut2 {target};
|
||||
|
||||
EXPECT_CALL(sut1, Notify(111)).Times(1);
|
||||
EXPECT_CALL(sut2, Notify(111)).Times(1);
|
||||
|
||||
target.Notify(int32_t {111});
|
||||
}
|
||||
|
||||
TEST(Observer, NotifyDestruction) {
|
||||
std::optional<ObserverTargetMock<int32_t>> target;
|
||||
target.emplace();
|
||||
ObserverMock<int32_t> sut {*target};
|
||||
|
||||
EXPECT_CALL(sut, NotifyDestruction(testing::_)).Times(1);
|
||||
|
||||
target = std::nullopt;
|
||||
}
|
29
iface/observer_test.hh
Normal file
29
iface/observer_test.hh
Normal file
@ -0,0 +1,29 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include "iface/observer.hh"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
|
||||
namespace nf7::iface::test {
|
||||
|
||||
template <typename T>
|
||||
class ObserverMock : public Observer<T> {
|
||||
public:
|
||||
explicit ObserverMock(Observer<T>::Target& target) : Observer<T>(target) {
|
||||
}
|
||||
|
||||
MOCK_METHOD1(Notify, void(const T&));
|
||||
MOCK_METHOD1(NotifyWithMove, void(T&&));
|
||||
MOCK_METHOD1(NotifyDestruction, void(const T*));
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ObserverTargetMock : public Observer<T>::Target {
|
||||
public:
|
||||
using Observer<T>::Target::Target;
|
||||
using Observer<T>::Target::Notify;
|
||||
};
|
||||
|
||||
} // namespace nf7::iface::test
|
10
thirdparty/CMakeLists.txt
vendored
Normal file
10
thirdparty/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
include(FetchContent)
|
||||
|
||||
# ---- gtest (BSD-3-Clause)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG v1.13.0
|
||||
)
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(googletest)
|
Loading…
x
Reference in New Issue
Block a user