diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..dc69733 --- /dev/null +++ b/.clang-format @@ -0,0 +1,83 @@ +# Google C/C++ Code Style settings +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# Author: Kehan Xue, kehan.xue (at) gmail.com + +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: None +AlignOperands: Align +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never # To avoid conflict, set this "Never" and each "if statement" should include brace when coding +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterStruct: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +ColumnLimit: 80 +CompactNamespaces: false +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false # Make sure the * or & align on the left +EmptyLineBeforeAccessModifier: LogicalBlock +FixNamespaceComments: true +IncludeBlocks: Preserve +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Left +ReflowComments: false +# SeparateDefinitionBlocks: Always # Only support since clang-format 14 +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++11 +TabWidth: 4 +UseTab: Never \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a8cda02..77ff4f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,7 @@ set(APP_SOURCES "src/DeleteMe.cpp" "src/core/basics/InitializeVariable.cpp" "src/core/basics/Operations.cpp" + "src/core/basics/TypeQualifier.cpp" "src/core/basics/ControlFlow.cpp" "src/core/datatypes/Fundamental.cpp" "src/core/datatypes/CArray.cpp" diff --git a/README.md b/README.md index 65f3d06..3caa08f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,21 @@ Make sure the following tools are installed before building the project: - **Git** - **lcov** (for code coverage) - **cppcheck** (for static analysis) +- **Optional:** Install `clang-format` to format C++ code. +Linux +```bash +sudo apt install clang-format +find . -regex '.*\.\(cpp\|h\|hpp\)' -exec clang-format -i {} \; +``` +Windows +```bash +# Install clang-format via Chocolatey +choco install clang-format + +# Apply clang-format recursively to .cpp, .h, .hpp files +Get-ChildItem -Recurse -Include *.cpp, *.h, *.hpp | ForEach-Object { clang-format -i $_.FullName } +``` ## 3. SETUP * Setup the Local Test Environment * 1.Using your own Ubuntu system diff --git a/include/DeleteMe.h b/include/DeleteMe.h index 717756d..5d1f603 100644 --- a/include/DeleteMe.h +++ b/include/DeleteMe.h @@ -1,14 +1,13 @@ #pragma once -class DeleteMe -{ -private: - int m_lastResult; +class DeleteMe { + private: + int m_lastResult; -public: - DeleteMe(); - ~DeleteMe(); - bool add(int addend1, int addend2); - bool mul(int factor1, int factor2); - bool div(int dividend, int divisor); - int get_last_result(); + public: + DeleteMe(); + ~DeleteMe(); + bool add(int addend1, int addend2); + bool mul(int factor1, int factor2); + bool div(int dividend, int divisor); + int get_last_result(); }; diff --git a/src/DeleteMe.cpp b/src/DeleteMe.cpp index 7ce5d23..75d112a 100644 --- a/src/DeleteMe.cpp +++ b/src/DeleteMe.cpp @@ -1,41 +1,32 @@ #include "DeleteMe.h" -DeleteMe::DeleteMe() -{ - m_lastResult = 0; +DeleteMe::DeleteMe() { + m_lastResult = 0; } -DeleteMe::~DeleteMe() -{ - -} +DeleteMe::~DeleteMe() {} -bool DeleteMe::add(int addend1, int addend2) -{ - m_lastResult = addend1 + addend2; - return true; +bool DeleteMe::add(int addend1, int addend2) { + m_lastResult = addend1 + addend2; + return true; } // This is for checking that code coverage will not be <100% -bool DeleteMe::mul(int factor1, int factor2) -{ - m_lastResult = factor1 * factor2; - return true; +bool DeleteMe::mul(int factor1, int factor2) { + m_lastResult = factor1 * factor2; + return true; } -bool DeleteMe::div(int dividend, int divisor) -{ - if (divisor == 0) - { - m_lastResult = 0; - return false; - } +bool DeleteMe::div(int dividend, int divisor) { + if (divisor == 0) { + m_lastResult = 0; + return false; + } - m_lastResult = dividend / divisor; - return true; + m_lastResult = dividend / divisor; + return true; } -int DeleteMe::get_last_result() -{ - return m_lastResult; +int DeleteMe::get_last_result() { + return m_lastResult; } diff --git a/src/core/basics/ControlFlow.cpp b/src/core/basics/ControlFlow.cpp index 7d7a121..6b4913a 100644 --- a/src/core/basics/ControlFlow.cpp +++ b/src/core/basics/ControlFlow.cpp @@ -2,156 +2,133 @@ #include using namespace std; -void conditionals() -{ - cout << "\n--- Conditional Examples ---\n"; - // if-else - int x = rand(); - cout << "x = " << x << "\n"; - if (x > 0) - { - cout << "x is positive\n"; - } - else if (x < 0) - { - cout << "x is negative\n"; - } - else - { - cout << "x is zero\n"; - } - - // switch - int choice = rand() % 2 + 1; - cout << "choice = " << x << "\n"; - switch (choice) - { +void conditionals() { + cout << "\n--- Conditional Examples ---\n"; + // if-else + int x = rand(); + cout << "x = " << x << "\n"; + if (x > 0) { + cout << "x is positive\n"; + } else if (x < 0) { + cout << "x is negative\n"; + } else { + cout << "x is zero\n"; + } + + // switch + int choice = rand() % 2 + 1; + cout << "choice = " << x << "\n"; + switch (choice) { case 1: - cout << "Choice is 1\n"; - break; + cout << "Choice is 1\n"; + break; case 2: - cout << "Choice is 2\n"; - break; + cout << "Choice is 2\n"; + break; default: - cout << "Choice is something else\n"; - break; - } + cout << "Choice is something else\n"; + break; + } } -void jumps() -{ - cout << "\n--- Jump Statement Examples ---\n"; +void jumps() { + cout << "\n--- Jump Statement Examples ---\n"; - // goto - int num = rand(); - if (num == 3) - goto jumpLabel; - cout << "This line will be skipped.\n"; + // goto + int num = rand(); + if (num == 3) + goto jumpLabel; + cout << "This line will be skipped.\n"; jumpLabel: - cout << "Jumped here using goto!\n"; - - // break / continue - for (int i = 0; i < 5; ++i) - { - if (i == 2) - continue; // skip 2 - if (i == 4) - break; // stop loop at 4 - cout << "i = " << i << "\n"; - } + cout << "Jumped here using goto!\n"; + + // break / continue + for (int i = 0; i < 5; ++i) { + if (i == 2) + continue; // skip 2 + if (i == 4) + break; // stop loop at 4 + cout << "i = " << i << "\n"; + } } -int square(int n) -{ - // return - return n * n; +int square(int n) { + // return + return n * n; } -void functionCalls() -{ - cout << "\n--- Function Call Examples ---\n"; - // function call - int result = square(5); - cout << "square(5) = " << result << "\n"; +void functionCalls() { + cout << "\n--- Function Call Examples ---\n"; + // function call + int result = square(5); + cout << "square(5) = " << result << "\n"; } -void loops() -{ - cout << "\n--- Loop Examples ---\n"; - - // while - int i = 0; - while (i < 3) - { - cout << "while loop i = " << i << "\n"; - ++i; - } - - // do-while - int j = 0; - do - { - cout << "do-while loop j = " << j << "\n"; - ++j; - } while (j < 2); - - // for - for (int k = 0; k < 3; ++k) - { - cout << "for loop k = " << k << "\n"; - } - - // ranged-for - const int arr[] = {10, 20, 30}; - for (int value : arr) - { - cout << "ranged-for value = " << value << "\n"; - } +void loops() { + cout << "\n--- Loop Examples ---\n"; + + // while + int i = 0; + while (i < 3) { + cout << "while loop i = " << i << "\n"; + ++i; + } + + // do-while + int j = 0; + do { + cout << "do-while loop j = " << j << "\n"; + ++j; + } while (j < 2); + + // for + for (int k = 0; k < 3; ++k) { + cout << "for loop k = " << k << "\n"; + } + + // ranged-for + const int arr[] = {10, 20, 30}; + for (int value : arr) { + cout << "ranged-for value = " << value << "\n"; + } } -void halts() -{ - cout << "\n--- Halt Examples ---\n"; +void halts() { + cout << "\n--- Halt Examples ---\n"; - // std::exit() — terminates the program normally - // std::abort() — terminates abnormally (no cleanup) - // Uncomment one at a time to see behavior + // std::exit() — terminates the program normally + // std::abort() — terminates abnormally (no cleanup) + // Uncomment one at a time to see behavior - // std::exit(0); - // std::abort(); + // std::exit(0); + // std::abort(); } -void exceptions() -{ - cout << "\n--- Exception Handling Examples ---\n"; - - // try - catch - throw - try - { - throw runtime_error("Something went wrong!"); - } - catch (const exception &e) - { - cout << "Caught exception: " << e.what() << "\n"; - } +void exceptions() { + cout << "\n--- Exception Handling Examples ---\n"; + + // try - catch - throw + try { + throw runtime_error("Something went wrong!"); + } catch (const exception& e) { + cout << "Caught exception: " << e.what() << "\n"; + } } // A struct that runs code when its object is created -struct ControlFlow -{ - ControlFlow() - { - cout << "\n" - << "\n" - << "ControlFlow\n"; - conditionals(); - jumps(); - functionCalls(); - loops(); - halts(); - exceptions(); - } +struct ControlFlow { + ControlFlow() { + cout << "\n" + << "\n" + << "ControlFlow\n"; + conditionals(); + jumps(); + functionCalls(); + loops(); + halts(); + exceptions(); + } }; // All global and static objects are constructed before main() begins. diff --git a/src/core/basics/InitializeVariable.cpp b/src/core/basics/InitializeVariable.cpp index 0b0dcc6..2ca9726 100644 --- a/src/core/basics/InitializeVariable.cpp +++ b/src/core/basics/InitializeVariable.cpp @@ -4,62 +4,52 @@ using namespace std; void initialize_variable(); // A struct that runs code when its object is created -struct InitializeVariable -{ - InitializeVariable() - { - cout << "\n" - << "\n" - << "InitializeVariable\n"; - initialize_variable(); - } +struct InitializeVariable { + InitializeVariable() { + cout << "\n" + << "\n" + << "InitializeVariable\n"; + initialize_variable(); + } }; // All global and static objects are constructed before main() begins. static InitializeVariable autoRunInstance; -struct Foo -{ - Foo() - { - cout << "Default constructor/ default init\n"; - } +struct Foo { + Foo() { cout << "Default constructor/ default init\n"; } - // Foo(int) - explicit Foo(int) - { - cout << "Constructor called with int / copy init\n"; - } + // Foo(int) + explicit Foo(int) { cout << "Constructor called with int / copy init\n"; } - Foo(const Foo &other) - { - std::cout << "Copy constructor called\n"; - } + Foo(const Foo& other) { std::cout << "Copy constructor called\n"; } }; -void initialize_variable() -{ - cout << "\n--- Variable Initialization Examples ---\n"; - // There are there common ways to intialize a variable - // * Default - [[maybe_unused]] int initDefaultVar; - [[maybe_unused]] Foo initDefaultObj; - - // * Traditional initialization - // * Copy-init: Type var = value; - // 1. Compiler tries to convert the value to a temporary Foo. - // 2. If the constructor is explicit, implicit conversion is blocked -> compilation error. - // 3. Otherwise, a temporary Foo is created and then copied/moved into the variable. - [[maybe_unused]] Foo copyInitObj = (Foo)2.3; // explicit cast: double 2.3 -> int 2 (implicit narrowing) -> Foo(int) -> temporary Foo -> copied/moved into copyInitObj - // Foo copyInitObjError = 2.3; // ERROR: implicit conversion blocked by explicit constructor - // We can explicitly prevent certain conversions using = delete or using {} - - // * direct-init: Type var(value); - [[maybe_unused]] Foo directInitObj(4); // call Foo(int) - [[maybe_unused]] Foo directInitObj2(4.3); // look for constructor -> implicit 4.3(float) -> 4(int) -> call Foo(int) -> - - // * Brace init - // calls the constructor directly without allowing implicit conversions. - [[maybe_unused]] Foo braceInit{3}; - // Foo braceInit2{3.3}; // ERORR => Prefer this way +void initialize_variable() { + cout << "\n--- Variable Initialization Examples ---\n"; + // There are there common ways to intialize a variable + // * Default + [[maybe_unused]] int initDefaultVar; + [[maybe_unused]] Foo initDefaultObj; + + // * Traditional initialization + // * Copy-init: Type var = value; + // 1. Compiler tries to convert the value to a temporary Foo. + // 2. If the constructor is explicit, implicit conversion is blocked -> compilation error. + // 3. Otherwise, a temporary Foo is created and then copied/moved into the variable. + [[maybe_unused]] Foo copyInitObj = + (Foo)2.3; // explicit cast: double 2.3 -> int 2 (implicit narrowing) -> Foo(int) + // -> temporary Foo -> copied/moved into copyInitObj + // Foo copyInitObjError = 2.3; // ERROR: implicit conversion blocked by explicit constructor + // We can explicitly prevent certain conversions using = delete or using {} + + // * direct-init: Type var(value); + [[maybe_unused]] Foo directInitObj(4); // call Foo(int) + [[maybe_unused]] Foo directInitObj2( + 4.3); // look for constructor -> implicit 4.3(float) -> 4(int) -> call Foo(int) -> + + // * Brace init + // calls the constructor directly without allowing implicit conversions. + [[maybe_unused]] Foo braceInit{3}; + // Foo braceInit2{3.3}; // ERORR => Prefer this way } \ No newline at end of file diff --git a/src/core/basics/Linkage.cpp b/src/core/basics/Linkage.cpp new file mode 100644 index 0000000..ba768a4 --- /dev/null +++ b/src/core/basics/Linkage.cpp @@ -0,0 +1,28 @@ +// cppcheck-suppress-file [unreadVariable] +#include +using namespace std; + +namespace { +namespace Internal { +void run() {} +} // namespace Internal + +namespace External { +void run() {} +} // namespace External + +namespace SharingGlobalConstantsAcrossMultipleFiles { +void run() {} +} // namespace SharingGlobalConstantsAcrossMultipleFiles + +} // namespace + +struct LinkageAutoRunner { + LinkageAutoRunner() { + cout << "\n" + << "\n" + << "Linkage\n"; + } +}; + +static LinkageAutoRunner autoRunInstance; diff --git a/src/core/basics/Operations.cpp b/src/core/basics/Operations.cpp index 7b0176b..f86009b 100644 --- a/src/core/basics/Operations.cpp +++ b/src/core/basics/Operations.cpp @@ -5,125 +5,122 @@ void arithmeticOperator(); void logicalOperator(); void bitWiseOperator(); -struct Operations -{ - Operations() - { - cout << "\n" - << "\n" - << "Operation\n"; - arithmeticOperator(); - logicalOperator(); - bitWiseOperator(); - } +struct Operations { + Operations() { + cout << "\n" + << "\n" + << "Operation\n"; + arithmeticOperator(); + logicalOperator(); + bitWiseOperator(); + } }; static Operations autoRunInstance; -void arithmeticOperator() -{ - cout << "\n--- ArithmeticOperator Examples ---\n"; - int a{100}, b{200}; - - // Addition - cout << "a = " << a << ", b = " << b << "\n"; - int sum = a + b; - cout << "sum = " << sum << "\n"; - - // Subtraction - cout << "a = " << a << ", b = " << b << "\n"; - int different = a - b; - cout << "different = " << different << "\n"; - - // Multiplication - cout << "a = " << a << ", b = " << b << "\n"; - int product = a * b; - cout << "product = " << product << "\n"; - - // Division - cout << "a = " << a << ", b = " << b << "\n"; - int quotient = a / b; - cout << "quotient = " << quotient << "\n"; - - // Modulus - cout << "a = " << a << ", b = " << b << "\n"; - int remainder = a % b; - cout << "remainder = " << remainder << "\n"; - - // Increment - cout << "a = " << a << "\n"; - int preIn = ++a; // increase a, return a - cout << "preIn = " << preIn << "\n"; - - cout << "a = " << a << "\n"; - int postIn = a++; // copy a to a copy, then increase a, return copy - cout << "postIn = " << postIn << "\n"; - - // Decrement - cout << "b = " << b << "\n"; - int preDe = --b; - cout << "preDe = " << preDe << "\n"; - - cout << "b = " << b << "\n"; - int postDe = b--; - cout << "postDe = " << postDe << "\n"; - - // Comma: - int value = (a++, b); // a is incremented, then b is returned - cout << "a = " << a << ", b = " << b << "\n"; - cout << "comma(a++, b) = " << value << "\n"; +void arithmeticOperator() { + cout << "\n--- ArithmeticOperator Examples ---\n"; + int a{100}, b{200}; + + // Addition + cout << "a = " << a << ", b = " << b << "\n"; + int sum = a + b; + cout << "sum = " << sum << "\n"; + + // Subtraction + cout << "a = " << a << ", b = " << b << "\n"; + int different = a - b; + cout << "different = " << different << "\n"; + + // Multiplication + cout << "a = " << a << ", b = " << b << "\n"; + int product = a * b; + cout << "product = " << product << "\n"; + + // Division + cout << "a = " << a << ", b = " << b << "\n"; + int quotient = a / b; + cout << "quotient = " << quotient << "\n"; + + // Modulus + cout << "a = " << a << ", b = " << b << "\n"; + int remainder = a % b; + cout << "remainder = " << remainder << "\n"; + + // Increment + cout << "a = " << a << "\n"; + int preIn = ++a; // increase a, return a + cout << "preIn = " << preIn << "\n"; + + cout << "a = " << a << "\n"; + int postIn = a++; // copy a to a copy, then increase a, return copy + cout << "postIn = " << postIn << "\n"; + + // Decrement + cout << "b = " << b << "\n"; + int preDe = --b; + cout << "preDe = " << preDe << "\n"; + + cout << "b = " << b << "\n"; + int postDe = b--; + cout << "postDe = " << postDe << "\n"; + + // Comma: + int value = (a++, b); // a is incremented, then b is returned + cout << "a = " << a << ", b = " << b << "\n"; + cout << "comma(a++, b) = " << value << "\n"; } -void logicalOperator() -{ - cout << "\n--- LogicalOperator Examples ---\n"; - bool a = true; - bool b = false; - bool c = true; +void logicalOperator() { + cout << "\n--- LogicalOperator Examples ---\n"; + bool a = true; + bool b = false; + bool c = true; - cout << boolalpha; // show true/false instead of 1/0 - cout << "a = " << a << ", b = " << b << ", c = " << c << "\n\n"; + cout << boolalpha; // show true/false instead of 1/0 + cout << "a = " << a << ", b = " << b << ", c = " << c << "\n\n"; - // AND (&&) - cout << "[AND] a && b = " << (a && b) << "\n"; + // AND (&&) + cout << "[AND] a && b = " << (a && b) << "\n"; - // OR (||) - cout << "[OR ] a || b = " << (a || b) << "\n"; + // OR (||) + cout << "[OR ] a || b = " << (a || b) << "\n"; - // NOT (!) - cout << "[NOT] !c = " << (!c) << "\n"; + // NOT (!) + cout << "[NOT] !c = " << (!c) << "\n"; } #include -void bitWiseOperator() -{ - cout << "\n--- BitWiseOperator Examples ---\n"; - bitset<8> bitsA{0b1111'1111}; - bitset<8> bitsB{0b1111'0000}; - - cout << "bitA = " << bitsA << ", bitB = " << bitsB << "\n"; - - // AND - bitset<8> result = bitsA & bitsB; - cout << "bitA && bitB= " << result << "\n"; - - // OR - result = bitsA | bitsB; - cout << "bitA | bitB= " << result << "\n"; - - // XOR - result = bitsA ^ bitsB; - cout << "bitA ^ bitB= " << result << "\n"; - - // NOT - result = ~bitsA; - cout << "~bitA = " << result << "\n"; - - // LEFT SHIFT - result = bitsA << 1; - cout << "bitA << 1 = " << result << "\n"; - - // RIGHT SHIFT - result = bitsA >> 1; - cout << "bitA >> 1 = " << result << "\n"; -} \ No newline at end of file +void bitWiseOperator() { + cout << "\n--- BitWiseOperator Examples ---\n"; + bitset<8> bitsA { + 0b1111'1111}; bitset<8> bitsB { + 0b1111'0000}; + + cout + << "bitA = " << bitsA << ", bitB = " << bitsB << "\n"; + + // AND + bitset<8> result = bitsA & bitsB; + cout << "bitA && bitB= " << result << "\n"; + + // OR + result = bitsA | bitsB; + cout << "bitA | bitB= " << result << "\n"; + + // XOR + result = bitsA ^ bitsB; + cout << "bitA ^ bitB= " << result << "\n"; + + // NOT + result = ~bitsA; + cout << "~bitA = " << result << "\n"; + + // LEFT SHIFT + result = bitsA << 1; + cout << "bitA << 1 = " << result << "\n"; + + // RIGHT SHIFT + result = bitsA >> 1; + cout << "bitA >> 1 = " << result << "\n"; + } \ No newline at end of file diff --git a/src/core/basics/TypeQualifier.cpp b/src/core/basics/TypeQualifier.cpp new file mode 100644 index 0000000..a00380f --- /dev/null +++ b/src/core/basics/TypeQualifier.cpp @@ -0,0 +1,39 @@ +// cppcheck-suppress-file [unreadVariable] +#include +using namespace std; + +namespace { +namespace Constant { +void run() { + // 1. Name constants + const double const_var_g{9.8}; +#define NAME "Phong" + + // 2. Literals constants + bool myNameIsAlex{true}; // true is a boolean literal -> type: bool + double d{3.4}; // 3.4 is a double literal -> type: double + std::cout + << "Hello, world!"; // "Hello, world!" is a C-style string literal -> type: const char[14] + + // 3. Constexpr variables + constexpr double constexpr_var_g{9.8}; // compile-time constant + + // 4. Constant expression + constexpr double something{ + constexpr_var_g}; // ok: sum is a constant expression + // constexpr double something2{const_var_g}; // error because const_var_g is not the constexprs +} +} // namespace Constant + +} // namespace + +struct TypeQualifier { + TypeQualifier() { + cout << "\n" + << "\n" + << "TypeQualifier\n"; + Constant::run(); + } +}; + +static TypeQualifier autoRunInstance; diff --git a/src/core/datatypes/CArray.cpp b/src/core/datatypes/CArray.cpp index 66ec736..aff4ebc 100644 --- a/src/core/datatypes/CArray.cpp +++ b/src/core/datatypes/CArray.cpp @@ -1,28 +1,24 @@ #include -void arrayExamples() -{ - std::cout << "\n--- Array Examples ---\n"; +void arrayExamples() { + std::cout << "\n--- Array Examples ---\n"; - const int arr[5] = {1, 2, 3, 4, 5}; - for (int i = 0; i < 5; ++i) - { - std::cout << "arr[" << i << "] = " << arr[i] << "\n"; - } + const int arr[5] = {1, 2, 3, 4, 5}; + for (int i = 0; i < 5; ++i) { + std::cout << "arr[" << i << "] = " << arr[i] << "\n"; + } - double matrix[2][3] = {{1.1, 2.2, 3.3}, {4.4, 5.5, 6.6}}; - for (int i = 0; i < 2; ++i) - { - for (int j = 0; j < 3; ++j) - { - std::cout << "matrix[" << i << "][" << j << "] = " << matrix[i][j] << "\n"; - } + double matrix[2][3] = {{1.1, 2.2, 3.3}, {4.4, 5.5, 6.6}}; + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < 3; ++j) { + std::cout << "matrix[" << i << "][" << j << "] = " << matrix[i][j] + << "\n"; } + } } -struct CArray -{ - CArray() { arrayExamples(); } +struct CArray { + CArray() { arrayExamples(); } }; static CArray autoRunArray; diff --git a/src/core/datatypes/CEnum.cpp b/src/core/datatypes/CEnum.cpp index 0e138cd..aef51f5 100644 --- a/src/core/datatypes/CEnum.cpp +++ b/src/core/datatypes/CEnum.cpp @@ -1,72 +1,53 @@ +#include // for uint8_t #include -#include // for uint8_t using namespace std; // A type defined in terms of other types // *0. Define some enums // Unscope enum -enum BasicEnum -{ - enumratorA, - enumratorB, - enumratorC -}; +enum BasicEnum { enumratorA, enumratorB, enumratorC }; // Scoped enum - enumrators are inside enum's scope -enum class ScopeEnumClass -{ - enumratorA, - enumratorB, - enumratorC -}; +enum class ScopeEnumClass { enumratorA, enumratorB, enumratorC }; // Scoped enum inside a namespace -namespace EnumNameSpace -{ - enum class ScopeEnumClass - { - enumratorA, - enumratorB, - enumratorC - }; +namespace EnumNameSpace { +enum class ScopeEnumClass { enumratorA, enumratorB, enumratorC }; } // Scoped enum with explicit base type -enum class ScopeEnumClassB : uint8_t -{ - enumratorA = 0, - enumratorB = 1, - enumratorC = 2 +enum class ScopeEnumClassB : uint8_t { + enumratorA = 0, + enumratorB = 1, + enumratorC = 2 }; -void enums() -{ - cout << "\n--- Enum Type Examples ---\n"; - // *1. Using unscope enum - [[maybe_unused]] BasicEnum unscope_e = enumratorA; +void enums() { + cout << "\n--- Enum Type Examples ---\n"; + // *1. Using unscope enum + [[maybe_unused]] BasicEnum unscope_e = enumratorA; - // *2. Using scoped enum - [[maybe_unused]] ScopeEnumClass scope_e_c = ScopeEnumClass::enumratorA; + // *2. Using scoped enum + [[maybe_unused]] ScopeEnumClass scope_e_c = ScopeEnumClass::enumratorA; - // *3. Using scoped enum namespace - [[maybe_unused]] EnumNameSpace::ScopeEnumClass scope_e_c_n = EnumNameSpace::ScopeEnumClass::enumratorA; + // *3. Using scoped enum namespace + [[maybe_unused]] EnumNameSpace::ScopeEnumClass scope_e_c_n = + EnumNameSpace::ScopeEnumClass::enumratorA; - // *4. Using scoped enum with base type - [[maybe_unused]] ScopeEnumClassB st = ScopeEnumClassB::enumratorA; + // *4. Using scoped enum with base type + [[maybe_unused]] ScopeEnumClassB st = ScopeEnumClassB::enumratorA; } // A struct that runs code when its object is created -struct CEnum -{ - CEnum() - { - cout << "\n" - << "\n" - << "Compound type: Enum\n"; - - enums(); - } +struct CEnum { + CEnum() { + cout << "\n" + << "\n" + << "Compound type: Enum\n"; + + enums(); + } }; // All global and static objects are constructed before main() begins. diff --git a/src/core/datatypes/CPointers.cpp b/src/core/datatypes/CPointers.cpp index a54be6c..114add5 100644 --- a/src/core/datatypes/CPointers.cpp +++ b/src/core/datatypes/CPointers.cpp @@ -3,122 +3,115 @@ using namespace std; // A type defined in terms of other types // Simple function to demonstrate function pointers -int foo(int x) -{ - cout << "x = " << x << endl; - return x; +int foo(int x) { + cout << "x = " << x << endl; + return x; } -void byPtr(int *x) -{ - (*x)++; - cout << "x = " << *x << endl; +void byPtr(int* x) { + (*x)++; + cout << "x = " << *x << endl; } -void byConstPtr(const int *const x) -{ - // *x++; // error - cout << "x = " << *x << endl; +void byConstPtr(const int* const x) { + // *x++; // error + cout << "x = " << *x << endl; } -void byPtrConst(const int *x) -{ - // *x++; // error - cout << "x = " << *x << endl; +void byPtrConst(const int* x) { + // *x++; // error + cout << "x = " << *x << endl; } -void byConstPtrConst(const int *const x) -{ - // *x++; // error - cout << "x = " << *x << endl; +void byConstPtrConst(const int* const x) { + // *x++; // error + cout << "x = " << *x << endl; } static int global = 42; -int *returnPtr() -{ - return &global; // the object must outlive the reference. +int* returnPtr() { + return &global; // the object must outlive the reference. } -void pointers() -{ - cout << "\n--- Pointers Type Examples ---\n"; - int a = 10; - cout << "a = " << a << "\n"; - - // * 1. Pointer basics - const int *null_ptr = nullptr; - cout << "Address of nullptr (null_ptr): " << null_ptr << "\n"; - - // '&' gives the address of a variable - int *ptr_a = &a; - cout << "Address of a (&a): " << &a << "\n"; - cout << "Value stored in pointer (ptr_a): " << ptr_a << "\n"; - - // '*' dereferences a pointer (accesses the value at that address) - cout << "Value of a via *ptr_a: " << *ptr_a << "\n"; - - // Change value of a through its pointer - *ptr_a = 2; - cout << "Value of a after *ptr_a = 2: " << a << "\n"; - - // * 2. Pointer to const - [[maybe_unused]] const int const_var = 100; - const int *ptr_const_var = &const_var; - // *ptr_const_var = 10; // cannot modify the value through pointer - cout << "Value of ptr_const_var " << *ptr_const_var << "\n"; - ptr_const_var = &a; // can point somewhere else - cout << "Value of ptr_const_var " << *ptr_const_var << "\n"; - - // * 3. Const pointer - int *const const_ptr_a = &a; - *const_ptr_a = 10; // can change value - // const_ptr_a = nullptr; //cannot point to another variable - - // * 4. Const pointer to const - [[maybe_unused]] const int *const const_ptr_const_var = &a; - // *const_ptr_const_var = 10; // cannot modify the value through pointer - // const_ptr_const_var = nullptr; //cannot point to another variable - - // * 5. Pointer to pointer - int **ptr_ptr_a = new int *[10]; // dynamically allocate an array of 10 int* - ptr_ptr_a[0] = &a; - cout << "Value via pointer-to-pointer (*ptr_ptr_a[0]): " << *ptr_ptr_a[0] << "\n"; - delete[] ptr_ptr_a; // always free heap memory - - // * 6. Void pointer (generic pointer) - // void *void_ptr = static_cast(&a); - void *void_ptr = &a; // C-style pointer casting [cstyleCast] - - cout << "Value via void pointer (after casting): " << *static_cast(void_ptr) << "\n"; - - // * 7. Function pointer - int (*fcn_ptr)(int) = &foo; - (*fcn_ptr)(5); // call via dereference - fcn_ptr(10); // call directly - - // 8. Passing pointers - int in = 10; - byPtr(&in); - byConstPtr(&in); - byPtrConst(&in); - byConstPtrConst(&in); - - // * 9. Return by ptr(address) - const int *b_ptr = returnPtr(); - std::cout << "By ptr: " << *b_ptr << '\n'; +void pointers() { + cout << "\n--- Pointers Type Examples ---\n"; + int a = 10; + cout << "a = " << a << "\n"; + + // * 1. Pointer basics + const int* null_ptr = nullptr; + cout << "Address of nullptr (null_ptr): " << null_ptr << "\n"; + + // '&' gives the address of a variable + int* ptr_a = &a; + cout << "Address of a (&a): " << &a << "\n"; + cout << "Value stored in pointer (ptr_a): " << ptr_a << "\n"; + + // '*' dereferences a pointer (accesses the value at that address) + cout << "Value of a via *ptr_a: " << *ptr_a << "\n"; + + // Change value of a through its pointer + *ptr_a = 2; + cout << "Value of a after *ptr_a = 2: " << a << "\n"; + + // * 2. Pointer to const + [[maybe_unused]] const int const_var = 100; + const int* ptr_const_var = &const_var; + // *ptr_const_var = 10; // cannot modify the value through pointer + cout << "Value of ptr_const_var " << *ptr_const_var << "\n"; + ptr_const_var = &a; // can point somewhere else + cout << "Value of ptr_const_var " << *ptr_const_var << "\n"; + + // * 3. Const pointer + int* const const_ptr_a = &a; + *const_ptr_a = 10; // can change value + // const_ptr_a = nullptr; //cannot point to another variable + + // * 4. Const pointer to const + [[maybe_unused]] const int* const const_ptr_const_var = &a; + // *const_ptr_const_var = 10; // cannot modify the value through pointer + // const_ptr_const_var = nullptr; //cannot point to another variable + + // * 5. Pointer to pointer + int** ptr_ptr_a = new int*[10]; // dynamically allocate an array of 10 int* + ptr_ptr_a[0] = &a; + cout << "Value via pointer-to-pointer (*ptr_ptr_a[0]): " << *ptr_ptr_a[0] + << "\n"; + delete[] ptr_ptr_a; // always free heap memory + + // * 6. Void pointer (generic pointer) + // void *void_ptr = static_cast(&a); + void* void_ptr = &a; // C-style pointer casting [cstyleCast] + + cout << "Value via void pointer (after casting): " + << *static_cast(void_ptr) << "\n"; + + // * 7. Function pointer + int (*fcn_ptr)(int) = &foo; + (*fcn_ptr)(5); // call via dereference + fcn_ptr(10); // call directly + + // 8. Passing pointers + int in = 10; + byPtr(&in); + byConstPtr(&in); + byPtrConst(&in); + byConstPtrConst(&in); + + // * 9. Return by ptr(address) + const int* b_ptr = returnPtr(); + std::cout << "By ptr: " << *b_ptr << '\n'; } // A struct that runs code when its object is created -struct CPointers -{ - CPointers() - { - cout << "\n" - << "\n" - << "Compound type: Pointers\n"; - - pointers(); - } +struct CPointers { + CPointers() { + cout << "\n" + << "\n" + << "Compound type: Pointers\n"; + + pointers(); + } }; // All global and static objects are constructed before main() begins. diff --git a/src/core/datatypes/CReferences.cpp b/src/core/datatypes/CReferences.cpp index f690537..83e6bbd 100644 --- a/src/core/datatypes/CReferences.cpp +++ b/src/core/datatypes/CReferences.cpp @@ -2,55 +2,51 @@ using namespace std; // A type defined in terms of other types -void increment(int &x) -{ - x += 1; +void increment(int& x) { + x += 1; } -void incrementConst(const int &x) -{ - // s cannot be modified here - // x += 1; +void incrementConst(const int& x) { + // s cannot be modified here + // x += 1; } static int global = 42; -int &returnRef() -{ - return global; // the object must outlive the reference. +int& returnRef() { + return global; // the object must outlive the reference. } -void references() -{ - cout << "\n--- References Type Examples ---\n"; - int a = 10; - cout << "a = " << a << "\n"; +void references() { + cout << "\n--- References Type Examples ---\n"; + int a = 10; + cout << "a = " << a << "\n"; - // *1. Reference basics - // int &ref_error; // reference must be initialized - int &ref_a = a; + // *1. Reference basics + // int &ref_error; // reference must be initialized + int& ref_a = a; - ref_a = 20; // modifies 'a', since ref is just an alias - cout << "ref_a =20, a = " << a << "\n"; // prints 20 + ref_a = 20; // modifies 'a', since ref is just an alias + cout << "ref_a =20, a = " << a << "\n"; // prints 20 - // Cannot reseat: once 'ref' is bound to 'a', it cannot be bound to another variable - // int b = 30; - // ref = &b; invalid, would assign value instead of rebinding + // Cannot reseat: once 'ref' is bound to 'a', it cannot be bound to another + // variable int b = 30; ref = &b; invalid, would assign value instead of + // rebinding - // *2. Pass by reference - increment(a); // avoids making a copy - std::cout << "a after increment = " << a << "\n"; // prints 21 + // *2. Pass by reference + increment(a); // avoids making a copy + std::cout << "a after increment = " << a << "\n"; // prints 21 - // *3. Pass by const reference - incrementConst(a); + // *3. Pass by const reference + incrementConst(a); - // *4. Return by reference - int const &b = returnRef(); - std::cout << "By reference: " << b << '\n'; + // *4. Return by reference + int const& b = returnRef(); + std::cout << "By reference: " << b << '\n'; } /** - * - a (lvalue) + * + a (lvalue) | | std::move(a) (rvalue reference) V @@ -60,45 +56,42 @@ source (rvalue reference parameter, but it's the lvalue inside the function) V b */ -namespace RvalueReference -{ - using namespace std; - - // Move-like function taking an rvalue reference as parameter - int copyConstructor(int &&x) - { - // Inside the function, x is a named lvalue that refers to the original object passed in (here, 'a') - int result = x * 10; // compute result based on x - x = 0; // reset the original object to 0 - return result; // return the computed result - } - - void run() - { - int a{10}; // original value - - // Call function with an rvalue reference using std::move - // std::move(a) casts a (lvalue) into an rvalue reference (int&&) - int b = copyConstructor(std::move(a)); - - cout << b << endl; // prints 100 - cout << a << endl; // prints 0, because x in the function referred to a and was reset - } +namespace RvalueReference { +using namespace std; + +// Move-like function taking an rvalue reference as parameter +int copyConstructor(int&& x) { + // Inside the function, x is a named lvalue that refers to the original object + // passed in (here, 'a') + int result = x * 10; // compute result based on x + x = 0; // reset the original object to 0 + return result; // return the computed result +} +void run() { + int a{10}; // original value + + // Call function with an rvalue reference using std::move + // std::move(a) casts a (lvalue) into an rvalue reference (int&&) + int b = copyConstructor(std::move(a)); + + cout << b << endl; // prints 100 + cout << a << endl; // prints 0, because x in the function referred to a and + // was reset } +} // namespace RvalueReference + // A struct that runs code when its object is created -struct CReferences -{ - CReferences() - { - cout << "\n" - << "\n" - << "Compound type: References\n"; - - references(); - RvalueReference::run(); - } +struct CReferences { + CReferences() { + cout << "\n" + << "\n" + << "Compound type: References\n"; + + references(); + RvalueReference::run(); + } }; // All global and static objects are constructed before main() begins. diff --git a/src/core/datatypes/CStruct.cpp b/src/core/datatypes/CStruct.cpp index eedb43b..f18eb7b 100644 --- a/src/core/datatypes/CStruct.cpp +++ b/src/core/datatypes/CStruct.cpp @@ -8,78 +8,68 @@ using namespace std; // structure alignment, padding, decreasing order of size // public access // In C, structs can only hold data members, not functions. -struct StructDataType -{ - /* data */ - double voltage{0.0}; // Default initialization - int id{0}; - char status{'N'}; - string label{"Unknown"}; +struct StructDataType { + /* data */ + double voltage{0.0}; // Default initialization + int id{0}; + char status{'N'}; + string label{"Unknown"}; - /* function */ - void print() const - { - cout << "Size of struct = " << sizeof(StructDataType) << " bytes" << endl; - cout << "Sensor " << id - << " [" << label << "] " - << "Voltage: " << voltage - << " Status: " << status << endl; - } + /* function */ + void print() const { + cout << "Size of struct = " << sizeof(StructDataType) << " bytes" << endl; + cout << "Sensor " << id << " [" << label << "] " << "Voltage: " << voltage + << " Status: " << status << endl; + } }; -void updateVoltageRef(StructDataType &data, double newV) -{ - data.voltage = newV; +void updateVoltageRef(StructDataType& data, double newV) { + data.voltage = newV; } -void updateVoltagePtr(StructDataType *data, double newV) -{ - // (*data).voltage = newV; - if (data) - data->voltage = newV; +void updateVoltagePtr(StructDataType* data, double newV) { + // (*data).voltage = newV; + if (data) + data->voltage = newV; } -StructDataType makeSensor(int id, double v, const string &label) -{ - return {v, id, 'N', label}; +StructDataType makeSensor(int id, double v, const string& label) { + return {v, id, 'N', label}; } -void structs() -{ - cout << "\n--- Struct Type Examples ---\n"; - // *1. Using struct - [[maybe_unused]] StructDataType data0; // default init - StructDataType data1{3.3, 1, 'N', "Temp1"}; - StructDataType data2 = {6.3, 2, 'N', "Temp2"}; +void structs() { + cout << "\n--- Struct Type Examples ---\n"; + // *1. Using struct + [[maybe_unused]] StructDataType data0; // default init + StructDataType data1{3.3, 1, 'N', "Temp1"}; + StructDataType data2 = {6.3, 2, 'N', "Temp2"}; - // *2. Access members - data1.print(); - const StructDataType *ptr_data1 = &data1; - ptr_data1->print(); + // *2. Access members + data1.print(); + const StructDataType* ptr_data1 = &data1; + ptr_data1->print(); - // *3. Passing by reference - updateVoltageRef(data2, 100); - data2.print(); - // *4. Passing by pointer - updateVoltagePtr(&data2, 200); - data2.print(); + // *3. Passing by reference + updateVoltageRef(data2, 100); + data2.print(); + // *4. Passing by pointer + updateVoltagePtr(&data2, 200); + data2.print(); - // *5. Return a temporary struct - StructDataType s2 = makeSensor(2, 5.0, "Pressure"); - s2.print(); + // *5. Return a temporary struct + StructDataType s2 = makeSensor(2, 5.0, "Pressure"); + s2.print(); } // A struct that runs code when its object is created -struct CStruct -{ - CStruct() - { - cout << "\n" - << "\n" - << "Compound type: Struct\n"; +struct CStruct { + CStruct() { + cout << "\n" + << "\n" + << "Compound type: Struct\n"; - structs(); - } + structs(); + } }; // All global and static objects are constructed before main() begins. diff --git a/src/core/datatypes/CUnion.cpp b/src/core/datatypes/CUnion.cpp index 4a5a4d2..022c4c1 100644 --- a/src/core/datatypes/CUnion.cpp +++ b/src/core/datatypes/CUnion.cpp @@ -3,51 +3,45 @@ // --- Union Definition --- // All members share the same memory -union UnionDataType -{ - int intValue; - float floatValue; - char charValue; - - void printAll() const - { - std::cout << "intValue = " << intValue - << ", floatValue = " << floatValue - << ", charValue = " << charValue << "\n"; - } +union UnionDataType { + int intValue; + float floatValue; + char charValue; + + void printAll() const { + std::cout << "intValue = " << intValue << ", floatValue = " << floatValue + << ", charValue = " << charValue << "\n"; + } }; // --- Demonstrate unique property --- -void unionDemo() -{ - std::cout << "\n--- Union Unique Behavior ---\n"; +void unionDemo() { + std::cout << "\n--- Union Unique Behavior ---\n"; - UnionDataType u; + UnionDataType u; - u.intValue = 65; - std::cout << "After assigning intValue = 65:\n"; - u.printAll(); // Only intValue is meaningful; others show overwritten memory + u.intValue = 65; + std::cout << "After assigning intValue = 65:\n"; + u.printAll(); // Only intValue is meaningful; others show overwritten memory - u.floatValue = 3.14f; - std::cout << "After assigning floatValue = 3.14:\n"; - u.printAll(); // Writing floatValue overwrites intValue + u.floatValue = 3.14f; + std::cout << "After assigning floatValue = 3.14:\n"; + u.printAll(); // Writing floatValue overwrites intValue - u.charValue = 'A'; - std::cout << "After assigning charValue = 'A':\n"; - u.printAll(); // Writing charValue overwrites both intValue and floatValue + u.charValue = 'A'; + std::cout << "After assigning charValue = 'A':\n"; + u.printAll(); // Writing charValue overwrites both intValue and floatValue - std::cout << "Size of union = " << sizeof(UnionDataType) << " bytes\n"; - std::cout << "Notice: Only one value is valid at a time.\n"; + std::cout << "Size of union = " << sizeof(UnionDataType) << " bytes\n"; + std::cout << "Notice: Only one value is valid at a time.\n"; } // --- Auto-run struct --- -struct CUnion -{ - CUnion() - { - std::cout << "\nCompound type: Union\n"; - unionDemo(); - } +struct CUnion { + CUnion() { + std::cout << "\nCompound type: Union\n"; + unionDemo(); + } }; // Constructed before main() diff --git a/src/core/datatypes/Fundamental.cpp b/src/core/datatypes/Fundamental.cpp index 55316ba..fc5c7e4 100644 --- a/src/core/datatypes/Fundamental.cpp +++ b/src/core/datatypes/Fundamental.cpp @@ -2,66 +2,63 @@ using namespace std; // A basic type built into the core C++ language -void primative() -{ - cout << "\n--- Primative Type Examples ---\n"; - // Boolean - bool isReady = true; - cout << "bool: " << isReady << "\n"; +void primative() { + cout << "\n--- Primative Type Examples ---\n"; + // Boolean + bool isReady = true; + cout << "bool: " << isReady << "\n"; - // Character - char c = 'A'; - unsigned char uc = 200; - wchar_t wc = L'Ω'; // Greek Omega - // char8_t c8 = u8'A'; // UTF-8 - char16_t c16 = u'ß'; // UTF-16 - char32_t c32 = U'中'; // UTF-32 - cout << "char: " << c << "\n"; - cout << "unsigned char: " << static_cast(uc) << "\n"; - wcout << L"wchar_t: " << wc << L"\n"; - // cout << "char8_t: " << static_cast(c8) << "\n"; - cout << "char16_t: (UTF-16 code) " << static_cast(c16) << "\n"; - cout << "char32_t: (UTF-32 code) " << static_cast(c32) << "\n"; + // Character + char c = 'A'; + unsigned char uc = 200; + wchar_t wc = L'Ω'; // Greek Omega + // char8_t c8 = u8'A'; // UTF-8 + char16_t c16 = u'ß'; // UTF-16 + char32_t c32 = U'中'; // UTF-32 + cout << "char: " << c << "\n"; + cout << "unsigned char: " << static_cast(uc) << "\n"; + wcout << L"wchar_t: " << wc << L"\n"; + // cout << "char8_t: " << static_cast(c8) << "\n"; + cout << "char16_t: (UTF-16 code) " << static_cast(c16) << "\n"; + cout << "char32_t: (UTF-32 code) " << static_cast(c32) << "\n"; - // Integer - short s = -10; - int i = 42; - unsigned int ui = 100; - long l = 123456L; - long long ll = 9876543210LL; - cout << "short: " << s << "\n"; - cout << "int: " << i << "\n"; - cout << "unsigned int: " << ui << "\n"; - cout << "long: " << l << "\n"; - cout << "long long: " << ll << "\n"; + // Integer + short s = -10; + int i = 42; + unsigned int ui = 100; + long l = 123456L; + long long ll = 9876543210LL; + cout << "short: " << s << "\n"; + cout << "int: " << i << "\n"; + cout << "unsigned int: " << ui << "\n"; + cout << "long: " << l << "\n"; + cout << "long long: " << ll << "\n"; - // Floating Point - float f = 3.14f; - double d = 2.718281828; - long double ld = 1.6180339887L; - cout << "float: " << f << "\n"; - cout << "double: " << d << "\n"; - cout << "long double: " << ld << "\n"; + // Floating Point + float f = 3.14f; + double d = 2.718281828; + long double ld = 1.6180339887L; + cout << "float: " << f << "\n"; + cout << "double: " << d << "\n"; + cout << "long double: " << ld << "\n"; - // Void - cout << "void: (no data type, used for functions)\n"; + // Void + cout << "void: (no data type, used for functions)\n"; - // pointer - int const *ptr = nullptr; - cout << "nullptr_t: " << ptr << "\n"; + // pointer + int const* ptr = nullptr; + cout << "nullptr_t: " << ptr << "\n"; } // A struct that runs code when its object is created -struct Fundamental -{ - Fundamental() - { - cout << "\n" - << "\n" - << "Fundamental\n"; +struct Fundamental { + Fundamental() { + cout << "\n" + << "\n" + << "Fundamental\n"; - primative(); - } + primative(); + } }; // All global and static objects are constructed before main() begins. diff --git a/src/core/datatypes/TypeConVersions.cpp b/src/core/datatypes/TypeConVersions.cpp index 49eeb0c..fb4b683 100644 --- a/src/core/datatypes/TypeConVersions.cpp +++ b/src/core/datatypes/TypeConVersions.cpp @@ -1,153 +1,142 @@ #include using namespace std; -class Base -{ -public: - virtual void show() { cout << "Base class\n"; } - virtual ~Base() = default; +class Base { + public: + virtual void show() { cout << "Base class\n"; } + virtual ~Base() = default; }; -class Derived : public Base -{ -public: - void show() override { cout << "Derived class\n"; } +class Derived : public Base { + public: + void show() override { cout << "Derived class\n"; } }; -void implicitConversion() -{ - cout << "\n--- Implicit Type Conversion ---\n"; - // *1. Numeric promotion (safe, no data loss) - char c = 'A'; - int i = c; // char → int - float f = 3.5f; - double d = f; // float → double - bool b = true; - int b_to_int = b; // bool → int (true=1) - - cout << "char to int: " << i << "\n"; - cout << "float to double: " << d << "\n"; - cout << "bool to int: " << b_to_int << "\n"; - - // *2. Numeric conversion - float pi = 3.14159; - double pi_double = pi; // Widening conversions - int pi_int = pi; // narrowing, may lose fractional part - cout << "float to double (didening): " << pi_double << "\n"; - cout << "float to int (narrowing): " << pi_int << "\n"; +void implicitConversion() { + cout << "\n--- Implicit Type Conversion ---\n"; + // *1. Numeric promotion (safe, no data loss) + char c = 'A'; + int i = c; // char → int + float f = 3.5f; + double d = f; // float → double + bool b = true; + int b_to_int = b; // bool → int (true=1) + + cout << "char to int: " << i << "\n"; + cout << "float to double: " << d << "\n"; + cout << "bool to int: " << b_to_int << "\n"; + + // *2. Numeric conversion + float pi = 3.14159; + double pi_double = pi; // Widening conversions + int pi_int = pi; // narrowing, may lose fractional part + cout << "float to double (didening): " << pi_double << "\n"; + cout << "float to int (narrowing): " << pi_int << "\n"; } -void explicitConversion() -{ - cout << "\n--- Explicit Type Conversion ---\n"; - - double pi = 3.14159; - - // *1. C-style cast - int pi_c = (int)pi; - cout << "C-style cast: " << pi_c << "\n"; - - // *2. static_cast - compile-time type checking - int pi_static = static_cast(pi); - cout << "static_cast: " << pi_static << "\n"; - - // object -> object - Derived derived{}; - [[maybe_unused]] Base baseObj = static_cast(derived); - // object -> reference - const Base &baseRef = static_cast(derived); - // object -> ptr - [[maybe_unused]] const Base *base_ptr = static_cast(&derived); - - // *3. const_cast: const_cast adds or removes the const qualifier - const double c_pi = 2.71828; - const double *pConst = &c_pi; - const double *pNonConst = const_cast(pConst); // remove const - cout << "const_cast: " << *pNonConst << " (removed const)\n"; - - // *4. reinterpret_cast: reinterpret memory (unsafe) - const void *pVoid = reinterpret_cast(&pi); - cout << "reinterpret_cast: address of pi = " << pVoid << "\n"; - - // *5. dynamic_cast: safe cast between related classes (runtime checked) - Base *basePtr = new Derived(); - const Derived *derivedPtr = dynamic_cast(basePtr); - if (derivedPtr) - cout << "dynamic_cast: Success (Base* -> Derived*)\n"; - else - cout << "dynamic_cast: Failed\n"; - - Base *anotherBase = new Base(); - const Derived *wrongCast = dynamic_cast(anotherBase); - if (!wrongCast) - cout << "dynamic_cast: nullptr (invalid downcast)\n"; - - delete basePtr; - delete anotherBase; +void explicitConversion() { + cout << "\n--- Explicit Type Conversion ---\n"; + + double pi = 3.14159; + + // *1. C-style cast + int pi_c = (int)pi; + cout << "C-style cast: " << pi_c << "\n"; + + // *2. static_cast - compile-time type checking + int pi_static = static_cast(pi); + cout << "static_cast: " << pi_static << "\n"; + + // object -> object + Derived derived{}; + [[maybe_unused]] Base baseObj = static_cast(derived); + // object -> reference + const Base& baseRef = static_cast(derived); + // object -> ptr + [[maybe_unused]] const Base* base_ptr = static_cast(&derived); + + // *3. const_cast: const_cast adds or removes the const qualifier + const double c_pi = 2.71828; + const double* pConst = &c_pi; + const double* pNonConst = const_cast(pConst); // remove const + cout << "const_cast: " << *pNonConst << " (removed const)\n"; + + // *4. reinterpret_cast: reinterpret memory (unsafe) + const void* pVoid = reinterpret_cast(&pi); + cout << "reinterpret_cast: address of pi = " << pVoid << "\n"; + + // *5. dynamic_cast: safe cast between related classes (runtime checked) + Base* basePtr = new Derived(); + const Derived* derivedPtr = dynamic_cast(basePtr); + if (derivedPtr) + cout << "dynamic_cast: Success (Base* -> Derived*)\n"; + else + cout << "dynamic_cast: Failed\n"; + + Base* anotherBase = new Base(); + const Derived* wrongCast = dynamic_cast(anotherBase); + if (!wrongCast) + cout << "dynamic_cast: nullptr (invalid downcast)\n"; + + delete basePtr; + delete anotherBase; } -void typeAliases() -{ - cout << "\n--- Type Aliases ---\n"; - - // *1. using - preferred - using MyDouble = double; - [[maybe_unused]] const MyDouble a = 3.14; - - // *2. typedef - old style - typedef double OldDouble; - [[maybe_unused]] OldDouble b = 2.718; - - // *3. Function pointer alias - using FuncType = int (*)(double, char); - auto func = [](double x, char c) - { - return static_cast(x) + c; - }; - FuncType fptr = func; - cout << "Function pointer alias result: " << fptr(2.5, 'A') << "\n"; +void typeAliases() { + cout << "\n--- Type Aliases ---\n"; + + // *1. using - preferred + using MyDouble = double; + [[maybe_unused]] const MyDouble a = 3.14; + + // *2. typedef - old style + typedef double OldDouble; + [[maybe_unused]] OldDouble b = 2.718; + + // *3. Function pointer alias + using FuncType = int (*)(double, char); + auto func = [](double x, char c) { + return static_cast(x) + c; + }; + FuncType fptr = func; + cout << "Function pointer alias result: " << fptr(2.5, 'A') << "\n"; } -int add(int x, int y) -{ - return (x + y); +int add(int x, int y) { + return (x + y); } -auto add_auto(int a, int b) -> int -{ - return a + b; +auto add_auto(int a, int b) -> int { + return a + b; } -void typeDeduction() -{ - cout << "\n--- Type Deduction ---\n"; +void typeDeduction() { + cout << "\n--- Type Deduction ---\n"; - // *1. auto deduces type - auto x = 42; // int - auto y = 3.14; // double - const auto z = x + y; // double, const ignored in type deduction + // *1. auto deduces type + auto x = 42; // int + auto y = 3.14; // double + const auto z = x + y; // double, const ignored in type deduction - cout << "auto x: " << x << ", y: " << y << ", z: " << z << "\n"; + cout << "auto x: " << x << ", y: " << y << ", z: " << z << "\n"; - // *2. Trailing return type - cout << "add(3,4) = " << add(3, 4) << "\n"; - cout << "add_auto(3,4) = " << add_auto(3, 4) << "\n"; + // *2. Trailing return type + cout << "add(3,4) = " << add(3, 4) << "\n"; + cout << "add_auto(3,4) = " << add_auto(3, 4) << "\n"; - // *3. std::common_type - std::common_type_t val = x + y; - cout << "common_type: " << val << "\n"; + // *3. std::common_type + std::common_type_t val = x + y; + cout << "common_type: " << val << "\n"; } -struct CTypeConversion -{ - CTypeConversion() - { - implicitConversion(); - explicitConversion(); +struct CTypeConversion { + CTypeConversion() { + implicitConversion(); + explicitConversion(); - typeAliases(); - typeDeduction(); - } + typeAliases(); + typeDeduction(); + } }; static CTypeConversion autoRunTypes; \ No newline at end of file diff --git a/src/core/datatypes/class/CConstructors.cpp b/src/core/datatypes/class/CConstructors.cpp index 9d4ed72..57947f6 100644 --- a/src/core/datatypes/class/CConstructors.cpp +++ b/src/core/datatypes/class/CConstructors.cpp @@ -2,160 +2,141 @@ using namespace std; // *1. Members initializer list constructor -namespace InitializerList -{ - class CConstructors - { - private: - int m_x, m_y, m_z; - - public: - CConstructors(int x, int y) : m_x(x), m_y(y) - { - cout << "Called CConstructors(int x, int y) : m_x(x), m_y(y) \n"; - // m_z{0}; // error, it already initialized, so only can do assigment - // m_z(0); // error, it already initialized, so only can do assigment - m_z = 0; - } - - // using brace init - CConstructors(int x, int y, int z) : m_x{x}, m_y{y}, m_z{z} - { - cout << "Called CConstructors(int x, int y, int z) : m_x{x}, m_y{y}, m_z{z} \n"; - } - - // default arguments : must always be the RIGHTMOST parameters - explicit CConstructors(int x = 1) - { - cout << "Called CConstructors(int x = 1) \n"; - m_x = x; - m_y = 0; - m_z = 0; - } - - // CConstructors() = default; error: - // ‘InitializerList::CConstructors::CConstructors(int)’ cannot be overloaded with - // ‘InitializerList::CConstructors::CConstructors(int x = 1)’ - - void print() const - { - cout << "m_x = " << m_x << ", m_y = " << m_y << ", m_z = " << m_z << "\n"; - } - }; - - void constructers() - { - cout << "\n--- InitializerList Constructer Examples ---\n"; - CConstructors obj; - CConstructors obj1 = CConstructors(1, 2); - obj1.print(); - - CConstructors obj2 = CConstructors(3, 4, 5); - obj2.print(); - } +namespace InitializerList { +class CConstructors { + private: + int m_x, m_y, m_z; + + public: + CConstructors(int x, int y) : m_x(x), m_y(y) { + cout << "Called CConstructors(int x, int y) : m_x(x), m_y(y) \n"; + // m_z{0}; // error, it already initialized, so only can do assigment + // m_z(0); // error, it already initialized, so only can do assigment + m_z = 0; + } + + // using brace init + CConstructors(int x, int y, int z) : m_x{x}, m_y{y}, m_z{z} { + cout << "Called CConstructors(int x, int y, int z) : m_x{x}, m_y{y}, " + "m_z{z} \n"; + } + + // default arguments : must always be the RIGHTMOST parameters + explicit CConstructors(int x = 1) { + cout << "Called CConstructors(int x = 1) \n"; + m_x = x; + m_y = 0; + m_z = 0; + } + + // CConstructors() = default; error: + // ‘InitializerList::CConstructors::CConstructors(int)’ cannot be overloaded + // with ‘InitializerList::CConstructors::CConstructors(int x = 1)’ + + void print() const { + cout << "m_x = " << m_x << ", m_y = " << m_y << ", m_z = " << m_z << "\n"; + } +}; + +void constructers() { + cout << "\n--- InitializerList Constructer Examples ---\n"; + CConstructors obj; + CConstructors obj1 = CConstructors(1, 2); + obj1.print(); + + CConstructors obj2 = CConstructors(3, 4, 5); + obj2.print(); } +} // namespace InitializerList // *2. Default constructor: // It is a constructor that accepts no arguments. // Generated if no constructors are declared -// Initializes members: basic types uninitialized, class types call their default constructor -namespace Default -{ - class UConstructors - { - public: - // User-defined default constructor without argument - UConstructors() - { - cout << "Called UConstructors() \n"; - } - }; - - class IConstructors - { - public: - // Implicit default constructor is generated by the compiler when the class has no user-declared constructors - }; - - class EConstructors - { - public: - // we already create the constructor ourselves - // EConstructors(int a) - explicit EConstructors(float a) // explicit -> [[maybe_unused]] EConstructors obj2 = 1; [ERROR] - - { - cout << "Called explicit EConstructors(int a) \n"; - } - - // Explicit default constructor : also want the compiler to generate the default constructor. - EConstructors() = default; - }; - - void constructers() - { - cout << "\n--- Default Constructer Examples ---\n"; - [[maybe_unused]] UConstructors obj1; - // [[maybe_unused]] UConstructors obj2(); // wrong, this is function declare - // FYI: - // void outer() - // { - // void helper(); - // helper(); // defined later in the same file - // } - - [[maybe_unused]] UConstructors obj3{}; - - [[maybe_unused]] IConstructors obj4; - [[maybe_unused]] IConstructors obj6{}; - - [[maybe_unused]] EConstructors obj7; - [[maybe_unused]] EConstructors obj9{}; - - [[maybe_unused]] EConstructors obj10(1.2); - [[maybe_unused]] EConstructors obj11{2}; - } +// Initializes members: basic types uninitialized, class types call their +// default constructor +namespace Default { +class UConstructors { + public: + // User-defined default constructor without argument + UConstructors() { cout << "Called UConstructors() \n"; } +}; + +class IConstructors { + public: + // Implicit default constructor is generated by the compiler when the class + // has no user-declared constructors +}; + +class EConstructors { + public: + // we already create the constructor ourselves + // EConstructors(int a) + explicit EConstructors(float a) // explicit -> [[maybe_unused]] + // EConstructors obj2 = 1; [ERROR] + + { + cout << "Called explicit EConstructors(int a) \n"; + } + + // Explicit default constructor : also want the compiler to generate the + // default constructor. + EConstructors() = default; +}; + +void constructers() { + cout << "\n--- Default Constructer Examples ---\n"; + [[maybe_unused]] UConstructors obj1; + // [[maybe_unused]] UConstructors obj2(); // wrong, this is function declare + // FYI: + // void outer() + // { + // void helper(); + // helper(); // defined later in the same file + // } + + [[maybe_unused]] UConstructors obj3{}; + + [[maybe_unused]] IConstructors obj4; + [[maybe_unused]] IConstructors obj6{}; + + [[maybe_unused]] EConstructors obj7; + [[maybe_unused]] EConstructors obj9{}; + + [[maybe_unused]] EConstructors obj10(1.2); + [[maybe_unused]] EConstructors obj11{2}; } +} // namespace Default + +// *3. Delegate constructor: allow to delegate initialization to another +// constructor Never generated automatically +namespace Delegate { +class CConstructor { + private: + int m_x; + int m_y; + + public: + CConstructor(int x, int y) : m_x{x}, m_y{y} { + cout << "Called CConstructor(int x, int y) : m_x{x}, m_y{y} \n"; + } + + explicit CConstructor(int x) : CConstructor{x, 1} { + cout << "Called CConstructor(int x):CConstructor(x,0) \n"; + } + + CConstructor() : CConstructor(0) { + cout << "Called CConstructor() : CConstructor(0) \n"; + } + + void print() const { cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; } +}; -// *3. Delegate constructor: allow to delegate initialization to another constructor -// Never generated automatically -namespace Delegate -{ - class CConstructor - { - private: - int m_x; - int m_y; - - public: - CConstructor(int x, int y) : m_x{x}, m_y{y} - { - cout << "Called CConstructor(int x, int y) : m_x{x}, m_y{y} \n"; - } - - explicit CConstructor(int x) : CConstructor{x, 1} - { - cout << "Called CConstructor(int x):CConstructor(x,0) \n"; - } - - CConstructor() : CConstructor(0) - { - cout << "Called CConstructor() : CConstructor(0) \n"; - } - - void print() const - { - cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; - } - }; - - void constructors() - { - cout << "\n--- Delegate Constructer Examples ---\n"; - CConstructor obj1 = CConstructor(); - obj1.print(); - } +void constructors() { + cout << "\n--- Delegate Constructer Examples ---\n"; + CConstructor obj1 = CConstructor(); + obj1.print(); } +} // namespace Delegate // *4. Copy constructor: initialize an copy object with an existing object // Generated if no copy/move constructor or `destructor` is declared @@ -166,201 +147,169 @@ namespace Delegate // Object obj1{obj2} // * Copy assigment // obj1 = obj1 -namespace Copy +namespace Copy { +class + ICConstructor // C++ will create a public implicit copy constructor for us + // if we do not provide a one. +{ + private: + int m_x; + int m_y; + + public: + ICConstructor(int x, int y) : m_x{x}, m_y{y} { + cout << "Called ICConstructor(int x, int y) : m_x{x}, m_y{y} \n"; + } + + void print() const { cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; } +}; + +class ECConstructor // explicitly define our own copy constructor { - class ICConstructor // C++ will create a public implicit copy constructor for us if we do not provide a one. - { - private: - int m_x; - int m_y; - - public: - ICConstructor(int x, int y) : m_x{x}, m_y{y} - { - cout << "Called ICConstructor(int x, int y) : m_x{x}, m_y{y} \n"; - } - - void print() const - { - cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; - } - }; - - class ECConstructor // explicitly define our own copy constructor - { - private: - int m_x; - int m_y; - - public: - ECConstructor(int x, int y) : m_x{x}, m_y{y} - { - cout << "Called ICConstructor(int x, int y) : m_x{x}, m_y{y} \n"; - } - - // Implicit Copy constructor if there is no desconstrutor - // using `default` keyword - // ECConstructor(const ECConstructor &ref) = default; - - // Explicit Copy constructor (a=ECConstructor(b)) - ECConstructor(const ECConstructor &ref) : m_x{ref.m_x}, m_y{ref.m_x} - { - cout << "Called ECConstructor(const ECConstructor& ref) : m_x{ref.m_x}, m_y{ref.m_x} \n"; - } - - // Copy assigment (a=b) - ECConstructor &operator=(const ECConstructor &other) - { - if (this != &other) - { - this->m_x = other.m_x; - this->m_y = other.m_y; - } - return *this; - } - - void print() const - { - cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; - } - }; - - class DECConstructor // Delete the copy constructor so no copies can be made - { - private: - int m_x; - int m_y; - - public: - DECConstructor(int x, int y) : m_x{x}, m_y{y} - { - cout << "Called DECConstructor(int x, int y) : m_x{x}, m_y{y} \n"; - } - - // using `delete` keyword - DECConstructor(const DECConstructor &fraction) = delete; - - void print() const - { - cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; - } - }; - - void constructors() - { - cout << "\n--- Copy Constructer Examples ---\n"; - ICConstructor obj1 = ICConstructor(1, 2); - ICConstructor obj2{obj1}; - obj1.print(); - obj2.print(); - - ECConstructor obj3 = ECConstructor(3, 4); - ECConstructor obj4{obj3}; - obj3.print(); - obj4.print(); - - DECConstructor obj5 = DECConstructor(5, 6); - // DECConstructor obj6{obj5}; error - obj5.print(); + private: + int m_x; + int m_y; + + public: + ECConstructor(int x, int y) : m_x{x}, m_y{y} { + cout << "Called ICConstructor(int x, int y) : m_x{x}, m_y{y} \n"; + } + + // Implicit Copy constructor if there is no desconstrutor + // using `default` keyword + // ECConstructor(const ECConstructor &ref) = default; + + // Explicit Copy constructor (a=ECConstructor(b)) + ECConstructor(const ECConstructor& ref) : m_x{ref.m_x}, m_y{ref.m_x} { + cout << "Called ECConstructor(const ECConstructor& ref) : m_x{ref.m_x}, " + "m_y{ref.m_x} \n"; + } + + // Copy assigment (a=b) + ECConstructor& operator=(const ECConstructor& other) { + if (this != &other) { + this->m_x = other.m_x; + this->m_y = other.m_y; } + return *this; + } + + void print() const { cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; } +}; + +class DECConstructor // Delete the copy constructor so no copies can be made +{ + private: + int m_x; + int m_y; + + public: + DECConstructor(int x, int y) : m_x{x}, m_y{y} { + cout << "Called DECConstructor(int x, int y) : m_x{x}, m_y{y} \n"; + } + + // using `delete` keyword + DECConstructor(const DECConstructor& fraction) = delete; + + void print() const { cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; } +}; + +void constructors() { + cout << "\n--- Copy Constructer Examples ---\n"; + ICConstructor obj1 = ICConstructor(1, 2); + ICConstructor obj2{obj1}; + obj1.print(); + obj2.print(); + + ECConstructor obj3 = ECConstructor(3, 4); + ECConstructor obj4{obj3}; + obj3.print(); + obj4.print(); + + DECConstructor obj5 = DECConstructor(5, 6); + // DECConstructor obj6{obj5}; error + obj5.print(); } +} // namespace Copy // *4. Move Constructor: // Move constructor and move assignment transfer resource ownership // from one object to another. This is usually cheaper than copying. // A move constructor is implicitly generated only if no user-declared // copy constructor, move constructor, or destructor exists. -namespace Move +namespace Move { +class Model // C++ will create a public implicit copy constructor for us if we + // do not provide a one. { - class Model // C++ will create a public implicit copy constructor for us if we do not provide a one. - { - private: - int m_x; - int m_y; - - public: - Model(int x, int y) : m_x{x}, m_y{y} - { - cout << "Call constructor \n"; - } - - ~Model() - { - cout << "Call destructor \n"; - } - - Model(const Model &other) : Model(other.m_x, other.m_y) - { - cout << "Call copy constructor \n"; - } - - Model &operator=(const Model &other) - { - cout << "Call copy assigment \n"; - if (this != &other) - { - this->m_x = other.m_x; - this->m_y = other.m_y; - } - return *this; - } - - Model(Model &&source) noexcept : m_x(source.m_x), m_y(source.m_y) - { - cout << "Call move constructor\n"; - - // reset source - source.m_x = 0; - source.m_y = 0; - } - - Model &operator=(Model &&source) noexcept - { - cout << "Call move assigment \n"; - if (this != &source) - { - this->m_x = source.m_x; - this->m_y = source.m_y; - - // reset source - source.m_x = 0; - source.m_y = 0; - } - - return *this; - } - - void print() const - { - cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; - } - }; - - void constructers() - { - cout << "\n--- Move Constructor Examples ---\n"; - Model a(10, 20); - - cout << "\nCase 1: Model b = std::move(a);\n"; - Model b = std::move(a); // move constructor - - cout << "\nCase 2: Model c(5,6); c = std::move(b);\n"; - Model c(5, 6); - c = std::move(b); // move assignment - cout << "\n"; - } -} + private: + int m_x; + int m_y; -struct CConstructorsAutoRuner -{ - CConstructorsAutoRuner() - { - InitializerList::constructers(); - Default::constructers(); - Delegate::constructors(); - Copy::constructors(); - Move::constructers(); + public: + Model(int x, int y) : m_x{x}, m_y{y} { cout << "Call constructor \n"; } + + ~Model() { cout << "Call destructor \n"; } + + Model(const Model& other) : Model(other.m_x, other.m_y) { + cout << "Call copy constructor \n"; + } + + Model& operator=(const Model& other) { + cout << "Call copy assigment \n"; + if (this != &other) { + this->m_x = other.m_x; + this->m_y = other.m_y; + } + return *this; + } + + Model(Model&& source) noexcept : m_x(source.m_x), m_y(source.m_y) { + cout << "Call move constructor\n"; + + // reset source + source.m_x = 0; + source.m_y = 0; + } + + Model& operator=(Model&& source) noexcept { + cout << "Call move assigment \n"; + if (this != &source) { + this->m_x = source.m_x; + this->m_y = source.m_y; + + // reset source + source.m_x = 0; + source.m_y = 0; } + + return *this; + } + + void print() const { cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; } +}; + +void constructers() { + cout << "\n--- Move Constructor Examples ---\n"; + Model a(10, 20); + + cout << "\nCase 1: Model b = std::move(a);\n"; + Model b = std::move(a); // move constructor + + cout << "\nCase 2: Model c(5,6); c = std::move(b);\n"; + Model c(5, 6); + c = std::move(b); // move assignment + cout << "\n"; +} +} // namespace Move + +struct CConstructorsAutoRuner { + CConstructorsAutoRuner() { + InitializerList::constructers(); + Default::constructers(); + Delegate::constructors(); + Copy::constructors(); + Move::constructers(); + } }; static CConstructorsAutoRuner instance; diff --git a/src/core/datatypes/class/CDestructors.cpp b/src/core/datatypes/class/CDestructors.cpp index 2baebde..06afc00 100644 --- a/src/core/datatypes/class/CDestructors.cpp +++ b/src/core/datatypes/class/CDestructors.cpp @@ -5,89 +5,63 @@ using namespace std; // Generated if no destructor is declared // Calls destructors of members automatically // Does not free dynamically allocated memory unless you write it -namespace Basic -{ - class CDestructors - { - public: - CDestructors() - { - cout << "Called CDestructors() \n"; - } +namespace Basic { +class CDestructors { + public: + CDestructors() { cout << "Called CDestructors() \n"; } - ~CDestructors() - { - cout << "Called ~CDestructors() \n"; - } + ~CDestructors() { cout << "Called ~CDestructors() \n"; } - // Using `default` keyword - // ~CDestructors() = default; + // Using `default` keyword + // ~CDestructors() = default; - // Using `delete` // I forbid this destructor - // ~CDestructors() = delete; - }; + // Using `delete` // I forbid this destructor + // ~CDestructors() = delete; +}; - void destructers() - { - cout << "\n--- Basic Destructer Examples ---\n"; - { - CDestructors obj; - } - } +void destructers() { + cout << "\n--- Basic Destructer Examples ---\n"; + { CDestructors obj; } } +} // namespace Basic // *2. Virtual Destructor -namespace Virtual +namespace Virtual { +class CDestructorsBase // final => cannot inherit { - class CDestructorsBase // final => cannot inherit - { - public: - CDestructorsBase() - { - cout << "Called CDestructorsBase() \n"; - } + public: + CDestructorsBase() { cout << "Called CDestructorsBase() \n"; } - virtual ~CDestructorsBase() - { - cout << "Called ~CDestructorsBase() \n"; - } + virtual ~CDestructorsBase() { cout << "Called ~CDestructorsBase() \n"; } - // Using `default` keyword - // ~CDestructorsBase() = default; - }; + // Using `default` keyword + // ~CDestructorsBase() = default; +}; - class CDestructorsDerived : public CDestructorsBase - { - public: - CDestructorsDerived() - { - cout << "Called CDestructorsDerived() \n"; - } +class CDestructorsDerived : public CDestructorsBase { + public: + CDestructorsDerived() { cout << "Called CDestructorsDerived() \n"; } - ~CDestructorsDerived() override - { - cout << "Called ~CDestructorsDerived() \n"; - } - }; + ~CDestructorsDerived() override { + cout << "Called ~CDestructorsDerived() \n"; + } +}; - void destructers() - { - cout << "\n--- Virtual Destructer Examples ---\n"; - CDestructorsDerived *derived = {new CDestructorsDerived()}; - CDestructorsBase *base{derived}; - delete base; - // without virtual -> only call ~CDestructorsBase() - // with virtual -> call ~CDestructorsBase() && ~CDestructorsDerived() - } +void destructers() { + cout << "\n--- Virtual Destructer Examples ---\n"; + CDestructorsDerived* derived = {new CDestructorsDerived()}; + CDestructorsBase* base{derived}; + delete base; + // without virtual -> only call ~CDestructorsBase() + // with virtual -> call ~CDestructorsBase() && ~CDestructorsDerived() } +} // namespace Virtual -struct CDestructorsAutoRuner -{ - CDestructorsAutoRuner() - { - Basic::destructers(); - Virtual::destructers(); - } +struct CDestructorsAutoRuner { + CDestructorsAutoRuner() { + Basic::destructers(); + Virtual::destructers(); + } }; static CDestructorsAutoRuner instance; diff --git a/src/core/datatypes/class/RoleOfThreeFiveZero.cpp b/src/core/datatypes/class/RoleOfThreeFiveZero.cpp index ac2e173..59374c7 100644 --- a/src/core/datatypes/class/RoleOfThreeFiveZero.cpp +++ b/src/core/datatypes/class/RoleOfThreeFiveZero.cpp @@ -4,306 +4,281 @@ #include #include -namespace -{ - namespace Problem - { - class Model - { - private: - char *cstring; - - public: - explicit Model(const char *s = "") : cstring{nullptr} - { - if (s) - { - cstring = new char[std::strlen(s) + 1]; // allocate - std::strcpy(cstring, s); // populate - } - } - - // I. destructor - ~Model() - { - // delete[] cstring; // [P2] Double call here - std::cout << "Deleted cstring at: " << static_cast(cstring) << "\n"; - } - - // [P] - // What happen if we didn't define - // II. copy constructor - // III. copy assignment - - // Helper functions - const char *c_str() const // accessor - { - return cstring; - } - - void set_first_char(char ch) - { - if (cstring) - cstring[0] = ch; - } - }; - - void run() - { - { - std::cout << "\n\nProblem\n"; - Model str1{"str1"}; - Model str2 = str1; // shallow copy with "assigment = " - std::cout << "Before change:\n"; - std::cout << " str1 = " << str1.c_str() << "\n"; - std::cout << " str2 = " << str2.c_str() << "\n"; - - std::cout << "\n[P1] Shallow copy (shared pointer).\n"; - str2.set_first_char('X'); - std::cout << "\nAfter modifying str2.set_first_char('X'):\n"; - std::cout << " str1 = " << str1.c_str() << " <-- also changed!\n"; - std::cout << " str2 = " << str2.c_str() << "\n"; - std::cout << "\n[P2] Decontructor issue when releasing the shared pointer.\n"; - } - } +namespace { +namespace Problem { +class Model { + private: + char* cstring; + + public: + explicit Model(const char* s = "") : cstring{nullptr} { + if (s) { + cstring = new char[std::strlen(s) + 1]; // allocate + std::strcpy(cstring, s); // populate } + } + + // I. destructor + ~Model() { + // delete[] cstring; // [P2] Double call here + std::cout << "Deleted cstring at: " << static_cast(cstring) + << "\n"; + } + + // [P] + // What happen if we didn't define + // II. copy constructor + // III. copy assignment + + // Helper functions + const char* c_str() const // accessor + { + return cstring; + } + + void set_first_char(char ch) { + if (cstring) + cstring[0] = ch; + } +}; - // Rule of Three: define - // - destructor - // - copy constructor - // - copy assignment - namespace RoleOfThree - { - class Model - { - private: - char *cstring; - - public: - explicit Model(const char *s = "") : cstring{nullptr} - { - if (s) - { - cstring = new char[std::strlen(s) + 1]; // allocate - std::strcpy(cstring, s); // populate - } - } - - // I. destructor - ~Model() - { - delete[] cstring; // [P2] Double call here - std::cout << "Deleted cstring at: " << static_cast(cstring) << "\n"; - } - - // II. copy constructor - Model(const Model &other) : Model(other.cstring) {} - - // III. copy assignment - Model &operator=(const Model &other) - { - Model temp(other); - std::swap(cstring, temp.cstring); - return *this; - } - - // Helper functions - const char *c_str() const // accessor - { - return cstring; - } - - void set_first_char(char ch) - { - if (cstring) - cstring[0] = ch; - } - }; - - void run() - { - { - std::cout << "\n\nRole of three\n"; - Model str1{"str1"}; - Model str2 = str1; // shallow copy with "assigment = " - std::cout << "Before change:\n"; - std::cout << " str1 = " << str1.c_str() << "\n"; - std::cout << " str2 = " << str2.c_str() << "\n"; - - str2.set_first_char('X'); - std::cout << "\nAfter modifying str2.set_first_char('X'):\n"; - std::cout << " str1 = " << str1.c_str() << " <-- not changed!\n"; - std::cout << " str2 = " << str2.c_str() << "\n"; - - // [P] Role of three not cover this std::move - Model str{"there"}; - Model str_move = std::move(str); - std::cout << "\nAfter moving str into str_move:\n"; - std::cout << " str.c_str() = " << (str.c_str() ? str.c_str() : "null") << "\n"; - std::cout << " str_move.c_str() = " << (str_move.c_str() ? str_move.c_str() : "null") << "\n"; - } - } +void run() { + { + std::cout << "\n\nProblem\n"; + Model str1{"str1"}; + Model str2 = str1; // shallow copy with "assigment = " + std::cout << "Before change:\n"; + std::cout << " str1 = " << str1.c_str() << "\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + std::cout << "\n[P1] Shallow copy (shared pointer).\n"; + str2.set_first_char('X'); + std::cout << "\nAfter modifying str2.set_first_char('X'):\n"; + std::cout << " str1 = " << str1.c_str() << " <-- also changed!\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + std::cout + << "\n[P2] Decontructor issue when releasing the shared pointer.\n"; + } +} +} // namespace Problem + +// Rule of Three: define +// - destructor +// - copy constructor +// - copy assignment +namespace RoleOfThree { +class Model { + private: + char* cstring; + + public: + explicit Model(const char* s = "") : cstring{nullptr} { + if (s) { + cstring = new char[std::strlen(s) + 1]; // allocate + std::strcpy(cstring, s); // populate } + } + + // I. destructor + ~Model() { + delete[] cstring; // [P2] Double call here + std::cout << "Deleted cstring at: " << static_cast(cstring) + << "\n"; + } + + // II. copy constructor + Model(const Model& other) : Model(other.cstring) {} + + // III. copy assignment + Model& operator=(const Model& other) { + Model temp(other); + std::swap(cstring, temp.cstring); + return *this; + } + + // Helper functions + const char* c_str() const // accessor + { + return cstring; + } + + void set_first_char(char ch) { + if (cstring) + cstring[0] = ch; + } +}; - // Rule of Five: Rule of Three and define - // - move constructor - // - move assignment - namespace RoleOfFive - { - class Model - { - private: - char *cstring; - - public: - explicit Model(const char *s = "") : cstring{nullptr} - { - if (s) - { - cstring = new char[std::strlen(s) + 1]; // allocate - std::strcpy(cstring, s); // populate - } - } - - // I. destructor - ~Model() - { - delete[] cstring; // [P2] Double call here - std::cout << "Deleted cstring at: " << static_cast(cstring) << "\n"; - } - - // II. copy constructor - Model(const Model &other) : Model(other.cstring) {} - - // III. copy assignment - Model &operator=(const Model &other) - { - Model temp(other); - std::swap(cstring, temp.cstring); - return *this; - } - - // IV. move constructor - // noexcept is a specifier that indicates a function will not throw exceptions. - Model(Model &&other) noexcept : cstring(std::exchange(other.cstring, nullptr)) {}; - - // V. move assignment - Model &operator=(Model &&other) noexcept - { - std::swap(cstring, other.cstring); - return *this; - } - - // Helper functions - const char *c_str() const // accessor - { - return cstring; - } - - void set_first_char(char ch) - { - if (cstring) - cstring[0] = ch; - } - }; - - void run() - { - { - std::cout << "\n\nRole of Five\n"; - Model str1{"str1"}; - Model str2 = str1; // shallow copy with "assigment = " - std::cout << "Before change:\n"; - std::cout << " str1 = " << str1.c_str() << "\n"; - std::cout << " str2 = " << str2.c_str() << "\n"; - - str2.set_first_char('X'); - std::cout << "\nAfter modifying str2.set_first_char('X'):\n"; - std::cout << " str1 = " << str1.c_str() << " <-- not changed!\n"; - std::cout << " str2 = " << str2.c_str() << "\n"; - - // Role of five covers this std::move - Model str{"there"}; - Model str_move = std::move(str); - std::cout << "\nAfter moving str into str_move:\n"; - std::cout << " str.c_str() = " << (str.c_str() ? str.c_str() : "null") << "\n"; - std::cout << " str_move.c_str() = " << (str_move.c_str() ? str_move.c_str() : "null") << "\n"; - } - } +void run() { + { + std::cout << "\n\nRole of three\n"; + Model str1{"str1"}; + Model str2 = str1; // shallow copy with "assigment = " + std::cout << "Before change:\n"; + std::cout << " str1 = " << str1.c_str() << "\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + str2.set_first_char('X'); + std::cout << "\nAfter modifying str2.set_first_char('X'):\n"; + std::cout << " str1 = " << str1.c_str() << " <-- not changed!\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + // [P] Role of three not cover this std::move + Model str{"there"}; + Model str_move = std::move(str); + std::cout << "\nAfter moving str into str_move:\n"; + std::cout << " str.c_str() = " << (str.c_str() ? str.c_str() : "null") + << "\n"; + std::cout << " str_move.c_str() = " + << (str_move.c_str() ? str_move.c_str() : "null") << "\n"; + } +} +} // namespace RoleOfThree + +// Rule of Five: Rule of Three and define +// - move constructor +// - move assignment +namespace RoleOfFive { +class Model { + private: + char* cstring; + + public: + explicit Model(const char* s = "") : cstring{nullptr} { + if (s) { + cstring = new char[std::strlen(s) + 1]; // allocate + std::strcpy(cstring, s); // populate } + } + + // I. destructor + ~Model() { + delete[] cstring; // [P2] Double call here + std::cout << "Deleted cstring at: " << static_cast(cstring) + << "\n"; + } + + // II. copy constructor + Model(const Model& other) : Model(other.cstring) {} + + // III. copy assignment + Model& operator=(const Model& other) { + Model temp(other); + std::swap(cstring, temp.cstring); + return *this; + } + + // IV. move constructor + // noexcept is a specifier that indicates a function will not throw + // exceptions. + Model(Model&& other) noexcept + : cstring(std::exchange(other.cstring, nullptr)){}; + + // V. move assignment + Model& operator=(Model&& other) noexcept { + std::swap(cstring, other.cstring); + return *this; + } + + // Helper functions + const char* c_str() const // accessor + { + return cstring; + } + + void set_first_char(char ch) { + if (cstring) + cstring[0] = ch; + } +}; - // Rule of Zero: use RAII (std::string, std::vector, smart pointers,...), no need to define any special functions except constructor - namespace RoleOfZero - { - class base_of_five_defaults - { - public: - base_of_five_defaults(const base_of_five_defaults &) = default; - base_of_five_defaults(base_of_five_defaults &&) = default; - base_of_five_defaults &operator=(const base_of_five_defaults &) = default; - base_of_five_defaults &operator=(base_of_five_defaults &&) = default; - virtual ~base_of_five_defaults() = default; - }; - - class Model - { - private: - std::string cstring; - // int* ptr; -> std::unique_ptr ptr; (exclusive ownership) - // int* arr; -> std::vector arr; (automatic array management) - // int* shared; -> std::shared_ptr shared; (shared ownership) - - public: - explicit Model(const std::string &str) : cstring{str} - { - } - - // Helper functions - const std::string c_str() const // accessor - { - return cstring; - } - - void set_first_char(char ch) - { - cstring.at(0) = ch; - } - }; - - void run() - { - { - std::cout << "\n\nRole of Zero\n"; - Model str1{"str1"}; - Model str2 = str1; // shallow copy with "assigment = " - std::cout << "Before change:\n"; - std::cout << " str1 = " << str1.c_str() << "\n"; - std::cout << " str2 = " << str2.c_str() << "\n"; - - str2.set_first_char('X'); - std::cout << "\nAfter modifying str2.set_first_char('X'):\n"; - std::cout << " str1 = " << str1.c_str() << " <-- not changed!\n"; - std::cout << " str2 = " << str2.c_str() << "\n"; - - Model str{"there"}; - Model str_move = std::move(str); - std::cout << "\nAfter moving str into str_move:\n"; - std::cout << " str.c_str() = " << str.c_str() << "\n"; - std::cout << " str_move.c_str() = " << str_move.c_str() << "\n"; - } - } - } +void run() { + { + std::cout << "\n\nRole of Five\n"; + Model str1{"str1"}; + Model str2 = str1; // shallow copy with "assigment = " + std::cout << "Before change:\n"; + std::cout << " str1 = " << str1.c_str() << "\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + str2.set_first_char('X'); + std::cout << "\nAfter modifying str2.set_first_char('X'):\n"; + std::cout << " str1 = " << str1.c_str() << " <-- not changed!\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + // Role of five covers this std::move + Model str{"there"}; + Model str_move = std::move(str); + std::cout << "\nAfter moving str into str_move:\n"; + std::cout << " str.c_str() = " << (str.c_str() ? str.c_str() : "null") + << "\n"; + std::cout << " str_move.c_str() = " + << (str_move.c_str() ? str_move.c_str() : "null") << "\n"; + } } -struct RoleOfThreeFiveZeroAutoRuner -{ - // Virtual default destructor: Does not break Rule of Three, Five, or Zero - RoleOfThreeFiveZeroAutoRuner() - { - Problem::run(); - RoleOfThree::run(); - RoleOfFive::run(); - RoleOfZero::run(); - } +} // namespace RoleOfFive + +// Rule of Zero: use RAII (std::string, std::vector, smart pointers,...), no +// need to define any special functions except constructor +namespace RoleOfZero { +class base_of_five_defaults { + public: + base_of_five_defaults(const base_of_five_defaults&) = default; + base_of_five_defaults(base_of_five_defaults&&) = default; + base_of_five_defaults& operator=(const base_of_five_defaults&) = default; + base_of_five_defaults& operator=(base_of_five_defaults&&) = default; + virtual ~base_of_five_defaults() = default; +}; + +class Model { + private: + std::string cstring; + // int* ptr; -> std::unique_ptr ptr; (exclusive ownership) + // int* arr; -> std::vector arr; (automatic array management) + // int* shared; -> std::shared_ptr shared; (shared ownership) + + public: + explicit Model(const std::string& str) : cstring{str} {} + + // Helper functions + const std::string c_str() const // accessor + { + return cstring; + } + + void set_first_char(char ch) { cstring.at(0) = ch; } +}; + +void run() { + { + std::cout << "\n\nRole of Zero\n"; + Model str1{"str1"}; + Model str2 = str1; // shallow copy with "assigment = " + std::cout << "Before change:\n"; + std::cout << " str1 = " << str1.c_str() << "\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + str2.set_first_char('X'); + std::cout << "\nAfter modifying str2.set_first_char('X'):\n"; + std::cout << " str1 = " << str1.c_str() << " <-- not changed!\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + Model str{"there"}; + Model str_move = std::move(str); + std::cout << "\nAfter moving str into str_move:\n"; + std::cout << " str.c_str() = " << str.c_str() << "\n"; + std::cout << " str_move.c_str() = " << str_move.c_str() << "\n"; + } +} +} // namespace RoleOfZero +} // namespace +struct RoleOfThreeFiveZeroAutoRuner { + // Virtual default destructor: Does not break Rule of Three, Five, or Zero + RoleOfThreeFiveZeroAutoRuner() { + Problem::run(); + RoleOfThree::run(); + RoleOfFive::run(); + RoleOfZero::run(); + } }; static RoleOfThreeFiveZeroAutoRuner instance; \ No newline at end of file diff --git a/src/core/datatypes/class/SallowDeepCopying.cpp b/src/core/datatypes/class/SallowDeepCopying.cpp index 28563a8..b89e2ef 100644 --- a/src/core/datatypes/class/SallowDeepCopying.cpp +++ b/src/core/datatypes/class/SallowDeepCopying.cpp @@ -2,191 +2,160 @@ #include -namespace -{ - namespace Shallow - { - class Model - { - private: - int m_x{0}, m_y{1}; - int *ptr; - - public: - // Default constructor: Model a = Model(1,2) - explicit Model(int x, int y, int z) : m_x{x}, m_y{y}, ptr{nullptr} - { - ptr = new int; - *ptr = z; - } - - ~Model() - { - // [P1] - // Shallow copying makes both objects use the same pointer. - // If one object deletes the pointer, the other object now - // points to invalid memory. - // delete ptr; // Commented out on purpose for demo - } - - void changePtr(int value) - { - if (ptr != nullptr) - { - *ptr = value; - } - } - - int getPtr() const - { - if (ptr != nullptr) - { - return *ptr; - } - return 0; - } - - // Implicit copy constructor: Model a{b}; - // Model(const Model &f) : m_x{f.m_x}, m_y{f.m_y} {} - - // Implicit assignment operator (like the way compiler gen this function): Model a = b; - // Model &operator=(const Model &f) - // { - // // self-assignment guard - // if (this == &f) - // { - // return *this; - // } - - // // do the copy - // m_x = f.m_x; - // m_y = f.m_y; - - // // return the existing object so we can chain this operator - // return *this; - // } - }; - - void run() - { - Model obj1 = Model(1, 2, 3); - Model obj2 = obj1; - obj2.changePtr(30); - // [P2] - std::cout << "\n=== Shallow Copy Demo ===\n"; - std::cout << "obj1.ptr = " << obj1.getPtr() << "\n"; - std::cout << "obj2.ptr = " << obj2.getPtr() << "\n"; - } +namespace { +namespace Shallow { +class Model { + private: + int m_x{0}, m_y{1}; + int* ptr; + + public: + // Default constructor: Model a = Model(1,2) + explicit Model(int x, int y, int z) : m_x{x}, m_y{y}, ptr{nullptr} { + ptr = new int; + *ptr = z; + } + + ~Model() { + // [P1] + // Shallow copying makes both objects use the same pointer. + // If one object deletes the pointer, the other object now + // points to invalid memory. + // delete ptr; // Commented out on purpose for demo + } + + void changePtr(int value) { + if (ptr != nullptr) { + *ptr = value; + } + } + int getPtr() const { + if (ptr != nullptr) { + return *ptr; } + return 0; + } + + // Implicit copy constructor: Model a{b}; + // Model(const Model &f) : m_x{f.m_x}, m_y{f.m_y} {} + + // Implicit assignment operator (like the way compiler gen this function): + // Model a = b; Model &operator=(const Model &f) + // { + // // self-assignment guard + // if (this == &f) + // { + // return *this; + // } + + // // do the copy + // m_x = f.m_x; + // m_y = f.m_y; + + // // return the existing object so we can chain this operator + // return *this; + // } +}; - // To do a deep copy on any non-null pointers being copied - // Requires that we write our own `copy constructors` and `overloaded assignment operators`. - namespace DeepCopying - { - class Model - { - private: - int m_x{0}, m_y{1}; - int *ptr; - - void deepCopy(const Model &source) - { - // sallow copy the normal fields - m_x = source.m_x; - m_y = source.m_y; - - // deep copy the ptr field - // m_data is a pointer, so we need to deep copy it if it is non-null - // first we need to deallocate any value that this Model is holding! - delete ptr; - if (source.ptr != nullptr) - { - // allocate memory for our copy - ptr = new int; - // do the copy - *ptr = *source.ptr; - } - else - { - ptr = nullptr; - } - } - - public: - // Constructor - explicit Model(int x, int y, int z) : m_x{x}, m_y{y}, ptr{nullptr} - { - ptr = new int; - *ptr = z; - } - - // Destructor - ~Model() - { - delete ptr; - } - - // Copy constructor - // no need to check self-copy [if (this != &source)] - // because it cannot happen a(a); - Model(const Model &source) - { - this->deepCopy(source); - } - - // Assignment operator - // need to check self-copy [if (this != &source)] - // because it posible a = a; - Model &operator=(const Model &source) - { - if (this != &source) - { - // now do the deep copy - this->deepCopy(source); - } - return *this; - } - - void changePtr(int value) - { - if (ptr != nullptr) - { - *ptr = value; - } - } - - int getPtr() const - { - if (ptr != nullptr) - { - return *ptr; - } - return 0; - } - }; - - void run() - { - Model obj1 = Model(1, 2, 3); - Model obj2 = obj1; - obj2.changePtr(30); - - std::cout << "\n=== Deep Copy Demo ===\n"; - std::cout << "obj1.ptr = " << obj1.getPtr() << "\n"; - std::cout << "obj2.ptr = " << obj2.getPtr() << "\n"; - } +void run() { + Model obj1 = Model(1, 2, 3); + Model obj2 = obj1; + obj2.changePtr(30); + // [P2] + std::cout << "\n=== Shallow Copy Demo ===\n"; + std::cout << "obj1.ptr = " << obj1.getPtr() << "\n"; + std::cout << "obj2.ptr = " << obj2.getPtr() << "\n"; +} +} // namespace Shallow + +// To do a deep copy on any non-null pointers being copied +// Requires that we write our own `copy constructors` and `overloaded assignment +// operators`. +namespace DeepCopying { +class Model { + private: + int m_x{0}, m_y{1}; + int* ptr; + + void deepCopy(const Model& source) { + // sallow copy the normal fields + m_x = source.m_x; + m_y = source.m_y; + + // deep copy the ptr field + // m_data is a pointer, so we need to deep copy it if it is non-null + // first we need to deallocate any value that this Model is holding! + delete ptr; + if (source.ptr != nullptr) { + // allocate memory for our copy + ptr = new int; + // do the copy + *ptr = *source.ptr; + } else { + ptr = nullptr; } -} + } + + public: + // Constructor + explicit Model(int x, int y, int z) : m_x{x}, m_y{y}, ptr{nullptr} { + ptr = new int; + *ptr = z; + } + + // Destructor + ~Model() { delete ptr; } + + // Copy constructor + // no need to check self-copy [if (this != &source)] + // because it cannot happen a(a); + Model(const Model& source) { this->deepCopy(source); } + + // Assignment operator + // need to check self-copy [if (this != &source)] + // because it posible a = a; + Model& operator=(const Model& source) { + if (this != &source) { + // now do the deep copy + this->deepCopy(source); + } + return *this; + } + + void changePtr(int value) { + if (ptr != nullptr) { + *ptr = value; + } + } -struct ShallowDeepCopying -{ - ShallowDeepCopying() - { - Shallow::run(); - DeepCopying::run(); + int getPtr() const { + if (ptr != nullptr) { + return *ptr; } + return 0; + } +}; + +void run() { + Model obj1 = Model(1, 2, 3); + Model obj2 = obj1; + obj2.changePtr(30); + + std::cout << "\n=== Deep Copy Demo ===\n"; + std::cout << "obj1.ptr = " << obj1.getPtr() << "\n"; + std::cout << "obj2.ptr = " << obj2.getPtr() << "\n"; +} + +} // namespace DeepCopying +} // namespace + +struct ShallowDeepCopying { + ShallowDeepCopying() { + Shallow::run(); + DeepCopying::run(); + } }; static ShallowDeepCopying instance; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 974edfb..d6f9f84 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,9 @@ #include + #include "version.h" -int main(int argc, char *argv[]) -{ - std::cout << APP_NAME << " v" << APP_VERSION << std::endl; - std::cout << APP_DESCRIPTION << std::endl; - return 0; +int main(int argc, char* argv[]) { + std::cout << APP_NAME << " v" << APP_VERSION << std::endl; + std::cout << APP_DESCRIPTION << std::endl; + return 0; } \ No newline at end of file diff --git a/src/patterns/behavioral/ChainOfCommand.cpp b/src/patterns/behavioral/ChainOfCommand.cpp index 5d52c1f..c206939 100644 --- a/src/patterns/behavioral/ChainOfCommand.cpp +++ b/src/patterns/behavioral/ChainOfCommand.cpp @@ -1,10 +1,11 @@ -// CoR is a behavioral design pattern that lets you pass requests along a chain of handlers. -// Upon receiving a request, each handler decides either to process the request -// or to pass it to the next handler in the chain. -// Allows an object to send a command without knowing what object will receive and handle it. +// CoR is a behavioral design pattern that lets you pass requests along a chain +// of handlers. Upon receiving a request, each handler decides either to process +// the request or to pass it to the next handler in the chain. Allows an object +// to send a command without knowing what object will receive and handle it. // Appicability: // (*) when it’s essential to execute several handlers in a particular order. -// (**)when the set of handlers and their order are supposed to change at runtime. +// (**)when the set of handlers and their order are supposed to change at +// runtime. // UML: docs/uml/patterns_behavioral_CoR.drawio.svg @@ -12,162 +13,132 @@ #include #include -namespace -{ - namespace CoR - { - /* - * Handler - defines an interface for handling requests - */ - class IHandler - { - public: - virtual void setNextHandler(IHandler *handler) = 0; - virtual IHandler *setNext(IHandler *handler) = 0; - virtual void handle(const std::string &request) = 0; - }; - - class AbstractHandler : public IHandler - { - private: - IHandler *m_setNext; - - public: - AbstractHandler() : m_setNext{nullptr} {}; - - void setNextHandler(IHandler *handler) override - { - this->m_setNext = handler; - } - - // handler1->setNext(handler2)->setNext(handler3) - IHandler *setNext(IHandler *handler) override - { - this->m_setNext = handler; - return handler; - } - - void handle(const std::string &request) override - { - if (this->m_setNext != nullptr) - { - this->m_setNext->handle(request); - } - else - { - std::cout << "\tNo handler processed request: " << request << "\n"; - } - } - }; - - /** - * CoreteHandlers - handles the requests it is responsible for - * If it can handle the request it does so, otherwise it sends the request to its successor - */ - class ConcreteHandlerGET : public AbstractHandler - { - private: - static constexpr const char *header = "GET"; - - public: - void handle(const std::string &request) override - { - if (request.rfind(header, 0) == 0) - { - // If request is eligible, handle it - std::cout << "\tHandle GET request: " << request << "\n"; - // In realworld, it should be other logics here - } - else - { - AbstractHandler::handle(request); - } - } - }; - - class ConcreteHandlerPUT : public AbstractHandler - { - private: - static constexpr const char *header = "PUT"; - - public: - void handle(const std::string &request) override - { - if (request.rfind(header, 0) == 0) - { - std::cout << "\tHandle PUT request: " << request << "\n"; - } - else - { - AbstractHandler::handle(request); - } - } - }; - - class ConcreteHandlerPOST : public AbstractHandler - { - private: - static constexpr const char *header = "POST"; - - public: - void handle(const std::string &request) override - { - if (request.rfind(header, 0) == 0) - { - std::cout << "\tHandle POST request: " << request << "\n"; - } - else - { - AbstractHandler::handle(request); - } - } - }; - - /** - * Client - sends commands to the first object in the chain that may handle the command - */ - namespace Client - { - void clientCode(IHandler &handler, const std::string &request) - { - handler.handle(request); - } - } - - void run() - { - // Setup Chain of Responsibility - IHandler *postHandler = new ConcreteHandlerPOST(); - IHandler *gettHandler = new ConcreteHandlerGET(); - IHandler *puttHandler = new ConcreteHandlerPUT(); - postHandler->setNext(gettHandler)->setNext(puttHandler); - - // Send requests to the chain - std::string dummy = "DUMMY .."; - std::string postRequest = "POST /test/demo_form.php HTTP/1.1 .."; - std::string getRequest = "GET /users/123 .."; - std::cout << "Send dummy request\n"; - Client::clientCode(*postHandler, dummy); - std::cout << "Send POST request\n"; - Client::clientCode(*postHandler, postRequest); - std::cout << "Send GET request\n"; - Client::clientCode(*postHandler, getRequest); - - delete postHandler; - delete gettHandler; - delete puttHandler; - } +namespace { +namespace CoR { +/* + * Handler - defines an interface for handling requests + */ +class IHandler { + public: + virtual void setNextHandler(IHandler* handler) = 0; + virtual IHandler* setNext(IHandler* handler) = 0; + virtual void handle(const std::string& request) = 0; +}; + +class AbstractHandler : public IHandler { + private: + IHandler* m_setNext; + + public: + AbstractHandler() : m_setNext{nullptr} {}; + + void setNextHandler(IHandler* handler) override { this->m_setNext = handler; } + + // handler1->setNext(handler2)->setNext(handler3) + IHandler* setNext(IHandler* handler) override { + this->m_setNext = handler; + return handler; + } + + void handle(const std::string& request) override { + if (this->m_setNext != nullptr) { + this->m_setNext->handle(request); + } else { + std::cout << "\tNo handler processed request: " << request << "\n"; } + } +}; -} +/** + * CoreteHandlers - handles the requests it is responsible for + * If it can handle the request it does so, otherwise it sends the request to + * its successor + */ +class ConcreteHandlerGET : public AbstractHandler { + private: + static constexpr const char* header = "GET"; + + public: + void handle(const std::string& request) override { + if (request.rfind(header, 0) == 0) { + // If request is eligible, handle it + std::cout << "\tHandle GET request: " << request << "\n"; + // In realworld, it should be other logics here + } else { + AbstractHandler::handle(request); + } + } +}; + +class ConcreteHandlerPUT : public AbstractHandler { + private: + static constexpr const char* header = "PUT"; -struct CoRAutoRunner -{ - CoRAutoRunner() - { - std::cout << "\n--- CoR Pattern Example ---\n"; - CoR::run(); + public: + void handle(const std::string& request) override { + if (request.rfind(header, 0) == 0) { + std::cout << "\tHandle PUT request: " << request << "\n"; + } else { + AbstractHandler::handle(request); } + } +}; + +class ConcreteHandlerPOST : public AbstractHandler { + private: + static constexpr const char* header = "POST"; + + public: + void handle(const std::string& request) override { + if (request.rfind(header, 0) == 0) { + std::cout << "\tHandle POST request: " << request << "\n"; + } else { + AbstractHandler::handle(request); + } + } +}; + +/** + * Client - sends commands to the first object in the chain that may handle the + * command + */ +namespace Client { +void clientCode(IHandler& handler, const std::string& request) { + handler.handle(request); +} +} // namespace Client + +void run() { + // Setup Chain of Responsibility + IHandler* postHandler = new ConcreteHandlerPOST(); + IHandler* gettHandler = new ConcreteHandlerGET(); + IHandler* puttHandler = new ConcreteHandlerPUT(); + postHandler->setNext(gettHandler)->setNext(puttHandler); + + // Send requests to the chain + std::string dummy = "DUMMY .."; + std::string postRequest = "POST /test/demo_form.php HTTP/1.1 .."; + std::string getRequest = "GET /users/123 .."; + std::cout << "Send dummy request\n"; + Client::clientCode(*postHandler, dummy); + std::cout << "Send POST request\n"; + Client::clientCode(*postHandler, postRequest); + std::cout << "Send GET request\n"; + Client::clientCode(*postHandler, getRequest); + + delete postHandler; + delete gettHandler; + delete puttHandler; +} +} // namespace CoR + +} // namespace + +struct CoRAutoRunner { + CoRAutoRunner() { + std::cout << "\n--- CoR Pattern Example ---\n"; + CoR::run(); + } }; static CoRAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/behavioral/Command.cpp b/src/patterns/behavioral/Command.cpp index b2bbe22..8c63e6a 100644 --- a/src/patterns/behavioral/Command.cpp +++ b/src/patterns/behavioral/Command.cpp @@ -1,9 +1,12 @@ -// Command is a behavioral design pattern that turns a request into a stand-alone object that contains all information about the request. (receivers, payloads) -// This transformation lets you pass requests as a method arguments, delay or queue a request’s execution, and support undoable operations. -// Allows an object to send a command without knowing what object will receive and handle it. -// Appicability: +// Command is a behavioral design pattern that turns a request into a +// stand-alone object that contains all information about the request. +// (receivers, payloads) This transformation lets you pass requests as a method +// arguments, delay or queue a request’s execution, and support undoable +// operations. Allows an object to send a command without knowing what object +// will receive and handle it. Appicability: // (*) when you want to parameterize objects with operations. -// (**) when you want to queue operations, schedule their execution, or execute them remotely. +// (**) when you want to queue operations, schedule their execution, or execute +// them remotely. // (***) when you want to implement reversible operations. // UML: docs/uml/patterns_behavioral_command.drawio.svg @@ -11,152 +14,122 @@ #include #include -namespace -{ - namespace Command - { - - /** - * The Command interface usually declares just a single method for executing the command. - * e.g. Save, Undo, Jump, Backup, CreateOrder - */ - class ICommand - { - public: - virtual void execute() const = 0; - }; - - /** - * The Receiver class contains some business logic. Almost any object may act as a receiver. - * Most commands only handle the details of how a request is passed to the receiver, while the receiver itself does the actual work. - * e.g. Document, GameCharacter, DB service - */ - class Receiver - { - public: - static void doCheck() - { - std::cout << "Receiver checking... \n"; - }; - static void doInit() - { - std::cout << "Receiver initializing... \n"; - }; - static void doLaunch(const std::string &arg) - { - std::cout << "Receiver launching... \n\t" << arg << "\n"; - }; - }; - - /** - * Concrete Commands implement various kinds of requests. - * A concrete command isn’t supposed to perform the work on its own, but rather to pass the call to one of the business logic objects. - * However, for the sake of simplifying the code, these classes can be merged. - */ - class SimpleConcreteCommand : public ICommand - { - public: - void execute() const override - { - std::cout << "\t SimpleCommand executed \n"; - } - }; - - class ComplexConcreteCommand : public ICommand - { - private: - Receiver *m_receiver; - std::string m_payload; - - public: - ComplexConcreteCommand(Receiver *receiver, const std::string &payload) : m_receiver{receiver}, m_payload{payload} {}; - - void execute() const override - { - std::cout << "\t ComplexCommand executed \n"; - this->m_receiver->doCheck(); - this->m_receiver->doInit(); - this->m_receiver->doLaunch(m_payload); - } - }; - - /** - *The Sender class (aka invoker) is responsible for initiating requests. - *This class must have a field for storing a reference to a command object. - *The sender triggers that command instead of sending the request directly to the receiver. - *Note that the sender isn’t responsible for creating the command object. Usually, it gets a pre-created command from the client via the constructor. - *e.g. Button, Shortcut, Scheduler, Event bus... - */ - class Invoker - { - private: - ICommand *m_on_start; - ICommand *m_on_finish; - - public: - explicit Invoker(ICommand *s = nullptr, ICommand *f = nullptr) : m_on_start{s}, m_on_finish{s} - { - } - - ~Invoker() - { - delete m_on_start; - delete m_on_finish; - } - - void setOnStart(ICommand *command) - { - this->m_on_start = command; - } - - void setOnFinish(ICommand *command) - { - this->m_on_finish = command; - } - - void invoke() const - { - if (m_on_start != nullptr) - { - m_on_start->execute(); - } - - if (m_on_finish != nullptr) - { - m_on_finish->execute(); - } - } - }; - - namespace Client - { - void clientCode(const Invoker *invoker) - { - invoker->invoke(); - } - - } - - void run() - { - Receiver *ui = new Receiver(); - // How to execute these command when something triggered - Invoker *invoker = new Invoker(); - invoker->setOnStart(new SimpleConcreteCommand()); - invoker->setOnFinish(new ComplexConcreteCommand(ui, "cmd --version")); - Client::clientCode(invoker); - delete ui; - } +namespace { +namespace Command { + +/** + * The Command interface usually declares just a single method for executing the + * command. e.g. Save, Undo, Jump, Backup, CreateOrder + */ +class ICommand { + public: + virtual void execute() const = 0; +}; + +/** + * The Receiver class contains some business logic. Almost any object may act as + * a receiver. Most commands only handle the details of how a request is passed + * to the receiver, while the receiver itself does the actual work. e.g. + * Document, GameCharacter, DB service + */ +class Receiver { + public: + static void doCheck() { std::cout << "Receiver checking... \n"; }; + static void doInit() { std::cout << "Receiver initializing... \n"; }; + static void doLaunch(const std::string& arg) { + std::cout << "Receiver launching... \n\t" << arg << "\n"; + }; +}; + +/** + * Concrete Commands implement various kinds of requests. + * A concrete command isn’t supposed to perform the work on its own, but rather + * to pass the call to one of the business logic objects. However, for the sake + * of simplifying the code, these classes can be merged. + */ +class SimpleConcreteCommand : public ICommand { + public: + void execute() const override { std::cout << "\t SimpleCommand executed \n"; } +}; + +class ComplexConcreteCommand : public ICommand { + private: + Receiver* m_receiver; + std::string m_payload; + + public: + ComplexConcreteCommand(Receiver* receiver, const std::string& payload) + : m_receiver{receiver}, m_payload{payload} {}; + + void execute() const override { + std::cout << "\t ComplexCommand executed \n"; + this->m_receiver->doCheck(); + this->m_receiver->doInit(); + this->m_receiver->doLaunch(m_payload); + } +}; + +/** + *The Sender class (aka invoker) is responsible for initiating requests. + *This class must have a field for storing a reference to a command object. + *The sender triggers that command instead of sending the request directly to + * the receiver. Note that the sender isn’t responsible for creating the command + * object. Usually, it gets a pre-created command from the client via the + * constructor. e.g. Button, Shortcut, Scheduler, Event bus... + */ +class Invoker { + private: + ICommand* m_on_start; + ICommand* m_on_finish; + + public: + explicit Invoker(ICommand* s = nullptr, ICommand* f = nullptr) + : m_on_start{s}, m_on_finish{s} {} + + ~Invoker() { + delete m_on_start; + delete m_on_finish; + } + + void setOnStart(ICommand* command) { this->m_on_start = command; } + + void setOnFinish(ICommand* command) { this->m_on_finish = command; } + + void invoke() const { + if (m_on_start != nullptr) { + m_on_start->execute(); } -} -struct CommandAutoRunner -{ - CommandAutoRunner() - { - std::cout << "\n--- Command Pattern Example ---\n"; - Command::run(); + if (m_on_finish != nullptr) { + m_on_finish->execute(); } + } +}; + +namespace Client { +void clientCode(const Invoker* invoker) { + invoker->invoke(); +} + +} // namespace Client + +void run() { + Receiver* ui = new Receiver(); + // How to execute these command when something triggered + Invoker* invoker = new Invoker(); + invoker->setOnStart(new SimpleConcreteCommand()); + invoker->setOnFinish(new ComplexConcreteCommand(ui, "cmd --version")); + Client::clientCode(invoker); + delete ui; +} +} // namespace Command +} // namespace + +struct CommandAutoRunner { + CommandAutoRunner() { + std::cout << "\n--- Command Pattern Example ---\n"; + Command::run(); + } }; static CommandAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/behavioral/Iterator.cpp b/src/patterns/behavioral/Iterator.cpp index 4ba2cd2..7e39d80 100644 --- a/src/patterns/behavioral/Iterator.cpp +++ b/src/patterns/behavioral/Iterator.cpp @@ -1,239 +1,206 @@ -// Iterator is a behavioral design pattern that lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.). -// Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. -// Appicability: -// (*) when your collection has a complex data structure under the hood, but you want to hide its complexity from clients (either for convenience or security reasons). +// Iterator is a behavioral design pattern that lets you traverse elements of a +// collection without exposing its underlying representation (list, stack, tree, +// etc.). Provide a way to access the elements of an aggregate object +// sequentially without exposing its underlying representation. Appicability: +// (*) when your collection has a complex data structure under the hood, but +// you want to hide its complexity from clients (either for convenience or +// security reasons). // (**) reduce duplication of the traversal code across your app. -// (***) when you want your code to be able to traverse different data structures or when types of these structures are unknown beforehand. +// (***) when you want your code to be able to traverse different data +// structures or when types of these structures are unknown beforehand. // UML: docs/uml/patterns_behavioral_iterator.drawio.svg #include +#include +#include #include #include -#include -#include -namespace -{ - namespace Iterator - { - class DataModel - { - private: - int m_value; - - public: - explicit DataModel(int value) : m_value{value} {} - - void setValue(int v) - { - m_value = v; - } - - int getValue() const { return m_value; } - }; - - /** - * Iterator interface: declares the operations required for traversing a collection: fetching the next element, retrieving the current position, restarting iteration, etc. - */ - template - class IIterator - { - public: - virtual bool hasNext() const = 0; - virtual const T *next() = 0; - virtual ~IIterator() = default; - }; - - /** - * Aggregate interface: declares one or multiple methods for getting iterators compatible with the collection. - * Note that the return type of the methods must be declared as the iterator interface so that the concrete collections can return various kinds of iterators. - */ - template - class IAggregate - { - public: - virtual IIterator *createIterator() = 0; - virtual ~IAggregate() = default; - }; - - /** - * Concrete Iterator: implement specific algorithms for traversing a collection. - * The iterator object should track the traversal progress on its own. This allows several iterators to traverse the same collection independently of each other. - */ - template - class VectorConcreteIterator : public IIterator - { - private: - const std::vector &m_data; - size_t m_currentIndex{0}; - - public: - explicit VectorConcreteIterator(const std::vector &data) : m_data{data} {} - - bool hasNext() const override - { - return m_currentIndex < m_data.size(); - } - - const T *next() override - { - if (hasNext()) - { - return (T *)&m_data[m_currentIndex++]; - } - else - { - return nullptr; - } - } - }; - - template - class ListConcreteIterator : public IIterator - { - private: - const std::list &m_data; - typename std::list::const_iterator m_it; - - public: - explicit ListConcreteIterator(const std::list &data) - : m_data(data), m_it(m_data.begin()) {} - - bool hasNext() const override - { - return m_it != m_data.end(); - } - - const T *next() override - { - if (!hasNext()) - return nullptr; - const T *ptr = &(*m_it); - ++m_it; - return ptr; - } - }; - - /** - * Concrete Aggregate: return new instances of a particular concrete iterator class each time the client requests one. - */ - template - class ListConreteAggregate : public IAggregate - { - private: - std::list m_data; - - public: - void add(const T &i) - { - m_data.push_back(i); - } - - IIterator *createIterator() override - { - return new ListConcreteIterator(m_data); - } - }; - - template - class VectorConcreteAggregate : public IAggregate - { - private: - std::vector m_data; - - public: - void add(const T &i) - { - m_data.push_back(i); - } - - IIterator *createIterator() override - { - return new VectorConcreteIterator(m_data); - } - }; - - /** - * The Client works with both collections and iterators via their interfaces. - * This way the client isn’t coupled to concrete classes, allowing you to use various collections and iterators with the same client code. - */ - namespace Client - { - - void clientCode(IAggregate *collection) - { - IIterator *iterator = collection->createIterator(); - - if (iterator != nullptr) - { - while (iterator->hasNext()) - { - std::cout << "int: " << *(iterator->next()) << "\n"; - } - } - - delete iterator; - } - - void clientCode(IAggregate *collection) - { - IIterator *iterator = collection->createIterator(); - - if (iterator != nullptr) - { - while (iterator->hasNext()) - { - std::cout << "data: " << iterator->next()->getValue() << "\n"; - } - } - delete iterator; - } - } - - void run() - { - std::cout << "\nVectorConcreteAggregate\n"; - VectorConcreteAggregate intCollection; - for (int i = 0; i < 10; ++i) - { - intCollection.add(i); - } - Client::clientCode(&intCollection); - std::cout << "\n"; - VectorConcreteAggregate dataCollection; - for (int i = 0; i < 10; ++i) - { - dataCollection.add(DataModel(i * 10)); - } - Client::clientCode(&dataCollection); - - std::cout << "\nListConreteAggregate\n"; - ListConreteAggregate intCollection2; - for (int i = 0; i < 10; ++i) - { - intCollection2.add(i); - } - - Client::clientCode(&intCollection2); - std::cout << "\n"; - ListConreteAggregate dataCollection2; - for (int i = 0; i < 10; ++i) - { - dataCollection2.add(DataModel(i * 10)); - } - Client::clientCode(&dataCollection2); - } +namespace { +namespace Iterator { +class DataModel { + private: + int m_value; + + public: + explicit DataModel(int value) : m_value{value} {} + + void setValue(int v) { m_value = v; } + + int getValue() const { return m_value; } +}; + +/** + * Iterator interface: declares the operations required for traversing a + * collection: fetching the next element, retrieving the current position, + * restarting iteration, etc. + */ +template +class IIterator { + public: + virtual bool hasNext() const = 0; + virtual const T* next() = 0; + virtual ~IIterator() = default; +}; + +/** + * Aggregate interface: declares one or multiple methods for getting iterators + * compatible with the collection. Note that the return type of the methods must + * be declared as the iterator interface so that the concrete collections can + * return various kinds of iterators. + */ +template +class IAggregate { + public: + virtual IIterator* createIterator() = 0; + virtual ~IAggregate() = default; +}; + +/** + * Concrete Iterator: implement specific algorithms for traversing a collection. + * The iterator object should track the traversal progress on its own. This + * allows several iterators to traverse the same collection independently of + * each other. + */ +template +class VectorConcreteIterator : public IIterator { + private: + const std::vector& m_data; + size_t m_currentIndex{0}; + + public: + explicit VectorConcreteIterator(const std::vector& data) : m_data{data} {} + + bool hasNext() const override { return m_currentIndex < m_data.size(); } + + const T* next() override { + if (hasNext()) { + return (T*)&m_data[m_currentIndex++]; + } else { + return nullptr; + } + } +}; + +template +class ListConcreteIterator : public IIterator { + private: + const std::list& m_data; + typename std::list::const_iterator m_it; + + public: + explicit ListConcreteIterator(const std::list& data) + : m_data(data), m_it(m_data.begin()) {} + + bool hasNext() const override { return m_it != m_data.end(); } + + const T* next() override { + if (!hasNext()) + return nullptr; + const T* ptr = &(*m_it); + ++m_it; + return ptr; + } +}; + +/** + * Concrete Aggregate: return new instances of a particular concrete iterator + * class each time the client requests one. + */ +template +class ListConreteAggregate : public IAggregate { + private: + std::list m_data; + + public: + void add(const T& i) { m_data.push_back(i); } + + IIterator* createIterator() override { + return new ListConcreteIterator(m_data); + } +}; + +template +class VectorConcreteAggregate : public IAggregate { + private: + std::vector m_data; + + public: + void add(const T& i) { m_data.push_back(i); } + + IIterator* createIterator() override { + return new VectorConcreteIterator(m_data); + } +}; + +/** + * The Client works with both collections and iterators via their interfaces. + * This way the client isn’t coupled to concrete classes, allowing you to use + * various collections and iterators with the same client code. + */ +namespace Client { + +void clientCode(IAggregate* collection) { + IIterator* iterator = collection->createIterator(); + + if (iterator != nullptr) { + while (iterator->hasNext()) { + std::cout << "int: " << *(iterator->next()) << "\n"; } + } + + delete iterator; } -struct IteratorAutoRunner -{ - IteratorAutoRunner() - { - std::cout << "\n--- Iterator Pattern Example ---\n"; - Iterator::run(); +void clientCode(IAggregate* collection) { + IIterator* iterator = collection->createIterator(); + + if (iterator != nullptr) { + while (iterator->hasNext()) { + std::cout << "data: " << iterator->next()->getValue() << "\n"; } + } + delete iterator; +} +} // namespace Client + +void run() { + std::cout << "\nVectorConcreteAggregate\n"; + VectorConcreteAggregate intCollection; + for (int i = 0; i < 10; ++i) { + intCollection.add(i); + } + Client::clientCode(&intCollection); + std::cout << "\n"; + VectorConcreteAggregate dataCollection; + for (int i = 0; i < 10; ++i) { + dataCollection.add(DataModel(i * 10)); + } + Client::clientCode(&dataCollection); + + std::cout << "\nListConreteAggregate\n"; + ListConreteAggregate intCollection2; + for (int i = 0; i < 10; ++i) { + intCollection2.add(i); + } + + Client::clientCode(&intCollection2); + std::cout << "\n"; + ListConreteAggregate dataCollection2; + for (int i = 0; i < 10; ++i) { + dataCollection2.add(DataModel(i * 10)); + } + Client::clientCode(&dataCollection2); +} +} // namespace Iterator +} // namespace + +struct IteratorAutoRunner { + IteratorAutoRunner() { + std::cout << "\n--- Iterator Pattern Example ---\n"; + Iterator::run(); + } }; static IteratorAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/behavioral/Mediator.cpp b/src/patterns/behavioral/Mediator.cpp index f262e13..c556480 100644 --- a/src/patterns/behavioral/Mediator.cpp +++ b/src/patterns/behavioral/Mediator.cpp @@ -1,179 +1,172 @@ -// Mediator is a behavioral design pattern that lets you reduce chaotic dependencies between objects. -// The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object. -// Usage examples: The most popular usage of the Mediator pattern in C++ code is facilitating communications between GUI components of an app. -// The synonym of the Mediator is the `Controller` part of MVC pattern. +// Mediator is a behavioral design pattern that lets you reduce chaotic +// dependencies between objects. The pattern restricts direct communications +// between the objects and forces them to collaborate only via a mediator +// object. Usage examples: The most popular usage of the Mediator pattern in C++ +// code is facilitating communications between GUI components of an app. The +// synonym of the Mediator is the `Controller` part of MVC pattern. // Appicability: -// (*) when your collection has a complex data structure under the hood, but you want to hide its complexity from clients (either for convenience or security reasons). -// (**) when you can’t reuse a component in a different program because it’s too dependent on other components. -// (***) when you find yourself creating tons of component subclasses just to reuse some basic behavior in various contexts. +// (*) when your collection has a complex data structure under the hood, but +// you want to hide its complexity from clients (either for convenience or +// security reasons). +// (**) when you can’t reuse a component in a different program because it’s +// too dependent on other components. +// (***) when you find yourself creating tons of component subclasses just to +// reuse some basic behavior in various contexts. // UML: docs/uml/patterns_behavioral_mediator.drawio.svg #include #include #include -namespace -{ - namespace Mediator - { - - enum class Event - { - CREATE = 0, - READ, - UPDATE, - DELETE, - }; - - static inline const char *getEventName(const Event &e) - { - switch (e) - { - case Event::CREATE: - return "CREATE"; - case Event::READ: - return "READ"; - case Event::UPDATE: - return "UPDATE"; - case Event::DELETE: - return "DELETE"; - } - return "invalid_event"; - } - - class IComponent - { - public: - virtual ~IComponent() = default; - - virtual void send(const Event e) = 0; - virtual void receive(const Event e) = 0; - }; - - /** - * The Mediator interface declares methods of communication with components, which usually include just a single notification method. - * Components may pass any context as arguments of this method, including their own objects, - * but only in such a way that no coupling occurs between a receiving component and the sender’s class. - */ - class IMediator - { - public: - virtual ~IMediator() = default; - virtual void registerComponent(IComponent *const c) = 0; - virtual void notify(IComponent *sender, const Event &e) = 0; - }; - - /** - * Concrete Mediators implement cooperative behavior by coordinating several components. - * Concrete mediators often keep references to all components they manage and sometimes even manage their lifecycle. - */ - class ComponentMediator : public IMediator - { - private: - std::vector m_components; - - public: - void registerComponent(IComponent *const c) override - { - m_components.push_back(c); - } - - void notify(IComponent *const sender, const Event &e) override - { - for (auto c : m_components) - { - if (c != sender) - { - c->receive(e); - } - } - } - }; - - /** - * Components are various classes that contain some business logic. - * Each component has a reference to a mediator, declared with the type of the mediator interface. - * The component isn’t aware of the actual class of the mediator, so you can reuse the component in other programs by linking it to a different mediator. - */ - class AbstractComponent : public IComponent - { - private: - const std::string m_id; - - protected: - IMediator *m_mediator; - void log(const Event &e, const std::string &msg) const - { - std::cout << "\t" + msg + "-id:" + m_id + "-event:" + getEventName(e) + "\n"; - } - - public: - explicit AbstractComponent(const std::string &id, IMediator *const m = nullptr) : m_id{id}, m_mediator{m} {}; - }; - - /** - * Concrete Components implement various functionality. They don't depend on - * other components. They also don't depend on any concrete mediator classes. - */ - class ConreteComponent : public AbstractComponent - { - public: - explicit ConreteComponent(const std::string &id, IMediator *const m = nullptr) : AbstractComponent{id, m} {} - - void send(const Event e) override - { - log(e, "[SEND]"); - if (m_mediator != nullptr) - m_mediator->notify(this, e); - } - - void receive(const Event e) override - { - log(e, "[RECV]"); - // Additional handling logic can go here - } - }; - - namespace Client - { - void clientCode(IComponent *comp) - { - comp->send(Event::READ); - } - } - - void run() - { - IMediator *mediator = new ComponentMediator(); - IComponent *c1 = new ConreteComponent("1763700876", mediator); - IComponent *c2 = new ConreteComponent("1763700882", mediator); - IComponent *c3 = new ConreteComponent("1763700899", mediator); - IComponent *c4 = new ConreteComponent("1763700900", mediator); - - // Only c1, c3, c4 receive notifications. - mediator->registerComponent(c1); - mediator->registerComponent(c3); - mediator->registerComponent(c4); - - // c2 triggers event => observed by others - Client::clientCode(c2); - - delete mediator; - delete c1; - delete c2; - delete c3; - delete c4; - } - } +namespace { +namespace Mediator { + +enum class Event { + CREATE = 0, + READ, + UPDATE, + DELETE, +}; + +static inline const char* getEventName(const Event& e) { + switch (e) { + case Event::CREATE: + return "CREATE"; + case Event::READ: + return "READ"; + case Event::UPDATE: + return "UPDATE"; + case Event::DELETE: + return "DELETE"; + } + return "invalid_event"; } -struct MediatorAutoRunner -{ - MediatorAutoRunner() - { - std::cout << "\n--- Mediator Pattern Example ---\n"; - Mediator::run(); +class IComponent { + public: + virtual ~IComponent() = default; + + virtual void send(const Event e) = 0; + virtual void receive(const Event e) = 0; +}; + +/** + * The Mediator interface declares methods of communication with components, + * which usually include just a single notification method. Components may pass + * any context as arguments of this method, including their own objects, but + * only in such a way that no coupling occurs between a receiving component and + * the sender’s class. + */ +class IMediator { + public: + virtual ~IMediator() = default; + virtual void registerComponent(IComponent* const c) = 0; + virtual void notify(IComponent* sender, const Event& e) = 0; +}; + +/** + * Concrete Mediators implement cooperative behavior by coordinating several + * components. Concrete mediators often keep references to all components they + * manage and sometimes even manage their lifecycle. + */ +class ComponentMediator : public IMediator { + private: + std::vector m_components; + + public: + void registerComponent(IComponent* const c) override { + m_components.push_back(c); + } + + void notify(IComponent* const sender, const Event& e) override { + for (auto c : m_components) { + if (c != sender) { + c->receive(e); + } } + } +}; + +/** + * Components are various classes that contain some business logic. + * Each component has a reference to a mediator, declared with the type of the + * mediator interface. The component isn’t aware of the actual class of the + * mediator, so you can reuse the component in other programs by linking it to a + * different mediator. + */ +class AbstractComponent : public IComponent { + private: + const std::string m_id; + + protected: + IMediator* m_mediator; + void log(const Event& e, const std::string& msg) const { + std::cout << "\t" + msg + "-id:" + m_id + "-event:" + getEventName(e) + + "\n"; + } + + public: + explicit AbstractComponent(const std::string& id, + IMediator* const m = nullptr) + : m_id{id}, m_mediator{m} {}; +}; + +/** + * Concrete Components implement various functionality. They don't depend on + * other components. They also don't depend on any concrete mediator classes. + */ +class ConreteComponent : public AbstractComponent { + public: + explicit ConreteComponent(const std::string& id, IMediator* const m = nullptr) + : AbstractComponent{id, m} {} + + void send(const Event e) override { + log(e, "[SEND]"); + if (m_mediator != nullptr) + m_mediator->notify(this, e); + } + + void receive(const Event e) override { + log(e, "[RECV]"); + // Additional handling logic can go here + } +}; + +namespace Client { +void clientCode(IComponent* comp) { + comp->send(Event::READ); +} +} // namespace Client + +void run() { + IMediator* mediator = new ComponentMediator(); + IComponent* c1 = new ConreteComponent("1763700876", mediator); + IComponent* c2 = new ConreteComponent("1763700882", mediator); + IComponent* c3 = new ConreteComponent("1763700899", mediator); + IComponent* c4 = new ConreteComponent("1763700900", mediator); + + // Only c1, c3, c4 receive notifications. + mediator->registerComponent(c1); + mediator->registerComponent(c3); + mediator->registerComponent(c4); + + // c2 triggers event => observed by others + Client::clientCode(c2); + + delete mediator; + delete c1; + delete c2; + delete c3; + delete c4; +} +} // namespace Mediator +} // namespace + +struct MediatorAutoRunner { + MediatorAutoRunner() { + std::cout << "\n--- Mediator Pattern Example ---\n"; + Mediator::run(); + } }; static MediatorAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/behavioral/Memento.cpp b/src/patterns/behavioral/Memento.cpp index cfca295..265041d 100644 --- a/src/patterns/behavioral/Memento.cpp +++ b/src/patterns/behavioral/Memento.cpp @@ -1,235 +1,201 @@ -// Memento is a behavioral design pattern that lets you save and restore the previous state of an object -// without violating encapsulation, captures and externalizes an object's internal state -// Appicability: -// (*) when you want to produce snapshots of the object’s state to be able to restore a previous state of the object. -// (**) when direct access to the object’s fields/getters/setters violates its encapsulation. -// UML: docs/uml/patterns_behavioral_memento.drawio.svg - +// Memento is a behavioral design pattern that lets you save and restore the +// previous state of an object without violating encapsulation, captures and +// externalizes an object's internal state Appicability: +// (*) when you want to produce snapshots of the object’s state to be able to +// restore a previous state of the object. +// (**) when direct access to the object’s fields/getters/setters violates its +// encapsulation. UML: docs/uml/patterns_behavioral_memento.drawio.svg + +#include // for std::rand, std::srand +#include // for std::time +#include #include +#include #include -#include // for std::time -#include // for std::rand, std::srand #include -#include -#include -namespace -{ - namespace Memento - { - /** - * Memento interface provides a way to retrieve the memento's metadata, such as creation date or name. - * However, it doesn't expose the Originator's state. - */ - class IMemento - { - public: - virtual ~IMemento() = default; - - virtual std::string getName() const = 0; - virtual std::string getDate() const = 0; - virtual std::string getState() const = 0; - }; - - /** - * Concrete Memento contains the infrastructure for storing the Originator's state. - */ - class ConcreteMemento : public IMemento - { - private: - std::string m_state; - std::string m_date; - std::string m_name; - - public: - explicit ConcreteMemento(const std::string &state) : m_state{state} - { - // Get current time - std::time_t now = std::time(nullptr); - std::tm *t = std::localtime(&now); - - // Format date as YYYYMMDD_HHMMSS - std::stringstream date_ss; - date_ss << std::put_time(t, "%Y%m%d_%H%M%S"); - m_date = date_ss.str(); - - // Append a random number for uniqueness - int rand_num = std::rand() % 10000; // optional: limit size - std::stringstream name_ss; - name_ss << "mem_" << m_date << "_" << rand_num; - m_name = name_ss.str(); - } - - std::string getName() const override - { - return m_name; - }; - - std::string getDate() const override - { - return this->m_date; - }; - - std::string getState() const override - { - return this->m_state; - } - }; - - /** - * Originator holds some important state that may change over time. - * It also defines a method for saving the state inside a memento and another method for - * restoring the state from it. - */ - class Originator - { - private: - std::string m_state; - - // Simulate new state using rand - static std::string generateRandomString(int len = 10) - { - // String literal concatenation - const char alphaNum[] = - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - - int strLen = sizeof(alphaNum) - 1; - std::string ranStr; - for (int i = 0; i < len; ++i) - { - ranStr += alphaNum[std::rand() % strLen]; - } - return ranStr; - } - - public: - explicit Originator(const std::string &state) : m_state{state} - { - std::cout << "[O]Initial state is: " << this->m_state << "\n"; - } - - void operation() - { - std::cout << "[O]Doing something important.\n"; - this->m_state = this->generateRandomString(30); - std::cout << "[O]The state has changed to: " << this->m_state << "\n"; - } - - // Save the current state inside a memento. - IMemento *save() - { - return new ConcreteMemento(this->m_state); - } - - // Restores the Originator's state from a memento object. - void restore(IMemento *mem) - { - this->m_state = mem->getState(); - std::cout << "[O]The state has restored to: " << this->m_state << "\n"; - - delete mem; - } - }; - - /** - * The Caretaker doesn't depend on the Concrete Memento class. Therefore, it - * doesn't have access to the originator's state, stored inside the memento. It - * works with all mementos via the base Memento interface. - */ - class CareTaker - { - private: - std::vector m_mementos; - Originator *m_originator; - - public: - explicit CareTaker(Originator *const org) : m_originator{org} {} - ~CareTaker() - { - for (IMemento *m : m_mementos) - { - delete m; - } - } - - void backup() - { - std::cout << "[C]Saving Originator's state...\n"; - this->m_mementos.push_back(this->m_originator->save()); - } - - void undo() - { - if (this->m_mementos.size() != 0) - { - IMemento *mem = m_mementos.back(); - this->m_mementos.pop_back(); - - std::cout << "[C]Restoring state to: " << mem->getName() << "\n"; - this->m_originator->restore(mem); - } - } - - void history() const - { - std::cout << "[C]The list of mementos:\n"; - for (const IMemento *m : m_mementos) - { - std::cout << "\t" << m->getName() << "\n"; - } - } - }; - - namespace Client - { - void clientCode(Originator *const org) - { - CareTaker *careTaker = new CareTaker(org); - - // 1 - careTaker->backup(); - org->operation(); - - // 2 - careTaker->backup(); - org->operation(); - - // 3 - careTaker->backup(); - org->operation(); - - careTaker->history(); - careTaker->undo(); - careTaker->undo(); - careTaker->history(); - careTaker->undo(); - - // [P] The previous state can’t be restored directly because m_state is private. - // We need a Memento to save and recover internal state safely. - } - } - - void run() - { - // Gen seed - std::srand(static_cast(std::time(NULL))); - - Originator *origin = new Originator("Hello World"); - Client::clientCode(origin); - - delete origin; - } +namespace { +namespace Memento { +/** + * Memento interface provides a way to retrieve the memento's metadata, such as + * creation date or name. However, it doesn't expose the Originator's state. + */ +class IMemento { + public: + virtual ~IMemento() = default; + + virtual std::string getName() const = 0; + virtual std::string getDate() const = 0; + virtual std::string getState() const = 0; +}; + +/** + * Concrete Memento contains the infrastructure for storing the Originator's + * state. + */ +class ConcreteMemento : public IMemento { + private: + std::string m_state; + std::string m_date; + std::string m_name; + + public: + explicit ConcreteMemento(const std::string& state) : m_state{state} { + // Get current time + std::time_t now = std::time(nullptr); + std::tm* t = std::localtime(&now); + + // Format date as YYYYMMDD_HHMMSS + std::stringstream date_ss; + date_ss << std::put_time(t, "%Y%m%d_%H%M%S"); + m_date = date_ss.str(); + + // Append a random number for uniqueness + int rand_num = std::rand() % 10000; // optional: limit size + std::stringstream name_ss; + name_ss << "mem_" << m_date << "_" << rand_num; + m_name = name_ss.str(); + } + + std::string getName() const override { return m_name; }; + + std::string getDate() const override { return this->m_date; }; + + std::string getState() const override { return this->m_state; } +}; + +/** + * Originator holds some important state that may change over time. + * It also defines a method for saving the state inside a memento and another + * method for restoring the state from it. + */ +class Originator { + private: + std::string m_state; + + // Simulate new state using rand + static std::string generateRandomString(int len = 10) { + // String literal concatenation + const char alphaNum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + int strLen = sizeof(alphaNum) - 1; + std::string ranStr; + for (int i = 0; i < len; ++i) { + ranStr += alphaNum[std::rand() % strLen]; } -} + return ranStr; + } + + public: + explicit Originator(const std::string& state) : m_state{state} { + std::cout << "[O]Initial state is: " << this->m_state << "\n"; + } + + void operation() { + std::cout << "[O]Doing something important.\n"; + this->m_state = this->generateRandomString(30); + std::cout << "[O]The state has changed to: " << this->m_state << "\n"; + } + + // Save the current state inside a memento. + IMemento* save() { return new ConcreteMemento(this->m_state); } + + // Restores the Originator's state from a memento object. + void restore(IMemento* mem) { + this->m_state = mem->getState(); + std::cout << "[O]The state has restored to: " << this->m_state << "\n"; + + delete mem; + } +}; + +/** + * The Caretaker doesn't depend on the Concrete Memento class. Therefore, it + * doesn't have access to the originator's state, stored inside the memento. It + * works with all mementos via the base Memento interface. + */ +class CareTaker { + private: + std::vector m_mementos; + Originator* m_originator; + + public: + explicit CareTaker(Originator* const org) : m_originator{org} {} + ~CareTaker() { + for (IMemento* m : m_mementos) { + delete m; + } + } + + void backup() { + std::cout << "[C]Saving Originator's state...\n"; + this->m_mementos.push_back(this->m_originator->save()); + } + + void undo() { + if (this->m_mementos.size() != 0) { + IMemento* mem = m_mementos.back(); + this->m_mementos.pop_back(); -struct MementoAutoRunner -{ - MementoAutoRunner() - { - std::cout << "\n--- Memento Pattern Example ---\n"; - Memento::run(); + std::cout << "[C]Restoring state to: " << mem->getName() << "\n"; + this->m_originator->restore(mem); } + } + + void history() const { + std::cout << "[C]The list of mementos:\n"; + for (const IMemento* m : m_mementos) { + std::cout << "\t" << m->getName() << "\n"; + } + } +}; + +namespace Client { +void clientCode(Originator* const org) { + CareTaker* careTaker = new CareTaker(org); + + // 1 + careTaker->backup(); + org->operation(); + + // 2 + careTaker->backup(); + org->operation(); + + // 3 + careTaker->backup(); + org->operation(); + + careTaker->history(); + careTaker->undo(); + careTaker->undo(); + careTaker->history(); + careTaker->undo(); + + // [P] The previous state can’t be restored directly because m_state is + // private. We need a Memento to save and recover internal state safely. +} +} // namespace Client + +void run() { + // Gen seed + std::srand(static_cast(std::time(NULL))); + + Originator* origin = new Originator("Hello World"); + Client::clientCode(origin); + + delete origin; +} +} // namespace Memento +} // namespace + +struct MementoAutoRunner { + MementoAutoRunner() { + std::cout << "\n--- Memento Pattern Example ---\n"; + Memento::run(); + } }; static MementoAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/behavioral/Observer.cpp b/src/patterns/behavioral/Observer.cpp index 80bbf5e..33b2dbd 100644 --- a/src/patterns/behavioral/Observer.cpp +++ b/src/patterns/behavioral/Observer.cpp @@ -1,204 +1,185 @@ -// Observer is a behavioral design pattern that lets you define a subscription mechanism -// to notify multiple objects about any events that happen to the object they’re observing. -// Usage examples: The most popular usage of the Observer pattern in C++ code is facilitating communications between GUI components of an app. -// The synonym of the Observer is the `Controller` part of MVC pattern. -// Appicability: -// (*) when changes to the state of one object may require changing other objects, and the actual set of objects is unknown beforehand or changes dynamically. -// (**) when some objects in your app must observe others, but only for a limited time or in specific cases. +// Observer is a behavioral design pattern that lets you define a subscription +// mechanism to notify multiple objects about any events that happen to the +// object they’re observing. Usage examples: The most popular usage of the +// Observer pattern in C++ code is facilitating communications between GUI +// components of an app. The synonym of the Observer is the `Controller` part of +// MVC pattern. Appicability: +// (*) when changes to the state of one object may require changing other +// objects, and the actual set of objects is unknown beforehand or changes +// dynamically. +// (**) when some objects in your app must observe others, but only for a +// limited time or in specific cases. // UML: docs/uml/patterns_behavioral_observer.drawio.svg #include -#include #include +#include -namespace -{ - namespace Observer - { - enum class Event - { - CREATE = 0, - READ, - UPDATE, - DELETE, - }; - - static inline const char *getEventName(const Event &e) - { - switch (e) - { - case Event::CREATE: - return "CREATE"; - case Event::READ: - return "READ"; - case Event::UPDATE: - return "UPDATE"; - case Event::DELETE: - return "DELETE"; - } - return "invalid_event"; - } - - /** - * IObserver aka Subscriber - * The Subscriber interface declares the notification interface. - * In most cases, it consists of a single update method. - * The method may have several parameters that let the publisher pass some event details along with the update. - * E.g. Event Listen to UI events - */ - class IListenerObserver - { - public: - virtual ~IListenerObserver() = default; - - // update - virtual void update(const Event e) = 0; - }; - - /** - * Subject aka Publisher - * The Publisher issues events of interest to other objects. - * These events occur when the publisher changes its state or executes some behaviors. - * Publishers contain a subscription infrastructure that lets new subscribers join and current subscribers leave the list. - * - * E.g Widget dispatches click events to observers - */ - class IWidgetSubject - { - public: - virtual ~IWidgetSubject() {}; - // addListener - virtual void attach(IListenerObserver *observer) = 0; - // removeLister - virtual void detach(IListenerObserver *observer) = 0; - // e.g.click - virtual void notify(const Event &e) = 0; - }; - - class ButtonConcreteSubject : public IWidgetSubject - { - private: - std::list m_listeners; - - public: - void attach(IListenerObserver *observer) override - { - m_listeners.push_back(observer); - } - - void detach(IListenerObserver *observer) override - { - m_listeners.remove(observer); - } - - void notify(const Event &e) override - { - std::cout << "[Subject] notify event-" << getEventName(e) << "\n"; - for (IListenerObserver *o : m_listeners) - { - o->update(e); - } - } - }; - - class AbstractListenerObserver : public IListenerObserver - { - private: - int m_num; - inline static int num_observers = 0; - - protected: - void log(const Event &e) const - { - std::cout << "\t-id:" << m_num << "-event:" << getEventName(e) << "\n"; - } - - public: - explicit AbstractListenerObserver() - { - m_num = ++num_observers; - } - }; - - /** - * Concrete Subscribers perform some actions in response to notifications issued by the publisher. - * All of these classes must implement the same interface so the publisher isn’t coupled to concrete classes. - */ - class ConcreteListenerObserverA : public AbstractListenerObserver - { - private: - static const inline char *type = "A-type"; - - public: - void update(const Event e) override - { - std::cout << "\tListener: " << type; - log(e); - } - }; - - class ConcreteListenerObserverB : public AbstractListenerObserver - { - private: - static const inline char *type = "B-type"; - - public: - void update(const Event e) override - { - std::cout << "\tListener: " << type; - log(e); - } - }; - - /** - * The Client creates publisher and subscriber objects separately - * and then registers subscribers for publisher updates. - */ - namespace Client - { - void clientCode(IWidgetSubject *const s) - { - s->notify(Event::UPDATE); - } - } - - void run() - { - IWidgetSubject *btn = new ButtonConcreteSubject(); - - IListenerObserver *listener_1 = new ConcreteListenerObserverA(); - IListenerObserver *listener_2 = new ConcreteListenerObserverA(); - IListenerObserver *listener_3 = new ConcreteListenerObserverA(); - IListenerObserver *listener_4 = new ConcreteListenerObserverB(); - - btn->attach(listener_1); - btn->attach(listener_2); - btn->attach(listener_3); - btn->attach(listener_4); - Client::clientCode(btn); - - std::cout << "Remove listener2\n"; - btn->detach(listener_2); - Client::clientCode(btn); - - delete btn; - delete listener_1; - delete listener_2; - delete listener_3; - delete listener_4; - } +namespace { +namespace Observer { - } +enum class Event { + CREATE = 0, + READ, + UPDATE, + DELETE, +}; + +static inline const char* getEventName(const Event& e) { + switch (e) { + case Event::CREATE: + return "CREATE"; + case Event::READ: + return "READ"; + case Event::UPDATE: + return "UPDATE"; + case Event::DELETE: + return "DELETE"; + } + return "invalid_event"; } -struct ObserverAutoRunner -{ - ObserverAutoRunner() - { - std::cout << "\n--- Observer Pattern Example ---\n"; - Observer::run(); +/** + * IObserver aka Subscriber + * The Subscriber interface declares the notification interface. + * In most cases, it consists of a single update method. + * The method may have several parameters that let the publisher pass some event + * details along with the update. E.g. Event Listen to UI events + */ +class IListenerObserver { + public: + virtual ~IListenerObserver() = default; + + // update + virtual void update(const Event e) = 0; +}; + +/** + * Subject aka Publisher + * The Publisher issues events of interest to other objects. + * These events occur when the publisher changes its state or executes some + * behaviors. Publishers contain a subscription infrastructure that lets new + * subscribers join and current subscribers leave the list. + * + * E.g Widget dispatches click events to observers + */ +class IWidgetSubject { + public: + virtual ~IWidgetSubject(){}; + // addListener + virtual void attach(IListenerObserver* observer) = 0; + // removeLister + virtual void detach(IListenerObserver* observer) = 0; + // e.g.click + virtual void notify(const Event& e) = 0; +}; + +class ButtonConcreteSubject : public IWidgetSubject { + private: + std::list m_listeners; + + public: + void attach(IListenerObserver* observer) override { + m_listeners.push_back(observer); + } + + void detach(IListenerObserver* observer) override { + m_listeners.remove(observer); + } + + void notify(const Event& e) override { + std::cout << "[Subject] notify event-" << getEventName(e) << "\n"; + for (IListenerObserver* o : m_listeners) { + o->update(e); } + } +}; + +class AbstractListenerObserver : public IListenerObserver { + private: + int m_num; + inline static int num_observers = 0; + + protected: + void log(const Event& e) const { + std::cout << "\t-id:" << m_num << "-event:" << getEventName(e) << "\n"; + } + + public: + explicit AbstractListenerObserver() { m_num = ++num_observers; } +}; + +/** + * Concrete Subscribers perform some actions in response to notifications issued + * by the publisher. All of these classes must implement the same interface so + * the publisher isn’t coupled to concrete classes. + */ +class ConcreteListenerObserverA : public AbstractListenerObserver { + private: + static const inline char* type = "A-type"; + + public: + void update(const Event e) override { + std::cout << "\tListener: " << type; + log(e); + } +}; + +class ConcreteListenerObserverB : public AbstractListenerObserver { + private: + static const inline char* type = "B-type"; + + public: + void update(const Event e) override { + std::cout << "\tListener: " << type; + log(e); + } +}; + +/** + * The Client creates publisher and subscriber objects separately + * and then registers subscribers for publisher updates. + */ +namespace Client { +void clientCode(IWidgetSubject* const s) { + s->notify(Event::UPDATE); +} +} // namespace Client + +void run() { + IWidgetSubject* btn = new ButtonConcreteSubject(); + + IListenerObserver* listener_1 = new ConcreteListenerObserverA(); + IListenerObserver* listener_2 = new ConcreteListenerObserverA(); + IListenerObserver* listener_3 = new ConcreteListenerObserverA(); + IListenerObserver* listener_4 = new ConcreteListenerObserverB(); + + btn->attach(listener_1); + btn->attach(listener_2); + btn->attach(listener_3); + btn->attach(listener_4); + Client::clientCode(btn); + + std::cout << "Remove listener2\n"; + btn->detach(listener_2); + Client::clientCode(btn); + + delete btn; + delete listener_1; + delete listener_2; + delete listener_3; + delete listener_4; +} + +} // namespace Observer +} // namespace + +struct ObserverAutoRunner { + ObserverAutoRunner() { + std::cout << "\n--- Observer Pattern Example ---\n"; + Observer::run(); + } }; static ObserverAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/behavioral/State.cpp b/src/patterns/behavioral/State.cpp index 22d9952..171f114 100644 --- a/src/patterns/behavioral/State.cpp +++ b/src/patterns/behavioral/State.cpp @@ -1,170 +1,142 @@ -// State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. -// It appears as if the object changed its class. -// Appicability: -// (*) when you have an object that behaves differently depending on its current state -// , the number of states is enormous, and the state-specific code changes frequently. +// State is a behavioral design pattern that lets an object alter its behavior +// when its internal state changes. It appears as if the object changed its +// class. Appicability: +// (*) when you have an object that behaves differently depending on its +// current state +// , the number of states is enormous, and the state-specific code +// changes frequently. // (**) when you have a class polluted with massive conditionals that alter -// how the class behaves according to the current values of the class’s fields. -// (***) when you have a lot of duplicate code across similar states and transitions of a condition-based state machine. -// UML: docs/uml/patterns_behavioral_state.drawio.svg +// how the class behaves according to the current values of the class’s +// fields. +// (***) when you have a lot of duplicate code across similar states and +// transitions of a condition-based state machine. UML: +// docs/uml/patterns_behavioral_state.drawio.svg #include #include -namespace -{ - namespace State - { - class DeviceContext; - - /** - * The State interface declares the state-specific methods. - * These methods should make sense for all concrete states because you don’t want some of your states - * to have useless methods that will never be called. - */ - class IState - { - public: - virtual ~IState() = default; - virtual void setContext(DeviceContext *ctx) = 0; - virtual void handle() = 0; - }; - - /* - * To avoid duplication of similar code across multiple states, you may provide intermediate abstract classes - * that encapsulate some common behavior. - */ - class AbstractState : public IState - { - protected: - DeviceContext *m_ctx; - - public: - void setContext(DeviceContext *ctx) override - { - this->m_ctx = ctx; - } - }; - - /** - * Context stores a reference to one of the concrete state objects and delegates to it all state-specific work. - * The context communicates with the state object via the state interface. - * The context exposes a setter for passing it a new state object. - */ - class DeviceContext - { - private: - IState *m_state; - - public: - explicit DeviceContext(IState *state) : m_state{nullptr} - { - this->changeState(state); - } - - ~DeviceContext() - { - delete m_state; - } - - void changeState(IState *state) - { - std::cout << "[DeviceContext]: Changing state\n"; - if (this->m_state != nullptr) - { - delete this->m_state; - } - this->m_state = state; - this->m_state->setContext(this); - } - - void operation() - { - this->m_state->handle(); - } - }; - - /** - * Concrete States provide their own implementations for the state-specific methods. - */ - class IdeConcreteState : public AbstractState - { - public: - void handle() override; - }; - - class ProcessingConcreteState : public AbstractState - { - public: - void handle() override; - }; - - class ErrorConcreteState : public AbstractState - { - public: - void handle() override - { - std::cout << "[Error] Device error! Reset required.\n"; - - // After recover => go Idle - this->m_ctx->changeState(new IdeConcreteState()); - } - }; - - void IdeConcreteState::handle() - { - std::cout << "[Ide] Device is idle. Waiting...\n"; - this->m_ctx->changeState(new ProcessingConcreteState()); - } - - void ProcessingConcreteState::handle() - { - std::cout << "[Processing] Processing data...\n"; - bool ok = true; // Example processing result - static int index = 0; - index++; - if (index % 2 == 0) - { - ok = true; - } - else - { - ok = false; - } - if (ok) - { - // Back to idle after finishing job - this->m_ctx->changeState(new IdeConcreteState()); - } - else - { - this->m_ctx->changeState(new ErrorConcreteState()); - } - } - - namespace Client - { - void clientCode(DeviceContext *const device) - { - device->operation(); - } - } - void run() - { - DeviceContext *device = new DeviceContext(new IdeConcreteState()); - for (int loopIdx = 0; loopIdx < 10; ++loopIdx) - Client::clientCode(device); - delete device; - } +namespace { +namespace State { +class DeviceContext; + +/** + * The State interface declares the state-specific methods. + * These methods should make sense for all concrete states because you don’t + * want some of your states to have useless methods that will never be called. + */ +class IState { + public: + virtual ~IState() = default; + virtual void setContext(DeviceContext* ctx) = 0; + virtual void handle() = 0; +}; + +/* + * To avoid duplication of similar code across multiple states, you may provide + * intermediate abstract classes that encapsulate some common behavior. + */ +class AbstractState : public IState { + protected: + DeviceContext* m_ctx; + + public: + void setContext(DeviceContext* ctx) override { this->m_ctx = ctx; } +}; + +/** + * Context stores a reference to one of the concrete state objects and delegates + * to it all state-specific work. The context communicates with the state object + * via the state interface. The context exposes a setter for passing it a new + * state object. + */ +class DeviceContext { + private: + IState* m_state; + + public: + explicit DeviceContext(IState* state) : m_state{nullptr} { + this->changeState(state); + } + + ~DeviceContext() { delete m_state; } + + void changeState(IState* state) { + std::cout << "[DeviceContext]: Changing state\n"; + if (this->m_state != nullptr) { + delete this->m_state; } + this->m_state = state; + this->m_state->setContext(this); + } + + void operation() { this->m_state->handle(); } +}; + +/** + * Concrete States provide their own implementations for the state-specific + * methods. + */ +class IdeConcreteState : public AbstractState { + public: + void handle() override; +}; + +class ProcessingConcreteState : public AbstractState { + public: + void handle() override; +}; + +class ErrorConcreteState : public AbstractState { + public: + void handle() override { + std::cout << "[Error] Device error! Reset required.\n"; + + // After recover => go Idle + this->m_ctx->changeState(new IdeConcreteState()); + } +}; + +void IdeConcreteState::handle() { + std::cout << "[Ide] Device is idle. Waiting...\n"; + this->m_ctx->changeState(new ProcessingConcreteState()); } -struct StateAutoRunner -{ - StateAutoRunner() - { - std::cout << "\n--- State Pattern Example ---\n"; - State::run(); - } +void ProcessingConcreteState::handle() { + std::cout << "[Processing] Processing data...\n"; + bool ok = true; // Example processing result + static int index = 0; + index++; + if (index % 2 == 0) { + ok = true; + } else { + ok = false; + } + if (ok) { + // Back to idle after finishing job + this->m_ctx->changeState(new IdeConcreteState()); + } else { + this->m_ctx->changeState(new ErrorConcreteState()); + } +} + +namespace Client { +void clientCode(DeviceContext* const device) { + device->operation(); +} +} // namespace Client +void run() { + DeviceContext* device = new DeviceContext(new IdeConcreteState()); + for (int loopIdx = 0; loopIdx < 10; ++loopIdx) + Client::clientCode(device); + delete device; +} +} // namespace State +} // namespace + +struct StateAutoRunner { + StateAutoRunner() { + std::cout << "\n--- State Pattern Example ---\n"; + State::run(); + } }; static StateAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/behavioral/Strategy.cpp b/src/patterns/behavioral/Strategy.cpp index fc778b5..12d08be 100644 --- a/src/patterns/behavioral/Strategy.cpp +++ b/src/patterns/behavioral/Strategy.cpp @@ -1,125 +1,111 @@ // cppcheck-suppress-file [functionStatic] -// Strategy is a behavioral design pattern that lets you define a family of algorithms, -// put each of them into a separate class, and make their objects interchangeable. -// Appicability: -// (*) when you want to use different variants of an algorithm within an object and be able to switch from one algorithm to another during runtime. -// (**) when you have a lot of similar classes that only differ in the way they execute some behavior. -// (***) when your class has a massive conditional statement that switches between different variants of the same algorithm. -// UML: docs/uml/patterns_behavioral_strategy.drawio.svg +// Strategy is a behavioral design pattern that lets you define a family of +// algorithms, put each of them into a separate class, and make their objects +// interchangeable. Appicability: +// (*) when you want to use different variants of an algorithm within an +// object and be able to switch from one algorithm to another during runtime. +// (**) when you have a lot of similar classes that only differ in the way +// they execute some behavior. +// (***) when your class has a massive conditional statement that switches +// between different variants of the same algorithm. UML: +// docs/uml/patterns_behavioral_strategy.drawio.svg #include #include -namespace -{ - namespace Strategy - { - class IExportStrategy - { - public: - virtual ~IExportStrategy() = default; - virtual std::string executeExportData(const std::string &content) const = 0; - }; +namespace { +namespace Strategy { +class IExportStrategy { + public: + virtual ~IExportStrategy() = default; + virtual std::string executeExportData(const std::string& content) const = 0; +}; - class ExportContext - { - private: - std::string m_content; - IExportStrategy *m_strategy; +class ExportContext { + private: + std::string m_content; + IExportStrategy* m_strategy; - public: - ~ExportContext() - { - delete m_strategy; - } + public: + ~ExportContext() { delete m_strategy; } - explicit ExportContext(std::string content, IExportStrategy *const strategy = nullptr) - : m_content{content}, m_strategy{strategy} {} + explicit ExportContext(std::string content, + IExportStrategy* const strategy = nullptr) + : m_content{content}, m_strategy{strategy} {} - void setExportStrategy(IExportStrategy *const strategy) - { - delete m_strategy; - this->m_strategy = strategy; - } + void setExportStrategy(IExportStrategy* const strategy) { + delete m_strategy; + this->m_strategy = strategy; + } - // The old approach using if-else for each format is commented out: - // if(format == "HTML") { - // // export HTML - // } else if(format == "JSON") { - // // export JSON - // } else if(format == "Markdown") { - // // export Markdown - // } - // - // 1. This approach mixes data (Context) and behavior (export logic), which is hard to maintain. - // 2. Adding new formats requires modifying this function, violating the Open/Closed Principle. - // 3. Strategy Pattern allows each format to be a separate class, and Context only holds data. - // ====================================================================================== - std::string exportDocument() const - { - if (m_strategy != nullptr) - { - return this->m_strategy->executeExportData(this->m_content); - } - else - { - std::cout << "Context: Strategy isn't set\n"; - return ""; - } - } - }; + // The old approach using if-else for each format is commented out: + // if(format == "HTML") { + // // export HTML + // } else if(format == "JSON") { + // // export JSON + // } else if(format == "Markdown") { + // // export Markdown + // } + // + // 1. This approach mixes data (Context) and behavior (export logic), which is + // hard to maintain. + // 2. Adding new formats requires modifying this function, violating the + // Open/Closed Principle. + // 3. Strategy Pattern allows each format to be a separate class, and Context + // only holds data. + // ====================================================================================== + std::string exportDocument() const { + if (m_strategy != nullptr) { + return this->m_strategy->executeExportData(this->m_content); + } else { + std::cout << "Context: Strategy isn't set\n"; + return ""; + } + } +}; - class JsonExportStrategy : public IExportStrategy - { - public: - std::string executeExportData(const std::string &content) const override - { - return "{\"content\": \"" + content + "\" }"; - } - }; +class JsonExportStrategy : public IExportStrategy { + public: + std::string executeExportData(const std::string& content) const override { + return "{\"content\": \"" + content + "\" }"; + } +}; - class HtmlExportStrategy : public IExportStrategy - { - public: - std::string executeExportData(const std::string &content) const override - { - return "

