From 1c78867d913f2be535099a82809a0ea831da9652 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 11 May 2021 18:53:16 -0400 Subject: [PATCH] add example of state pattern in C++ --- cpp/patterns/CMakeLists.txt | 1 + .../prototype/{prototype.cpp => state.cpp} | 0 cpp/patterns/state/CMakeLists.txt | 20 +++ cpp/patterns/state/main.cpp | 31 ++++ cpp/patterns/state/state.cpp | 13 ++ cpp/patterns/state/state.hpp | 147 ++++++++++++++++++ 6 files changed, 212 insertions(+) rename cpp/patterns/prototype/{prototype.cpp => state.cpp} (100%) create mode 100644 cpp/patterns/state/CMakeLists.txt create mode 100644 cpp/patterns/state/main.cpp create mode 100644 cpp/patterns/state/state.cpp create mode 100644 cpp/patterns/state/state.hpp diff --git a/cpp/patterns/CMakeLists.txt b/cpp/patterns/CMakeLists.txt index 7f365d2..33b78ef 100644 --- a/cpp/patterns/CMakeLists.txt +++ b/cpp/patterns/CMakeLists.txt @@ -21,3 +21,4 @@ add_subdirectory(bridge) add_subdirectory(factory) add_subdirectory(prototype) add_subdirectory(abstract-factory) +add_subdirectory(state) diff --git a/cpp/patterns/prototype/prototype.cpp b/cpp/patterns/prototype/state.cpp similarity index 100% rename from cpp/patterns/prototype/prototype.cpp rename to cpp/patterns/prototype/state.cpp diff --git a/cpp/patterns/state/CMakeLists.txt b/cpp/patterns/state/CMakeLists.txt new file mode 100644 index 0000000..70adfbb --- /dev/null +++ b/cpp/patterns/state/CMakeLists.txt @@ -0,0 +1,20 @@ +############################################################################### +## Author: Shaun Reed ## +## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## +## About: A project for practicing the state C++ design pattern ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +############################################################################## +# +cmake_minimum_required(VERSION 3.15) +project( + #[[NAME]] State + VERSION 1.0 + DESCRIPTION "An example of the state design pattern in C++" + LANGUAGES CXX +) +add_compile_options("-Wall") + +add_library(state "state.cpp") +add_executable(state-test "main.cpp") +target_link_libraries(state-test state) diff --git a/cpp/patterns/state/main.cpp b/cpp/patterns/state/main.cpp new file mode 100644 index 0000000..67bbf3e --- /dev/null +++ b/cpp/patterns/state/main.cpp @@ -0,0 +1,31 @@ + +#include + +#include "state.hpp" + +int main(const int argc, const char * argv[]) { + Car * testCar = new Car; + testCar->report(); + + // Test transition from Stopped->Running + testCar->Start(); + testCar->report(); + + // Test transition from Running->Stopped + testCar->Stop(); + testCar->report(); + + // Test transition from Stopped->Broken + testCar->Smash(); + testCar->report(); + + std::cout << "\nTesting burning state...\n"; + + Car *newCar = new Car; + newCar->report(); + + // Test transition from Stopped->Running->Burning + newCar->Start(); + newCar->Smash(); + newCar->report(); +} diff --git a/cpp/patterns/state/state.cpp b/cpp/patterns/state/state.cpp new file mode 100644 index 0000000..b399af1 --- /dev/null +++ b/cpp/patterns/state/state.cpp @@ -0,0 +1,13 @@ + +#include "state.hpp" + +// Delegate calls from State->changeState to Car->changeState +void State::changeState(Car *c, State *s) { c->changeState(s);} + +// Stopped state transitions +void Stopped::Start(Car *c) { changeState(c, Running::instance());} +void Stopped::Smash(Car *c) { changeState(c, Broken::instance());} + +// Running state transitions +void Running::Stop(Car *c) { changeState(c, Stopped::instance());} +void Running::Smash(Car *c) { changeState(c, Burning::instance());} diff --git a/cpp/patterns/state/state.hpp b/cpp/patterns/state/state.hpp new file mode 100644 index 0000000..ffea418 --- /dev/null +++ b/cpp/patterns/state/state.hpp @@ -0,0 +1,147 @@ + +#ifndef STATE_HPP +#define STATE_HPP + +#include +#include + +#include "state.hpp" + +class Car; + +// State base class +class State { +public: + State() = default; + ~State() = default; + + // Calls c->changeState(s) + void changeState(Car *c, State *s); + + // Virtual state transition functions + // + By default, nothing happens; Derived States can override if needed + virtual void Start(Car *c) {} + virtual void Stop(Car *c) {} + virtual void Smash(Car *c) {} + virtual std::string report() = 0; + +private: + State(const State &) = default; + State &operator=(const State&) { return *this;} +}; + + +/******************************************************************************/ +// Derived State: Stopped +class Stopped : public State { +public: + static State *instance() + { + // For all derived states, there only needs to be a single instance + // + Use singleton pattern to prevent duplicate states + // + A single instance of a state can be applied to N cars + static State *oneInstance = new Stopped; + return oneInstance; + } + + // State transitions valid for this state + void Start(Car *c) override; + void Smash(Car *c) override; + + std::string report() override { return "Stopped";} + +private: + Stopped() = default; + ~Stopped() = default; + Stopped(const Stopped &) = default; + Stopped &operator=(const Stopped &) { return *this;} +}; + + +/******************************************************************************/ +// Derived State: Running +class Running : public State { +public: + static State *instance() + { + static State *oneInstance = new Running; + return oneInstance; + } + + // State transitions valid for this state + void Stop(Car *c) override; + void Smash(Car *c) override; + + std::string report() override { return "Running";} + +private: + Running() = default; + ~Running() = default; + Running(const Running &) = default; + Running &operator=(const Running &) { return *this;} +}; + +/******************************************************************************/ +// Derived State: Burning +class Burning : public State { +public: + static State *instance() + { + static State *oneInstance = new Burning; + return oneInstance; + } + + // No state transitions possible; The car is burning. + + std::string report() override { return "Burning";} + +private: + Burning() = default; + ~Burning() = default; + Burning(const Burning &) = default; + Burning &operator=(const Burning &) { return *this;} +}; + +/******************************************************************************/ +// Derived State: Broken +class Broken : public State { +public: + static State *instance() + { + static State *oneInstance = new Broken; + return oneInstance; + } + + // No state transitions possible; The car is broken. + + std::string report() override { return "Broken";} + +private: + Broken() = default; + ~Broken() = default; + Broken(const Broken &) = default; + Broken &operator=(const Broken &) { return *this;} +}; + + +/******************************************************************************/ +// Car controlled by current State +class Car { +public: + Car() {state = Stopped::instance();} + ~Car() = default; + + void report() const { std::cout << "State: " << state->report() << std::endl;} + + // Delegate calls to state transitions relative to that current state + virtual void Start() { state->Start(this);} + virtual void Stop() { state->Stop(this);} + virtual void Smash() { state->Smash(this);} + + void changeState(State *s) { state = s;} + +private: + State *state; +}; + +#endif // STATE_HPP