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)
|
project(nf7 C CXX)
|
||||||
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
|
||||||
include(cmake/git_hash.cmake)
|
include(cmake/git_hash.cmake)
|
||||||
|
|
||||||
add_subdirectory(iface)
|
add_subdirectory(iface)
|
||||||
|
@ -1,10 +1,25 @@
|
|||||||
add_library(nf7_iface)
|
add_library(nf7_iface)
|
||||||
target_include_directories(nf7_iface PUBLIC ${PROJECT_SOURCE_DIR})
|
target_include_directories(nf7_iface PUBLIC ${PROJECT_SOURCE_DIR})
|
||||||
target_link_libraries(nf7_iface PRIVATE git_hash)
|
target_link_libraries(nf7_iface PRIVATE git_hash)
|
||||||
|
|
||||||
target_sources(nf7_iface
|
target_sources(nf7_iface
|
||||||
PRIVATE
|
PRIVATE
|
||||||
version.cc
|
version.cc
|
||||||
PUBLIC
|
PUBLIC
|
||||||
|
observer.hh
|
||||||
version.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