" + content + "

"; - } - }; +class HtmlExportStrategy : public IExportStrategy { + public: + std::string executeExportData(const std::string& content) const override { + return "

" + content + "

"; + } +}; - namespace Client - { - void clientCode(const ExportContext *ctx) - { - std::cout << ctx->exportDocument(); - std::cout << "\n"; - } - } - void run() - { - ExportContext *ctx = new ExportContext{"This is the report content."}; - Client::clientCode(ctx); +namespace Client { +void clientCode(const ExportContext* ctx) { + std::cout << ctx->exportDocument(); + std::cout << "\n"; +} +} // namespace Client +void run() { + ExportContext* ctx = new ExportContext{"This is the report content."}; + Client::clientCode(ctx); - std::cout << " ===HTML Export ===\n"; - ctx->setExportStrategy(new HtmlExportStrategy()); - Client::clientCode(ctx); + std::cout << " ===HTML Export ===\n"; + ctx->setExportStrategy(new HtmlExportStrategy()); + Client::clientCode(ctx); - std::cout << " ===JSON Export ===\n"; - ctx->setExportStrategy(new JsonExportStrategy()); - Client::clientCode(ctx); + std::cout << " ===JSON Export ===\n"; + ctx->setExportStrategy(new JsonExportStrategy()); + Client::clientCode(ctx); - delete ctx; - } - } + delete ctx; } +} // namespace Strategy +} // namespace -struct StrategyAutoRunner -{ - StrategyAutoRunner() - { - std::cout << "\n--- Strategy Pattern Example ---\n"; - Strategy::run(); - } +struct StrategyAutoRunner { + StrategyAutoRunner() { + std::cout << "\n--- Strategy Pattern Example ---\n"; + Strategy::run(); + } }; static StrategyAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/behavioral/TemplateMethod.cpp b/src/patterns/behavioral/TemplateMethod.cpp index 87a2d32..4fda119 100644 --- a/src/patterns/behavioral/TemplateMethod.cpp +++ b/src/patterns/behavioral/TemplateMethod.cpp @@ -1,123 +1,113 @@ // cppcheck-suppress-file [functionStatic] -// is a behavioral design pattern that defines the skeleton of an algorithm in the `superclass` -// but lets `subclasses` override specific steps of the algorithm without changing its structure. -// Appicability: -// (*) when you want to let clients extend only particular steps of an algorithm, but not the whole algorithm or its structure. -// (**) when you have several classes that contain almost identical algorithms with some minor differences. -// As a result, you might need to modify all classes when the algorithm changes. -// (**) when a behavior makes sense only in some classes of a class hierarchy, but not in others. -// UML: docs/uml/patterns_behavioral_templatemethod.drawio.svg +// is a behavioral design pattern that defines the skeleton of an algorithm in +// the `superclass` +// but lets `subclasses` override specific steps of the algorithm without +// changing its structure. Appicability: +// (*) when you want to let clients extend only particular steps of an +// algorithm, but not the whole algorithm or its structure. +// (**) when you have several classes that contain almost identical algorithms +// with some minor differences. +// As a result, you might need to modify all classes when the algorithm +// changes. +// (**) when a behavior makes sense only in some classes of a class hierarchy, +// but not in others. UML: +// docs/uml/patterns_behavioral_templatemethod.drawio.svg #include -namespace -{ - namespace TemplateMethod - { - class AbstractClass - { - public: - virtual ~AbstractClass() = default; - // Template Method (non-virtual => cannot be overridden) - // Defines the algorithm's skeleton and ensures subclasses cannot change the flow. - void templateMethod() const - { - baseOperation1(); - abstractMethod1(); - hookOperation1(); - - baseOperation2(); - abstractMethod2(); - hookOperation2(); - } - - protected: - // 1. Base operations: These have full implementations and cannot be overridden. - void baseOperation1() const - { - // Common logic step 1 - std::cout << "[AbstractClass]\t Executed base operation - 1\n"; - } - - void baseOperation2() const - { - // Common logic step 2 - std::cout << "[AbstractClass]\t Executed base operation - 2\n"; - } - - // 2. Hook methods: Subclasses may override them to extend behavior, but overriding is optional. - virtual void hookOperation1() const {} - virtual void hookOperation2() const {} - - // 3. Abstract methods: Subclasses MUST provide implementations. - virtual void abstractMethod1() const = 0; - virtual void abstractMethod2() const = 0; - }; - - class ConcreteClass1 : public AbstractClass - { - public: - void abstractMethod1() const override - { - std::cout << "[ConcreteClass1]\t Implemented Operation - 2\n"; - } - - void abstractMethod2() const override - { - std::cout << "[ConcreteClass1]\t Implemented Operation - 2\n"; - } - - void hookOperation1() const override - { - std::cout << "[ConcreteClass1]\t Overridden Hook - 1\n"; - } - }; - - class ConcreteClass2 : public AbstractClass - { - public: - void abstractMethod1() const override - { - std::cout << "[ConcreteClass2]\t Implemented Operation - 2\n"; - } - - void abstractMethod2() const override - { - std::cout << "[ConcreteClass2]\t Implemented Operation - 2\n"; - } - }; - - namespace Client - { - void clientCode(const AbstractClass *clazz) - { - clazz->templateMethod(); - } - } - void run() - { - std::cout << "\t[ConcreteClass1] Executed templateMethod\n"; - AbstractClass *clazz1 = new ConcreteClass1(); - Client::clientCode(clazz1); - - std::cout << "\t[ConcreteClass1] Executed templateMethod\n"; - AbstractClass *clazz2 = new ConcreteClass2(); - Client::clientCode(clazz2); - - delete clazz1; - delete clazz2; - } - } -} +namespace { +namespace TemplateMethod { +class AbstractClass { + public: + virtual ~AbstractClass() = default; + // Template Method (non-virtual => cannot be overridden) + // Defines the algorithm's skeleton and ensures subclasses cannot change the + // flow. + void templateMethod() const { + baseOperation1(); + abstractMethod1(); + hookOperation1(); + + baseOperation2(); + abstractMethod2(); + hookOperation2(); + } + + protected: + // 1. Base operations: These have full implementations and cannot be + // overridden. + void baseOperation1() const { + // Common logic step 1 + std::cout << "[AbstractClass]\t Executed base operation - 1\n"; + } + + void baseOperation2() const { + // Common logic step 2 + std::cout << "[AbstractClass]\t Executed base operation - 2\n"; + } + + // 2. Hook methods: Subclasses may override them to extend behavior, but + // overriding is optional. + virtual void hookOperation1() const {} + virtual void hookOperation2() const {} + + // 3. Abstract methods: Subclasses MUST provide implementations. + virtual void abstractMethod1() const = 0; + virtual void abstractMethod2() const = 0; +}; + +class ConcreteClass1 : public AbstractClass { + public: + void abstractMethod1() const override { + std::cout << "[ConcreteClass1]\t Implemented Operation - 2\n"; + } + + void abstractMethod2() const override { + std::cout << "[ConcreteClass1]\t Implemented Operation - 2\n"; + } + + void hookOperation1() const override { + std::cout << "[ConcreteClass1]\t Overridden Hook - 1\n"; + } +}; -struct TemplateMethodAutoRunner -{ - TemplateMethodAutoRunner() - { - std::cout << "\n--- TemplateMethod Pattern Example ---\n"; - TemplateMethod::run(); - } +class ConcreteClass2 : public AbstractClass { + public: + void abstractMethod1() const override { + std::cout << "[ConcreteClass2]\t Implemented Operation - 2\n"; + } + + void abstractMethod2() const override { + std::cout << "[ConcreteClass2]\t Implemented Operation - 2\n"; + } +}; + +namespace Client { +void clientCode(const AbstractClass* clazz) { + clazz->templateMethod(); +} +} // namespace Client +void run() { + std::cout << "\t[ConcreteClass1] Executed templateMethod\n"; + AbstractClass* clazz1 = new ConcreteClass1(); + Client::clientCode(clazz1); + + std::cout << "\t[ConcreteClass1] Executed templateMethod\n"; + AbstractClass* clazz2 = new ConcreteClass2(); + Client::clientCode(clazz2); + + delete clazz1; + delete clazz2; +} +} // namespace TemplateMethod +} // namespace + +struct TemplateMethodAutoRunner { + TemplateMethodAutoRunner() { + std::cout << "\n--- TemplateMethod Pattern Example ---\n"; + TemplateMethod::run(); + } }; static TemplateMethodAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/behavioral/Visitor.cpp b/src/patterns/behavioral/Visitor.cpp index 58b8bd1..3170963 100644 --- a/src/patterns/behavioral/Visitor.cpp +++ b/src/patterns/behavioral/Visitor.cpp @@ -1,284 +1,246 @@ -// Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate. -// Appicability: -// (*) when you need to perform an operation on all elements of a complex object structure (for example, an object tree, document). +// Visitor is a behavioral design pattern that lets you separate algorithms from +// the objects on which they operate. Appicability: +// (*) when you need to perform an operation on all elements of a complex +// object structure (for example, an object tree, document). // (**) to clean up the business logic of auxiliary behaviors. -// (**) when a behavior makes sense only in some classes of a class hierarchy, but not in others. -// UML: docs/uml/patterns_behavioral_visitor.drawio.svg +// (**) when a behavior makes sense only in some classes of a class hierarchy, +// but not in others. UML: docs/uml/patterns_behavioral_visitor.drawio.svg #include #include #include -namespace -{ - namespace Visitor - { - class TextConcreteElement; - class ImageConcreteElement; - class TableConcreteElement; - - /** - * The Visitor interface declares a set of visiting methods that can take concrete elements of an object structure as arguments. - * These methods may have the same names if the program is written in a language that supports overloading, - * but the type of their parameters must be different. - */ - class IVisitor - { - public: - virtual ~IVisitor() = default; - - virtual void visitText(const TextConcreteElement *t) = 0; - virtual void visitImage(const ImageConcreteElement *i) = 0; - virtual void visitTable(const TableConcreteElement *t) = 0; - }; - - /** - * The Element interface declares a method for “accepting” visitors. - * This method should have one parameter declared with the type of the visitor interface. - */ - class IElement - { - public: - virtual ~IElement() = default; - // [P1] - // if we implement exportHtml(), exportMarkdown(), exportJson(), etc.. here, - // every time we add a new export format we must modify ALL element classes. - // This violates the Open/Closed Principle - // and makes each element hold multiple unrelated behaviors. - // => Use `Visitor Pattern` to separate export logic from the element classes. - - virtual void accept(IVisitor *visitor) = 0; - }; - - /** - * The complex object structure - */ - class DocumentConcreteStructure - { - private: - std::vector m_elements; - - public: - ~DocumentConcreteStructure() - { - for (IElement *e : this->m_elements) - { - delete e; - } - m_elements.clear(); - } - - void add(IElement *e) - { - m_elements.push_back(e); - } - - std::vector &get() - { - return this->m_elements; - }; - }; - - /** - * Each Concrete Element must implement the acceptance method. - * The purpose of this method is to redirect the call to the proper visitor’s method corresponding to the current element class. - * Be aware that even if a base element class implements this method, all subclasses must still override this method - * in their own classes and call the appropriate method on the visitor object. - */ - class TextConcreteElement : public IElement - { - private: - std::string m_content; - - public: - explicit TextConcreteElement(const std::string &c) : m_content{c} {}; - std::string getContent() const - { - return this->m_content; - } - - void accept(IVisitor *visitor) override - { - visitor->visitText(this); - } - }; - - class ImageConcreteElement : public IElement - { - private: - std::string m_path; - - public: - explicit ImageConcreteElement(const std::string &p) : m_path{p} {}; - - std::string getPath() const - { - return this->m_path; - } - - void accept(IVisitor *visitor) override - { - visitor->visitImage(this); - } - }; - - class TableConcreteElement : public IElement - { - private: - int m_rows, m_cols; - - public: - TableConcreteElement(int r, int c) : m_rows{r}, m_cols{c} {}; - int getRows() const { return this->m_rows; } - int getCols() const { return this->m_cols; } - - void accept(IVisitor *visitor) override - { - visitor->visitTable(this); - } - }; - - /** - * Each Concrete Visitor implements several versions of the same behaviors, - * tailored for different concrete element classes. - */ - class HtmlExportConcreteVisitor : public IVisitor - { - void visitText(const TextConcreteElement *t) override - { - std::cout << "

