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/.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/CMakeLists.txt b/CMakeLists.txt index 2b24087..d35c739 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,22 @@ -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) 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 + 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 new file mode 100644 index 0000000..847665b --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +C++ Design Patterns +---------------------- + +### Creational Patters +##### Singleton +Done +##### 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 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 new file mode 100644 index 0000000..f4ca38b --- /dev/null +++ b/Test/ObserverTest.cpp @@ -0,0 +1,54 @@ +/** + * @file + * Implementation of unit tests for observer pattern. + */ + +#include "gtest/gtest.h" + +#include "Subject.h" + +class EventObserver{ +public: + virtual void event1(){}; + virtual void event2(int a, const std::string &b)=0; +}; + +class MySubject: public Subject{ +public: + void fireEvent1(){notify(&EventObserver::event1);} + void fireEvent2(){notify(&EventObserver::event2, 42, "test");} +}; + +class MyObserver: public EventObserver{ +public: + bool event1Fired = false; + bool event2Fired = false; + + virtual void event1() override{ + event1Fired = true; + } + + virtual void event2(int a, const std::string &b) override{ + ASSERT_EQ(a, 42); + ASSERT_EQ(b, "test"); + event2Fired = true; + } +}; + +// Test if message is received. +TEST(OBS_Subject, test){ + MySubject subject; + std::shared_ptr observer = std::make_shared(); + subject.registerObserver(observer); + + ASSERT_FALSE(observer->event1Fired); + ASSERT_FALSE(observer->event2Fired); + + subject.fireEvent1(); + ASSERT_TRUE(observer->event1Fired); + ASSERT_FALSE(observer->event2Fired); + + subject.fireEvent2(); + ASSERT_TRUE(observer->event1Fired); + ASSERT_TRUE(observer->event2Fired); +} 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/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/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/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 diff --git a/include/Subject.h b/include/Subject.h new file mode 100644 index 0000000..1a8ffd6 --- /dev/null +++ b/include/Subject.h @@ -0,0 +1,96 @@ +#ifndef PATTERNS_SUBJECT_H +#define PATTERNS_SUBJECT_H + +/** + * @file Implementation of Subject for observer pattern. + */ + +#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 // PATTERNS_SUBJECT_H diff --git a/lib/googletest b/lib/googletest index 79b3be9..15392f1 160000 --- a/lib/googletest +++ b/lib/googletest @@ -1 +1 @@ -Subproject commit 79b3be94cbaed93b462f3cea726c91201960eb7b +Subproject commit 15392f1a38fa0b8c3f13a9732e94b209069efa1c