C++ Design Patterns#
1 Basics of Design Patterns#
1.1 Basic Principles of Design Patterns#
Ultimate goal: High cohesion, low coupling
(1) Open-Closed Principle (OCP, Open For Extension, Closed For Modification)
Changes to classes should be made by adding code, not modifying existing code.
(2) Single Responsibility Principle (SRP, Single Responsibility Principle)
A class should have a single responsibility and provide only one functionality, with only one reason for its changes.
(3) Dependency Inversion Principle (DIP, Dependency Inversion Principle)
Depend on abstractions (interfaces), not on concrete implementations (classes), meaning programming against interfaces.
(4) Interface Segregation Principle (ISP, Interface Segregation Principle)
Clients should not be forced to depend on interfaces they do not use; an interface should provide only one functionality and should not encapsulate all operations into a single interface.
(5) Liskov Substitution Principle (LSP, Liskov Substitution Principle)
Any place where an abstract class appears can be replaced with its implementation class, which is essentially a virtual mechanism that implements object-oriented functionality at the language level.
(6) Prefer Composition Over Inheritance Principle (CAPP, Composite/Aggregate Reuse Principle)
Using inheritance can lead to any changes in the parent class potentially affecting the behavior of child classes. Using object composition reduces this dependency.
(7) Law of Demeter (LOD, Law of Demeter)
An object should know as little as possible about other objects, thereby reducing coupling between objects and improving system maintainability. For example, in a program, when modules call each other, a unified interface is usually provided to achieve this. This way, other modules do not need to know the internal implementation details of another module, so when an implementation within a module changes, it does not affect the usage of other modules.
2 Dependency Inversion and Law of Demeter#
2.1 Dependency Inversion (DIP, Dependency Inversion Principle)#
In the following program, the Computer class does not depend on concrete classes but rather on three abstract classes. By overriding these three classes, we achieve the desired functionality. We initialize Computer with the overridden subclasses, reducing dependency and increasing flexibility.
As shown in the figure:
The code is as follows:
// Dependency Inversion Principle
// Depend on abstractions (interfaces), not on concrete implementations (classes), meaning programming against interfaces.
#include <iostream>
using namespace std;
// Abstract class HardDisk
class HardDisk{
public:
virtual void work() = 0;
};
// Abstract class Memory
class Memory{
public:
virtual void work() = 0;
};
// Abstract class Cpu
class Cpu{
public:
virtual void work() = 0;
};
// Inherit HardDisk and override work
class JSDhardDisk: public HardDisk{
public:
void work(){
cout << "JSDhardDisk is working" << endl;
}
};
// Inherit Memory and override work
class XSMemory: public Memory{
public:
void work(){
cout << "XSMemory is working" << endl;
}
};
// Inherit Cpu and override work
class InterCpu: public Cpu{
public:
void work(){
cout << "InterCpu is working" << endl;
}
};
// Define Computer class, using composition rather than inheritance
class Computer{
public:
// Constructor, initializing three composite members
Computer(HardDisk *hardDisk, Memory *memory, Cpu *cpu){
this->m_hardDisk = hardDisk;
this->m_memory = memory;
this->m_cpu = cpu;
}
// Execute function
void work(){
m_hardDisk->work();
m_memory->work();
m_cpu->work();
}
private:
// Define three classes, composed in Computer
HardDisk *m_hardDisk;
Memory *m_memory;
Cpu *m_cpu;
};
// Test function
void test01(void){
// Define three abstract class pointers
HardDisk *hardDisk = NULL;
Memory *memory = NULL;
Cpu *cpu = NULL;
// Use their subclasses for construction
hardDisk = new JSDhardDisk;
memory = new XSMemory;
cpu = new InterCpu;
Computer computer(hardDisk, memory, cpu);
computer.work();
delete cpu;
delete memory;
delete hardDisk;
}
// Main function
int main(void){
test01();
system("pause");
return 0;
}
2.2 Law of Demeter (LOD, Law of Demeter)#
Method 1:
The code is as follows:
#include <iostream>
using namespace std;
class Stranger{
public:
void speak(void){
cout << "I am Stranger" << endl;
}
};
class Friend{
public:
void speak(void){
cout << "I am your friend" << endl;
}
Stranger* get_Stranger(){
return new Stranger();
}
};
// Tom does not directly access Stranger, but through Friend
class Tom{
public:
void speak(Friend &fri){
fri.speak();
Stranger *stranger = fri.get_Stranger();
stranger->speak();
}
};
void test01(void){
Tom tom;
Stranger stranger;
Friend fri;
tom.speak(fri);
}
int main(void){
test01();
system("pause");
return 0;
}
Method 2:
#include <iostream>
using namespace std;
class Stranger{
public:
void play(void){
cout << "I am Stranger" << endl;
}
};
class Friend{
public:
void play(void){
cout << "I am your friend" << endl;
}
void playWithStranger(void){
Stranger *stranger = new Stranger;
stranger->play();
}
};
class Tom{
public:
void play(void){
cout << "I am Tom" << endl;
}
void setFriend(Friend *fri){
this->m_fri = fri;
}
void playWithFri(void){
m_fri->play();
}
void playWithStranger(void){
m_fri->playWithStranger();
}
private:
Friend *m_fri;
};
void test01(void){
Tom tom;
Friend *fri;
tom.setFriend(fri);
tom.playWithStranger();
delete fri;
}
int main(void){
test01();
system("pause");
return 0;
}
Method 3:
Tom depends on the abstraction of Stranger and does not depend on the concrete Stranger.
#include <iostream>
using namespace std;
class Stranger{
public:
virtual void play(void) = 0;
};
class StrangerA: public Stranger{
public:
void play(void){
cout << "I am StrangerA" << endl;
}
};
class Friend{
public:
void play(void){
cout << "I am your friend" << endl;
}
};
class Tom{
public:
void setFriend(Friend *fri){
this->m_fri = fri;
}
void setStranger(Stranger *stranger){
this->m_stranger = stranger;
}
void play(void){
cout << "I am Tom" << endl;
m_fri->play();
m_stranger->play();
}
private:
Friend *m_fri;
Stranger *m_stranger;
};
void test01(void){
Tom tom;
Friend *fri = new Friend();
Stranger *stranger = new StrangerA();
tom.setFriend(fri);
tom.setStranger(stranger);
tom.play();
delete stranger;
delete fri;
}
int main(void){
test01();
system("pause");
return 0;
}
3 Creational Patterns#
3.1 Singleton Pattern#
3.1.1 Lazy Initialization and Eager Initialization#
Singleton is achieved by making the constructor private, so it can only be constructed within the class. A static variable is set in the class, and a static function is used to initialize it, ensuring that a class can only instantiate one object.
Lazy initialization initializes the static object globally and assigns it to NULL. It only constructs when its static function is called, hence it is called lazy initialization.
The code for lazy initialization is as follows:
#include <iostream>
using namespace std;
class Singleton{
public:
static Singleton *getInstance(void){
if(m_singleton == NULL){
m_singleton = new Singleton();
}
return m_singleton;
}
static void freeInstance(void){
if(m_singleton != NULL){
delete m_singleton;
m_singleton = NULL;
}
}
private:
static Singleton* m_singleton;
Singleton(void){
cout << "The structure of Singleton" << endl;
}
};
Singleton* Singleton::m_singleton = NULL;
void test01(void){
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
if(p1 == p2){
cout << "p1 = p2" << endl;
}
else{
cout << "p1 != p2" << endl;
}
}
int main(void){
test01();
system("pause");
return 0;
}
Eager initialization initializes the static object globally. The difference from lazy initialization is:
static Singleton *getInstance(void){
return m_singleton;
}
Singleton* Singleton::m_singleton = new Singleton();
3.1.2 Lazy Initialization in Multithreading#
Lazy initialization can cause issues in multithreading because it only constructs when its provided interface is called, and a class can only construct one function. If the execution time of the constructor is too long (exceeding the time slice allocated to a single thread), after the time slice ends, subsequent threads executing the judgment that the object to be constructed is still NULL will concurrently construct the same object. As a result, a single object may be constructed multiple times, losing its singleton characteristic.
// Issues with lazy initialization in multithreading
#include <iostream>
using namespace std;
#include <windows.h>
#include <process.h>
class Singleton{
public:
static Singleton *getInstance(void){
if(m_singleton == NULL){
count++;
m_singleton = new Singleton();
}
return m_singleton;
}
static void freeInstance(void){
if(m_singleton != NULL){
delete m_singleton;
m_singleton = NULL;
}
}
static void printS(void){
cout << "Singleton printS test\n";
}
private:
static Singleton* m_singleton;
static int count;
Singleton(void){
cout << "The structure of Singleton(begin)\n";
cout << "The structure of Singleton(end)\n";
Sleep(1000);
}
};
Singleton* Singleton::m_singleton = NULL;
int Singleton::count = 0;
void myThreadFunc(void*){
Singleton::getInstance()->printS();
}
void test01(void){
HANDLE hThread[10];
for(int i=0; i<3; i++){
hThread[i] = (HANDLE)_beginthread(myThreadFunc, 0, NULL);
}
for(int i=0; i<3; i++){
WaitForSingleObject(hThread[i],INFINITE);
}
Singleton::freeInstance();
}
int main(void){
test01();
system("pause");
return 0;
}
3.1.3 Lazy Initialization with Multithreading Synchronization Optimization#
Lazy initialization can be optimized in multithreading by setting a critical section and performing double-checking. This optimization ensures that a class can only instantiate one object, avoiding repeated construction and maintaining the singleton characteristic.
The core code is as follows:
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs); // Initialize critical section
static Singleton *getInstance(void){
if(m_singleton == NULL){
EnterCriticalSection(&cs); // Enter critical section
if(m_singleton == NULL){
m_singleton = new Singleton();
}
LeaveCriticalSection(&cs); // Leave critical section
}
return m_singleton;
}
3.2 Simple Factory Pattern#
The simple factory pattern is not a standard design pattern because it does not comply with the open-closed principle, but it is simple and easy to use.
The logical diagram is as follows:
The example code constructs objects in the public method of the Factory, returning a pointer to the abstract class. Through the polymorphism of this class, different types of pointers can be returned, achieving factory production of objects. The code is as follows:
#include <iostream>
using namespace std;
#include <string.h>
// Define abstract class
class Fruit{
public:
virtual void getFruit(void) = 0;
};
// Inherit abstract class, override getFruit
class Banana: public Fruit{
public:
void getFruit(void){
cout << "I am Banana" << endl;
}
};
// Inherit abstract class, override getFruit
class Apple: public Fruit{
public:
void getFruit(void){
cout << "I am Apple" << endl;
}
};
// Define factory, constructing instance objects through its public function creat
class Factor{
public:
Fruit *creat(string p){
if(p == "Banana"){
return new Banana;
}
else if(p == "Apple"){
return new Apple;
}
else{
cout << "Not supported" << endl;
return NULL;
}
}
};
// Test function
void test01(void){
Factor *fac = new Factor;
Fruit *fru = NULL;
fru = fac->creat("Banana");
fru->getFruit();
delete fru;
fru = fac->creat("Apple");
fru->getFruit();
delete fru;
delete fac;
}
int main(void){
test01();
system("pause");
return 0;
}
3.3 Factory Pattern#
In this example, the factory pattern implements two abstract classes: the abstract fruit class and the abstract factory class. First, instantiate the abstract factory to generate an instantiated factory, and then use this factory to produce specified fruits. One factory produces one type of fruit, and when wanting to add a new type of fruit, simply add a new fruit class and a factory for that class without modifying other code, complying with the open-closed principle, allowing for flexible code expansion.
The thought diagram is as follows:
The example code is as follows:
#include <iostream>
using namespace std;
// Define abstract class Fruit
class Fruit{
public:
virtual void sayName(void) = 0;
};
// Inherit Fruit, override sayName, define Banana.
class Banana: public Fruit{
public:
void sayName(void){
cout << "I am Banana" << endl;
}
};
// Inherit Fruit, override sayName, define Apple.
class Apple: public Fruit{
public:
void sayName(void){
cout << "I am Apple" << endl;
}
};
// Define abstract factory AbFactor
class AbFactor{
public:
virtual Fruit *creat(void) = 0;
};
// Inherit abstract factory, override creat, define BananaFactor
class BananaFactor: public AbFactor{
public:
Fruit *creat(void){
return new Banana;
}
};
// Inherit abstract factory, override creat, define AppleFactor
class AppleFactor: public AbFactor{
public:
Fruit *creat(void){
return new Apple;
}
};
/////////////////////////////////////////////////////////////////
// After using the factory pattern, when adding new classes, the original code does not need to be modified, complying with the open-closed principle
class Pear: public Fruit{
public:
void sayName(void){
cout << "I am pear" << endl;
}
};
class PearFactor: public AbFactor{
Fruit *creat(void){
return new Pear;
}
};
// Test function
void test01(void){
AbFactor *factor = NULL;
Fruit *fruit = NULL;
factor = new BananaFactor;
fruit = factor->creat();
fruit->sayName();
delete fruit;
delete factor;
factor = new AppleFactor;
fruit = factor->creat();
fruit->sayName();
delete fruit;
delete factor;
factor = new PearFactor;
fruit = factor->creat();
fruit->sayName();
delete fruit;
delete factor;
}
int main(void){
test01();
system("pause");
return 0;
}
3.4 Abstract Factory#
The factory pattern can only produce one product (either banana or apple), while the abstract factory can produce a family of products (composed of many products). Compared to the factory pattern, the abstract factory can produce more, but its extensibility is reduced.
In the example code, both the factory and the fruit are abstracted, dividing them into southern and northern factories, both producing Apple and Banana. The thought diagram is as follows:
The example code is as follows:
#include <iostream>
using namespace std;
// Abstract class Fruit
class Fruit{
public:
virtual void sayName(void) = 0;
};
// Abstract factory
class AbFactory{
public:
virtual Fruit *creatBanana(void) = 0;
virtual Fruit *creatApple(void) = 0;
};
class NorthBanana: public Fruit{
public:
void sayName(void){
cout << "I am Banana, I am from North" << endl;
}
};
class NorthApple: public Fruit{
public:
void sayName(void){
cout << "I am Apple, I am from North" << endl;
}
};
class SouthBanana: public Fruit{
public:
void sayName(void){
cout << "I am Banana, I am from south" << endl;
}
};
class SouthApple: public Fruit{
public:
void sayName(void){
cout << "I am Apple, I am from south" << endl;
}
};
class NorthFactor: public AbFactory{
public:
Fruit *creatBanana(void){
return new NorthBanana;
}
Fruit *creatApple(void){
return new NorthApple;
}
};
class SouthFactor: public AbFactory{
public:
Fruit *creatBanana(void){
return new SouthBanana;
}
Fruit *creatApple(void){
return new SouthApple;
}
};
// Test program
void test01(void){
AbFactory *factor = NULL;
Fruit *fruit = NULL;
factor = new NorthFactor;
fruit = factor->creatApple();
fruit->sayName();
delete fruit;
fruit = factor->creatBanana();
fruit->sayName();
delete fruit;
delete factor;
factor = new SouthFactor();
fruit = factor->creatApple();
fruit->sayName();
delete fruit;
fruit = factor->creatBanana();
fruit->sayName();
delete fruit;
delete factor;
}
int main(void){
test01();
system("pause");
return 0;
}
3.5 Builder Pattern#
Applicable situation: When the construction of an object is complex, separating the construction and representation of an object.
The builder pattern involves a director controlling the builder to perform construction, with the construction process in the director's hands. The builder does not need to care about the specific situation of the construction; it only builds the required objects.
The specific logic involves passing the builder to the director and assigning it to the director's private attribute, calling the builder's attributes to construct the object within the director. After construction, the builder's attributes have changed, and the house can be obtained through getHouse(), allowing for various get methods to query the construction situation.
The thought diagram is as follows:
The code is as follows:
#include <iostream>
using namespace std;
class House{
public:
void setDoor(string door){
this->m_door = door;
}
void setWall(string wall){
this->m_wall = wall;
}
void setWindow(string window){
this->m_window = window;
}
string getDoor(void){
cout << m_door << endl;
return m_door;
}
string getWall(void){
cout << m_wall << endl;
return m_wall;
}
string getWindow(void){
cout << m_window << endl;
return m_window;
}
private:
string m_door;
string m_wall;
string m_window;
};
// Abstract building
class Builder{
public:
virtual void buildWall(void) = 0;
virtual void buildDoor(void) = 0;
virtual void buildWindow(void) = 0;
virtual House *getHouse(void) = 0;
};
// Concrete building Flat
class FlatBuilder: public Builder{
public:
FlatBuilder(void){
this->m_house = new House;
}
void buildWall(void){
m_house->setWall("Flat wall");
}
void buildDoor(void){
m_house->setDoor("Flat door");
}
void buildWindow(void){
m_house->setWindow("Flat window");
}
House *getHouse(void){
return this->m_house;
}
private:
House *m_house;
};
// Concrete building Villa
class VillaBuilder: public Builder{
public:
VillaBuilder(void){
this->m_house = new House;
}
void buildWall(void){
m_house->setWall("Villa wall");
}
void buildDoor(void){
m_house->setDoor("Villa door");
}
void buildWindow(void){
m_house->setWindow("Villa window");
}
House *getHouse(void){
return m_house;
}
private:
House *m_house;
};
// Designer (director), responsible for construction logic
// Builder does the specific work
class Director{
public:
Director(Builder *builder){
this->m_builder = builder;
}
void Construct(void){
m_builder->buildWall();
m_builder->buildWindow();
m_builder->buildDoor();
}
private:
Builder *m_builder;
};
// Test program
void test01(void){
House *house = NULL;
Builder *builder = NULL;
Director *director = NULL;
builder = new VillaBuilder;
director = new Director(builder);
director->Construct();
house = builder->getHouse();
house->getDoor();
house->getWall();
house->getWindow();
delete house;
delete builder;
delete director;
builder = new FlatBuilder;
director = new Director(builder);
director->Construct();
house = builder->getHouse();
house->getDoor();
house->getWall();
house->getWindow();
delete house;
delete builder;
delete director;
}
int main(void){
test01();
system("pause");
return 0;
}
3.6 Prototype Pattern#
The prototype pattern principle: A complex object has self-copying capabilities (sometimes attention is needed for deep and shallow copies), unifying a set of interfaces.
The example thought diagram is as follows:
The example code is as follows:
// This demonstrates the prototype pattern
#include <iostream>
using namespace std;
#include <string.h>
class Person{
public:
virtual Person *clone(void) = 0;
virtual void printT(void) = 0;
};
class CppProgrammer: public Person{
public:
CppProgrammer(void){
this->m_name = "";
this->m_age = 0;
}
CppProgrammer(string name, int age){
this->m_name = name;
this->m_age = age;
}
virtual void printT(void){
cout << "name: " << m_name << " age: " << m_age << endl;
}
virtual Person *clone(void){
CppProgrammer *tmp = new CppProgrammer;
*tmp = *this;
return tmp;
}
private:
string m_name;
int m_age;
};
void test01(void){
Person *p1 = new CppProgrammer("Tom",18);
p1->printT();
Person *p2 = p1->clone();
p2->printT();
}
int main(void){
test01();
system("pause");
return 0;
}
4 Structural Patterns#
The significance of structural patterns lies in combining classes and objects to achieve a larger structure.
4.1 Proxy Pattern#
The proxy pattern, also known as the delegate pattern, provides a proxy object for a certain object, with the proxy controlling access to the original object. The proxy is simply an intermediary in our daily lives.
The example thought diagram is as follows:
In the example code, an abstract class Subject is created, inherited by RealSubjectBook and DangdangProxy. The proxy DangdangProxy overrides the sailbook method and obtains a RealSubjectBook object through composition. We can call the overridden method of RealSubjectBook in the sailbook method inherited by DangdangProxy and can add new methods, thus achieving proxy functionality.
The example code is as follows:
#include <iostream>
using namespace std;
class Subject{
public:
virtual void sailbook(void) = 0;
};
class RealSubjectBook: public Subject{
public:
virtual void sailbook(void){
cout << "sail book" << endl;
}
};
class DangdangProxy: public Subject{
public:
DangdangProxy(void){
m_subject = new RealSubjectBook;
}
virtual void sailbook(void){
dazhe();
m_subject->sailbook();
dazhe();
}
void dazhe(void){
cout << "11.11 discount" << endl;
}
private:
Subject *m_subject;
};
void test01(void){
Subject *s = NULL;
s = new DangdangProxy;
s->sailbook();
delete s;
}
int main(void){
test01();
system("pause");
return 0;
}
4.2 Decorator Pattern#
The decorator pattern, also known as the wrapper pattern, extends the functionality of an object in a way that is transparent to the client, serving as an alternative to inheritance.
The decorator pattern involves placing the additional functionalities in separate classes and having these classes contain the object to be decorated. When execution is needed, the client can selectively use the decoration functionality to wrap the object in sequence.
The logical example diagram is as follows:
In the example code, RunCar, SwimCarDecorator, and FlyCarDecorator all inherit from the base class Car. Unlike RunCar's simple override of the show() function, both Decorators set their constructors to be parameterized, requiring the object to be decorated to be passed in during initialization. The overridden show function first calls the show function of the decorated property. Through layer upon layer of decoration, functionality can be continuously expanded.
The example code is as follows:
#include <iostream>
using namespace std;
class Car{
public:
virtual void show(void) = 0;
};
class RunCar: public Car{
public:
virtual void show(void){
cout << "I can run" << endl;
}
};
class FlyCarDecorator: public Car{
public:
FlyCarDecorator(Car *car){
m_car = car;
}
void show(void){
m_car->show();
cout << "I can fly" << endl;
}
private:
Car *m_car;
};
class SwimCarDecorator: public Car{
public:
SwimCarDecorator(Car *car){
m_car = car;
}
void show(void){
m_car->show();
cout << "I can swim" << endl;
}
private:
Car *m_car;
};
void test01(void){
RunCar *runcar = NULL;
SwimCarDecorator *smCarDec = NULL;
FlyCarDecorator *flyCarDec = NULL;
cout << "RunCar:" << endl;
runcar = new RunCar;
runcar->show();
cout << endl;
cout << "Add Swim function:" << endl;
smCarDec = new SwimCarDecorator(runcar);
smCarDec->show();
cout << endl;
cout << "Add fly function:" << endl;
flyCarDec = new FlyCarDecorator(smCarDec);
flyCarDec->show();
}
int main(void){
test01();
system("pause");
return 0;
}
4.3 Adapter Pattern#
The adapter can change the interface of an existing class, suitable for converting the interface of a class into another interface that the client expects, allowing classes that cannot work together due to incompatible interfaces to work together.
The logical diagram of the adapter pattern is as follows:
In the code, the client's requirement is for 220v. We achieve this by inheriting the abstract class Current18v and overriding useCurrent18v, where we call the method of the private attribute initialized from the constructor. Thus, useCurrent18v prints "I am 220v, welcome".
The code is as follows:
#include <iostream>
using namespace std;
class Current18v{
public:
virtual void useCurrent18v(void) = 0;
};
class Current220v{
public:
virtual void useCurrent220v(void){
cout << "I am 220v, welcome" << endl;
}
};
class Adapter: public Current18v{
public:
Adapter(Current220v *current){
m_current = current;
}
virtual void useCurrent18v(void){
cout << "adapt for Current220v" << endl;
m_current->useCurrent220v();
}
private:
Current220v *m_current;
};
void test01(void){
Current18v *c18v = NULL;
Current220v *c220v = NULL;
Adapter *adapter = NULL;
c220v = new Current220v;
adapter = new Adapter(c220v);
adapter->useCurrent18v();
}
int main(void){
test01();
system("pause");
return 0;
}
4.4 Composite Pattern#
The composite pattern (Composite Pattern), also known as the part-whole pattern, is one of the structural design patterns among the 23 design patterns of GoF. The composite pattern is used to treat a group of similar objects as a single object. It combines objects based on a tree structure to represent part and whole hierarchies, creating a tree structure of object groups.
The example diagram is as follows:
The example code divides files into folder files and ordinary files, placing pointers (including file pointers and folder pointers) into a list and nesting lists within lists to achieve a tree structure. The showTree() function recursively prints the file list. The code is as follows:
#include <iostream>
using namespace std;
#include <list>
#include <string>
class IFile{
public:
virtual void display(void) = 0;
virtual int add(IFile *ifile) = 0;
virtual int remove(IFile *ifile) = 0;
virtual list<IFile*> *getChild(void) = 0;
};
class File: public IFile{
public:
File(string name){
m_name = name;
}
virtual void display(void){
cout << m_name << endl;
}
virtual int add(IFile *ifile){
return -1;
}
virtual int remove(IFile *ifile){
return -1;
}
virtual list<IFile*> *getChild(void){
return NULL;
}
private:
string m_name;
};
class Dir: public IFile{
public:
Dir(string name){
m_name = name;
m_list = new list<IFile*>;
m_list->clear();
}
virtual void display(void){
cout << m_name << endl;
}
virtual int add(IFile *ifile){
m_list->push_back(ifile);
return 0;
}
virtual int remove(IFile *ifile){
m_list->remove(ifile);
return 0;
}
virtual list<IFile*> *getChild(void){
return m_list;
}
private:
string m_name;
list<IFile *> *m_list;
};
// Recursively display the tree
void showTree(IFile *root, int num=0){
// Display root node
// If the root node has children
// If the child is a file, display the name
// If the child is a directory, display the directory name, showTree(subdirectory)
if(root == NULL){
return;
}
for(int i=0; i<num; i++){
printf("\t");
}
// Display root node
root->display();
list<IFile*> *mylist = root->getChild();
if(mylist != NULL){ // Indicates it is a directory
for(list<IFile*>::iterator it = mylist->begin(); it!=mylist->end(); it++){
if((*it)->getChild() == NULL){
for(int i=0; i<=num; i++){
printf("\t");
}
(*it)->display();
}
else{
showTree(*it,num+1);
}
}
}
}
void test01(void){
Dir *root = new Dir("C");
Dir *dir = new Dir("aaa.text");
File *file = new File("111.dir");
root->add(dir);
root->add(file);
list<IFile*> *f_list = root->getChild();
for(list<IFile*>::iterator it = f_list->begin(); it!=f_list->end(); it++){
(*it)->display();
}
cout << "\nAll files under root:" << endl;
Dir *dir2 = new Dir("222.dir");
File *file2 = new File("bbb.text");
Dir *dir3 = new Dir("333.dir");
File *file3 = new File("ccc.text");
dir->add(dir2);
dir->add(file2);
dir2->add(dir3);
dir2->add(file3);
showTree(root);
}
int main(void){
test01();
system("pause");
return 0;
}
4.5 Bridge Pattern#
The bridge pattern is used to decouple abstraction from implementation, allowing both to vary independently. This type of design pattern belongs to structural patterns and provides a bridge structure between abstraction and implementation to achieve decoupling.
This pattern involves an interface that acts as a bridge, allowing the functionality of entity classes to be independent of the interface implementation class. These two types of classes can be structurally changed without affecting each other.
The example thought diagram is as follows:
In the example code, Engine and Car are abstracted, allowing the Car class to hold a pointer to Engine, thus conforming to interface-oriented programming. In this case, subclasses of Car and subclasses of Engine can change independently without affecting the object.
The code is as follows:
#include <iostream>
using namespace std;
// Abstract class Engine
class Engine{
public:
virtual void installEngine(void) = 0;
};
class Engine4400c: public Engine{
public:
virtual void installEngine(void){
cout << "I am Engine4400c, install completed" << endl;
}
};
class Engine4500c: public Engine{
public:
virtual void installEngine(void){
cout << "I am Engine4500c, install completed" << endl;
}
};
class Car{
public:
Car(Engine *engine){
m_engine = engine;
}
virtual void installEngine(void) = 0;
protected:
Engine *m_engine;
};
class BMW5: public Car{
public:
BMW5(Engine *engine): Car(engine){
}
virtual void installEngine(void){
m_engine->installEngine();
}
};
class BMW7: public Car{
public:
BMW7(Engine *engine): Car(engine){
}
virtual void installEngine(void){
m_engine->installEngine();
}
};
void test01(void){
Engine *engine = NULL;
BMW7 *bmw7 = NULL;
engine = new Engine4500c;
bmw7 = new BMW7(engine);
bmw7->installEngine();
}
int main(void){
test01();
system("pause");
return 0;
}
4.6 Facade Pattern#
The facade pattern, also known as the gateway pattern, is a pattern that provides a consistent interface to multiple complex subsystems, making these subsystems easier to access. This pattern has a unified interface externally, and external applications do not need to care about the specific details of the internal subsystems, greatly reducing the complexity of the application and improving the maintainability of the program.
The thought diagram is as follows:
In the example, the various processes needed during execution are encapsulated into a single class, so that when we call it, we do not need to perform complicated operations but can directly use the Facade class to operate the processes we need. The code is as follows:
#include <iostream>
using namespace std;
class SubSystemA{
public:
void doThing(void){
cout << "SubSystemA run" << endl;
}
};
class SubSystemB{
public:
void doThing(void){
cout << "SubSystemB run" << endl;
}
};
class SubSystemC{
public:
void doThing(void){
cout << "SubSystemC run" << endl;
}
};
class Facade{
public:
Facade(void){
sysa = new SubSystemA;
sysb = new SubSystemB;
sysc = new SubSystemC;
}
~Facade(void){
delete sysa;
delete sysb;
delete sysc;
}
void doThing(void){
sysa->doThing();
sysb->doThing();
sysc->doThing();
}
private:
SubSystemA *sysa;
SubSystemB *sysb;
SubSystemC *sysc;
};
void test01(void){
Facade *facade = new Facade;
facade->doThing();
delete facade;
}
int main(void){
test01();
system("pause");
return 0;
}
4.7 Flyweight Pattern#
-
The flyweight pattern (Flyweight Pattern) is also called the flyweight pattern: it effectively supports a large number of fine-grained objects using shared technology.
-
It is commonly used in system-level development to solve performance issues. For example, in a database connection pool, there are pre-created connection objects. If we need one, we can directly use it; if we do not have the one we need, we create a new one.
-
The flyweight pattern can solve the problem of memory waste caused by duplicate objects when there are a large number of similar objects in the system that require a buffer pool. Instead of always creating new objects, we can take them from the buffer pool. This can reduce system memory while improving efficiency.
The logical thought diagram is as follows:
In the example code, there is a FlyWeightFactor class that has a getTeacher method. We create objects through it; if we already have the object corresponding to this serial number, we do not need to create it again but directly take it from the map. If we do not have it, we need to create a new object. Through this pattern, we solve the problem of object memory waste and improve efficiency.
#include <iostream>
using namespace std;
#include <map>
class Person{
public:
Person(string name, int age){
m_name = name;
m_age = age;
}
virtual void printT(void) = 0;
protected:
string m_name;
int m_age;
};
class Teacher: public Person{
public:
Teacher(string name, int age, string id): Person(name, age){
m_id = id;
}
void printT(void){
cout << "name: " << m_name << " age: " << m_age << " id: " << m_id << endl;
}
private:
string m_id;
};
class FlyWeightFactor{
public:
FlyWeightFactor(void){
m_map1.clear();
}
~FlyWeightFactor(void){
for(map<string,Person*>::iterator it = m_map1.begin(); it != m_map1.end(); it++){
delete (it->second);
}
}
Person *getTeacher(string id){
map<string, Person*>::iterator it;
it = m_map1.find(id);
Person *tmp = NULL;
if(it == m_map1.end()){
string name;
int age;
cout << "Please input the name of teacher" << endl;
cin >> name;
cout << "Please input the age of the teacher" << endl;
cin >> age;
tmp = new Teacher(name, age, id);
m_map1.insert(pair<string, Person*>(id, tmp));
}
else{
tmp = it->second;
}
return tmp;
}
private:
map<string, Person*> m_map1;
};
void test01(void){
FlyWeightFactor *fwf = NULL;
Person *p1 = NULL;
Person *p2 = NULL;
Person *p3 = NULL;
fwf = new FlyWeightFactor;
p1 = fwf->getTeacher("10");
p1->printT();
p2 = fwf->getTeacher("9");
p2->printT();
p3 = fwf->getTeacher("9");
p2->printT();
}
int main(void){
test01();
system("pause");
return 0;
}
5 Behavioral Patterns#
5.1 Template Pattern#
The template method encapsulates certain necessary processes in an algorithm with specific steps, delegating the implementation of abstract methods to subclasses. By inheriting from the abstract class, subclasses can change the behavior of the entire algorithm through different implementations of the abstract methods.
Summary:
In the abstract class, unify the operation steps and specify the interface; let subclasses implement the interface. This decouples the specific subclasses from the operation steps.
The logical thought diagram is as follows:
The example code shows that we have a run method in the Base class, which specifies the specific execution process. We inherit Base to obtain the Son class, where we override some methods to achieve polymorphism. The code is as follows:
// This program implements the template method
#include <iostream>
using namespace std;
class Base{
public:
void run(void){
step1();
if(step2()){
step3();
}
for(int i=0; i<4; i++){
step4();
}
step5();
}
virtual ~Base(void){ };
protected:
void step1(void){
cout << "step1 call" << endl;
}
void step3(void){
cout << "step3 call" << endl;
}
void step5(void){
cout << "step5 call" << endl;
}
virtual bool step2(void) = 0;
virtual void step4(void) = 0;
int *p;
};
class Son:public Base{
protected:
virtual bool step2(void){
return 1;
}
virtual void step4(void){
cout << "step4 call" << endl;
}
};
int main(void){
Base *p = new Son();
p->run();
delete p;
system("pause");
return 0;
}
5.2 Command Pattern#
The command pattern is a high-cohesion pattern defined as: encapsulating a request as an object, allowing you to parameterize clients with different requests, queue requests, or log requests, and providing the ability to undo and redo commands.
The logical thought diagram is as follows:
#include <iostream>
using namespace std;
#include <string>
#include <list>
class Doctor{
public:
void treat_eyes(void){
cout << "treat eyes" << endl;
}
void treat_nose(void){
cout << "treat nose" << endl;
}
};
class Command{
public:
virtual void treat(void) = 0;
};
class CommandTreatEyes: public Command{
public:
CommandTreatEyes(Doctor *doctor){
m_doctor = doctor;
}
virtual void treat(void){
m_doctor->treat_eyes();
}
private:
Doctor *m_doctor;
};
class CommandTreatNose: public Command{
public:
CommandTreatNose(Doctor *doctor){
m_doctor = doctor;
}
void treat(void){
m_doctor->treat_nose();
}
private:
Doctor *m_doctor;
};
class Nurse{
public:
Nurse(Command *command){
m_command = command;
}
void submittedCase(void){
m_command->treat();
}
private:
Command *m_command;
};
class HeadNurse{
public:
HeadNurse(){
m_list.clear();
}
void setCommand(Command *command){
m_list.push_back(command);
}
void SubmittedCommand(void){
for(list<Command*>::iterator it = m_list.begin(); it!=m_list.end(); it++){
(*it)->treat();
}
}
private:
list<Command*> m_list;
};
void test01(void){
Doctor *doctor = NULL;
Command *command = NULL;
Nurse *nurse = NULL;
doctor = new Doctor;
command = new CommandTreatEyes(doctor);
nurse = new Nurse(command);
nurse->submittedCase();
}
void test02(void){
Doctor *doctor = NULL;
Command *command1 = NULL;
Command *command2 = NULL;
Command *command3 = NULL;
HeadNurse *hnurse = NULL;
doctor = new Doctor;
command1 = new CommandTreatEyes(doctor);
command2 = new CommandTreatNose(doctor);
command3 = new CommandTreatEyes(doctor);
hnurse = new HeadNurse;
hnurse->setCommand(command1);
hnurse->setCommand(command2);
hnurse->setCommand(command3);
hnurse->SubmittedCommand();
}
int main(void){
// test01();
test02();
system("pause");
return 0;
}
5.3 Chain of Responsibility Pattern#
To avoid coupling the request sender with multiple request handlers, all request handlers are linked together by having each object remember a reference to its next object; when a request occurs, it can be passed along this chain until an object handles it.
The logical thought diagram is as follows:
In the example code, we abstract a class CarHandle, with three classes inheriting from CarHandle and overriding its methods handleCar and setNextHandle. Importantly, the setNextHandle function assigns a value to its protected attribute m_carhandle, and in handleCar, it automatically calls its handleCar method. This method allows us to set the propagation order of the responsibility chain, and using the head's handleCar allows for chained access to all handleCar functions. The code is as follows:
#include <iostream>
using namespace std;
class CarHandle{
public:
virtual void handleCar(void) = 0;
virtual void setNextHandle(CarHandle *carhandle) = 0;
protected:
CarHandle *m_carhandle;
};
class HeadCarHandle: public CarHandle{
public:
virtual void handleCar(void){
cout << "Handle the head of the car" << endl;
if(m_carhandle != NULL){
m_carhandle->handleCar();
}
}
virtual void setNextHandle(CarHandle *carhandle){
m_carhandle = carhandle;
}
};
class BodyCarHandle: public CarHandle{
public:
virtual void handleCar(void){
cout << "Handle the body of the car" << endl;
if(m_carhandle != NULL){
m_carhandle->handleCar();
}
}
virtual void setNextHandle(CarHandle *carhandle){
m_carhandle = carhandle;
}
};
class TailCarHandle: public CarHandle{
public:
virtual void handleCar(void){
cout << "Handle the tail of the car" << endl;
if(m_carhandle != NULL){
m_carhandle->handleCar();
}
}
virtual void setNextHandle(CarHandle *carhandle){
m_carhandle = carhandle;
}
};
void test01(void){
CarHandle *head = new HeadCarHandle;
CarHandle *body = new BodyCarHandle;
CarHandle *tail = new TailCarHandle;
head->setNextHandle(body);
body->setNextHandle(tail);
tail->setNextHandle(NULL);
head->handleCar();
delete head;
delete body;
delete tail;
}
int main(void){
test01();
system("pause");
return 0;
}
5.4 Strategy Pattern#
The strategy pattern is a simple yet commonly used design pattern with a wide range of application scenarios. This pattern defines a series of algorithms, encapsulating each algorithm so they can be interchanged, and the change of algorithms does not affect the clients using the algorithms. The strategy pattern belongs to behavioral patterns, separating the responsibility of using algorithms from the implementation of the algorithms, delegating the management of these algorithms to different objects.
This pattern mainly solves the complexity and maintenance difficulties caused by using if...else
in cases with multiple similar algorithms. Its advantages include the ability to freely switch algorithms, avoiding multiple if...else
judgments, and having good extensibility.
The code logical diagram is as follows:
In the example code, an abstract TaxSterategy interface is abstracted, and the SalesOrder implementation depends on it. Several classes inherit from TaxSterategy and override its methods. SalesOrder initializes objects using subclasses of TaxSterategy, achieving different effects through polymorphism. If you want to extend the program, you only need to inherit and override the base class, which has advantages in code maintenance compared to traditional branching if...else...
, and complies with the open-closed principle. The code is as follows:
// This experiment is on the strategy method
// The strategy method has characteristics of extensibility and easy maintenance compared to traditional multi-branch implementation
#include <iostream>
using namespace std;
// Abstract Tax type
class TaxSterategy{
public:
virtual double calculate() = 0;
virtual ~TaxSterategy(){ };
};
// Specific classification (CN_Tax class), inheriting from abstract Tax
class CN_Tax:public TaxSterategy{
public:
virtual double calculate(){
cout << "CN_Tax" << endl;
return 1;
}
};
// Specific classification (US_Tax class), inheriting from abstract Tax
class US_Tax:public TaxSterategy{
public:
virtual double calculate(){
cout << "US_Tax" << endl;
return 2;
}
};
// Specific classification (DE_Tax class), inheriting from abstract Tax
class DE_Tax:public TaxSterategy{
public:
virtual double calculate(){
cout << "DE_Tax" << endl;
return 3;
}
};
// Specific classification (FR_Tax class), inheriting from abstract Tax
class FR_Tax:public TaxSterategy{
public:
virtual double calculate(){
cout << "FR_Tax" << endl;
return 4;
}
};
// Define a class to operate on them
class SalesOrder{
private:
TaxSterategy *tax;
public:
// Constructor with parameters
SalesOrder(TaxSterategy *country){
this->tax = country;
}
// Destructor, deleting tax pointer
~SalesOrder(){
delete tax;
}
double CalculateTax(){
return this->tax->calculate();
}
};
// Test program
void test01(void){
FR_Tax *fr = new FR_Tax();
SalesOrder order(fr);
cout << order.CalculateTax() << endl;
}
// Main function
int main(void){
test01();
system("pause");
return 0;
}
5.5 Mediator Pattern#
The mediator pattern defines a mediator object to encapsulate a series of object interactions, allowing objects to interact without explicitly referencing each other, thus loosening coupling and allowing them to change their interactions independently. The mediator pattern is also known as the broker pattern and is a type of behavioral pattern.
The logical thought diagram is as follows:
In the example code, we abstract a Mediator class and pass its pointer to various classes, so we no longer connect different Person objects through themselves but through the subclass object of Mediator to act on the two Person objects. By using different subclass objects of Mediator, we can perform different operations on the connected objects, as exemplified in the logic of finding partners. The code is as follows:
#include <iostream>
using namespace std;
#include <string>
class Mediator;
class Person{
public:
Person(string name, int sex, int condi, Mediator *m){
m_name = name;
m_sex = sex;
m_condi = condi;
m_mediator = m;
}
int getCondi(void){
return m_condi;
}
int getSex(void){
return m_sex;
}
string getName(void){
return m_name;
}
virtual void getParter(Person *person) = 0;
protected:
string m_name;
int m_sex;
int m_condi;
Mediator *m_mediator;
};
class Mediator{
public:
virtual void setMen(Person *men) = 0;
virtual void setWomen(Person *women) = 0;
virtual void getParter(void) = 0;
protected:
Person *pmen;
Person *pwomen;
};
class Mediator_a: public Mediator{
public:
virtual void setMen(Person *men){
pmen = men;
}
virtual void setWomen(Person *women){
pwomen = women;
}
virtual void getParter(void){
if(pmen->getSex() == pwomen->getSex()){
cout << "I am not gay" << endl;
}
else if(pmen->getCondi() == pwomen->getCondi()){
cout << pmen->getName() << " and " << pwomen->getName() << " are a perfect match" << endl;
}
else{
cout << pmen->getName() << " and " << pwomen->getName() << " are not a match" << endl;
}
}
};
class Men: public Person{
public:
Men(string name, int sex, int condi, Mediator *m): Person(name, sex, condi, m){
}
virtual void getParter(Person *person){
m_mediator->setMen(this);
m_mediator->setWomen(person);
m_mediator->getParter();
}
};
class Women: public Person{
public:
Women(string name, int sex, int condi, Mediator *m): Person(name, sex, condi, m){
}
virtual void getParter(Person *person){
m_mediator->setMen(person);
m_mediator->setWomen(this);
m_mediator->getParter();
}
};
// // Directly find partners
// void test01(void){
// Person *p1 = new Women("lucy", 0, 2);
// Person *p2 = new Men("jack", 1, 4);
// Person *p3 = new Men("Mark", 1, 2);
// p1->getParter(p2);
// p1->getParter(p3);
// }
// Find partners through mediator
void test02(void){
Mediator *m = new Mediator_a;
Person *p1 = new Women("lucy", 0, 2, m);
Person *p2 = new Men("jack", 1, 4, m);
Person *p3 = new Men("Mark", 1, 2, m);
p1->getParter(p2);
p1->getParter(p3);
p2->getParter(p3);
}
int main(void){
// test01();
test02();
system("pause");
return 0;
}
5.6 Observer Pattern#
The observer pattern is a type of behavioral pattern that defines a one-to-many dependency between objects, allowing multiple observer objects to listen to a single subject object. When the state of this subject object changes, it notifies all observer objects, allowing them to automatically update themselves.
The logical thought diagram is as follows:
In the example code, the Boss class collects hero objects in a list. The hero acts as the observer. When the boss deletes a hero, it retrieves the hero object from the iterator in the notify function and informs it. This model can be modified to specify which hero is being notified. The code is as follows:
// This experiment is on the observer pattern
#include <iostream>
#include <windows.h>
#include <string>
#include <list>
using namespace std;
// Abstract hero class
class AbstractHero{
public:
virtual void update(void) = 0;
};
// Inherit abstract hero class, define Hero1
class Hero1: public AbstractHero{
public:
Hero1(){
cout << "Hero1 is call" << endl;
}
virtual void update(void){
cout << "Hero1 is stop" << endl;
}
};
// Inherit abstract hero class, define Hero2
class Hero2: public AbstractHero{
public:
Hero2(){
cout << "Hero2 is call" << endl;
}
virtual void update(){
cout << "Hero2 is stop" << endl;
}
};
// Inherit abstract hero class, define Hero3
class Hero3: public AbstractHero{
public:
Hero3(){
cout << "Hero3 is call" << endl;
}
virtual void update(){
cout << "Hero3 is stop" << endl;
}
};
// Abstract Boss class
class AbstractBoss{
public:
// Add observer
virtual void addHero(AbstractHero *hero) = 0;
// Remove observer
virtual void deleteHero(AbstractHero *hero) = 0;
// Notify function
virtual void notify(void) = 0;
};
// Inherit AbstractBoss, define Boss1
class Boss1: public AbstractBoss{
public:
Boss1(void){
this->list_hero.clear();
}
virtual void addHero(AbstractHero *hero){
list_hero.push_back(hero);
}
virtual void deleteHero(AbstractHero *hero){
list_hero.remove(hero);
}
virtual void notify(void){
for(list<AbstractHero*>::iterator it = list_hero.begin(); it!=list_hero.end(); it++){
(*it)->update();
}
}
private:
list<AbstractHero*> list_hero;
};
void test01(void){
AbstractHero *hero1 = new Hero1;
AbstractHero *hero2 = new Hero2;
AbstractHero *hero3 = new Hero3;
AbstractBoss *bos = new Boss1;
bos->addHero(hero1);
bos->addHero(hero2);
bos->addHero(hero3);
cout << "1**********" << endl;
bos->notify();
bos->deleteHero(hero3);
cout << "2**********" << endl;
bos->notify();
delete hero1;
delete hero2;
delete hero3;
delete bos;
}
int main(void){
test01();
system("pause");
return 0;
}
5.7 Memento Pattern#
The memento pattern is one of the behavioral patterns. Its purpose is to protect the internal state of an object and restore the object's previous state when needed.
The logical thought diagram is as follows:
The Person object holds a pointer to a MemoTo, performing copy operations on its data and restoring it when needed. The CareTaker class can also copy the Person's data, allowing it to restore without holding a pointer to Person. The code is as follows:
#include <iostream>
using namespace std;
#include <string>
class MemoTo{
public:
MemoTo(string name, int age){
m_name = name;
m_age = age;
}
void setName(string name){
m_name = name;
}
void setAge(int age){
m_age = age;
}
string getName(void){
return m_name;
}
int getAge(void){
return m_age;
}
private:
string m_name;
int m_age;
};
class Person{
public:
Person(string name, int age){
m_name = name;
m_age = age;
}
void setName(string name){
m_name = name;
}
void setAge(int age){
m_age = age;
}
string getName(void){
return m_name;
}
int getAge(void){
return m_age;
}
MemoTo *creatMeno(void){
m_meno = new MemoTo(m_name, m_age);
return m_meno;
}
void recover(void){
m_name = m_meno->getName();
m_age = m_meno->getAge();
}
void recover(MemoTo *memo){
m_name = memo->getName();
m_age = memo->getAge();
}
void printD(void){
cout << "name: " << m_name << " age: " << m_age << endl;
}
private:
string m_name;
int m_age;
MemoTo *m_meno;
};
class CareTaker{
public:
CareTaker(MemoTo *memo){
m_memo = memo;
}
void setMemo(MemoTo *memo){
m_memo = memo;
}
MemoTo *recover(void){
return m_memo;
}
private:
MemoTo *m_memo;
};
// void test01(void){
// Person *p1 = new Person("Tom", 18);
// p1->creatMeno();
// p1->setName("Jack");
// p1->setAge(21);
// p1->printD();
// p1->recover();
// p1->printD();
// delete p1;
// }
void test02(void){
Person *p1 = new Person("Tom", 18);
CareTaker *ca = new CareTaker(p1->creatMeno());
cout << "before: " << endl;
p1->printD();
p1->setName("Mark");
p1->setAge(27);
cout << "after: " << endl;
p1->printD();
p1->recover(ca->recover());
cout << "recover: "<< endl;
p1->printD();
}
int main(void){
// test01();
test02();
system("pause");
return 0;
}
5.8 Visitor Pattern#
The visitor pattern is defined as: separating operations that act on elements within a data structure into independent classes, allowing new operations to be added to these elements without changing the data structure. It provides multiple access methods for each element in the data structure. It separates the operations on data from the data structure, making it one of the most complex behavioral patterns.
The logical thought diagram is as follows:
In the example code, the data structure ParkElement and the data operation Visitor are separated. ParkElement accepts Visitor subclass objects to access it. The code is as follows:
#include <iostream>
using namespace std;
#include <list>
class ParkElement;
class Visitor{
public:
virtual void visit(ParkElement *pm) = 0;
};
class ParkElement{
public:
virtual void accept(Visitor *visitor) = 0;
};
class ParkA: public ParkElement{
public:
virtual void accept(Visitor *visitor){
visitor->visit(this);
}
};
class ParkB: public ParkElement{
public:
virtual void accept(Visitor *visitor){
visitor->visit(this);
}
};
class Park: public ParkElement{
public:
Park(void){
m_list.clear();
}
void setParkElement(ParkElement *pe){
m_list.push_back(pe);
}
virtual void accept(Visitor *v){
for(list<ParkElement*>::iterator it = m_list.begin(); it!=m_list.end(); it++){
(*it)->accept(v);
}
}
private:
list<ParkElement*> m_list;
};
class VisitorA: public Visitor{
public:
virtual void visit(ParkElement *pm){
cout << "VisitorA is call" << endl;
}
};
class VisitorB: public Visitor{
public:
virtual void visit(ParkElement *pm){
cout << "VisitorB is call" << endl;
}
};
class Manager: public Visitor{
public:
virtual void visit(ParkElement *pm){
cout << "Manager visit" << endl;
}
};
// void test01(void){
// Visitor *v1 = new VisitorA;
// Visitor *v2 = new VisitorB;
// ParkElement *pm1 = new ParkA;
// ParkElement *pm2 = new ParkB;
// pm1->accept(v1);
// pm2->accept(v2);
// delete v1;
// delete v2;
// delete pm1;
// delete pm2;
// }
void test02(void){
Visitor *manager = new Manager;
ParkElement *p1 = new ParkA;
ParkElement *p2 = new ParkB;
ParkElement *p3 = new ParkA;
Park *parkAll = new Park;
parkAll->setParkElement(p1);
parkAll->setParkElement(p2);
parkAll->setParkElement(p3);
parkAll->accept(manager);
}
int main(void){
// test01();
test02();
system("pause");
return 0;
}
5.9 State Pattern#
The state pattern, also known as the state object pattern, is a behavioral pattern. The state pattern allows an object to change its behavior when its internal state changes. The object appears to change its class.
The logical thought diagram is as follows:
In the example code, there are three states: state1, state2, and unknown (set as the default state, but not executed). The code controls the change of its internal state through modifications to m_hour. The code is as follows:
#include <iostream>
using namespace std;
class Worker;
class State{
public:
virtual void doSomething(Worker *worker) = 0;
};
class Worker{
public:
void setHour(int hour){
m_hour = hour;
}
void setState(State *state){
m_curstate = state;
}
int getHour(void){
return m_hour;
}
State *getCurState(void){
return m_curstate;
}
void doSomething(void){
m_curstate->doSomething(this);
}
private:
int m_hour;
State *m_curstate;
};
class State1: public State{
public:
virtual void doSomething(Worker *w);
};
class State2: public State{
public:
virtual void doSomething(Worker *w);
};
void State1::doSomething(Worker *w){
if(w->getHour() == 7 || w->getHour() == 8){
cout << "Have breakfast" << endl;
}
else{
delete w->getCurState();
w->setState(new State2);
w->getCurState()->doSomething(w);
}
}
void State2::doSomething(Worker *w){
if(w->getHour() == 11 || w->getHour() == 12){
cout << "Eat lunch" << endl;
}
else if(w->getHour() == 7 || w->getHour() == 8){
delete w->getCurState();
w->setState(new State1);
w->doSomething();
}
else{
delete w->getCurState();
w->setState(new State1);
cout << "Current time: " << w->getHour() << " Unknown" << endl;
}
}
void test01(void){
Worker *w1 = new Worker;
State *s1 = new State1;
w1->setHour(12);
w1->setState(s1);
w1->doSomething();
w1->setHour(11);
w1->doSomething();
w1->setHour(7);
w1->doSomething();
delete w1;
delete s1;
}
int main(void){
test01();
system("pause");
return 0;
}
5.10 Interpreter Pattern#
The interpreter pattern belongs to behavioral patterns. Its definition is: given a language, define a representation of its grammar and an interpreter that uses this representation to interpret sentences in the language.
The example code is as follows:
The example code interprets Context class using PlusExpression and MinusExpression, which are addition and subtraction interpretations, respectively. The code is as follows:
#include <iostream>
using namespace std;
class Context{
public:
Context(int num){
m_num = num;
}
void setNum(int num){
m_num = num;
}
void setRes(int res){
m_res = res;
}
int getNum(void){
return m_num;
}
int getRes(void){
return m_res;
}
void printT(void){
cout << "num: " << m_num << " res: " << m_res << endl;
}
private:
int m_num;
int m_res;
};
class Expression{
public:
virtual void interpreter(Context *context) = 0;
};
class PlusExpression: public Expression{
public:
virtual void interpreter(Context *context){
int num = context->getNum();
num++;
context->setNum(num);
context->setRes(num);
}
};
class MinusExpression: public Expression{
public:
virtual void interpreter(Context *context){
int num = context->getNum();
num--;
context->setNum(num);
context->setRes(num);
}
};
void test01(void){
Context *c1 = new Context(10);
Expression *e1 = new PlusExpression;
e1->interpreter(c1);
c1->printT();
}
int main(void){
test01();
system("pause");
return 0;
}
5.11 Iterator Pattern#
The iterator pattern provides a way to sequentially access various elements in an aggregate object without exposing the internal representation of that object. It is a widely used design pattern.
The example diagram is as follows:
In the example code, we define createIterator in the Aggregate class, allowing it to create an iterator and pass itself to the iterator object. The iterator gains a reference to the data and can access the data through this reference. The example demonstrates sequential access using next, iterating in a loop. The code is as follows:
#include <iostream>
using namespace std;
#define SIZE 5
// Abstract iterator
class Myiterator{
public:
virtual void first(void) = 0;
virtual void next(void) = 0;
virtual bool IsDone(void) = 0;
virtual int currentItem(void) = 0;
};
// Abstract data
class Aggregate{
public:
virtual Myiterator *createIterator(void) = 0;
virtual int getItem(int index) = 0;
virtual int getSize(void) = 0;
protected:
int object[SIZE];
};
// Concrete class
class ContreteIterator: public Myiterator{
public:
ContreteIterator(Aggregate *ag){
this->ag = ag;
first();
}
virtual void first(void){
_current_index = 0;
}
virtual void next(void){
if(_current_index < ag->getSize()){
_current_index++;
}
}
virtual bool IsDone(void){
return (_current_index == ag->getSize());
}
virtual int currentItem(void){
return (ag->getItem(_current_index));
}
private:
int _current_index;
Aggregate *ag;
};
class ContreteAggregate: public Aggregate{
public:
ContreteAggregate(void){
for(int i=0; i<SIZE; i++){
object[i] = 100+i;
}
}
Myiterator *createIterator(void){
return new ContreteIterator(this);
}
virtual int getItem(int index){
return object[index];
}
virtual int getSize(void){
return SIZE;
}
};
void test01(void){
Aggregate *ag = new ContreteAggregate;
Myiterator *it = ag->createIterator();
for(; !(it->IsDone()); it->next()){
cout <<