From cf7cc9a8f2262e7f25465505c6d29efaae147c8c Mon Sep 17 00:00:00 2001 From: Joey De Pauw Date: Mon, 12 Feb 2018 18:14:37 +0100 Subject: [PATCH 1/8] rm dir --- lib/googletest | 1 - 1 file changed, 1 deletion(-) delete mode 160000 lib/googletest diff --git a/lib/googletest b/lib/googletest deleted file mode 160000 index 79b3be9..0000000 --- a/lib/googletest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 79b3be94cbaed93b462f3cea726c91201960eb7b From 24584935096be627c24a252bc556f87c3fb13410 Mon Sep 17 00:00:00 2001 From: Joey De Pauw Date: Mon, 12 Feb 2018 18:15:07 +0100 Subject: [PATCH 2/8] added submodule gtest --- .gitmodules | 3 +++ lib/googletest | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 lib/googletest diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..80925b8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/googletest"] + path = lib/googletest + url = https://github.com/google/googletest.git diff --git a/lib/googletest b/lib/googletest new file mode 160000 index 0000000..15392f1 --- /dev/null +++ b/lib/googletest @@ -0,0 +1 @@ +Subproject commit 15392f1a38fa0b8c3f13a9732e94b209069efa1c From 4d9894f0e5473ece90afa2520bd4c08b22ab2ab0 Mon Sep 17 00:00:00 2001 From: Joey De Pauw Date: Mon, 12 Feb 2018 18:28:46 +0100 Subject: [PATCH 3/8] Observer Pattern --- CMakeLists.txt | 10 ++++- Test/ObserverTest.cpp | 73 ++++++++++++++++++++++++++++++++ Test/main.cpp | 12 ------ include/Subject.h | 97 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 14 deletions(-) create mode 100644 Test/ObserverTest.cpp delete mode 100644 Test/main.cpp create mode 100644 include/Subject.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b24087..5e0b183 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,12 +4,18 @@ project(patterns) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") add_subdirectory(lib/googletest) +include_directories(include) include_directories(lib/googletest/googletest/include) include_directories(lib/googletest/googlemock/include) -set(SOURCE_FILES main.cpp) -set(SOURCE_FILES_TESTING Test/main.cpp) +set(SOURCE_FILES + main.cpp +) + +set(SOURCE_FILES_TESTING + Test/ObserverTest.cpp +) add_executable(patterns ${SOURCE_FILES}) add_executable(patterns_testing ${SOURCE_FILES_TESTING}) diff --git a/Test/ObserverTest.cpp b/Test/ObserverTest.cpp new file mode 100644 index 0000000..0aa867f --- /dev/null +++ b/Test/ObserverTest.cpp @@ -0,0 +1,73 @@ +/* + * This is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * The software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with the software. If not, see . + * + * Copyright 2017, Willem L, Kuylen E, Stijven S & Broeckhove J + */ + +/** + * @file + * Implementation of unit tests for observer pattern. + */ + +#include "gtest/gtest.h" + +#include "Subject.h" + +class Topic{ +public: + virtual ~Topic() = default; + + virtual void func1(){}; + virtual void func2(int a, const std::string& b)=0; +}; + +class ActualSubject: public Subject{ +public: + void callFunc1(){notify(&Topic::func1);} + void callFunc2(){notify(&Topic::func2, 42, "test");} +}; + +class Observer: public Topic{ +public: + bool func1Called = false; + bool func2Called = false; + + virtual ~Observer(){} + + virtual void func1(){; + func1Called = true; + } + + virtual void func2(int a, const std::string& b){ + ASSERT_EQ(a, 42); + ASSERT_EQ(b, "test"); + func2Called = true; + } +}; + +// Test if message is received. +TEST(OBS_Subject, test){ + ActualSubject subject; + std::shared_ptr observer = std::make_shared(); + subject.registerObserver(observer); + + ASSERT_FALSE(observer->func1Called); + ASSERT_FALSE(observer->func2Called); + + subject.callFunc1(); + ASSERT_TRUE(observer->func1Called); + ASSERT_FALSE(observer->func2Called); + + subject.callFunc2(); + ASSERT_TRUE(observer->func1Called); + ASSERT_TRUE(observer->func2Called); +} diff --git a/Test/main.cpp b/Test/main.cpp deleted file mode 100644 index 7565286..0000000 --- a/Test/main.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include - -#include -#include - -int main( int argc, char * argv[]) { - - testing::InitGoogleTest(&argc, argv); - RUN_ALL_TESTS(); - return 0; - -} \ No newline at end of file diff --git a/include/Subject.h b/include/Subject.h new file mode 100644 index 0000000..7fdbf81 --- /dev/null +++ b/include/Subject.h @@ -0,0 +1,97 @@ +#ifndef SUBJECT_H_INCLUDED +#define SUBJECT_H_INCLUDED + +/** + * @file Implementation of Subject for observer pattern. + */ + +#include +#include +#include +#include + +/** + * @brief A subject can be observed by subclasses of the Observer interface. Subjects call specific methods of the Observer interface you can subscribe to. + */ +template +class Subject { +private: + /// Observers should stay alive as long as what they watch is alive. Hence shared_ptr. + mutable std::vector > observers; + +protected: + + /** + * Implementation for std::function. + * @brief Calls the func on all observers. + * @param func: Function to call. + * @param args: Args of the function. + */ + template + void notify(std::function func, Args2... args); + + /** + * Implementation for function pointer. + * @brief Calls the func on all observers. + * @param func: Function to call. + * @param args: Args of the function. + */ + template + void notify(void (Observer::*f)(Args1...), Args2&&... args); + +public: + + ///@param obs: Observer to register. + void registerObserver(std::shared_ptr obs) const; + + /// @param obs: Observer to remove from the observers list. + void unregisterObserver(std::shared_ptr obs); + + /// Remove all observers. + void unregisterAll(); + + virtual ~Subject(){} +}; + +// implementation + +template +void Subject::registerObserver(std::shared_ptr obs) const{ + observers.push_back(obs); +} + +template +void Subject::unregisterObserver(std::shared_ptr obs) { + typename std::vector::iterator it = std::find(observers.begin(), observers.end(), obs); + if(it != observers.end()){ + observers.erase(it); + }else{ + std::cerr << "Tried to remove an observer that wasn't registered." << std::endl; + } +} + +template +void Subject::unregisterAll() { + observers.clear(); +} + +// std::function +template +template +void Subject::notify(std::function func, Args2... args){ + for(std::shared_ptr obs : observers){ + func(*obs, std::forward(args)...); + } +} + +// function pointer +template +template +void Subject::notify(void (Observer::*f)(Args1...), Args2&&... args){ + std::function func = f; + for(std::shared_ptr obs : observers){ + func(*obs, std::forward(args)...); + } +} + +#endif From 45accbf8e0a99cebfa64d65f00ba53b0ca75dbbe Mon Sep 17 00:00:00 2001 From: Joey De Pauw Date: Wed, 21 Feb 2018 12:06:45 +0100 Subject: [PATCH 4/8] lowered Cmake version, styling changes --- .gitignore | 3 ++- CMakeLists.txt | 4 ++-- Test/ObserverTest.cpp | 19 ------------------- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 1b72d0a..606ea33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ cmake-build-debug -.idea \ No newline at end of file +.idea +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e0b183..8171627 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.5) project(patterns) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") add_subdirectory(lib/googletest) include_directories(include) diff --git a/Test/ObserverTest.cpp b/Test/ObserverTest.cpp index 0aa867f..bd2befc 100644 --- a/Test/ObserverTest.cpp +++ b/Test/ObserverTest.cpp @@ -1,18 +1,3 @@ -/* - * This is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * The software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with the software. If not, see . - * - * Copyright 2017, Willem L, Kuylen E, Stijven S & Broeckhove J - */ - /** * @file * Implementation of unit tests for observer pattern. @@ -24,8 +9,6 @@ class Topic{ public: - virtual ~Topic() = default; - virtual void func1(){}; virtual void func2(int a, const std::string& b)=0; }; @@ -41,8 +24,6 @@ class Observer: public Topic{ bool func1Called = false; bool func2Called = false; - virtual ~Observer(){} - virtual void func1(){; func1Called = true; } From f8d4a18620c98ac9bdc678f2fe3bb8f842b788fe Mon Sep 17 00:00:00 2001 From: Joey De Pauw Date: Wed, 21 Feb 2018 13:10:01 +0100 Subject: [PATCH 5/8] added README --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b590609 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +C++ Design Patterns +---------------------- + +### Creational Patters +##### Singleton +TODO Robbe +##### Abstract Factory +TODO +##### Builder +Done +##### Dependency Injection +##### Factory Method +##### Lazy Initialization +##### Multiton +##### Object Pool +##### Prototype + +### Structural Patterns +##### Adapter +##### Bridge +##### Composite +##### Decorator +##### Facade +##### Flyweight +##### Extension Point + + +### Behavioral Patterns +##### Observer +Done +##### Publish/Subscribe +Ready, not integrated yet +##### Visitor +TODO From d2f3171f4af7b8a768f91f57b1885de171edf3f5 Mon Sep 17 00:00:00 2001 From: Joey De Pauw Date: Wed, 21 Feb 2018 13:10:28 +0100 Subject: [PATCH 6/8] Builder pattern, styling changes --- CMakeLists.txt | 3 ++- Test/BuilderTest.cpp | 60 +++++++++++++++++++++++++++++++++++++++++++ Test/ObserverTest.cpp | 4 +-- include/Builder.h | 30 ++++++++++++++++++++++ include/Subject.h | 7 +++-- 5 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 Test/BuilderTest.cpp create mode 100644 include/Builder.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8171627..a61c48f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,10 +11,11 @@ include_directories(lib/googletest/googlemock/include) set(SOURCE_FILES main.cpp -) + include/Builder.h) set(SOURCE_FILES_TESTING Test/ObserverTest.cpp + Test/BuilderTest.cpp ) add_executable(patterns ${SOURCE_FILES}) diff --git a/Test/BuilderTest.cpp b/Test/BuilderTest.cpp new file mode 100644 index 0000000..bd13754 --- /dev/null +++ b/Test/BuilderTest.cpp @@ -0,0 +1,60 @@ +/** + * @file + * Implementation of unit tests for observer pattern. + */ + +#include "gtest/gtest.h" + +#include "Builder.h" + +struct CarData{ +protected: + int numSeats = 4; + bool GPS = false; + +public: + virtual bool operator==(const CarData& other) const{return numSeats == other.numSeats && GPS == other.GPS;} +}; + +class Car: public CarData{ +public: + explicit Car(const CarData& data):CarData(data){} + + int getNumSeats() const{return numSeats;} + bool hasGPS() const{return GPS;} +}; + +class CarBuilder: public Builder{ +public: + void setNumSeats(int numSeats){ + this->numSeats = numSeats; + } + + void setGPS(bool GPS){ + this->GPS = GPS; + } + + virtual bool isValid() override{ + return numSeats < 8; + } +}; + +// Test builder methods +TEST(Builder, test){ + CarBuilder builder; + builder.setNumSeats(5); + builder.setGPS(true); + + ASSERT_TRUE(builder.isValid()); + + builder.setNumSeats(100); + ASSERT_DEATH(builder.getResult(), "Assertion .* failed."); + + builder.setNumSeats(5); + Car car = builder.getResult(); + std::shared_ptr car_p = builder.getResultNew(); + + ASSERT_EQ(car.getNumSeats(), 5); + ASSERT_EQ(car.hasGPS(), true); + ASSERT_EQ(car, *car_p); +} diff --git a/Test/ObserverTest.cpp b/Test/ObserverTest.cpp index bd2befc..e544aed 100644 --- a/Test/ObserverTest.cpp +++ b/Test/ObserverTest.cpp @@ -24,11 +24,11 @@ class Observer: public Topic{ bool func1Called = false; bool func2Called = false; - virtual void func1(){; + virtual void func1() override{; func1Called = true; } - virtual void func2(int a, const std::string& b){ + virtual void func2(int a, const std::string& b) override{ ASSERT_EQ(a, 42); ASSERT_EQ(b, "test"); func2Called = true; diff --git a/include/Builder.h b/include/Builder.h new file mode 100644 index 0000000..557a4cb --- /dev/null +++ b/include/Builder.h @@ -0,0 +1,30 @@ +#ifndef PATTERNS_BUILDER_H +#define PATTERNS_BUILDER_H + +/** + * @file Implementation of Builder for builder pattern. + */ + + +#include + +/** + * @brief A builder interface for a specific class + */ +template +class Builder: protected Data { +public: + virtual Result getResult(){ + assert(isValid()); + return Result((Data)*this); + } + + virtual std::shared_ptr getResultNew(){ + assert(isValid()); + return std::make_shared((Data)*this); + }; + + virtual bool isValid(){return true;} +}; + +#endif //PATTERNS_BUILDER_H diff --git a/include/Subject.h b/include/Subject.h index 7fdbf81..1a8ffd6 100644 --- a/include/Subject.h +++ b/include/Subject.h @@ -1,5 +1,5 @@ -#ifndef SUBJECT_H_INCLUDED -#define SUBJECT_H_INCLUDED +#ifndef PATTERNS_SUBJECT_H +#define PATTERNS_SUBJECT_H /** * @file Implementation of Subject for observer pattern. @@ -8,7 +8,6 @@ #include #include #include -#include /** * @brief A subject can be observed by subclasses of the Observer interface. Subjects call specific methods of the Observer interface you can subscribe to. @@ -94,4 +93,4 @@ void Subject::notify(void (Observer::*f)(Args1...), Args2&&... args){ } } -#endif +#endif // PATTERNS_SUBJECT_H From a27bd9d8fd8aba90e364f7f241d6db5bc52d6ec3 Mon Sep 17 00:00:00 2001 From: Joey De Pauw Date: Mon, 18 Jun 2018 15:07:53 +0200 Subject: [PATCH 7/8] renamed test to make example clearer --- Test/ObserverTest.cpp | 46 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Test/ObserverTest.cpp b/Test/ObserverTest.cpp index e544aed..f4ca38b 100644 --- a/Test/ObserverTest.cpp +++ b/Test/ObserverTest.cpp @@ -7,48 +7,48 @@ #include "Subject.h" -class Topic{ +class EventObserver{ public: - virtual void func1(){}; - virtual void func2(int a, const std::string& b)=0; + virtual void event1(){}; + virtual void event2(int a, const std::string &b)=0; }; -class ActualSubject: public Subject{ +class MySubject: public Subject{ public: - void callFunc1(){notify(&Topic::func1);} - void callFunc2(){notify(&Topic::func2, 42, "test");} + void fireEvent1(){notify(&EventObserver::event1);} + void fireEvent2(){notify(&EventObserver::event2, 42, "test");} }; -class Observer: public Topic{ +class MyObserver: public EventObserver{ public: - bool func1Called = false; - bool func2Called = false; + bool event1Fired = false; + bool event2Fired = false; - virtual void func1() override{; - func1Called = true; + virtual void event1() override{ + event1Fired = true; } - virtual void func2(int a, const std::string& b) override{ + virtual void event2(int a, const std::string &b) override{ ASSERT_EQ(a, 42); ASSERT_EQ(b, "test"); - func2Called = true; + event2Fired = true; } }; // Test if message is received. TEST(OBS_Subject, test){ - ActualSubject subject; - std::shared_ptr observer = std::make_shared(); + MySubject subject; + std::shared_ptr observer = std::make_shared(); subject.registerObserver(observer); - ASSERT_FALSE(observer->func1Called); - ASSERT_FALSE(observer->func2Called); + ASSERT_FALSE(observer->event1Fired); + ASSERT_FALSE(observer->event2Fired); - subject.callFunc1(); - ASSERT_TRUE(observer->func1Called); - ASSERT_FALSE(observer->func2Called); + subject.fireEvent1(); + ASSERT_TRUE(observer->event1Fired); + ASSERT_FALSE(observer->event2Fired); - subject.callFunc2(); - ASSERT_TRUE(observer->func1Called); - ASSERT_TRUE(observer->func2Called); + subject.fireEvent2(); + ASSERT_TRUE(observer->event1Fired); + ASSERT_TRUE(observer->event2Fired); } From d991a10afd96c085c38025c4c978ba140ab6325e Mon Sep 17 00:00:00 2001 From: Joey De Pauw Date: Mon, 18 Jun 2018 16:15:03 +0200 Subject: [PATCH 8/8] Singleton pattern --- CMakeLists.txt | 4 ++-- README.md | 2 +- Test/SingletonTest.cpp | 43 ++++++++++++++++++++++++++++++++++++++++++ include/Singleton.h | 30 +++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 Test/SingletonTest.cpp create mode 100644 include/Singleton.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a61c48f..d35c739 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,12 +11,12 @@ include_directories(lib/googletest/googlemock/include) set(SOURCE_FILES main.cpp - include/Builder.h) + include/Builder.h include/Singleton.h) set(SOURCE_FILES_TESTING Test/ObserverTest.cpp Test/BuilderTest.cpp -) + Test/SingletonTest.cpp) add_executable(patterns ${SOURCE_FILES}) add_executable(patterns_testing ${SOURCE_FILES_TESTING}) diff --git a/README.md b/README.md index b590609..847665b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ C++ Design Patterns ### Creational Patters ##### Singleton -TODO Robbe +Done ##### Abstract Factory TODO ##### Builder diff --git a/Test/SingletonTest.cpp b/Test/SingletonTest.cpp new file mode 100644 index 0000000..e5bcee9 --- /dev/null +++ b/Test/SingletonTest.cpp @@ -0,0 +1,43 @@ +/** + * @file + * Implementation of unit tests for singleton pattern. + */ + +#include "gtest/gtest.h" + +#include "Singleton.h" + + +class MySingleton: public Singleton{ +protected: + MySingleton() = default; // make it so no objects can be constructed from the outside. +public: + int a = 3; + + bool operator==(const MySingleton& other) const{ + return a == other.a; + } +}; + +// Test if exactly one valid instance. +TEST(Singleton, one_valid){ + MySingleton& instance1 = MySingleton::getInstance(); + MySingleton& instance2 = MySingleton::getInstance(); + + ASSERT_EQ(instance1, instance2); + ASSERT_EQ(&instance1, &instance2); + + instance1.a = 5; + + ASSERT_EQ(instance1, instance2); + ASSERT_EQ(&instance1, &instance2); + + instance1 = instance2; + + ASSERT_EQ(instance1, instance2); + ASSERT_EQ(&instance1, &instance2); +} + +// Test if extra instances can be made. These statements should fail +//MySingleton instance1 = MySingleton::getInstance(); // copy +//MySingleton instance2; // construct diff --git a/include/Singleton.h b/include/Singleton.h new file mode 100644 index 0000000..a2c361a --- /dev/null +++ b/include/Singleton.h @@ -0,0 +1,30 @@ +#ifndef PATTERNS_SINGLETON_H +#define PATTERNS_SINGLETON_H + +/** + * @file Implementation of Singleton pattern. + */ + +template +class Singleton { +private: + class A: public T{ + public: + A() = default; + }; + +public: + Singleton() = default; + Singleton(Singleton&) = delete; + Singleton(Singleton&&) = delete; + Singleton& operator=(Singleton&) = default; + Singleton& operator=(Singleton&&) = default; + + static T& getInstance(){ + static A instance; + return instance; + } +}; + + +#endif //PATTERNS_SINGLETON_H