" + t->getContent() + "

\n"; - } - - void visitImage(const ImageConcreteElement *i) override - { - std::cout << "getPath() + "\" />\n"; - } - - void visitTable(const TableConcreteElement *t) override - { - std::cout << "\n"; - for (int r = 0; r < t->getRows(); ++r) - { - std::cout << " "; - for (int c = 0; c < t->getCols(); ++c) - { - std::cout << ""; - } - std::cout << "\n"; - } - std::cout << "
cell
\n"; - } - }; - - class JsonExportConcreteVisitor : public IVisitor - { - void visitText(const TextConcreteElement *t) override - { - std::cout << "{ \"type\": \"text\", \"content\": \"" << t->getContent() << "\" }\n"; - } - void visitImage(const ImageConcreteElement *i) override - { - std::cout << "{ \"type\": \"image\", \"path\": \"" << i->getPath() << "\" }\n"; - } - - void visitTable(const TableConcreteElement *t) override - { - std::cout << "{ \"type\": \"table\", \"rows\": " << t->getRows() - << ", \"cols\": " << t->getCols() << " }\n"; - } - }; - - class MarkdownExportConcreteVisitor : public IVisitor - { - void visitText(const TextConcreteElement *t) override - { - std::cout << t->getContent() << "\n\n"; - } - - void visitImage(const ImageConcreteElement *i) override - { - std::cout << "![](" << i->getPath() << ")\n\n"; - } - - void visitTable(const TableConcreteElement *t) override - { - for (int r = 0; r < t->getRows(); ++r) - { - for (int c = 0; c < t->getCols(); ++c) - { - std::cout << "| cell "; - } - std::cout << "|\n"; - } - std::cout << "\n"; - } - }; - - /** - * clients aren’t aware of all the concrete element classes because they work with objects from that collection via some abstract interface. - */ - namespace Client - { - void clientCode(DocumentConcreteStructure *const doc, IVisitor *const visitor) - { - // [P2] Instead of adding exportHtml(), exportMarkdown(), exportJson() in each element, - // we use the Visitor pattern to separate data from behavior. - // This avoids bloating element classes with multiple unrelated functions - // and makes it easier to add new export formats without modifying the elements. - for (IElement *ele : doc->get()) - { - ele->accept(visitor); - } - } - } - void run() - { - DocumentConcreteStructure *document = new DocumentConcreteStructure(); - // Add images - document->add(new ImageConcreteElement("header.png")); - document->add(new ImageConcreteElement("diagram1.png")); - document->add(new ImageConcreteElement("chart.png")); - - // Add text paragraphs - document->add(new TextConcreteElement("Introduction: This document demonstrates the Visitor pattern.")); - document->add(new TextConcreteElement("Section 1: Visitor allows adding new operations without modifying elements.")); - document->add(new TextConcreteElement("Section 2: Elements are data holders, visitors implement behaviors.")); - - // Add tables - document->add(new TableConcreteElement(3, 4)); // e.g., 3 rows x 4 columns table - document->add(new TableConcreteElement(2, 2)); // smaller table - document->add(new TableConcreteElement(4, 3)); // another table - - // Add more text - document->add(new TextConcreteElement("Conclusion: Using Visitor pattern makes code open for extension but closed for modification.")); - - IVisitor *visitor = new HtmlExportConcreteVisitor(); - std::cout << " ===HTML Export ===\n"; - Client::clientCode(document, visitor); - std::cout << " ==================\n"; - - visitor = new JsonExportConcreteVisitor(); - std::cout << " ===JSON Export ===\n"; - Client::clientCode(document, visitor); - std::cout << " ==================\n"; - - visitor = new MarkdownExportConcreteVisitor(); - std::cout << " ===MD Export ===\n"; - Client::clientCode(document, visitor); - std::cout << " ==================\n"; - delete visitor; - delete document; - } +namespace { +namespace Visitor { +class TextConcreteElement; +class ImageConcreteElement; +class TableConcreteElement; + +/** + * The Visitor interface declares a set of visiting methods that can take + * concrete elements of an object structure as arguments. These methods may have + * the same names if the program is written in a language that supports + * overloading, but the type of their parameters must be different. + */ +class IVisitor { + public: + virtual ~IVisitor() = default; + + virtual void visitText(const TextConcreteElement* t) = 0; + virtual void visitImage(const ImageConcreteElement* i) = 0; + virtual void visitTable(const TableConcreteElement* t) = 0; +}; + +/** + * The Element interface declares a method for “accepting” visitors. + * This method should have one parameter declared with the type of the visitor + * interface. + */ +class IElement { + public: + virtual ~IElement() = default; + // [P1] + // if we implement exportHtml(), exportMarkdown(), exportJson(), etc.. here, + // every time we add a new export format we must modify ALL element classes. + // This violates the Open/Closed Principle + // and makes each element hold multiple unrelated behaviors. + // => Use `Visitor Pattern` to separate export logic from the element classes. + + virtual void accept(IVisitor* visitor) = 0; +}; + +/** + * The complex object structure + */ +class DocumentConcreteStructure { + private: + std::vector m_elements; + + public: + ~DocumentConcreteStructure() { + for (IElement* e : this->m_elements) { + delete e; } -} + m_elements.clear(); + } + + void add(IElement* e) { m_elements.push_back(e); } + + std::vector& get() { return this->m_elements; }; +}; + +/** + * Each Concrete Element must implement the acceptance method. + * The purpose of this method is to redirect the call to the proper visitor’s + * method corresponding to the current element class. Be aware that even if a + * base element class implements this method, all subclasses must still override + * this method in their own classes and call the appropriate method on the + * visitor object. + */ +class TextConcreteElement : public IElement { + private: + std::string m_content; + + public: + explicit TextConcreteElement(const std::string& c) : m_content{c} {}; + std::string getContent() const { return this->m_content; } + + void accept(IVisitor* visitor) override { visitor->visitText(this); } +}; + +class ImageConcreteElement : public IElement { + private: + std::string m_path; + + public: + explicit ImageConcreteElement(const std::string& p) : m_path{p} {}; + + std::string getPath() const { return this->m_path; } + + void accept(IVisitor* visitor) override { visitor->visitImage(this); } +}; + +class TableConcreteElement : public IElement { + private: + int m_rows, m_cols; + + public: + TableConcreteElement(int r, int c) : m_rows{r}, m_cols{c} {}; + int getRows() const { return this->m_rows; } + int getCols() const { return this->m_cols; } + + void accept(IVisitor* visitor) override { visitor->visitTable(this); } +}; + +/** + * Each Concrete Visitor implements several versions of the same behaviors, + * tailored for different concrete element classes. + */ +class HtmlExportConcreteVisitor : public IVisitor { + void visitText(const TextConcreteElement* t) override { + std::cout << "

" + t->getContent() + "

\n"; + } + + void visitImage(const ImageConcreteElement* i) override { + std::cout << "getPath() + "\" />\n"; + } + + void visitTable(const TableConcreteElement* t) override { + std::cout << "\n"; + for (int r = 0; r < t->getRows(); ++r) { + std::cout << " "; + for (int c = 0; c < t->getCols(); ++c) { + std::cout << ""; + } + std::cout << "\n"; + } + std::cout << "
cell
\n"; + } +}; + +class JsonExportConcreteVisitor : public IVisitor { + void visitText(const TextConcreteElement* t) override { + std::cout << "{ \"type\": \"text\", \"content\": \"" << t->getContent() + << "\" }\n"; + } + void visitImage(const ImageConcreteElement* i) override { + std::cout << "{ \"type\": \"image\", \"path\": \"" << i->getPath() + << "\" }\n"; + } + + void visitTable(const TableConcreteElement* t) override { + std::cout << "{ \"type\": \"table\", \"rows\": " << t->getRows() + << ", \"cols\": " << t->getCols() << " }\n"; + } +}; -struct VisitorAutoRunner -{ - VisitorAutoRunner() - { - std::cout << "\n--- Visitor Pattern Example ---\n"; - Visitor::run(); +class MarkdownExportConcreteVisitor : public IVisitor { + void visitText(const TextConcreteElement* t) override { + std::cout << t->getContent() << "\n\n"; + } + + void visitImage(const ImageConcreteElement* i) override { + std::cout << "![](" << i->getPath() << ")\n\n"; + } + + void visitTable(const TableConcreteElement* t) override { + for (int r = 0; r < t->getRows(); ++r) { + for (int c = 0; c < t->getCols(); ++c) { + std::cout << "| cell "; + } + std::cout << "|\n"; } + std::cout << "\n"; + } +}; + +/** + * clients aren’t aware of all the concrete element classes because they work + * with objects from that collection via some abstract interface. + */ +namespace Client { +void clientCode(DocumentConcreteStructure* const doc, IVisitor* const visitor) { + // [P2] Instead of adding exportHtml(), exportMarkdown(), exportJson() in each + // element, we use the Visitor pattern to separate data from behavior. This + // avoids bloating element classes with multiple unrelated functions and makes + // it easier to add new export formats without modifying the elements. + for (IElement* ele : doc->get()) { + ele->accept(visitor); + } +} +} // namespace Client +void run() { + DocumentConcreteStructure* document = new DocumentConcreteStructure(); + // Add images + document->add(new ImageConcreteElement("header.png")); + document->add(new ImageConcreteElement("diagram1.png")); + document->add(new ImageConcreteElement("chart.png")); + + // Add text paragraphs + document->add(new TextConcreteElement( + "Introduction: This document demonstrates the Visitor pattern.")); + document->add( + new TextConcreteElement("Section 1: Visitor allows adding new operations " + "without modifying elements.")); + document->add(new TextConcreteElement( + "Section 2: Elements are data holders, visitors implement behaviors.")); + + // Add tables + document->add( + new TableConcreteElement(3, 4)); // e.g., 3 rows x 4 columns table + document->add(new TableConcreteElement(2, 2)); // smaller table + document->add(new TableConcreteElement(4, 3)); // another table + + // Add more text + document->add(new TextConcreteElement( + "Conclusion: Using Visitor pattern makes code open for extension but " + "closed for modification.")); + + IVisitor* visitor = new HtmlExportConcreteVisitor(); + std::cout << " ===HTML Export ===\n"; + Client::clientCode(document, visitor); + std::cout << " ==================\n"; + + visitor = new JsonExportConcreteVisitor(); + std::cout << " ===JSON Export ===\n"; + Client::clientCode(document, visitor); + std::cout << " ==================\n"; + + visitor = new MarkdownExportConcreteVisitor(); + std::cout << " ===MD Export ===\n"; + Client::clientCode(document, visitor); + std::cout << " ==================\n"; + delete visitor; + delete document; +} +} // namespace Visitor +} // namespace + +struct VisitorAutoRunner { + VisitorAutoRunner() { + std::cout << "\n--- Visitor Pattern Example ---\n"; + Visitor::run(); + } }; static VisitorAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/creational/AbstractFactory.cpp b/src/patterns/creational/AbstractFactory.cpp new file mode 100644 index 0000000..3a0767f --- /dev/null +++ b/src/patterns/creational/AbstractFactory.cpp @@ -0,0 +1,27 @@ +// cppcheck-suppress-file [functionStatic] + +// Abstract Factory is a creational design pattern that lets you produce +// families of related objects without specifying their concrete classes. +// Appicability: +// (*) when your code needs to work with various families of related products, +// but you don’t want it to depend on the concrete classes +// of those products—they might be unknown beforehand or you simply want to +// allow for future extensibility. +// (**) when you have a class with a set of Factory Methods that blur its +// primary responsibility. + +// UML: docs/uml/patterns_behavioral_iterator.drawio.svg + +#include + +namespace {} + +// struct SingletonAutoRuner +// { +// SingletonAutoRuner() +// { +// std::cout << "\n--- Singleton Pattern Example ---\n"; +// } +// }; + +// static SingletonAutoRuner instance; \ No newline at end of file diff --git a/src/patterns/creational/Builder.cpp b/src/patterns/creational/Builder.cpp index db86b96..b6b5a84 100644 --- a/src/patterns/creational/Builder.cpp +++ b/src/patterns/creational/Builder.cpp @@ -1,213 +1,171 @@ // cppcheck-suppress-file [functionStatic] -// Builder is a creational design pattern that lets you construct complex objects step by step. -// The pattern allows you to produce different types and representations of an object using the same construction code. -// Appicability: +// Builder is a creational design pattern that lets you construct complex +// objects step by step. The pattern allows you to produce different types and +// representations of an object using the same construction code. Appicability: // (*) Use the Builder pattern to get rid of a “telescoping constructor”. -// (**) when you want your code to be able to create different representations of some product (for example, stone and wooden houses). +// (**) when you want your code to be able to create different representations +// of some product (for example, stone and wooden houses). // UML: docs/uml/patterns_behavioral_iterator.drawio.svg #include - -#include +#include #include #include -#include -namespace -{ - namespace BuilderPattern - { - class Product - { - private: - std::vector m_parts; - - public: - void addPart(const std::string &part) - { - m_parts.push_back(part); - } - - void print() const - { - std::cout << "Product parts: "; - for (size_t i = 0; i < m_parts.size(); ++i) - { - std::cout << m_parts[i]; - if (i + 1 < m_parts.size()) - std::cout << ", "; - } - std::cout << "\n\n"; - } - }; - - /** - * The Builder interface specifies methods for creating the different parts of - * the Product objects. - */ - class IBuilder - { - public: - virtual ~IBuilder() = default; - virtual IBuilder &reset() = 0; - virtual IBuilder &producePart1() = 0; - virtual IBuilder &producePart2() = 0; - virtual IBuilder &producePart3() = 0; - - virtual Product *const build() = 0; - }; - - class AbstractBuilder : public IBuilder - { - protected: - Product *m_product; - - public: - explicit AbstractBuilder() - { - m_product = new Product(); - } - - ~AbstractBuilder() - { - delete m_product; - } - - AbstractBuilder(const AbstractBuilder &other) - { - if (m_product != nullptr) - { - delete m_product; - } - m_product = new Product(); - *m_product = *other.m_product; - } - - AbstractBuilder &operator=(const AbstractBuilder &other) - { - if (this == &other) - { - return *this; - } - if (m_product != nullptr) - { - delete m_product; - } - m_product = new Product(); - *m_product = *other.m_product; - return *this; - } - - IBuilder &reset() override final - { - if (m_product != nullptr) - { - delete m_product; - } - m_product = new Product(); - - return *this; - } - }; - - /** - * The Concrete Builder classes follow the Builder interface and provide - * specific implementations of the building steps. Your program may have several - * variations of Builders, implemented differently. - */ - class SimpleBuilder : public AbstractBuilder - { - public: - IBuilder &producePart1() override - { - m_product->addPart("PART1"); - return *this; - } - - IBuilder &producePart2() override - { - m_product->addPart("PART2"); - return *this; - } - - IBuilder &producePart3() override - { - m_product->addPart("PART3"); - return *this; - } - - Product *const build() override - { - return m_product; - } - }; - - class ComplexBuilder : public AbstractBuilder - { - public: - IBuilder &producePart1() override - { - m_product->addPart("PART_1-X9a7Fq!2@Lm#48Z"); - return *this; - } - - IBuilder &producePart2() override - { - m_product->addPart("PART_2-X9a7Fq!2@Lm#48Z"); - return *this; - } - - IBuilder &producePart3() override - { - m_product->addPart("PART_3-X9a7Fq!2@Lm#48Z"); - return *this; - } - - Product *const build() override - { - return m_product; - } - }; - - namespace Client - { - void clientCode(IBuilder *const builder) - { - const Product *product1 = (*builder).producePart1().producePart2().producePart3().build(); - product1->print(); - - const Product *product2 = (*builder).reset().producePart1().build(); - product2->print(); - } - } - - void run() - { - { - std::cout << "ConcreteBuilder: Simple\n"; - IBuilder *builder = new SimpleBuilder(); - Client::clientCode(builder); - delete builder; - } - { - std::cout << "ConcreteBuilder: Complex\n"; - IBuilder *builder = new ComplexBuilder(); - Client::clientCode(builder); - delete builder; - } - } +namespace { +namespace BuilderPattern { +class Product { + private: + std::vector m_parts; + + public: + void addPart(const std::string& part) { m_parts.push_back(part); } + + void print() const { + std::cout << "Product parts: "; + for (size_t i = 0; i < m_parts.size(); ++i) { + std::cout << m_parts[i]; + if (i + 1 < m_parts.size()) + std::cout << ", "; } -} + std::cout << "\n\n"; + } +}; + +/** + * The Builder interface specifies methods for creating the different parts of + * the Product objects. + */ +class IBuilder { + public: + virtual ~IBuilder() = default; + virtual IBuilder& reset() = 0; + virtual IBuilder& producePart1() = 0; + virtual IBuilder& producePart2() = 0; + virtual IBuilder& producePart3() = 0; + + virtual Product* const build() = 0; +}; + +class AbstractBuilder : public IBuilder { + protected: + Product* m_product; + + public: + explicit AbstractBuilder() { m_product = new Product(); } + + ~AbstractBuilder() { delete m_product; } -struct BuilderAutoRunner -{ - BuilderAutoRunner() - { - std::cout << "\n--- Builder Pattern Example ---\n"; - BuilderPattern::run(); + AbstractBuilder(const AbstractBuilder& other) { + if (m_product != nullptr) { + delete m_product; + } + m_product = new Product(); + *m_product = *other.m_product; + } + + AbstractBuilder& operator=(const AbstractBuilder& other) { + if (this == &other) { + return *this; + } + if (m_product != nullptr) { + delete m_product; } + m_product = new Product(); + *m_product = *other.m_product; + return *this; + } + + IBuilder& reset() override final { + if (m_product != nullptr) { + delete m_product; + } + m_product = new Product(); + + return *this; + } +}; + +/** + * The Concrete Builder classes follow the Builder interface and provide + * specific implementations of the building steps. Your program may have several + * variations of Builders, implemented differently. + */ +class SimpleBuilder : public AbstractBuilder { + public: + IBuilder& producePart1() override { + m_product->addPart("PART1"); + return *this; + } + + IBuilder& producePart2() override { + m_product->addPart("PART2"); + return *this; + } + + IBuilder& producePart3() override { + m_product->addPart("PART3"); + return *this; + } + + Product* const build() override { return m_product; } +}; + +class ComplexBuilder : public AbstractBuilder { + public: + IBuilder& producePart1() override { + m_product->addPart("PART_1-X9a7Fq!2@Lm#48Z"); + return *this; + } + + IBuilder& producePart2() override { + m_product->addPart("PART_2-X9a7Fq!2@Lm#48Z"); + return *this; + } + + IBuilder& producePart3() override { + m_product->addPart("PART_3-X9a7Fq!2@Lm#48Z"); + return *this; + } + + Product* const build() override { return m_product; } +}; + +namespace Client { +void clientCode(IBuilder* const builder) { + const Product* product1 = + (*builder).producePart1().producePart2().producePart3().build(); + product1->print(); + + const Product* product2 = (*builder).reset().producePart1().build(); + product2->print(); +} +} // namespace Client + +void run() { + { + std::cout << "ConcreteBuilder: Simple\n"; + IBuilder* builder = new SimpleBuilder(); + Client::clientCode(builder); + delete builder; + } + { + std::cout << "ConcreteBuilder: Complex\n"; + IBuilder* builder = new ComplexBuilder(); + Client::clientCode(builder); + delete builder; + } +} +} // namespace BuilderPattern +} // namespace + +struct BuilderAutoRunner { + BuilderAutoRunner() { + std::cout << "\n--- Builder Pattern Example ---\n"; + BuilderPattern::run(); + } }; static BuilderAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/creational/FactoryMethod.cpp b/src/patterns/creational/FactoryMethod.cpp index 10766b3..8a80c10 100644 --- a/src/patterns/creational/FactoryMethod.cpp +++ b/src/patterns/creational/FactoryMethod.cpp @@ -1,174 +1,140 @@ // cppcheck-suppress-file [functionStatic] -// Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, -// but allows subclasses to alter the type of objects that will be created. -// Appicability: -// (*) when you don’t know beforehand the exact types and dependencies of the objects your code should work with. -// (**) when you want to provide users of your library or framework with a way to extend its internal components. -// (***)when you want to save system resources by reusing existing objects instead of rebuilding them each time. +// Factory Method is a creational design pattern that provides an interface for +// creating objects in a superclass, but allows subclasses to alter the type of +// objects that will be created. Appicability: +// (*) when you don’t know beforehand the exact types and dependencies of the +// objects your code should work with. +// (**) when you want to provide users of your library or framework with a way +// to extend its internal components. +// (***)when you want to save system resources by reusing existing objects +// instead of rebuilding them each time. // UML: docs/uml/patterns_creational_factorymethod.drawio.svg #include #include -namespace -{ - namespace FactoryMethod - { - /** - * The Product interface declares the operations that all concrete products must - * implement. - */ - class IGdbProduct - { - public: - virtual ~IGdbProduct() = default; - virtual void launch() const = 0; - }; - - /** - * Concrete Products provide various implementations of the Product interface. - */ - class LinuxGdbProduct : public IGdbProduct - { - public: - void launch() const override - { - std::cout - << "\tsudo apt update && sudo apt install -y gdb && gdb --version\n"; - } - }; - - class WindowsGdbProduct : public IGdbProduct - { - public: - void launch() const override - { - std::cout << "\tpacman -Syu mingw-w64-x86_64-gdb && gdb --version\n"; - } - }; - - class MacOsGdbProduct : public IGdbProduct - { - public: - void launch() const override - { - std::cout << "\tbrew install gdb && gdb --version\n"; - } - }; - - /** - * The Creator class declares the factory method that is supposed to return an - * object of a Product class. The Creator's subclasses usually provide the - * implementation of this method. - */ - class IGdbCreator - { - public: - virtual ~IGdbCreator() = default; - virtual IGdbProduct *factoryMethod() = 0; - virtual void launchGdb() = 0; - }; - - class AbstractGdbCreater : public IGdbCreator - { - public: - // Call the factory method to create a Product object. - - void launchGdb() override final - { - IGdbProduct *gdb = this->factoryMethod(); - gdb->launch(); - delete gdb; - } - }; - - /** - * Concrete Creators override the factory method in order to change the - * resulting product's type. - */ - class WindowsGdbCreator : public AbstractGdbCreater - { - public: - IGdbProduct *factoryMethod() override - { - return new WindowsGdbProduct(); - } - }; - - class LinuxGdbCreator : public AbstractGdbCreater - { - public: - IGdbProduct *factoryMethod() override - { - return new LinuxGdbProduct(); - } - }; - - class MacOsGdbCreator : public AbstractGdbCreater - { - public: - IGdbProduct *factoryMethod() override - { - return new MacOsGdbProduct(); - } - }; - - /** - * The client code works with an instance of a concrete creator, albeit through its base interface. - * As long as the client keeps working with the creator via the base interface, you can pass it any creator's subclass. - */ - namespace ClientCode - { - void clientCode(IGdbCreator *gdb) - { - if (gdb != nullptr) - gdb->launchGdb(); - } - } - - class GdbCreatorFactory - { - public: - static IGdbCreator *createGdbCreator(const std::string &os) - { - if (os == "linux") - { - return new LinuxGdbCreator(); - } - else if (os == "windows") - { - return new WindowsGdbCreator(); - } - else if (os == "macos") - { - return new MacOsGdbCreator(); - } - else - { - std::cout << "OS not support yet - " << os << "\n"; - return nullptr; - } - } - }; - - void run() - { - std::string os = "linux"; - IGdbCreator *gdb = GdbCreatorFactory::createGdbCreator(os); - ClientCode::clientCode(gdb); - delete gdb; - } - } -} +namespace { +namespace FactoryMethod { +/** + * The Product interface declares the operations that all concrete products must + * implement. + */ +class IGdbProduct { + public: + virtual ~IGdbProduct() = default; + virtual void launch() const = 0; +}; + +/** + * Concrete Products provide various implementations of the Product interface. + */ +class LinuxGdbProduct : public IGdbProduct { + public: + void launch() const override { + std::cout + << "\tsudo apt update && sudo apt install -y gdb && gdb --version\n"; + } +}; + +class WindowsGdbProduct : public IGdbProduct { + public: + void launch() const override { + std::cout << "\tpacman -Syu mingw-w64-x86_64-gdb && gdb --version\n"; + } +}; + +class MacOsGdbProduct : public IGdbProduct { + public: + void launch() const override { + std::cout << "\tbrew install gdb && gdb --version\n"; + } +}; + +/** + * The Creator class declares the factory method that is supposed to return an + * object of a Product class. The Creator's subclasses usually provide the + * implementation of this method. + */ +class IGdbCreator { + public: + virtual ~IGdbCreator() = default; + virtual IGdbProduct* factoryMethod() = 0; + virtual void launchGdb() = 0; +}; + +class AbstractGdbCreater : public IGdbCreator { + public: + // Call the factory method to create a Product object. -struct FactoryMethodAutoRunner -{ - FactoryMethodAutoRunner() - { - std::cout << "\n--- FactoryMethod Pattern Example ---\n"; - FactoryMethod::run(); + void launchGdb() override final { + IGdbProduct* gdb = this->factoryMethod(); + gdb->launch(); + delete gdb; + } +}; + +/** + * Concrete Creators override the factory method in order to change the + * resulting product's type. + */ +class WindowsGdbCreator : public AbstractGdbCreater { + public: + IGdbProduct* factoryMethod() override { return new WindowsGdbProduct(); } +}; + +class LinuxGdbCreator : public AbstractGdbCreater { + public: + IGdbProduct* factoryMethod() override { return new LinuxGdbProduct(); } +}; + +class MacOsGdbCreator : public AbstractGdbCreater { + public: + IGdbProduct* factoryMethod() override { return new MacOsGdbProduct(); } +}; + +/** + * The client code works with an instance of a concrete creator, albeit through + * its base interface. As long as the client keeps working with the creator via + * the base interface, you can pass it any creator's subclass. + */ +namespace ClientCode { +void clientCode(IGdbCreator* gdb) { + if (gdb != nullptr) + gdb->launchGdb(); +} +} // namespace ClientCode + +class GdbCreatorFactory { + public: + static IGdbCreator* createGdbCreator(const std::string& os) { + if (os == "linux") { + return new LinuxGdbCreator(); + } else if (os == "windows") { + return new WindowsGdbCreator(); + } else if (os == "macos") { + return new MacOsGdbCreator(); + } else { + std::cout << "OS not support yet - " << os << "\n"; + return nullptr; } + } +}; + +void run() { + std::string os = "linux"; + IGdbCreator* gdb = GdbCreatorFactory::createGdbCreator(os); + ClientCode::clientCode(gdb); + delete gdb; +} +} // namespace FactoryMethod +} // namespace + +struct FactoryMethodAutoRunner { + FactoryMethodAutoRunner() { + std::cout << "\n--- FactoryMethod Pattern Example ---\n"; + FactoryMethod::run(); + } }; static FactoryMethodAutoRunner instance; \ No newline at end of file diff --git a/src/patterns/creational/Prototype.cpp b/src/patterns/creational/Prototype.cpp new file mode 100644 index 0000000..95b4150 --- /dev/null +++ b/src/patterns/creational/Prototype.cpp @@ -0,0 +1,24 @@ +// cppcheck-suppress-file [functionStatic] + +// Prototype is a creational design pattern that lets you copy existing objects +// without making your code dependent on their classes. Appicability: +// (*) when your code shouldn’t depend on the concrete classes of objects that +// you need to copy. +// (**) when you want to reduce the number of subclasses that only differ in the +// way they initialize their respective objects. + +// UML: docs/uml/patterns_behavioral_iterator.drawio.svg + +#include + +namespace {} + +// struct SingletonAutoRuner +// { +// SingletonAutoRuner() +// { +// std::cout << "\n--- Singleton Pattern Example ---\n"; +// } +// }; + +// static SingletonAutoRuner instance; \ No newline at end of file diff --git a/src/patterns/creational/Singleton.cpp b/src/patterns/creational/Singleton.cpp index 449c9fc..74f04bd 100644 --- a/src/patterns/creational/Singleton.cpp +++ b/src/patterns/creational/Singleton.cpp @@ -1,89 +1,80 @@ // cppcheck-suppress-file [functionStatic] -// Singleton is a creational design pattern that lets you ensure that a class has only one instance, -// while providing a global access point to this instance. -// Appicability: -// (*) when a class in your program should have just a single instance available to all clients; for example, a single database object shared by different parts of the program. +// Singleton is a creational design pattern that lets you ensure that a class +// has only one instance, while providing a global access point to this +// instance. Appicability: +// (*) when a class in your program should have just a single instance +// available to all clients; for example, a single database object shared by +// different parts of the program. // (**) when you need stricter control over global variables. // UML: docs/uml/patterns_creational_singleton.drawio.svg #include -namespace -{ - namespace SingletonPattern - { - - /** - * The Singleton class defines the `GetInstance` method that serves as an - * alternative to constructor and lets clients access the same instance of this - * class over and over. - */ - class Singleton - { - private: - static inline Singleton *instance = nullptr; - static inline int num = 0; - /** - * The Singleton's constructor should always be private to prevent direct - * construction calls with the `new` operator. - */ - Singleton() = default; - - public: - // 1. Should not be cloneable. - Singleton(const Singleton &other) = delete; - - // 2. Should not be assignable - Singleton &operator=(const Singleton &other) = delete; - - static Singleton *getInstance() - { - if (instance == nullptr) - { - instance = new Singleton(); - num++; - } - - return instance; - } - - void operation() const - { - std::cout << "Singleton operating num:" << num << "\n"; - } - }; - - namespace Client - { - void clientCode(const Singleton *const s) - { - s->operation(); - } - } - - void run() - { - const Singleton *s1 = Singleton::getInstance(); - Client::clientCode(s1); - - const Singleton *s2 = Singleton::getInstance(); - Client::clientCode(s2); - - // Singleton* s3 = new Singleton(); // ERROR - } - +namespace { +namespace SingletonPattern { + +/** + * The Singleton class defines the `GetInstance` method that serves as an + * alternative to constructor and lets clients access the same instance of this + * class over and over. + */ +class Singleton { + private: + static inline Singleton* instance = nullptr; + static inline int num = 0; + /** + * The Singleton's constructor should always be private to prevent direct + * construction calls with the `new` operator. + */ + Singleton() = default; + + public: + // 1. Should not be cloneable. + Singleton(const Singleton& other) = delete; + + // 2. Should not be assignable + Singleton& operator=(const Singleton& other) = delete; + + static Singleton* getInstance() { + if (instance == nullptr) { + instance = new Singleton(); + num++; } + + return instance; + } + + void operation() const { + std::cout << "Singleton operating num:" << num << "\n"; + } +}; + +namespace Client { +void clientCode(const Singleton* const s) { + s->operation(); } +} // namespace Client -struct SingletonAutoRuner -{ - SingletonAutoRuner() - { - std::cout << "\n--- Singleton Pattern Example ---\n"; - SingletonPattern::run(); - } +void run() { + const Singleton* s1 = Singleton::getInstance(); + Client::clientCode(s1); + + const Singleton* s2 = Singleton::getInstance(); + Client::clientCode(s2); + + // Singleton* s3 = new Singleton(); // ERROR +} + +} // namespace SingletonPattern +} // namespace + +struct SingletonAutoRuner { + SingletonAutoRuner() { + std::cout << "\n--- Singleton Pattern Example ---\n"; + SingletonPattern::run(); + } }; static SingletonAutoRuner instance; \ No newline at end of file diff --git a/src/patterns/structural/Adapter.cpp b/src/patterns/structural/Adapter.cpp index 822c8aa..7c81695 100644 --- a/src/patterns/structural/Adapter.cpp +++ b/src/patterns/structural/Adapter.cpp @@ -1,176 +1,152 @@ -// Adapters is a structural design pattern that allows objects with incompatible interfaces to collaborate. -// Appicability: -// (*) When you want to use some existing class, but its interface isn’t compatible with the rest of your code -// (**)When you want to reuse several existing subclasses that lack some common functionality that can’t be added to the superclass. +// Adapters is a structural design pattern that allows objects with incompatible +// interfaces to collaborate. Appicability: +// (*) When you want to use some existing class, but its interface isn’t +// compatible with the rest of your code +// (**)When you want to reuse several existing subclasses that lack some common +// functionality that can’t be added to the superclass. // UML: docs/uml/patterns_structural_adapter.drawio.svg #include -namespace AdapterPattern -{ - /** - * The Adaptee contains some useful behavior, but its interface is incompatible - * with the existing client code. The Adaptee needs some adaptation before the - * client code can use it. - */ - class Adaptee - { - public: - static std::string specificRequest() - { - return "Adaptee: The adaptee's behavior."; - } - }; - - /** - * The Target defines the domain-specific interface used by the client code. - */ - class Target - { - public: - virtual std::string request() const - { - return "Target: The target's behavior."; - } - }; - - // ============================================================================================================ - // [Q] How can we make the clientCode works with Adaptee without change this function (e.g this is in front-end) - // => Create an Adapter - // ============================================================================================================ - - /** - * The Adapter makes the Adaptee's interface compatible with the Target's - * interface. - */ - class Adapter : public Target - { - private: - Adaptee *m_adaptee; - - public: - explicit Adapter(Adaptee *adaptee) : m_adaptee{adaptee} - { - std::cout << "Adapter constructer.\n"; - } - - std::string request() const override - { - return m_adaptee->specificRequest(); - } - }; - - /** - * The client code supports all classes that follow the Target interface. - */ - - namespace Client - { - void clientCode(const Target *target) - { - if (target != nullptr) - std::cout << "Output: " << target->request() << "\n"; - } - } - - void run() - { - std::cout << "Client: Can work just fine with the Target objects:\n"; - Target target = Target(); - std::cout << "Target: " << target.request() << "\n"; - Client::clientCode(&target); - std::cout << "\n\n"; - - std::cout << "Client: Cannot work with the Adaptee objects:\n"; - Adaptee adaptee = Adaptee(); - std::cout << "Adaptee: " << adaptee.specificRequest() << "\n"; - // Client::clientCode(&adaptee); // error - - std::cout << "Client: But can work with it via the Adapter:\n"; - Adapter adapter = Adapter(&adaptee); - Client::clientCode(&adapter); - std::cout << "\n"; - } -} +namespace AdapterPattern { +/** + * The Adaptee contains some useful behavior, but its interface is incompatible + * with the existing client code. The Adaptee needs some adaptation before the + * client code can use it. + */ +class Adaptee { + public: + static std::string specificRequest() { + return "Adaptee: The adaptee's behavior."; + } +}; -namespace CaseStudy -{ - // Target interface expected by the existing system - class PaymentSystem - { - public: - virtual void payWithCard(const std::string &cardNumber) - { - std::cout << "Payment using card: " << cardNumber << "\n"; - } - - virtual ~PaymentSystem() = default; - }; - - // Adaptee: a new payment API with an incompatible interface - class PayPalAPI - { - public: - static void sendPayment(const std::string &email) - { - std::cout << "Payment sent via PayPal to " << email << "\n"; - } - }; - - // Adapter: makes PayPalAPI compatible with PaymentSystem - class PayPalAdapter : public PaymentSystem - { - private: - PayPalAPI paypal; - - public: - void payWithCard(const std::string &cardNumber) override - { - // Treat the cardNumber parameter as a PayPal email - paypal.sendPayment(cardNumber); - } - }; - - // Client code: uses the old interface without modification - void run() - { - std::string method, input; - method = std::string("card") + std::string(""); - input = "1234-5678-9999"; - // method = std::string("paypal") + std::string("");input = "user@example.com"; - - std::cout << "Choose payment method (card/paypal): " << method << "\n"; - - PaymentSystem *paymentSystem = nullptr; - - if (method == "card") - { - paymentSystem = new PaymentSystem(); - paymentSystem->payWithCard(input); - } - else if (method == "paypal") - { - paymentSystem = new PayPalAdapter(); - paymentSystem->payWithCard(input); - } - else - { - std::cout << "Unsupported payment method!\n"; - } - - delete paymentSystem; - } +/** + * The Target defines the domain-specific interface used by the client code. + */ +class Target { + public: + virtual std::string request() const { + return " Target: The target's behavior."; + } +}; + +// ============================================================================================================ +// [Q] How can we make the clientCode works with Adaptee without change this +// function (e.g this is in front-end) +// => Create an Adapter +// ============================================================================================================ + +/** + * The Adapter makes the Adaptee's interface compatible with the Target's + * interface. + */ +class Adapter : public Target { + private: + Adaptee* m_adaptee; + + public: + explicit Adapter(Adaptee* adaptee) : m_adaptee{adaptee} { + std::cout << "Adapter constructer.\n"; + } + + std::string request() const override { return m_adaptee->specificRequest(); } +}; + +/** + * The client code supports all classes that follow the Target interface. + */ + +namespace Client { +void clientCode(const Target* target) { + if (target != nullptr) + std::cout << "Output: " << target->request() << "\n"; } +} // namespace Client + +void run() { + std::cout << "Client: Can work just fine with the Target objects:\n"; + Target target = Target(); + std::cout << "Target: " << target.request() << "\n"; + Client::clientCode(&target); + std::cout << "\n\n"; + + std::cout << "Client: Cannot work with the Adaptee objects:\n"; + Adaptee adaptee = Adaptee(); + std::cout << "Adaptee: " << adaptee.specificRequest() << "\n"; + // Client::clientCode(&adaptee); // error + + std::cout << "Client: But can work with it via the Adapter:\n"; + Adapter adapter = Adapter(&adaptee); + Client::clientCode(&adapter); + std::cout << "\n"; +} +} // namespace AdapterPattern + +namespace CaseStudy { +// Target interface expected by the existing system +class PaymentSystem { + public: + virtual void payWithCard(const std::string& cardNumber) { + std::cout << "Payment using card: " << cardNumber << "\n"; + } -struct AdapterAutoRuner -{ - AdapterAutoRuner() - { - std::cout << "\n--- Factory Pattern Example ---\n"; - AdapterPattern::run(); - CaseStudy::run(); - } + virtual ~PaymentSystem() = default; +}; + +// Adaptee: a new payment API with an incompatible interface +class PayPalAPI { + public: + static void sendPayment(const std::string& email) { + std::cout << "Payment sent via PayPal to " << email << "\n"; + } +}; + +// Adapter: makes PayPalAPI compatible with PaymentSystem +class PayPalAdapter : public PaymentSystem { + private: + PayPalAPI paypal; + + public: + void payWithCard(const std::string& cardNumber) override { + // Treat the cardNumber parameter as a PayPal email + paypal.sendPayment(cardNumber); + } +}; + +// Client code: uses the old interface without modification +void run() { + std::string method, input; + method = std::string("card") + std::string(""); + input = "1234-5678-9999"; + // method = std::string("paypal") + std::string("");input = + // "user@example.com"; + + std::cout << "Choose payment method (card/paypal): " << method << "\n"; + + PaymentSystem* paymentSystem = nullptr; + + if (method == "card") { + paymentSystem = new PaymentSystem(); + paymentSystem->payWithCard(input); + } else if (method == "paypal") { + paymentSystem = new PayPalAdapter(); + paymentSystem->payWithCard(input); + } else { + std::cout << "Unsupported payment method!\n"; + } + + delete paymentSystem; +} +} // namespace CaseStudy + +struct AdapterAutoRuner { + AdapterAutoRuner() { + std::cout << "\n--- Factory Pattern Example ---\n"; + AdapterPattern::run(); + CaseStudy::run(); + } }; static AdapterAutoRuner instance; \ No newline at end of file diff --git a/src/patterns/structural/Bridge.cpp b/src/patterns/structural/Bridge.cpp index e2adc81..246d6ea 100644 --- a/src/patterns/structural/Bridge.cpp +++ b/src/patterns/structural/Bridge.cpp @@ -2,219 +2,177 @@ // into two separate hierarchies—abstraction and implementation // which can be developed independently of each other. // Appicability: -// (*) when you want to divide and organize a monolithic class that has several variants of some functionality +// (*) when you want to divide and organize a monolithic class that has +// several variants of some functionality // (for example, if the class can work with various database servers). -// (**) when you need to extend a class in several orthogonal (independent) dimensions. +// (**) when you need to extend a class in several orthogonal (independent) +// dimensions. // (***) when you you need to be able to switch implementations at runtime. // UML: docs/uml/patterns_structural_bridge.drawio.svg #include -namespace -{ - namespace Problem - { - class Widget - { - public: - virtual ~Widget() = default; - virtual std::string clickOn() const = 0; - }; - - /* Concrete variations for Button */ - class Button : public Widget - { - public: - virtual ~Button() = default; - - public: - std::string clickOn() const override - { - return "Click on: Button\n"; - } - }; - - class ButtonWindows : public Button - { - public: - std::string clickOn() const override - { - return "[Linux]" + Button::clickOn(); - } - }; - - class ButtonLinux : public Button - { - public: - std::string clickOn() const override - { - return "[Windows]" + Button::clickOn(); - } - }; - - /* Concrete variations for Label */ - class Label : public Widget - { - public: - virtual ~Label() = default; - - public: - std::string clickOn() const override - { - return "Click on: Label\n"; - } - }; - - class LabelWindows : public Label - { - public: - std::string clickOn() const override - { - return "[Windows]" + Label::clickOn(); - } - }; - - class LabelLinux : public Label - { - public: - std::string clickOn() const override - { - return "[Linux]" + Label::clickOn(); - } - }; - - /* Concrete variations for others widgets like Text,CCombo or new platform macOS etc*/ - // [Problem 1] We have to write the Text/TextLinux ... - - namespace Client - { - void clientCode(const Widget *widget) - { - if (widget != nullptr) - std::cout << widget->clickOn(); - } - } - - void run() - { - // [Problem 2] : Use the Bridge if you need to be able to switch implementations at runtime. how to exmaple for this - // still don't know - Widget *button = new ButtonWindows(); - Client::clientCode(button); - delete button; - } - } - - namespace BridgePattern - { - /** - * The Implementation defines the interface for all implementation classes. It - * doesn't have to match the Abstraction's interface. In fact, the two - * interfaces can be entirely different. Typically the Implementation interface - * provides only primitive Widgets, while the Abstraction defines higher- - * level Widgets based on those primitives. - */ - class OsImplemetation - { - public: - virtual std::string clickOnImplement() const = 0; - virtual ~OsImplemetation() = default; - }; - - class WindowsImplemetation : public OsImplemetation - { - public: - std::string clickOnImplement() const override - { - return "[Windows]"; - } - }; - - class LinuxImplemetation : public OsImplemetation - { - public: - std::string clickOnImplement() const override - { - return "[Linux]"; - } - }; - - /** - * The Abstraction defines the interface for the "control" part of the two class - * hierarchies. It maintains a reference to an object of the Implementation - * hierarchy and delegates all of the real work to this object. - */ - class WidgetAbstraction - { - protected: - OsImplemetation *_implementation; - - public: - explicit WidgetAbstraction(OsImplemetation *implemetation) : _implementation{implemetation} - { - } - virtual ~WidgetAbstraction() = default; - - virtual std::string clickOn() const = 0; - }; - - /** - * We can extend the Abstraction without changing the Implementation classes. - */ - class ButtonAbstraction : public WidgetAbstraction - { - public: - explicit ButtonAbstraction(OsImplemetation *implemetation) : WidgetAbstraction{implemetation} {} - std::string clickOn() const override - { - return this->_implementation->clickOnImplement() + "Click on: Button\n"; - } - }; - - class LabelAbstraction : public WidgetAbstraction - { - public: - explicit LabelAbstraction(OsImplemetation *implemetation) : WidgetAbstraction{implemetation} {} - std::string clickOn() const override - { - return this->_implementation->clickOnImplement() + "Click on: Label\n"; - } - }; - - namespace Client - { - void clientCode(const WidgetAbstraction *widget) - { - if (widget != nullptr) - std::cout << widget->clickOn(); - } - } - - void run() - { - // TODO: check memory leak here - OsImplemetation *os = new WindowsImplemetation(); - WidgetAbstraction *widget = new ButtonAbstraction(os); - Client::clientCode(widget); - - os = new LinuxImplemetation(); - widget = new LabelAbstraction(os); - Client::clientCode(widget); - - delete os; - delete widget; - } - } - - struct BridgeAutoruner - { - BridgeAutoruner() - { - std::cout << "\n--- Bridge Pattern Example ---\n"; - Problem::run(); - BridgePattern::run(); - } - }; - - static BridgeAutoruner instance; -} \ No newline at end of file +namespace { +namespace Problem { +class Widget { + public: + virtual ~Widget() = default; + virtual std::string clickOn() const = 0; +}; + +/* Concrete variations for Button */ +class Button : public Widget { + public: + virtual ~Button() = default; + + public: + std::string clickOn() const override { return "Click on: Button\n"; } +}; + +class ButtonWindows : public Button { + public: + std::string clickOn() const override { return "[Linux]" + Button::clickOn(); } +}; + +class ButtonLinux : public Button { + public: + std::string clickOn() const override { + return "[Windows]" + Button::clickOn(); + } +}; + +/* Concrete variations for Label */ +class Label : public Widget { + public: + virtual ~Label() = default; + + public: + std::string clickOn() const override { return "Click on: Label\n"; } +}; + +class LabelWindows : public Label { + public: + std::string clickOn() const override { + return "[Windows]" + Label::clickOn(); + } +}; + +class LabelLinux : public Label { + public: + std::string clickOn() const override { return "[Linux]" + Label::clickOn(); } +}; + +/* Concrete variations for others widgets like Text,CCombo or new platform + * macOS etc*/ +// [Problem 1] We have to write the Text/TextLinux ... + +namespace Client { +void clientCode(const Widget* widget) { + if (widget != nullptr) + std::cout << widget->clickOn(); +} +} // namespace Client + +void run() { + // [Problem 2] : Use the Bridge if you need to be able to switch + // implementations at runtime. how to exmaple for this still don't know + Widget* button = new ButtonWindows(); + Client::clientCode(button); + delete button; +} +} // namespace Problem + +namespace BridgePattern { +/** + * The Implementation defines the interface for all implementation classes. It + * doesn't have to match the Abstraction's interface. In fact, the two + * interfaces can be entirely different. Typically the Implementation interface + * provides only primitive Widgets, while the Abstraction defines higher- + * level Widgets based on those primitives. + */ +class OsImplemetation { + public: + virtual std::string clickOnImplement() const = 0; + virtual ~OsImplemetation() = default; +}; + +class WindowsImplemetation : public OsImplemetation { + public: + std::string clickOnImplement() const override { return "[Windows]"; } +}; + +class LinuxImplemetation : public OsImplemetation { + public: + std::string clickOnImplement() const override { return "[Linux]"; } +}; + +/** + * The Abstraction defines the interface for the "control" part of the two class + * hierarchies. It maintains a reference to an object of the Implementation + * hierarchy and delegates all of the real work to this object. + */ +class WidgetAbstraction { + protected: + OsImplemetation* _implementation; + + public: + explicit WidgetAbstraction(OsImplemetation* implemetation) + : _implementation{implemetation} {} + virtual ~WidgetAbstraction() = default; + + virtual std::string clickOn() const = 0; +}; + +/** + * We can extend the Abstraction without changing the Implementation classes. + */ +class ButtonAbstraction : public WidgetAbstraction { + public: + explicit ButtonAbstraction(OsImplemetation* implemetation) + : WidgetAbstraction{implemetation} {} + std::string clickOn() const override { + return this->_implementation->clickOnImplement() + "Click on: Button\n"; + } +}; + +class LabelAbstraction : public WidgetAbstraction { + public: + explicit LabelAbstraction(OsImplemetation* implemetation) + : WidgetAbstraction{implemetation} {} + std::string clickOn() const override { + return this->_implementation->clickOnImplement() + "Click on: Label\n"; + } +}; + +namespace Client { +void clientCode(const WidgetAbstraction* widget) { + if (widget != nullptr) + std::cout << widget->clickOn(); +} +} // namespace Client + +void run() { + // TODO: check memory leak here + OsImplemetation* os = new WindowsImplemetation(); + WidgetAbstraction* widget = new ButtonAbstraction(os); + Client::clientCode(widget); + + os = new LinuxImplemetation(); + widget = new LabelAbstraction(os); + Client::clientCode(widget); + + delete os; + delete widget; +} +} // namespace BridgePattern + +struct BridgeAutoruner { + BridgeAutoruner() { + std::cout << "\n--- Bridge Pattern Example ---\n"; + Problem::run(); + BridgePattern::run(); + } +}; + +static BridgeAutoruner instance; +} // namespace \ No newline at end of file diff --git a/src/patterns/structural/Composite.cpp b/src/patterns/structural/Composite.cpp index 9a903e1..89dfb65 100644 --- a/src/patterns/structural/Composite.cpp +++ b/src/patterns/structural/Composite.cpp @@ -3,540 +3,434 @@ // and then work with these structures as if they were individual objects. // Appicability: // (*) when you have to implement a tree-like object structure. -// (**) when you want the client code to treat both simple and complex elements uniformly. -// UML: docs/uml/patterns_structural_composite.drawio.svg +// (**) when you want the client code to treat both simple and complex elements +// uniformly. UML: docs/uml/patterns_structural_composite.drawio.svg +#include #include -#include #include -#include +#include + +namespace { +namespace Problem { +class File { + private: + std::string _name; + + public: + explicit File(const std::string& fileName) : _name{fileName} {} + std::string getName() const { return this->_name; } + + void setName(const std::string& name) { this->_name = name; } + + void open() const { std::cout << "Open file: " << _name << "\n"; } +}; + +// [P1] Have the NewTypeFile => Update Folder (fields, functions) + +class Folder { + private: + std::string _name; + std::list + _files; // Should store the pointer to the actual file so when we delete + // the file it should delete in the list + std::list _subFolders; + // [P2] What about the parrent ? + + public: + explicit Folder(const std::string& name) : _name{name} {} -namespace -{ - namespace Problem - { - class File - { - private: - std::string _name; - - public: - explicit File(const std::string &fileName) : _name{fileName} {} - std::string getName() const - { - return this->_name; - } - - void setName(const std::string &name) - { - this->_name = name; - } - - void open() const - { - std::cout << "Open file: " << _name << "\n"; - } - }; - - // [P1] Have the NewTypeFile => Update Folder (fields, functions) - - class Folder - { - private: - std::string _name; - std::list _files; // Should store the pointer to the actual file so when we delete the file it should delete in the list - std::list _subFolders; - // [P2] What about the parrent ? - - public: - explicit Folder(const std::string &name) : _name{name} - { - } - - ~Folder() - { - for (File *f : _files) - { - delete f; - } - - for (Folder *sf : _subFolders) - { - delete sf; - } - } - - void removeFile(const File *file) - { - _files.remove_if([file](const File *f) - { return f == file; }); - } - - void removeFileByName(const std::string &name) - { - - // for (auto i = _files.begin(); i != _files.end(); ++i) - // { - // if ((*i)->getName() == name) - // { - // delete (*i); - // _files.erase(i); - // return; - // } - // } - - auto it = std::find_if(_files.begin(), _files.end(), - [&name](const File *f) - { return f->getName() == name; }); - - if (it != _files.end()) - { - delete *it; // free the memory - _files.erase(it); // remove the pointer from the list - } - } - - void removeFolderByName(const std::string &name) - { - // for (auto i = _subFolders.begin(); i != _subFolders.end(); ++i) - // { - // if ((*i)->getName() == name) - // { - // delete (*i); - // _subFolders.erase(i); - // return; - // } - // } - auto it = std::find_if(_subFolders.begin(), _subFolders.end(), - [&name](const Folder *f) - { return f->getName() == name; }); - - if (it != _subFolders.end()) - { - delete *it; // free the memory - _subFolders.erase(it); // remove the pointer from the list - } - } - - void addFile(File *file) - { - _files.push_back(file); - } - - void addFolder(Folder *folder) - { - _subFolders.push_back(folder); - } - - void removeFolder(const Folder *folder) - { - _subFolders.remove_if([folder](const Folder *f) - { return f == folder; }); - } - - std::string getName() const - { - return _name; - } - - void open() const - { - std::cout << "Open Folder: " << _name << "\n"; - } - - int size() const - { - int size = static_cast(_files.size()); - - // Consider using std::accumulate algorithm instead of a raw loop. - std::for_each(_subFolders.begin(), _subFolders.end(), [&size](const Folder *sf) - { size += sf->size(); }); - - // for (const Folder *subFolder : _subFolders) - // { - // size += subFolder->size(); - // } - - return size; - } - - const std::list &getSubFolders() const - { - return _subFolders; - } - - const std::list &getFiles() const - { - return _files; - } - - void getFilesRecursive(std::list &out) const - { - out.insert(out.end(), _files.begin(), _files.end()); - for (const Folder *sf : _subFolders) - { - sf->getFilesRecursive(out); - } - } - - void getSubFoldersRecursive(std::list &out) const - { - out.insert(out.end(), _subFolders.begin(), _subFolders.end()); - for (const Folder *sf : _subFolders) - { - sf->getSubFoldersRecursive(out); - } - } - }; - - namespace Client - { - void clientCode(const Folder *folder) - { - std::cout << "File name: " << folder->getName() << "\n"; - folder->open(); - - std::cout << "Folder size: " << folder->getName() << ", size: " << folder->size() << "\n"; - std::list files; - folder->getFilesRecursive(files); - for (const File *f : files) - { - std::cout << "\t\tFile: " << f->getName() << "\n"; - } - - std::list subFolders; - folder->getSubFoldersRecursive(subFolders); - for (const Folder *sf : subFolders) - { - std::cout << "\t\tFolder: " << sf->getName() << "\n"; - } - } - } - - void run() - { - std::cout << "\n\n"; - Folder *root = new Folder("root"); - root->open(); - - // Prepare files - File *file1 = new File("fileName1.txt"); - File *file2 = new File("fileName2.txt"); - File *file3 = new File("fileName3.txt"); - root->addFile(new File(*file1)); - root->addFile(new File(*file2)); - root->addFile(new File(*file3)); - - Folder *subFolder1 = new Folder("subFolder1"); - Folder *subFolder2 = new Folder("subFolder2"); - Folder *subFolder3 = new Folder("subFolder3"); - - root->addFolder(new Folder(*subFolder1)); - root->addFolder(new Folder(*subFolder2)); - root->addFolder(new Folder(*subFolder3)); - - Client::clientCode(root); - - // Clean up memory - delete subFolder1; - delete subFolder2; - delete subFolder3; - delete file1; - delete file2; - delete file3; - - root->removeFileByName("fileName1.txt"); - Client::clientCode(root); - delete root; // deletes all files/subfolders inside recursively - } + ~Folder() { + for (File* f : _files) { + delete f; + } + for (Folder* sf : _subFolders) { + delete sf; + } + } + + void removeFile(const File* file) { + _files.remove_if([file](const File* f) { return f == file; }); + } + + void removeFileByName(const std::string& name) { + + // for (auto i = _files.begin(); i != _files.end(); ++i) + // { + // if ((*i)->getName() == name) + // { + // delete (*i); + // _files.erase(i); + // return; + // } + // } + + auto it = + std::find_if(_files.begin(), _files.end(), + [&name](const File* f) { return f->getName() == name; }); + + if (it != _files.end()) { + delete *it; // free the memory + _files.erase(it); // remove the pointer from the list } + } + + void removeFolderByName(const std::string& name) { + // for (auto i = _subFolders.begin(); i != _subFolders.end(); ++i) + // { + // if ((*i)->getName() == name) + // { + // delete (*i); + // _subFolders.erase(i); + // return; + // } + // } + auto it = + std::find_if(_subFolders.begin(), _subFolders.end(), + [&name](const Folder* f) { return f->getName() == name; }); + + if (it != _subFolders.end()) { + delete *it; // free the memory + _subFolders.erase(it); // remove the pointer from the list + } + } + + void addFile(File* file) { _files.push_back(file); } - namespace CompositePattern - { - /** - * Component is the abstraction for leafs and composites. - * It defines the interface that must be implemented by the objects in the composition. - * For example a file system resource defines move, copy, rename, and getSize methods for files and folders. - */ - class FileSystem - { - private: - FileSystem *_parent; - std::string _name; - - public: - explicit FileSystem(const std::string &fileName) : _parent{nullptr}, _name{fileName} {} - virtual ~FileSystem() - { - std::cout << "Destructor: " << this->getName() << "\n"; - } - - FileSystem *getParent() const - { - return _parent; - } - - void setParent(FileSystem *parent) - { - _parent = parent; - } - - std::string getName() const - { - return this->_name; - } - - virtual int size() const - { - return 1; - } - - void setName(const std::string &name) - { - this->_name = name; - } - - virtual void add(FileSystem *fs) - { - // do nothing here - } - - virtual void remove(FileSystem *fs) - { - // do nothing here - } - - /** - * Check if the component is composite or not - * You can provide a method that lets the client code figure out whether a - * component can bear children. - */ - virtual bool isComposite() const - { - return false; - } - - virtual void open() const = 0; - }; - - /** - * Composite - A Composite stores child components in addition to implementing methods defined by the component interface. - * Composites implement methods defined in the Component interface by delegating to child components. - * In addition composites provide additional methods for adding, removing, as well as getting components. - */ - class Folder : public FileSystem - { - private: - std::list _children; - - // Remove children - [[maybe_unused]] void removeChildren(const FileSystem *file) - { - _children.remove_if([file](const FileSystem *f) - { return f == file; }); - } - - [[maybe_unused]] void removeChildrenByName(const std::string &name) - { - - auto it = std::find_if(_children.begin(), _children.end(), - [&name](const FileSystem *f) - { return f->getName() == name; }); - - if (it != _children.end()) - { - delete *it; // free the memory - _children.erase(it); // remove the pointer from the list - } - } - - [[maybe_unused]] const std::list &getChildren() const - { - return _children; - } - - void getChildrensRecursive(std::list &out) const - { - for (FileSystem *fs : _children) - { - if (fs->isComposite()) - { - const Folder *f = static_cast(fs); - if (f != nullptr) - f->getChildrensRecursive(out); - } - else - { - out.push_back(fs); - } - } - } - - public: - explicit Folder(const std::string &name) : FileSystem{name} - { - } - - ~Folder() - { - // Delete folder should delete all children - for (auto f : _children) - { - std::cout << "Folder '" << this->getName() << "' deleted : " << f->getName() << "\n"; - delete f; - } - } - - void add(FileSystem *fs) override - { - std::cout << "Folder '" << this->getName() << "' added : " << fs->getName() << "\n"; - _children.push_back(fs); - fs->setParent(this); - } - - void remove(FileSystem *fs) override - { - std::cout << "Folder: " << this->getName() << "removed : " << fs->getName() << "\n"; - _children.remove(fs); - fs->setParent(nullptr); - } - - void open() const override - { - std::cout << "Open Folder: " << this->getName() << "\n"; - } - - int size() const override - { - int size = static_cast(_children.size()); - std::for_each(_children.begin(), _children.end(), [&size](FileSystem *fs) - { - if(fs->isComposite()){ - const Folder* f = static_cast(fs); - if(f!= nullptr) - size += f->size(); - } }); - - return size; - } - }; - - /** - * The Leaf class represents the end objects of a composition. A leaf can't have - * any children. - * - * Usually, it's the Leaf objects that do the actual work, whereas Composite - * objects only delegate to their sub-components. - */ - class File : public FileSystem - { - public: - explicit File(const std::string &name) : FileSystem{name} - { - } - - void open() const override - { - std::cout << "Open File: " << this->getName() << "\n"; - } - }; - - class ZipFile : public FileSystem - { - public: - explicit ZipFile(const std::string &name) : FileSystem{name} - { - } - - void open() const override - { - std::cout << "Open ZipFile: " << this->getName() << "\n"; - } - }; - - class ShortCut : public FileSystem - { - private: - FileSystem *origin; - - public: - explicit ShortCut(FileSystem *fs) : FileSystem{fs->getName()} - { - origin = fs; - } - - void open() const override - { - std::cout << "Open ShortCut: " << this->getName() << "\n"; - if (origin != nullptr) - { - std::cout << "Navigate to: " << origin->getName() << "\n"; - } - else - { - std::cout << "Original file no longer exist\n"; - } - } - }; - - namespace Client - { - void clientCode(const FileSystem *fs) - { - std::cout << "File name: " << fs->getName() << ", size: " << fs->size() << "\n"; - fs->open(); - } - } - - void run() - { - std::cout << "\n\n"; - FileSystem *root = new Folder("root"); - root->open(); - - // Prepare files - FileSystem *file1 = new File("file_name.txt"); - FileSystem *file2 = new ZipFile("zipfile_name.txt"); - FileSystem *file3 = new ShortCut(file2); - root->add(file1); - root->add(file2); - root->add(file3); - Client::clientCode(file1); - Client::clientCode(root); - - Folder *subFolder1 = new Folder("subFolder1"); - Folder *subFolder2 = new Folder("subFolder2"); - Folder *subFolder3 = new Folder("subFolder3"); - root->add(subFolder1); - root->add(subFolder2); - root->add(subFolder3); - Client::clientCode(root); - - root->remove(file1); - delete file1; - - root->remove(file2); - delete file2; - - root->remove(file3); - delete file3; - - delete root; // deletes all files/subfolders inside recursively - } + void addFolder(Folder* folder) { _subFolders.push_back(folder); } + void removeFolder(const Folder* folder) { + _subFolders.remove_if([folder](const Folder* f) { return f == folder; }); + } + + std::string getName() const { return _name; } + + void open() const { std::cout << "Open Folder: " << _name << "\n"; } + + int size() const { + int size = static_cast(_files.size()); + + // Consider using std::accumulate algorithm instead of a raw loop. + std::for_each(_subFolders.begin(), _subFolders.end(), + [&size](const Folder* sf) { size += sf->size(); }); + + // for (const Folder *subFolder : _subFolders) + // { + // size += subFolder->size(); + // } + + return size; + } + + const std::list& getSubFolders() const { return _subFolders; } + + const std::list& getFiles() const { return _files; } + + void getFilesRecursive(std::list& out) const { + out.insert(out.end(), _files.begin(), _files.end()); + for (const Folder* sf : _subFolders) { + sf->getFilesRecursive(out); + } + } + + void getSubFoldersRecursive(std::list& out) const { + out.insert(out.end(), _subFolders.begin(), _subFolders.end()); + for (const Folder* sf : _subFolders) { + sf->getSubFoldersRecursive(out); } + } +}; +namespace Client { +void clientCode(const Folder* folder) { + std::cout << "File name: " << folder->getName() << "\n"; + folder->open(); + + std::cout << "Folder size: " << folder->getName() + << ", size: " << folder->size() << "\n"; + std::list files; + folder->getFilesRecursive(files); + for (const File* f : files) { + std::cout << "\t\tFile: " << f->getName() << "\n"; + } + + std::list subFolders; + folder->getSubFoldersRecursive(subFolders); + for (const Folder* sf : subFolders) { + std::cout << "\t\tFolder: " << sf->getName() << "\n"; + } } +} // namespace Client + +void run() { + std::cout << "\n\n"; + Folder* root = new Folder("root"); + root->open(); + + // Prepare files + File* file1 = new File("fileName1.txt"); + File* file2 = new File("fileName2.txt"); + File* file3 = new File("fileName3.txt"); + root->addFile(new File(*file1)); + root->addFile(new File(*file2)); + root->addFile(new File(*file3)); + + Folder* subFolder1 = new Folder("subFolder1"); + Folder* subFolder2 = new Folder("subFolder2"); + Folder* subFolder3 = new Folder("subFolder3"); + + root->addFolder(new Folder(*subFolder1)); + root->addFolder(new Folder(*subFolder2)); + root->addFolder(new Folder(*subFolder3)); + + Client::clientCode(root); + + // Clean up memory + delete subFolder1; + delete subFolder2; + delete subFolder3; + delete file1; + delete file2; + delete file3; + + root->removeFileByName("fileName1.txt"); + Client::clientCode(root); + delete root; // deletes all files/subfolders inside recursively +} + +} // namespace Problem + +namespace CompositePattern { +/** + * Component is the abstraction for leafs and composites. + * It defines the interface that must be implemented by the objects in the + * composition. For example a file system resource defines move, copy, rename, + * and getSize methods for files and folders. + */ +class FileSystem { + private: + FileSystem* _parent; + std::string _name; + + public: + explicit FileSystem(const std::string& fileName) + : _parent{nullptr}, _name{fileName} {} + virtual ~FileSystem() { + std::cout << "Destructor: " << this->getName() << "\n"; + } -struct CompositeAutoRuner -{ - CompositeAutoRuner() - { - std::cout << "\n--- Composite Pattern Example ---\n"; - Problem::run(); - CompositePattern::run(); + FileSystem* getParent() const { return _parent; } + + void setParent(FileSystem* parent) { _parent = parent; } + + std::string getName() const { return this->_name; } + + virtual int size() const { return 1; } + + void setName(const std::string& name) { this->_name = name; } + + virtual void add(FileSystem* fs) { + // do nothing here + } + + virtual void remove(FileSystem* fs) { + // do nothing here + } + + /** + * Check if the component is composite or not + * You can provide a method that lets the client code figure out whether a + * component can bear children. + */ + virtual bool isComposite() const { return false; } + + virtual void open() const = 0; +}; + +/** + * Composite - A Composite stores child components in addition to implementing + * methods defined by the component interface. Composites implement methods + * defined in the Component interface by delegating to child components. In + * addition composites provide additional methods for adding, removing, as well + * as getting components. + */ +class Folder : public FileSystem { + private: + std::list _children; + + // Remove children + [[maybe_unused]] void removeChildren(const FileSystem* file) { + _children.remove_if([file](const FileSystem* f) { return f == file; }); + } + + [[maybe_unused]] void removeChildrenByName(const std::string& name) { + + auto it = std::find_if( + _children.begin(), _children.end(), + [&name](const FileSystem* f) { return f->getName() == name; }); + + if (it != _children.end()) { + delete *it; // free the memory + _children.erase(it); // remove the pointer from the list + } + } + + [[maybe_unused]] const std::list& getChildren() const { + return _children; + } + + void getChildrensRecursive(std::list& out) const { + for (FileSystem* fs : _children) { + if (fs->isComposite()) { + const Folder* f = static_cast(fs); + if (f != nullptr) + f->getChildrensRecursive(out); + } else { + out.push_back(fs); + } + } + } + + public: + explicit Folder(const std::string& name) : FileSystem{name} {} + + ~Folder() { + // Delete folder should delete all children + for (auto f : _children) { + std::cout << "Folder '" << this->getName() + << "' deleted : " << f->getName() << "\n"; + delete f; } + } + + void add(FileSystem* fs) override { + std::cout << "Folder '" << this->getName() << "' added : " << fs->getName() + << "\n"; + _children.push_back(fs); + fs->setParent(this); + } + + void remove(FileSystem* fs) override { + std::cout << "Folder: " << this->getName() << "removed : " << fs->getName() + << "\n"; + _children.remove(fs); + fs->setParent(nullptr); + } + + void open() const override { + std::cout << "Open Folder: " << this->getName() << "\n"; + } + + int size() const override { + int size = static_cast(_children.size()); + std::for_each(_children.begin(), _children.end(), [&size](FileSystem* fs) { + if (fs->isComposite()) { + const Folder* f = static_cast(fs); + if (f != nullptr) + size += f->size(); + } + }); + + return size; + } +}; + +/** + * The Leaf class represents the end objects of a composition. A leaf can't have + * any children. + * + * Usually, it's the Leaf objects that do the actual work, whereas Composite + * objects only delegate to their sub-components. + */ +class File : public FileSystem { + public: + explicit File(const std::string& name) : FileSystem{name} {} + + void open() const override { + std::cout << "Open File: " << this->getName() << "\n"; + } +}; + +class ZipFile : public FileSystem { + public: + explicit ZipFile(const std::string& name) : FileSystem{name} {} + + void open() const override { + std::cout << "Open ZipFile: " << this->getName() << "\n"; + } +}; + +class ShortCut : public FileSystem { + private: + FileSystem* origin; + + public: + explicit ShortCut(FileSystem* fs) : FileSystem{fs->getName()} { origin = fs; } + + void open() const override { + std::cout << "Open ShortCut: " << this->getName() << "\n"; + if (origin != nullptr) { + std::cout << "Navigate to: " << origin->getName() << "\n"; + } else { + std::cout << "Original file no longer exist\n"; + } + } +}; + +namespace Client { +void clientCode(const FileSystem* fs) { + std::cout << "File name: " << fs->getName() << ", size: " << fs->size() + << "\n"; + fs->open(); +} +} // namespace Client + +void run() { + std::cout << "\n\n"; + FileSystem* root = new Folder("root"); + root->open(); + + // Prepare files + FileSystem* file1 = new File("file_name.txt"); + FileSystem* file2 = new ZipFile("zipfile_name.txt"); + FileSystem* file3 = new ShortCut(file2); + root->add(file1); + root->add(file2); + root->add(file3); + Client::clientCode(file1); + Client::clientCode(root); + + Folder* subFolder1 = new Folder("subFolder1"); + Folder* subFolder2 = new Folder("subFolder2"); + Folder* subFolder3 = new Folder("subFolder3"); + root->add(subFolder1); + root->add(subFolder2); + root->add(subFolder3); + Client::clientCode(root); + + root->remove(file1); + delete file1; + + root->remove(file2); + delete file2; + + root->remove(file3); + delete file3; + + delete root; // deletes all files/subfolders inside recursively +} + +} // namespace CompositePattern + +} // namespace + +struct CompositeAutoRuner { + CompositeAutoRuner() { + std::cout << "\n--- Composite Pattern Example ---\n"; + Problem::run(); + CompositePattern::run(); + } }; static CompositeAutoRuner instance; \ No newline at end of file diff --git a/src/patterns/structural/Decorator.cpp b/src/patterns/structural/Decorator.cpp index 1d2cd4f..000a491 100644 --- a/src/patterns/structural/Decorator.cpp +++ b/src/patterns/structural/Decorator.cpp @@ -1,228 +1,196 @@ -// Decorator is a structural design pattern that lets you attach new behaviors to objects -// by placing these objects inside special wrapper objects that contain the behaviors. -// Appicability: -// (*) when you need to be able to assign extra behaviors to objects at runtime without breaking the code that uses these objects. -// (**) when it’s awkward or not possible to extend an object’s behavior using inheritance. -// UML: docs/uml/patterns_structural_decorator.drawio.svg +// Decorator is a structural design pattern that lets you attach new behaviors +// to objects by placing these objects inside special wrapper objects that +// contain the behaviors. Appicability: +// (*) when you need to be able to assign extra behaviors to objects at +// runtime without breaking the code that uses these objects. +// (**) when it’s awkward or not possible to extend an object’s behavior using +// inheritance. UML: docs/uml/patterns_structural_decorator.drawio.svg +#include #include -#include #include -#include +#include + +namespace { +namespace Problem { + +class IComponent { + public: + virtual ~IComponent() {} + virtual std::string operation() const = 0; +}; + +class ConcreteComponent : public IComponent { + public: + std::string operation() const override { return "ConcreteComponent"; } +}; + +class ComponentWithA : public ConcreteComponent { + public: + std::string operation() const override { + return ConcreteComponent::operation() + " + FeatureA"; + } +}; + +class ComponentWithB : public ConcreteComponent { + public: + std::string operation() const override { + return ConcreteComponent::operation() + " + FeatureB"; + } +}; + +class ComponentWithAandB : public ConcreteComponent { + public: + std::string operation() const override { + return ConcreteComponent::operation() + " + FeatureA + FeatureB"; + } +}; + +// [P1] +// If you have 3 features , e.g FeatureC -> many combinations +// If you have 5 features -> 32 subclasses + +namespace Client { +void clientCode(const IComponent& comp) { + std::cout << comp.operation() << "\n"; +} +} // namespace Client + +void run() { + std::cout << "\n\nProblem\n"; + IComponent* simple = new ConcreteComponent; + Client::clientCode(*simple); + + IComponent* withA = new ComponentWithA; + Client::clientCode(*withA); + + IComponent* withB = new ComponentWithB; + Client::clientCode(*withB); + + IComponent* withAB = new ComponentWithAandB; + Client::clientCode(*withAB); + + delete simple; + delete withA; + delete withB; + delete withAB; +} + +} // namespace Problem + +namespace DecoratorPattern { +class IComponent { + public: + virtual ~IComponent() {} + virtual std::string operation() const = 0; +}; + +/** + * Concrete Component + * - is a class of objects being wrapped. + * - defines the basic behavior, which can be altered by decorators. + */ +class ConcreteComponent : public IComponent { + public: + std::string operation() const override { return "ConcreteComponent"; } +}; -namespace -{ - namespace Problem - { - - class IComponent - { - public: - virtual ~IComponent() {} - virtual std::string operation() const = 0; - }; - - class ConcreteComponent : public IComponent - { - public: - std::string operation() const override - { - return "ConcreteComponent"; - } - }; - - class ComponentWithA : public ConcreteComponent - { - public: - std::string operation() const override - { - return ConcreteComponent::operation() + " + FeatureA"; - } - }; - - class ComponentWithB : public ConcreteComponent - { - public: - std::string operation() const override - { - return ConcreteComponent::operation() + " + FeatureB"; - } - }; - - class ComponentWithAandB : public ConcreteComponent - { - public: - std::string operation() const override - { - return ConcreteComponent::operation() + " + FeatureA + FeatureB"; - } - }; - - // [P1] - // If you have 3 features , e.g FeatureC -> many combinations - // If you have 5 features -> 32 subclasses - - namespace Client - { - void clientCode(const IComponent &comp) - { - std::cout << comp.operation() << "\n"; - } - } - - void run() - { - std::cout << "\n\nProblem\n"; - IComponent *simple = new ConcreteComponent; - Client::clientCode(*simple); - - IComponent *withA = new ComponentWithA; - Client::clientCode(*withA); - - IComponent *withB = new ComponentWithB; - Client::clientCode(*withB); - - IComponent *withAB = new ComponentWithAandB; - Client::clientCode(*withAB); - - delete simple; - delete withA; - delete withB; - delete withAB; - } - - } - - namespace DecoratorPattern - { - class IComponent - { - public: - virtual ~IComponent() {} - virtual std::string operation() const = 0; - }; - - /** - * Concrete Component - * - is a class of objects being wrapped. - * - defines the basic behavior, which can be altered by decorators. - */ - class ConcreteComponent : public IComponent - { - public: - std::string operation() const override - { - return "ConcreteComponent"; - } - }; - - /** - * The base Decorator class follows the same interface as the other components. - * - has a field for referencing a wrapped object. - * - the field’s type should be declared as the component interface so it can contain both concrete components and decorators. - * - the base decorator delegates all operations to the wrapped object. - */ - class BaseDecorator : public IComponent - { - protected: - IComponent *m_component; - - public: - explicit BaseDecorator(IComponent *component) : m_component{component} {} - - /** - * The Decorator delegates all work to the wrapped component. - */ - std::string operation() const override - { - return m_component->operation(); - } - }; - - /** - * Concrete Decorators : - * - call the wrapped object and alter its result in some way. - * - define extra behaviors that can be added to components dynamically. - * - override methods of the base decorator and execute their behavior either before - * or after calling the parent method. - */ - class ConcreteDecoratorA : public BaseDecorator - { - public: - explicit ConcreteDecoratorA(IComponent *component) : BaseDecorator{component} {} - - std::string operation() const override - { - return BaseDecorator::operation() + " + FeatureA"; - } - }; - - class ConcreteDecoratorB : public BaseDecorator - { - public: - explicit ConcreteDecoratorB(IComponent *component) : BaseDecorator{component} {} - - std::string operation() const override - { - return BaseDecorator::operation() + " + FeatureB"; - } - }; - - class ConcreteDecoratorC : public BaseDecorator - { - public: - explicit ConcreteDecoratorC(IComponent *component) : BaseDecorator{component} {} - - std::string operation() const override - { - return BaseDecorator::operation() + " + FeatureC"; - } - }; - - namespace Client - { - void clientCode(const IComponent &comp) - { - std::cout << comp.operation() << "\n"; - } - } - - void run() - { - std::cout << "\n\nDecorator\n"; - IComponent *simple = new ConcreteComponent; - Client::clientCode(*simple); - - IComponent *withA = new ConcreteDecoratorA(simple); - Client::clientCode(*withA); - - IComponent *withB = new ConcreteDecoratorB(simple); - Client::clientCode(*withB); - - IComponent *withAB = new ConcreteDecoratorB(withA); - Client::clientCode(*withAB); - - IComponent *withABC = new ConcreteDecoratorC(withAB); - Client::clientCode(*withABC); - - delete simple; - delete withA; - delete withB; - delete withAB; - delete withABC; - } - } +/** + * The base Decorator class follows the same interface as the other components. + * - has a field for referencing a wrapped object. + * - the field’s type should be declared as the component interface so it can + * contain both concrete components and decorators. + * - the base decorator delegates all operations to the wrapped object. + */ +class BaseDecorator : public IComponent { + protected: + IComponent* m_component; + + public: + explicit BaseDecorator(IComponent* component) : m_component{component} {} + + /** + * The Decorator delegates all work to the wrapped component. + */ + std::string operation() const override { return m_component->operation(); } +}; + +/** + * Concrete Decorators : + * - call the wrapped object and alter its result in some way. + * - define extra behaviors that can be added to components dynamically. + * - override methods of the base decorator and execute their behavior either + * before or after calling the parent method. + */ +class ConcreteDecoratorA : public BaseDecorator { + public: + explicit ConcreteDecoratorA(IComponent* component) + : BaseDecorator{component} {} + + std::string operation() const override { + return BaseDecorator::operation() + " + FeatureA"; + } +}; + +class ConcreteDecoratorB : public BaseDecorator { + public: + explicit ConcreteDecoratorB(IComponent* component) + : BaseDecorator{component} {} + + std::string operation() const override { + return BaseDecorator::operation() + " + FeatureB"; + } +}; +class ConcreteDecoratorC : public BaseDecorator { + public: + explicit ConcreteDecoratorC(IComponent* component) + : BaseDecorator{component} {} + + std::string operation() const override { + return BaseDecorator::operation() + " + FeatureC"; + } +}; + +namespace Client { +void clientCode(const IComponent& comp) { + std::cout << comp.operation() << "\n"; } +} // namespace Client + +void run() { + std::cout << "\n\nDecorator\n"; + IComponent* simple = new ConcreteComponent; + Client::clientCode(*simple); + + IComponent* withA = new ConcreteDecoratorA(simple); + Client::clientCode(*withA); + + IComponent* withB = new ConcreteDecoratorB(simple); + Client::clientCode(*withB); + + IComponent* withAB = new ConcreteDecoratorB(withA); + Client::clientCode(*withAB); + + IComponent* withABC = new ConcreteDecoratorC(withAB); + Client::clientCode(*withABC); + + delete simple; + delete withA; + delete withB; + delete withAB; + delete withABC; +} +} // namespace DecoratorPattern + +} // namespace -struct DecoratorAutoRuner -{ - DecoratorAutoRuner() - { - std::cout << "\n--- Decorator Pattern Example ---\n"; - Problem::run(); - DecoratorPattern::run(); - } +struct DecoratorAutoRuner { + DecoratorAutoRuner() { + std::cout << "\n--- Decorator Pattern Example ---\n"; + Problem::run(); + DecoratorPattern::run(); + } }; static DecoratorAutoRuner instance; \ No newline at end of file diff --git a/src/patterns/structural/Facade.cpp b/src/patterns/structural/Facade.cpp index 6f708e2..3a5534f 100644 --- a/src/patterns/structural/Facade.cpp +++ b/src/patterns/structural/Facade.cpp @@ -1,274 +1,220 @@ // cppcheck-suppress-file [functionStatic] -// Facade is a structural design pattern that provides a simplified interface +// Facade is a structural design pattern that provides a simplified interface // to a library, a framework, or any other complex set of classes. // Appicability: -// (*) when you need to have a limited but straightforward interface to a complex subsystem. +// (*) when you need to have a limited but straightforward interface to a +// complex subsystem. // (**) when you want to structure a subsystem into layers. // UML: docs/uml/patterns_structural_facade.drawio.svg #include -namespace -{ - namespace Problem - { +namespace { +namespace Problem { - class AuthSubSystem - { - public: - virtual ~AuthSubSystem() = default; - void login() const - { - std::cout << "AuthSubSystem: login\n"; - } - }; - - class ValidatorSubSystem - { - public: - virtual ~ValidatorSubSystem() = default; - virtual void check() const - { - std::cout << "ValidatorSubSystem: check input\n"; - } - }; - - class LoggerSubSystem - { - public: - virtual ~LoggerSubSystem() = default; - void write() const - { - std::cout << "LoggerSubSystem: write log\n"; - } - }; - - class BackendSubSystem - { - public: - virtual ~BackendSubSystem() = default; - void send() const - { - std::cout << "BackendSubSystem: send request\n"; - } - }; - - namespace Client1 - { - // The client must manually interact with each subsystem. - // This creates unnecessary complexity. - void clientCode(const ValidatorSubSystem &s1, const AuthSubSystem &s2, const LoggerSubSystem &s3, const BackendSubSystem &s4) - { - s1.check(); - s2.login(); - s3.write(); - s4.send(); - - // In real code, the client must also - // - know call ordering - // - combine subsystem operations - // - manage lifecycle and error handling - } - } - - namespace Client2 - { - // The client must manually interact with each subsystem. - // This creates unnecessary complexity. - void clientCode(const ValidatorSubSystem &s1) - { - s1.check(); - } - } - - void run() - { - std::cout << "\n\nProblem\n"; - - ValidatorSubSystem v; - AuthSubSystem a; - LoggerSubSystem l; - BackendSubSystem b; +class AuthSubSystem { + public: + virtual ~AuthSubSystem() = default; + void login() const { std::cout << "AuthSubSystem: login\n"; } +}; - Client1::clientCode(v, a, l, b); - Client2::clientCode(v); - } - } +class ValidatorSubSystem { + public: + virtual ~ValidatorSubSystem() = default; + virtual void check() const { + std::cout << "ValidatorSubSystem: check input\n"; + } +}; - namespace Facade - { +class LoggerSubSystem { + public: + virtual ~LoggerSubSystem() = default; + void write() const { std::cout << "LoggerSubSystem: write log\n"; } +}; - class AuthSubSystem - { - public: - virtual ~AuthSubSystem() = default; - void login() const - { - std::cout << "AuthSubSystem: login\n"; - } - }; +class BackendSubSystem { + public: + virtual ~BackendSubSystem() = default; + void send() const { std::cout << "BackendSubSystem: send request\n"; } +}; - class ValidatorSubSystem - { - public: - virtual ~ValidatorSubSystem() = default; - void check() const - { - std::cout << "ValidatorSubSystem: check input\n"; - } - }; +namespace Client1 { +// The client must manually interact with each subsystem. +// This creates unnecessary complexity. +void clientCode(const ValidatorSubSystem& s1, const AuthSubSystem& s2, + const LoggerSubSystem& s3, const BackendSubSystem& s4) { + s1.check(); + s2.login(); + s3.write(); + s4.send(); + + // In real code, the client must also + // - know call ordering + // - combine subsystem operations + // - manage lifecycle and error handling +} +} // namespace Client1 - class LoggerSubSystem - { - public: - virtual ~LoggerSubSystem() = default; - void write() const - { - std::cout << "LoggerSubSystem: write log\n"; - } - }; +namespace Client2 { +// The client must manually interact with each subsystem. +// This creates unnecessary complexity. +void clientCode(const ValidatorSubSystem& s1) { + s1.check(); +} +} // namespace Client2 - class BackendSubSystem - { - public: - virtual ~BackendSubSystem() = default; - virtual void send() const - { - std::cout << "BackendSubSystem: send request\n"; - } - }; +void run() { + std::cout << "\n\nProblem\n"; - // ======================= - // Mock subclasses (for testing) - // ======================= - class MockBackendSubSystem : public BackendSubSystem - { - public: - void send() const override - { - std::cout << "[MockBackend] fake-send\n"; - } - }; + ValidatorSubSystem v; + AuthSubSystem a; + LoggerSubSystem l; + BackendSubSystem b; - /** - * The Facade class provides a simple interface to the complex logic of one or - * several subsystems. The Facade delegates the client requests to the - * appropriate objects within the subsystem. The Facade is also responsible for - * managing their lifecycle. All of this shields the client from the undesired - * complexity of the subsystem. - */ - class RequestFacade - { - protected: - const AuthSubSystem *auth_; - const ValidatorSubSystem *validator_; - const LoggerSubSystem *logger_; - const BackendSubSystem *backend_; + Client1::clientCode(v, a, l, b); + Client2::clientCode(v); +} +} // namespace Problem - /** - * Depending on your application's needs, you can provide the Facade with - * existing subsystem objects or force the Facade to create them on its own. - */ - public: - /** - * In this case we will delegate the memory ownership to Facade Class - */ - explicit RequestFacade(const AuthSubSystem *s1 = nullptr, - const ValidatorSubSystem *s2 = nullptr, - const LoggerSubSystem *s3 = nullptr, - const BackendSubSystem *s4 = nullptr) - { - this->auth_ = s1 ?: new AuthSubSystem; - this->validator_ = s2 ?: new ValidatorSubSystem; - this->logger_ = s3 ?: new LoggerSubSystem; - this->backend_ = s4 ?: new BackendSubSystem; - } +namespace Facade { - ~RequestFacade() - { - delete auth_; - delete validator_; - delete logger_; - delete backend_; - } +class AuthSubSystem { + public: + virtual ~AuthSubSystem() = default; + void login() const { std::cout << "AuthSubSystem: login\n"; } +}; - /** - * The Facade's methods are convenient shortcuts to the sophisticated - * functionality of the subsystems. However, clients get only to a fraction of - * a subsystem's capabilities. - */ - void sendRequest() const - { - validator_->check(); - auth_->login(); - logger_->write(); - backend_->send(); - } +class ValidatorSubSystem { + public: + virtual ~ValidatorSubSystem() = default; + void check() const { std::cout << "ValidatorSubSystem: check input\n"; } +}; - void validate() const - { - validator_->check(); - } - }; +class LoggerSubSystem { + public: + virtual ~LoggerSubSystem() = default; + void write() const { std::cout << "LoggerSubSystem: write log\n"; } +}; - namespace Client1 - { - /** - * The client code works with complex subsystems through a simple interface - * provided by the Facade. When a facade manages the lifecycle of the subsystem, - * the client might not even know about the existence of the subsystem. This - * approach lets you keep the complexity under control. - */ - void clientCode(const RequestFacade &facade) - { - facade.sendRequest(); - } - } +class BackendSubSystem { + public: + virtual ~BackendSubSystem() = default; + virtual void send() const { std::cout << "BackendSubSystem: send request\n"; } +}; - namespace Client2 - { - /** - * The client code works with complex subsystems through a simple interface - * provided by the Facade. When a facade manages the lifecycle of the subsystem, - * the client might not even know about the existence of the subsystem. This - * approach lets you keep the complexity under control. - */ - void clientCode(const RequestFacade &facade) - { - facade.validate(); - } - } +// ======================= +// Mock subclasses (for testing) +// ======================= +class MockBackendSubSystem : public BackendSubSystem { + public: + void send() const override { std::cout << "[MockBackend] fake-send\n"; } +}; - void run() - { - std::cout << "\n\nFacade\n"; - { - RequestFacade *facade = new RequestFacade(); - Client1::clientCode(*facade); - Client2::clientCode(*facade); - delete facade; - } +/** + * The Facade class provides a simple interface to the complex logic of one or + * several subsystems. The Facade delegates the client requests to the + * appropriate objects within the subsystem. The Facade is also responsible for + * managing their lifecycle. All of this shields the client from the undesired + * complexity of the subsystem. + */ +class RequestFacade { + protected: + const AuthSubSystem* auth_; + const ValidatorSubSystem* validator_; + const LoggerSubSystem* logger_; + const BackendSubSystem* backend_; + + /** + * Depending on your application's needs, you can provide the Facade with + * existing subsystem objects or force the Facade to create them on its own. + */ + public: + /** + * In this case we will delegate the memory ownership to Facade Class + */ + explicit RequestFacade(const AuthSubSystem* s1 = nullptr, + const ValidatorSubSystem* s2 = nullptr, + const LoggerSubSystem* s3 = nullptr, + const BackendSubSystem* s4 = nullptr) { + this->auth_ = s1 ?: new AuthSubSystem; + this->validator_ = s2 ?: new ValidatorSubSystem; + this->logger_ = s3 ?: new LoggerSubSystem; + this->backend_ = s4 ?: new BackendSubSystem; + } + + ~RequestFacade() { + delete auth_; + delete validator_; + delete logger_; + delete backend_; + } + + /** + * The Facade's methods are convenient shortcuts to the sophisticated + * functionality of the subsystems. However, clients get only to a fraction of + * a subsystem's capabilities. + */ + void sendRequest() const { + validator_->check(); + auth_->login(); + logger_->write(); + backend_->send(); + } + + void validate() const { validator_->check(); } +}; - { - // injected subsystems for mocktest - std::cout << "\n"; - const MockBackendSubSystem *b = new MockBackendSubSystem(); - RequestFacade *facade = new RequestFacade(nullptr, nullptr, nullptr, b); - Client1::clientCode(*facade); - delete facade; - } - } - } +namespace Client1 { +/** + * The client code works with complex subsystems through a simple interface + * provided by the Facade. When a facade manages the lifecycle of the subsystem, + * the client might not even know about the existence of the subsystem. This + * approach lets you keep the complexity under control. + */ +void clientCode(const RequestFacade& facade) { + facade.sendRequest(); } - -struct FacadeAutoRuner -{ - FacadeAutoRuner() - { - std::cout << "\n--- Facade Pattern Example ---\n"; - Problem::run(); - Facade::run(); - } +} // namespace Client1 + +namespace Client2 { +/** + * The client code works with complex subsystems through a simple interface + * provided by the Facade. When a facade manages the lifecycle of the subsystem, + * the client might not even know about the existence of the subsystem. This + * approach lets you keep the complexity under control. + */ +void clientCode(const RequestFacade& facade) { + facade.validate(); +} +} // namespace Client2 + +void run() { + std::cout << "\n\nFacade\n"; + { + RequestFacade* facade = new RequestFacade(); + Client1::clientCode(*facade); + Client2::clientCode(*facade); + delete facade; + } + + { + // injected subsystems for mocktest + std::cout << "\n"; + const MockBackendSubSystem* b = new MockBackendSubSystem(); + RequestFacade* facade = new RequestFacade(nullptr, nullptr, nullptr, b); + Client1::clientCode(*facade); + delete facade; + } +} +} // namespace Facade +} // namespace + +struct FacadeAutoRuner { + FacadeAutoRuner() { + std::cout << "\n--- Facade Pattern Example ---\n"; + Problem::run(); + Facade::run(); + } }; static FacadeAutoRuner instance; \ No newline at end of file diff --git a/src/patterns/structural/Flyweight.cpp b/src/patterns/structural/Flyweight.cpp index eaa6a9d..5c5f363 100644 --- a/src/patterns/structural/Flyweight.cpp +++ b/src/patterns/structural/Flyweight.cpp @@ -1,369 +1,338 @@ // Flyweight is a structural design pattern that lets you fit more objects -// into the available amount of RAM by sharing common parts of state between multiple objects +// into the available amount of RAM by sharing common parts of state between +// multiple objects // instead of keeping all of the data in each object. // Appicability: -// (*) when your program must support a huge number of objects which barely fit into available RAM, -// and the objects contain duplicate states which can be extracted and shared between multiple objects +// (*) when your program must support a huge number of objects which barely fit +// into available RAM, +// and the objects contain duplicate states which can be extracted and +// shared between multiple objects // UML: docs/uml/patterns_structural_flyweight.drawio.svg #include #include #include -namespace -{ - namespace Problem - { - class ImageContext - { - protected: - private: - // Shared attrs - std::string fileName_; - int width_, height_, dpi_; - int *data_; - - // Unique attrs - float scale_; - float opacity_; - - void deepCopy(const ImageContext &other) - { - // normal field - this->fileName_ = other.fileName_; - this->width_ = other.width_; - this->height_ = other.height_; - this->dpi_ = other.dpi_; - this->scale_ = other.scale_; - this->opacity_ = other.opacity_; - - // ptr field - delete[] this->data_; - if (other.data_ != nullptr) - { - int size = this->width_ * this->height_; - this->data_ = new int[size]; - for (int i = 0; i < size; ++i) - { - this->data_[i] = other.data_[i]; - } - } - else - { - this->data_ = nullptr; - } - } - - public: - // Role of five - // CONSTRUCTOR - explicit ImageContext(const std::string &fileName, - int w = 8, int h = 8, - int dpi = 96, - float scale = 1, float opacity = 1) : fileName_{fileName}, - width_{w}, height_{h}, - dpi_{dpi}, - data_{new int[w * h]}, - scale_{scale}, opacity_{opacity} - { - std::cout << "Image: " << fileName_ << " created.\n"; - } - - // DESTRUCTOR - virtual ~ImageContext() - { - delete[] data_; - } - - // COPY CONSTRUCTOR - ImageContext(const ImageContext &other) - { - this->deepCopy(other); - } - - // COPY ASSIGNMENT - ImageContext &operator=(const ImageContext &other) - { - if (this != &other) - { - this->deepCopy(other); - } - return *this; - } - - void setScale(const float scale) - { - this->scale_ = scale; - } - - void setOpacity(const float opacity) - { - this->opacity_ = opacity; - } - - void display() const - { - std::cout << "Image: " << fileName_ << " displayed. \n"; - std::cout << "\tWidth: " << width_ << " Height: " << height_ << " DPI: " << dpi_ << "\n"; - std::cout << "\tScale: " << scale_ << " Opacity: " << opacity_ << "\n"; - } - }; - - namespace Client - { - void clientCode(const ImageContext *img) - { - img->display(); - } - } - - void run() - { - std::cout << "\n\nProblem\n"; - ImageContext **imgs = new ImageContext *[5]; - for (int i = 0; i < 5; i++) - { - imgs[i] = new ImageContext("img1.svg"); - imgs[i]->setOpacity(1 - (float)1 / (i + 1)); - imgs[i]->setScale((float)1 / (i + 1)); - Client::clientCode(imgs[i]); - } - - // delete objects first - for (int i = 0; i < 5; i++) - { - delete imgs[i]; - } - - // then delete array - delete[] imgs; - std::cout << "Size of objects: " << sizeof(ImageContext) * 5 << "\n"; - } +namespace { +namespace Problem { +class ImageContext { + protected: + private: + // Shared attrs + std::string fileName_; + int width_, height_, dpi_; + int* data_; + + // Unique attrs + float scale_; + float opacity_; + + void deepCopy(const ImageContext& other) { + // normal field + this->fileName_ = other.fileName_; + this->width_ = other.width_; + this->height_ = other.height_; + this->dpi_ = other.dpi_; + this->scale_ = other.scale_; + this->opacity_ = other.opacity_; + + // ptr field + delete[] this->data_; + if (other.data_ != nullptr) { + int size = this->width_ * this->height_; + this->data_ = new int[size]; + for (int i = 0; i < size; ++i) { + this->data_[i] = other.data_[i]; + } + } else { + this->data_ = nullptr; } + } + + public: + // Role of five + // CONSTRUCTOR + explicit ImageContext(const std::string& fileName, int w = 8, int h = 8, + int dpi = 96, float scale = 1, float opacity = 1) + : fileName_{fileName}, + width_{w}, + height_{h}, + dpi_{dpi}, + data_{new int[w * h]}, + scale_{scale}, + opacity_{opacity} { + std::cout << "Image: " << fileName_ << " created.\n"; + } + + // DESTRUCTOR + virtual ~ImageContext() { delete[] data_; } + + // COPY CONSTRUCTOR + ImageContext(const ImageContext& other) { this->deepCopy(other); } + + // COPY ASSIGNMENT + ImageContext& operator=(const ImageContext& other) { + if (this != &other) { + this->deepCopy(other); + } + return *this; + } - namespace FlyweightPattern - { - - /** - * The Flyweight stores a common portion of the state (also called intrinsic - * state) that belongs to multiple real business entities. - * The Flyweight accepts the rest of the state (extrinsic state, unique for each entity) via its - * method parameters. - */ - class ImageFlyweight - { - private: - std::string fileName_; - int width_, height_, dpi_; - int *data_; - - void deepCopy(const ImageFlyweight &other) - { - // normal field - this->fileName_ = other.fileName_; - this->width_ = other.width_; - this->height_ = other.height_; - this->dpi_ = other.dpi_; - - // ptr field - delete[] this->data_; - if (other.data_ != nullptr) - { - int size = this->width_ * this->height_; - this->data_ = new int[size]; - for (int i = 0; i < size; ++i) - { - this->data_[i] = other.data_[i]; - } - } - else - { - this->data_ = nullptr; - } - } - - public: - explicit ImageFlyweight(const std::string &fileName, int w = 8, int h = 8, int dpi = 96) : fileName_{fileName}, width_{w}, height_{h}, dpi_{dpi}, data_{new int[w * h]} {}; - - ~ImageFlyweight() - { - delete[] data_; - } - - // Note: - // [Copy constructor/assignment]: creates a new copy(shallow or deep) of the source object's data. - // -> Allocates new memory and duplicates all elements. - // -> Source object remains unchanged. - // - // [Move constructor/assignment[] ("steal"): transfers ownership of the source's resources - // -> No new memory allocation; destination uses the same memory as source. - // -> Source is reset to a safe empty state (nullptr, 0, etc.). - // ==> Much faster for large objects since no deep copy occurs. - - ImageFlyweight(const ImageFlyweight &other) - { - this->deepCopy(other); - } - - ImageFlyweight &operator=(const ImageFlyweight &other) - { - if (this != &other) - { - this->deepCopy(other); - } - return *this; - } - - ImageFlyweight(ImageFlyweight &&source) noexcept - : fileName_(std::move(source.fileName_)), - width_(source.width_), - height_(source.height_), - dpi_(source.dpi_), - data_(source.data_) - { - source.width_ = 0; - source.height_ = 0; - source.dpi_ = 0; - source.data_ = nullptr; - } - - ImageFlyweight &operator=(ImageFlyweight &&source) noexcept - { - if (this != &source) - { - // stealSource - // free current resource - delete[] data_; - - // move resource from source - fileName_ = std::move(source.fileName_); // move string - width_ = source.width_; - height_ = source.height_; - dpi_ = source.dpi_; - data_ = source.data_; - - // reset source - source.width_ = 0; - source.height_ = 0; - source.dpi_ = 0; - source.data_ = nullptr; - } - return *this; - } - - /** - * Display the image at give scale, opacity - */ - void display(const float &scale, const float &opacity) const - { - std::cout << "Image: " << fileName_ << " displayed. \n"; - std::cout << "\tWidth: " << width_ << " Height: " << height_ << " DPI: " << dpi_ << "\n"; - std::cout << "\tScale: " << scale << " Opacity: " << opacity << "\n"; - } - }; - - /** - * The Flyweight Factory manages a pool of existing flyweights. - * With the factory, clients don’t create flyweights directly. Instead, they call the factory, passing it bits of the intrinsic state of the desired flyweight. - * The factory looks over previously created flyweights and either returns an existing one that matches search criteria or creates a new one if nothing is found. - */ - class ImageFlyweightFactory - { - private: - std::unordered_map m_imageFlyweights; // unordered_map => need to role of five - static std::string getKey(const std::string &name, int w, int h, int dpi) - { - return "key_" + name + std::to_string(w * h * dpi); - } - - public: - ImageFlyweight &getImangeFlyweight(const std::string &fileName, int w = 8, int h = 8, int dpi = 96) - { - std::string key = getKey(fileName, w, h, dpi); - std::cout << "Key: " << key << "\n"; - if (this->m_imageFlyweights.find(key) == this->m_imageFlyweights.end()) - { - std::cout << "ImageFlyweightFactory: Can't find a image flyweight, creating new one.\n"; - this->m_imageFlyweights.insert(std::make_pair(key, ImageFlyweight(fileName, w, h, dpi))); - } - else - { - std::cout << "ImageFlyweightFactory: Reusing existing image flyweight.\n"; - } - return this->m_imageFlyweights.at(key); - } - }; - - /** - * The Context class contains the extrinsic state, unique across all original objects. - * When a context is paired with one of the flyweight objects, it represents the full state of the original object. - */ - class ImageContext - { - private: - float scale_; - float opacity_; - ImageFlyweight *m_flyweight; - - public: - ImageContext(ImageFlyweight &imageFlyweight, const float &s, const float &o) : scale_{s}, opacity_{o}, m_flyweight{&imageFlyweight} - { - } - - void display() const - { - this->m_flyweight->display(scale_, opacity_); - } - }; - - /** - * The Client calculates or stores the extrinsic state of flyweights. - * From the client’s perspective, a flyweight is a template object which can be configured at runtime by passing some contextual data into parameters of its methods. - */ - namespace Client - { - void clientCode(const ImageContext *img) - { - img->display(); - } - } - - void run() - { - std::cout << "\n\nFlyweight\n"; - // Regis the images to the factory - ImageFlyweightFactory *imageRegister = new ImageFlyweightFactory(); - ImageContext *img1 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.1); - Client::clientCode(img1); - ImageContext *img2 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.2); - Client::clientCode(img2); - ImageContext *img3 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.3); - Client::clientCode(img3); - ImageContext *img4 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.4); - Client::clientCode(img4); - ImageContext *img5 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.5); - Client::clientCode(img5); - std::cout << "Size of objects: " << sizeof(ImageFlyweightFactory) + sizeof(ImageContext) * 5 << "\n"; - - delete img1; - delete img2; - delete img3; - delete img4; - delete img5; - delete imageRegister; - } + void setScale(const float scale) { this->scale_ = scale; } - } + void setOpacity(const float opacity) { this->opacity_ = opacity; } + + void display() const { + std::cout << "Image: " << fileName_ << " displayed. \n"; + std::cout << "\tWidth: " << width_ << " Height: " << height_ + << " DPI: " << dpi_ << "\n"; + std::cout << "\tScale: " << scale_ << " Opacity: " << opacity_ << "\n"; + } +}; + +namespace Client { +void clientCode(const ImageContext* img) { + img->display(); } +} // namespace Client + +void run() { + std::cout << "\n\nProblem\n"; + ImageContext** imgs = new ImageContext*[5]; + for (int i = 0; i < 5; i++) { + imgs[i] = new ImageContext("img1.svg"); + imgs[i]->setOpacity(1 - (float)1 / (i + 1)); + imgs[i]->setScale((float)1 / (i + 1)); + Client::clientCode(imgs[i]); + } + + // delete objects first + for (int i = 0; i < 5; i++) { + delete imgs[i]; + } + + // then delete array + delete[] imgs; + std::cout << "Size of objects: " << sizeof(ImageContext) * 5 << "\n"; +} +} // namespace Problem + +namespace FlyweightPattern { + +/** + * The Flyweight stores a common portion of the state (also called intrinsic + * state) that belongs to multiple real business entities. + * The Flyweight accepts the rest of the state (extrinsic state, unique for each + * entity) via its method parameters. + */ +class ImageFlyweight { + private: + std::string fileName_; + int width_, height_, dpi_; + int* data_; + + void deepCopy(const ImageFlyweight& other) { + // normal field + this->fileName_ = other.fileName_; + this->width_ = other.width_; + this->height_ = other.height_; + this->dpi_ = other.dpi_; + + // ptr field + delete[] this->data_; + if (other.data_ != nullptr) { + int size = this->width_ * this->height_; + this->data_ = new int[size]; + for (int i = 0; i < size; ++i) { + this->data_[i] = other.data_[i]; + } + } else { + this->data_ = nullptr; + } + } + + public: + explicit ImageFlyweight(const std::string& fileName, int w = 8, int h = 8, + int dpi = 96) + : fileName_{fileName}, + width_{w}, + height_{h}, + dpi_{dpi}, + data_{new int[w * h]} {}; + + ~ImageFlyweight() { delete[] data_; } + + // Note: + // [Copy constructor/assignment]: creates a new copy(shallow or deep) of the + // source object's data. + // -> Allocates new memory and duplicates all elements. + // -> Source object remains unchanged. + // + // [Move constructor/assignment[] ("steal"): transfers ownership of the + // source's resources + // -> No new memory allocation; destination uses the + // same memory as source. + // -> Source is reset to a safe empty state (nullptr, + // 0, etc.). + // ==> Much faster for large objects since no deep copy + // occurs. + + ImageFlyweight(const ImageFlyweight& other) { this->deepCopy(other); } + + ImageFlyweight& operator=(const ImageFlyweight& other) { + if (this != &other) { + this->deepCopy(other); + } + return *this; + } + + ImageFlyweight(ImageFlyweight&& source) noexcept + : fileName_(std::move(source.fileName_)), + width_(source.width_), + height_(source.height_), + dpi_(source.dpi_), + data_(source.data_) { + source.width_ = 0; + source.height_ = 0; + source.dpi_ = 0; + source.data_ = nullptr; + } + + ImageFlyweight& operator=(ImageFlyweight&& source) noexcept { + if (this != &source) { + // stealSource + // free current resource + delete[] data_; + + // move resource from source + fileName_ = std::move(source.fileName_); // move string + width_ = source.width_; + height_ = source.height_; + dpi_ = source.dpi_; + data_ = source.data_; + + // reset source + source.width_ = 0; + source.height_ = 0; + source.dpi_ = 0; + source.data_ = nullptr; + } + return *this; + } + + /** + * Display the image at give scale, opacity + */ + void display(const float& scale, const float& opacity) const { + std::cout << "Image: " << fileName_ << " displayed. \n"; + std::cout << "\tWidth: " << width_ << " Height: " << height_ + << " DPI: " << dpi_ << "\n"; + std::cout << "\tScale: " << scale << " Opacity: " << opacity << "\n"; + } +}; -struct FlyweightAutoRuner -{ - FlyweightAutoRuner() - { - std::cout << "\n--- Flyweight Pattern Example ---\n"; - Problem::run(); - FlyweightPattern::run(); +/** + * The Flyweight Factory manages a pool of existing flyweights. + * With the factory, clients don’t create flyweights directly. Instead, they + * call the factory, passing it bits of the intrinsic state of the desired + * flyweight. The factory looks over previously created flyweights and either + * returns an existing one that matches search criteria or creates a new one if + * nothing is found. + */ +class ImageFlyweightFactory { + private: + std::unordered_map + m_imageFlyweights; // unordered_map => need to role of five + static std::string getKey(const std::string& name, int w, int h, int dpi) { + return "key_" + name + std::to_string(w * h * dpi); + } + + public: + ImageFlyweight& getImangeFlyweight(const std::string& fileName, int w = 8, + int h = 8, int dpi = 96) { + std::string key = getKey(fileName, w, h, dpi); + std::cout << "Key: " << key << "\n"; + if (this->m_imageFlyweights.find(key) == this->m_imageFlyweights.end()) { + std::cout << "ImageFlyweightFactory: Can't find a image flyweight, " + "creating new one.\n"; + this->m_imageFlyweights.insert( + std::make_pair(key, ImageFlyweight(fileName, w, h, dpi))); + } else { + std::cout << "ImageFlyweightFactory: Reusing existing image flyweight.\n"; } + return this->m_imageFlyweights.at(key); + } +}; + +/** + * The Context class contains the extrinsic state, unique across all original + * objects. When a context is paired with one of the flyweight objects, it + * represents the full state of the original object. + */ +class ImageContext { + private: + float scale_; + float opacity_; + ImageFlyweight* m_flyweight; + + public: + ImageContext(ImageFlyweight& imageFlyweight, const float& s, const float& o) + : scale_{s}, opacity_{o}, m_flyweight{&imageFlyweight} {} + + void display() const { this->m_flyweight->display(scale_, opacity_); } +}; + +/** + * The Client calculates or stores the extrinsic state of flyweights. + * From the client’s perspective, a flyweight is a template object which can be + * configured at runtime by passing some contextual data into parameters of its + * methods. + */ +namespace Client { +void clientCode(const ImageContext* img) { + img->display(); +} +} // namespace Client + +void run() { + std::cout << "\n\nFlyweight\n"; + // Regis the images to the factory + ImageFlyweightFactory* imageRegister = new ImageFlyweightFactory(); + ImageContext* img1 = + new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.1); + Client::clientCode(img1); + ImageContext* img2 = + new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.2); + Client::clientCode(img2); + ImageContext* img3 = + new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.3); + Client::clientCode(img3); + ImageContext* img4 = + new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.4); + Client::clientCode(img4); + ImageContext* img5 = + new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.5); + Client::clientCode(img5); + std::cout << "Size of objects: " + << sizeof(ImageFlyweightFactory) + sizeof(ImageContext) * 5 << "\n"; + + delete img1; + delete img2; + delete img3; + delete img4; + delete img5; + delete imageRegister; +} + +} // namespace FlyweightPattern +} // namespace + +struct FlyweightAutoRuner { + FlyweightAutoRuner() { + std::cout << "\n--- Flyweight Pattern Example ---\n"; + Problem::run(); + FlyweightPattern::run(); + } }; static FlyweightAutoRuner instance; \ No newline at end of file diff --git a/src/patterns/structural/Proxy.cpp b/src/patterns/structural/Proxy.cpp index 3095e4a..2ce66f4 100644 --- a/src/patterns/structural/Proxy.cpp +++ b/src/patterns/structural/Proxy.cpp @@ -1,266 +1,228 @@ -// Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. -// A proxy controls access to the original object, allowing you to perform something -// either before or after the request gets through to the original object. -// A proxy receives client requests, does some work (access control, caching, etc.) and then passes the request to a service object. -// Appicability: -// (*) Lazy initialization (virtual proxy) / Logging requests (logging proxy) / Caching request results (caching proxy) -// (**) Access control (protection proxy). -// (***) Local execution of a remote service (remote proxy). This is when the service object is located on a remote server. +// Proxy is a structural design pattern that lets you provide a substitute or +// placeholder for another object. A proxy controls access to the original +// object, allowing you to perform something either before or after the request +// gets through to the original object. A proxy receives client requests, does +// some work (access control, caching, etc.) and then passes the request to a +// service object. Appicability: +// (*) Lazy initialization (virtual proxy) / Logging requests (logging proxy) +// / Caching request results (caching proxy) +// (**) Access control (protection proxy). +// (***) Local execution of a remote service (remote proxy). This is when the +// service object is located on a remote server. // Composition - lazy // Aggregation - x // UML: docs/uml/patterns_structural_proxy.drawio.svg #include -namespace -{ - namespace Problem - { - const std::string admin = "admin"; - - class IServer - { - public: - virtual ~IServer() = default; - virtual void request1() = 0; - virtual void request2() = 0; - virtual void request3() = 0; - }; - - class Server : public IServer - { - private: - std::string _id; - - public: - explicit Server(const std::string &id) : _id{id} - { - // [P1] Heavy or complex construction, so ideally should be lazy-loaded - std::cout << "[Server] Constructor: " << _id << "\n"; - } - - // [P2] Need access control - // [P3] Need to log requests without modifying the Server itself - void request1() override - { - if (_id != admin) - { - std::cout << "[Server] Invalid ID: " << _id << "\n"; - return; - } - std::cout << "[Server] Handling request-1 for: " << _id << "\n"; - } - - void request2() override - { - if (_id != admin) - { - std::cout << "[Server] Invalid ID: " << _id << "\n"; - return; - } - std::cout << "[Server] Handling request-2 for: " << _id << "\n"; - } - - void request3() override - { - if (_id != admin) - { - std::cout << "[Server] Invalid ID: " << _id << "\n"; - return; - } - std::cout << "[Server] Handling request-3 for: " << _id << "\n"; - } - }; - - namespace Client - { - void clientCode(IServer *s) - { - if (s != nullptr) - { - s->request1(); - s->request2(); - s->request3(); - } - } - } - - void run() - { - std::cout << "\n\n"; - - { - std::string connectionId = "admin"; - // [P4] The Server is constructed immediately even if we do not call any requests - IServer *server = new Server(connectionId); - std::cout << "User request\n"; - Client::clientCode(server); - delete server; - } - - { - // [P4] Server is constructed even for invalid ID, wasting resources - std::string invalidId = "xxx"; - Server *server = new Server(invalidId); - Client::clientCode(server); - delete server; - } - } +namespace { +namespace Problem { +const std::string admin = "admin"; + +class IServer { + public: + virtual ~IServer() = default; + virtual void request1() = 0; + virtual void request2() = 0; + virtual void request3() = 0; +}; + +class Server : public IServer { + private: + std::string _id; + + public: + explicit Server(const std::string& id) : _id{id} { + // [P1] Heavy or complex construction, so ideally should be lazy-loaded + std::cout << "[Server] Constructor: " << _id << "\n"; + } + + // [P2] Need access control + // [P3] Need to log requests without modifying the Server itself + void request1() override { + if (_id != admin) { + std::cout << "[Server] Invalid ID: " << _id << "\n"; + return; } + std::cout << "[Server] Handling request-1 for: " << _id << "\n"; + } - namespace ProxyPattern - { - const std::string admin = "admin"; - - class IServer - { - public: - virtual ~IServer() = default; - virtual void request1() = 0; - virtual void request2() = 0; - virtual void request3() = 0; - }; - - class Server : public IServer - { - private: - std::string _id; - - public: - explicit Server(const std::string &id) : _id{id} - { - std::cout << "[Server] Constructor: " << _id << "\n"; - } - - void request1() override - { - std::cout << "[Server] Handling request-1 for: " << _id << "\n"; - } - - void request2() override - { - std::cout << "[Server] Handling request-2 for: " << _id << "\n"; - } - - void request3() override - { - std::cout << "[Server] Handling request-3 for: " << _id << "\n"; - } - }; - - class ServerProxy : public IServer - { - private: - std::string _id; - Server *_server; - - bool checkAccess() - { - std::cout << "[Proxy] Checking access before forwarding request.\n"; - if (_id != admin) - { - return false; - } - - // Lazy initialization: construct Server only on first access - if (_server == nullptr) - { - _server = new Server(_id); - } - return true; - } - - void logAccess() const - { - std::cout << "[Proxy] Logging request time: " << _id << " .\n"; - } - - public: - explicit ServerProxy(const std::string &id) : _id{id}, _server{nullptr} - { - std::cout << "[Proxy] Constructor: " << _id << "\n"; - } - - ~ServerProxy() - { - std::cout << "[Proxy] Destructor: " << _id << "\n"; - if (_server != nullptr) - { - delete _server; - } - } - - void request1() override - { - if (checkAccess()) - { - _server->request1(); - logAccess(); - } - } - - void request2() override - { - if (checkAccess()) - { - _server->request2(); - logAccess(); - } - } - - void request3() override - { - if (checkAccess()) - { - _server->request3(); - logAccess(); - } - } - }; - - namespace Client - { - void clientCode(IServer *s) - { - if (s != nullptr) - { - s->request1(); - s->request2(); - s->request3(); - } - } - } - - void run() - { - std::cout << "\n\n"; - - { - std::string connectionId = "admin"; - // Server is not constructed until first request is made - IServer *server = new ServerProxy(connectionId); - std::cout << "User request\n"; - Client::clientCode(server); - delete server; - } - - // Server is not constructed if id is invalid - std::string invalidId = "xxx"; - Server *server = new Server(invalidId); - Client::clientCode(server); - delete server; - } + void request2() override { + if (_id != admin) { + std::cout << "[Server] Invalid ID: " << _id << "\n"; + return; } + std::cout << "[Server] Handling request-2 for: " << _id << "\n"; + } - struct ProxyAutoRunner - { - ProxyAutoRunner() - { - std::cout << "\n--- Proxy Pattern Example ---\n"; - Problem::run(); - ProxyPattern::run(); - } - }; - - static ProxyAutoRunner instance; + void request3() override { + if (_id != admin) { + std::cout << "[Server] Invalid ID: " << _id << "\n"; + return; + } + std::cout << "[Server] Handling request-3 for: " << _id << "\n"; + } +}; + +namespace Client { +void clientCode(IServer* s) { + if (s != nullptr) { + s->request1(); + s->request2(); + s->request3(); + } +} +} // namespace Client + +void run() { + std::cout << "\n\n"; + + { + std::string connectionId = "admin"; + // [P4] The Server is constructed immediately even if we do not call any + // requests + IServer* server = new Server(connectionId); + std::cout << "User request\n"; + Client::clientCode(server); + delete server; + } + + { + // [P4] Server is constructed even for invalid ID, wasting resources + std::string invalidId = "xxx"; + Server* server = new Server(invalidId); + Client::clientCode(server); + delete server; + } +} +} // namespace Problem + +namespace ProxyPattern { +const std::string admin = "admin"; + +class IServer { + public: + virtual ~IServer() = default; + virtual void request1() = 0; + virtual void request2() = 0; + virtual void request3() = 0; +}; + +class Server : public IServer { + private: + std::string _id; + + public: + explicit Server(const std::string& id) : _id{id} { + std::cout << "[Server] Constructor: " << _id << "\n"; + } + + void request1() override { + std::cout << "[Server] Handling request-1 for: " << _id << "\n"; + } + + void request2() override { + std::cout << "[Server] Handling request-2 for: " << _id << "\n"; + } + + void request3() override { + std::cout << "[Server] Handling request-3 for: " << _id << "\n"; + } +}; + +class ServerProxy : public IServer { + private: + std::string _id; + Server* _server; + + bool checkAccess() { + std::cout << "[Proxy] Checking access before forwarding request.\n"; + if (_id != admin) { + return false; + } + + // Lazy initialization: construct Server only on first access + if (_server == nullptr) { + _server = new Server(_id); + } + return true; + } + + void logAccess() const { + std::cout << "[Proxy] Logging request time: " << _id << " .\n"; + } + + public: + explicit ServerProxy(const std::string& id) : _id{id}, _server{nullptr} { + std::cout << "[Proxy] Constructor: " << _id << "\n"; + } + + ~ServerProxy() { + std::cout << "[Proxy] Destructor: " << _id << "\n"; + if (_server != nullptr) { + delete _server; + } + } + + void request1() override { + if (checkAccess()) { + _server->request1(); + logAccess(); + } + } + + void request2() override { + if (checkAccess()) { + _server->request2(); + logAccess(); + } + } + + void request3() override { + if (checkAccess()) { + _server->request3(); + logAccess(); + } + } +}; + +namespace Client { +void clientCode(IServer* s) { + if (s != nullptr) { + s->request1(); + s->request2(); + s->request3(); + } +} +} // namespace Client + +void run() { + std::cout << "\n\n"; + + { + std::string connectionId = "admin"; + // Server is not constructed until first request is made + IServer* server = new ServerProxy(connectionId); + std::cout << "User request\n"; + Client::clientCode(server); + delete server; + } + + // Server is not constructed if id is invalid + std::string invalidId = "xxx"; + Server* server = new Server(invalidId); + Client::clientCode(server); + delete server; } +} // namespace ProxyPattern + +struct ProxyAutoRunner { + ProxyAutoRunner() { + std::cout << "\n--- Proxy Pattern Example ---\n"; + Problem::run(); + ProxyPattern::run(); + } +}; + +static ProxyAutoRunner instance; +} // namespace diff --git a/tests/DeleteMeTest.cpp b/tests/DeleteMeTest.cpp index 59ebdba..77870d4 100644 --- a/tests/DeleteMeTest.cpp +++ b/tests/DeleteMeTest.cpp @@ -1,50 +1,50 @@ #include "DeleteMe.h" + #include TEST(AddTest, ValidNumbers) { - DeleteMe calc = DeleteMe(); + DeleteMe calc = DeleteMe(); - ASSERT_TRUE(calc.add(1, 2)); - ASSERT_EQ(3, calc.get_last_result()); + ASSERT_TRUE(calc.add(1, 2)); + ASSERT_EQ(3, calc.get_last_result()); - ASSERT_TRUE(calc.add(0, 5)); - ASSERT_EQ(5, calc.get_last_result()); + ASSERT_TRUE(calc.add(0, 5)); + ASSERT_EQ(5, calc.get_last_result()); - ASSERT_TRUE(calc.add(5, 0)); - ASSERT_EQ(5, calc.get_last_result()); + ASSERT_TRUE(calc.add(5, 0)); + ASSERT_EQ(5, calc.get_last_result()); - ASSERT_TRUE(calc.add(4, -6)); - ASSERT_EQ(-2, calc.get_last_result()); + ASSERT_TRUE(calc.add(4, -6)); + ASSERT_EQ(-2, calc.get_last_result()); - ASSERT_TRUE(calc.add(-4, 6)); - ASSERT_EQ(2, calc.get_last_result()); + ASSERT_TRUE(calc.add(-4, 6)); + ASSERT_EQ(2, calc.get_last_result()); - ASSERT_TRUE(calc.add(-4, -6)); - ASSERT_EQ(-10, calc.get_last_result()); + ASSERT_TRUE(calc.add(-4, -6)); + ASSERT_EQ(-10, calc.get_last_result()); } TEST(DivTest, ValidNumbers) { - DeleteMe calc = DeleteMe(); + DeleteMe calc = DeleteMe(); - ASSERT_TRUE(calc.div(10, 2)); - ASSERT_EQ(5, calc.get_last_result()); + ASSERT_TRUE(calc.div(10, 2)); + ASSERT_EQ(5, calc.get_last_result()); - ASSERT_TRUE(calc.div(-12, 3)); - ASSERT_EQ(-4, calc.get_last_result()); + ASSERT_TRUE(calc.div(-12, 3)); + ASSERT_EQ(-4, calc.get_last_result()); - ASSERT_TRUE(calc.div(12, -3)); - ASSERT_EQ(-4, calc.get_last_result()); + ASSERT_TRUE(calc.div(12, -3)); + ASSERT_EQ(-4, calc.get_last_result()); } TEST(DivTest, InvalidNumbers) { - DeleteMe calc = DeleteMe(); + DeleteMe calc = DeleteMe(); - ASSERT_FALSE(calc.div(7, 0)); - ASSERT_EQ(0, calc.get_last_result()); + ASSERT_FALSE(calc.div(7, 0)); + ASSERT_EQ(0, calc.get_last_result()); } -int main(int argc, char **argv) -{ - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); }