×
Community Blog Discussing the Four Aspects of the C++ Design Pattern

Discussing the Four Aspects of the C++ Design Pattern

This article describes the design pattern of C++ from four aspects: design principle, creational pattern, structural pattern, and behavior pattern.

1

By Hengsuo

Design Principles

Single Responsibility Principle

Definition: The responsibility in the Single Responsibility Principle [1] refers to the cause of class change. If a class has more than one motivation to change, the class has more than one responsibility. The Single Responsibility Principle means a class or module should only have one reason to change.

Bad Case: The iPhone class is responsible for protocol management (Dial and HangUp) and data transfer (Chat).

2

Good Case:

3

Liskov Substitution Principle

Definition: The Liskov Substitution Principle (LSP) [2] states that a subclass can appear wherever a base class can appear.

Bad Case: ToyGun inherits AbstractGun, but Solider will report an error (ToyGun cannot kill the enemy) when calling KillEnemy(); ToyGun cannot fully perform AbstractGun's responsibilities.

4

Good Case: AbstractToy delegates the processing of sounds and shapes to AbstractGun.

If the subclass cannot fully implement the methods of the parent class, or some methods of the parent class have been distorted in the subclass, it is recommended to break the parent-child inheritance relationship and replace it with dependency, aggregation, combination, and other relationships.

5

Dependency Inversion Principle

Definition: The Dependency Inversion Principle [3] states that the program should depend on abstract interfaces rather than concrete implementations. Simply put, it is required to program the abstraction, not the implementation, thus reducing the coupling between the customer and the implementation module.

Bad Case: The driver is strongly dependent on Mercedes.

6

Good Case:

7

Interface Segregation Principle

Definition: The Interface Segregation Principle [4] states that the client should not depend on an interface that it does not need. The dependency of one class on another should be built on the smallest interface.

Bad Case: Talent scouts look for class diagrams of pretty girls, whereas the number of IpettyGirl is too large, accommodating too many variables.

8

Good Case: The flexibility and maintainability are improved by splitting the interface.

9

Law of Demeter

Definition: The Law of Demeter 5 states that the less a class knows about other classes, the better, which means an object should have as little knowledge as possible about other objects, communicate only with friends, and not talk to strangers.

Bad Case: Teacher asks GroupLeader to count the number of girls. Teacher should not rely on girls here.

10

Good Case:

11

Open-Closed Principle

Definition: The Open-Close Principle [6] states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification in the field of object-oriented programming.

Take a bookstore sales class diagram as an example: when a discount operation is to be added in the bookstore

Bad Case: Modify the implementation class and add a method GetOffPrice() to the IBook

Good Case: Add a subclass of OffNovelBook by extending implementation changes

12

Creational Pattern

Factory Method

Define an interface Product* CreateProduct() for creating objects and let the subclass decide which class to instantiate. The factory method design pattern delays the instantiation of classes to subclasses, thus avoiding the problem of tight coupling of class names when we create objects in the parent class while improving the extensibility and maintainability of the code. (The advantage of the factory method is decoupling. When we modify a specific class, the caller does not need to modify it at all.)

13

class Product {    // Abstract product
public:
  virtual void Method() = 0;
};
class ConcreteProduct1 : public Product {
public:
  void Method() { cout << "ConcreteProduct1" << endl; }
};
class ConcreteProduct2 : public Product {
public:
  void Method() { cout << "ConcreteProduct2" << endl; }
};

class Factory {    // Abstract factory
public:
  virtual Product* CreateProduct() = 0;
};
class ConcreteFactory1 : public Factory {
public:
  Product* CreateProduct() {return new ConcreteProduct1(); }
};
class ConcreteFactory2 : public Factory {
public:
  Product* CreateProduct() {return new ConcreteProduct2(); }
};

int main () {
  Factory *factory1 = new ConcreteFactory1();
  Factory *factory2 = new ConcreteFactory2();
  Product *product1 = factory1->CreateProduct();
  Product *product2 = factory2->CreateProduct();
  product1->Method();
  product2->Method();
}

Abstract Factory Pattern

Abstract Factory provides an interface for creating a set of related or interdependent objects without specifying their concrete classes. (The factory method pattern is aimed at a single product level structure, while the abstract factory pattern is aimed at multiple product levels. The abstract factory pattern is mainly used to produce a series of products.)

14

class AbstractProductA {
 public:
  virtual ~AbstractProductA(){};
  virtual std::string FunctionA() const = 0;
};

class ProductA1 : public AbstractProductA {
 public:
  std::string FunctionA() const override { return "The result of the product A1."; }
};

class ProductA2 : public AbstractProductA {
  std::string FunctionA() const override { return "The result of the product A2."; }
};

class AbstractProductB {
 public:
  virtual ~AbstractProductB(){};
  virtual std::string FunctionB() const = 0;
};

class ProductB1 : public AbstractProductB {
 public:
  std::string FunctionB() const override { return "The result of the product B1."; }
};

class ProductB2 : public AbstractProductB {
 public:
  std::string FunctionB() const override { return "The result of the product B2."; }
};

class AbstractFactory {
 public:
  virtual AbstractProductA *CreateProductA() const = 0;
  virtual AbstractProductB *CreateProductB() const = 0;
};

class Factory1 : public AbstractFactory {
 public:
  AbstractProductA *CreateProductA() const override { return new ProductA1(); }
  AbstractProductB *CreateProductB() const override { return new ProductB1(); }
};

class Factory2 : public AbstractFactory {
 public:
  AbstractProductA *CreateProductA() const override { return new ProductA2(); }
  AbstractProductB *CreateProductB() const override { return new ProductB2(); }
};

void Client(const AbstractFactory &factory) {
  const AbstractProductA *productA = factory.CreateProductA();
  const AbstractProductB *productB = factory.CreateProductB();
  std::cout << productA->FunctionA() << "\n";
  std::cout << productB->FunctionB() << "\n";
  delete productA;
  delete productB;
}

int main() {
  Factory1 *f1 = new Factory1();
  Client(*f1);
  delete f1;
  Factory2 *f2 = new Factory2();
  Client(*f2);
  delete f2;
  return 0;
}

Builder Pattern

Builder pattern separates the construction of a complex object from its representation so the same construction process can create different representations. (Builder pattern focuses on the part type and assembly process (sequence ).)

15

class Product1{
public:
    std::vector<std::string> mParts;
    void ListParts()const{
        std::cout << "Product parts: ";
        for (size_t i=0;i<mParts.size();i++){
            if(mParts[i]== mParts.back()){ std::cout << mParts[i];
            }else{ std::cout << mParts[i] << ", "; }
        }
        std::cout << "\n\n"; 
    }
};

class Builder{
    public:
    virtual ~Builder(){}
    virtual void ProducePartA() const = 0;
    virtual void ProducePartB() const = 0;
    virtual void ProducePartC() const = 0;
};

class ConcreteBuilder1 : public Builder{
    Product1* mProduct;
public:
    ConcreteBuilder1(){ Reset(); }
    ~ConcreteBuilder1(){ delete mProduct; }

    void Reset() { mProduct = new Product1(); }
    void ProducePartA()const override{ this->mProduct->mParts.push_back("PartA1"); }
    void ProducePartB()const override{ this->mProduct->mParts.push_back("PartB1"); }
    void ProducePartC()const override{ this->mProduct->mParts.push_back("PartC1"); }

    Product1* GetProduct() { 
        Product1* result= mProduct;
        Reset();
        return result; 
    }
};

class Director {
    Builder* mbuilder;
public:
    void set_builder(Builder* builder){ mbuilder = builder; }

    void BuildMinimalViableProduct(){ mbuilder->ProducePartA(); }

    void BuildFullFeaturedProduct(){
        mbuilder->ProducePartA();
        mbuilder->ProducePartB();
        mbuilder->ProducePartC();
    }
};

void ClientCode(Director& director)
{
    ConcreteBuilder1* builder = new ConcreteBuilder1();
    director.set_builder(builder);
    std::cout << "Standard basic product:\n"; 
    director.BuildMinimalViableProduct();

    Product1* p= builder->GetProduct();
    p->ListParts();
    delete p;

    std::cout << "Standard full featured product:\n"; 
    director.BuildFullFeaturedProduct();

    p= builder->GetProduct();
    p->ListParts();
    delete p;

    // Remember, the Builder pattern can be used without a Director class.
    std::cout << "Custom product:\n";
    builder->ProducePartA();
    builder->ProducePartC();
    p=builder->GetProduct();
    p->ListParts();
    delete p;
    delete builder;
}

int main(){
    Director* director= new Director();
    ClientCode(*director);
    delete director;
    return 0;    
}

Prototype Pattern

Specify the object types to create with prototype instances and create new objects by copying these prototypes. (The prototype pattern implements a Clone interface. Note that it is an interface, a Clone virtual function based on a multi-state model.)

class Prototype {
 protected:
  string mPrototypeName;
  float mPrototypeField;

public:
  Prototype() {}
  Prototype(string prototypeName)
      : mPrototypeName(prototypeName) {
  }
  virtual ~Prototype() {}
  virtual Prototype *Clone() const = 0;
  virtual void Function(float prototype_field) {
    this->mPrototypeField = prototype_field;
    std::cout << "Call Function from " << mPrototypeName << " with field : " << prototype_field << std::endl;
  }
};

class ConcretePrototype1 : public Prototype {
private:
  float mConcretePrototypeField;

public:
  ConcretePrototype1(string prototypeName, float concretePrototypeField)
      : Prototype(prototypeName), mConcretePrototypeField(concretePrototypeField) {
  }

  Prototype *Clone() const override {
    return new ConcretePrototype1(*this);
  }
};

class ConcretePrototype2 : public Prototype {
private:
  float mConcretePrototypeField;

public:
  ConcretePrototype2(string prototypeName, float concretePrototypeField)
      : Prototype(prototypeName), mConcretePrototypeField(concretePrototypeField) {
  }
  Prototype *Clone() const override {
    return new ConcretePrototype2(*this);
  }
};

class PrototypeFactory {
private:
  std::unordered_map<Type, Prototype *, std::hash<int>> mPrototypes;

public:
  PrototypeFactory() {
    mPrototypes[Type::PROTOTYPE_1] = new ConcretePrototype1("PROTOTYPE_1 ", 50.f);
    mPrototypes[Type::PROTOTYPE_2] = new ConcretePrototype2("PROTOTYPE_2 ", 60.f);
  }

  ~PrototypeFactory() {
    delete mPrototypes[Type::PROTOTYPE_1];
    delete mPrototypes[Type::PROTOTYPE_2];
  }

  Prototype *CreatePrototype(Type type) {
    return mPrototypes[type]->Clone();
  }
};

void Client(PrototypeFactory &prototypeFactory) {
  std::cout << "Let's create a Prototype 1\n";
  Prototype *prototype = prototypeFactory.CreatePrototype(Type::PROTOTYPE_1);
  prototype->Function(90);
  delete prototype;

  std::cout << "Let's create a Prototype 2 \n";
  prototype = prototypeFactory.CreatePrototype(Type::PROTOTYPE_2);
  prototype->Function(10);
  delete prototype;
}

int main() {
  PrototypeFactory *prototypeFactory = new PrototypeFactory();
  Client(*prototypeFactory);
  delete prototypeFactory;

  return 0;
}

16

Singleton Pattern

The singleton pattern ensures that a class can only generate one instance throughout the system lifecycle to ensure that the class is unique.

class SingleInstance
{
public:
    static SingleInstance* GetInstance();
    void Print();
private:
    // Construction, destruction, copy constructs, and assignment constructs are private to prevent multiple objects from being constructed.
    SingleInstance();
    ~SingleInstance();
    SingleInstance(const SingleInstance &instance);
    const SingleInstance &operator=(const SingleInstance &instance);

    static SingleInstance* mInstancePtr;
};
SingleInstance* SingleInstance::mInstancePtr = nullptr;
SingleInstance* SingleInstance::GetInstance()
{
    if (mInstancePtr == nullptr)
        mInstancePtr = new SingleInstance();
    return mInstancePtr;
}

Extension:

  • In the pattern of 2 or 3 instances (a class can produce 2 to 3 instances), define a List of classes in the private attribute, and the List contains all instances.
  • In the case of public classes, you need to implement a Manager management class to implement the singleton pattern. The Manager class generates instances of various public classes during initialization and then returns the instances through GetInstance().

17

Structural Pattern

Adapter Pattern

Convert the interface of one class into another interface that the client wants, so those classes that could not work together because of incompatible interfaces can work together. The adapter pattern is a compensation pattern (or remedy pattern), typically used to resolve interface incompatibility.

class Target {  // Target, the interface expected by the customer. It can be a concrete or abstract class, or an interface.
public:
    virtual void Request() = 0;
    virtual ~Target(){};
};

class Adaptee { // The class to be adapted
public:
    void SpecificRequest() { cout << "Adaptee" << endl; }
};

class Adapter : public Target { // Convert the source interface to the target interface by wrapping an Adaptee object internally:
private:
    Adaptee* mAdaptee;
public:
    Adapter() { mAdaptee = new Adaptee(); }
    void Request() { mAdaptee->SpecificRequest(); }  // 调用Request()方法会转换成调用adaptee.SpecificRequest()
    ~Adapter() { delete mAdaptee; }
};

int main() {
    Target* target = new Adapter();
    target->Request();

    delete target;
    return 0;
}

Additional Information

  • Target: The interface expected by the customer
  • Adapter: Convert the source interface to the target interface by wrapping an Adaptee object internally
  • Adaptee: The class to be adapted

18

Bridge Pattern

The bridge pattern Decouples abstraction from implementation so the two can change independently.

class OperationSys
{
public:
  OperationSys() {}
  ~OperationSys(){}

  virtual void SetName() = 0;
  virtual void OutputName() = 0;

protected:
  std::string mName;
};

class IOSSystem:public OperationSys
{
public:
  IOSSystem() {}
  ~IOSSystem() {}
  virtual void SetName() { mName = "IOS-SYS"; }
  virtual void OutputName() { std::cout << "I am IOS,name:" << mName << std::endl; }
};

class HarmonySystem :public OperationSys
{
public:
  HarmonySystem() {}
  ~HarmonySystem() {}
  virtual void SetName() { mName = "HarmonySystem"; }
  virtual void OutputName() { std::cout << "I am Harmony operation system,name:" << mName << std::endl; }
};

class AndroidSystem :public OperationSys
{
public:
  AndroidSystem() {}
  ~AndroidSystem() {}
  virtual void SetName() { mName = "AndroidSystem"; }
  virtual void OutputName() { std::cout << "I am Android operation system,name:" << mName << std::endl; }
};

class Phone
{
public:
  Phone() {}
  ~Phone(){}

  virtual void SetName() = 0;
  virtual void OutputName() = 0;
  virtual void SetOperation(OperationSys* sys) { mOperSystem = sys; }
  virtual void OutputSysName() = 0;

protected:
  OperationSys* mOperSystem;
  std::string mName;
};

class IPhone :public Phone
{
public:
  IPhone() {}
  ~IPhone(){}

  virtual void SetName() { mName = "IPhone"; }
  virtual void OutputName() { std::cout << "I am IPhone,Name:" << mName << std::endl; }
  virtual void SetOperation(OperationSys* sys) { mOperSystem = sys; }
  virtual void OutputSysName() { mOperSystem->OutputName(); }
};

class HwPhone :public Phone
{
public:
  HwPhone() {}
  ~HwPhone() {}

  virtual void SetName() { mName = "HuaWeiPhone"; }
  virtual void OutputName() { std::cout << "I am HuaWei,Name:" << mName << std::endl; }
  virtual void SetOperation(OperationSys* sys) { mOperSystem = sys; }
  virtual void OutputSysName() { mOperSystem->OutputName(); }
};

class MiPhone :public Phone
{
public:
  MiPhone() {}
  ~MiPhone() {}

  virtual void SetName() { mName = "MiPhone"; }
  virtual void OutputName() { std::cout << "I am XiaoMi,Name:" << mName << std::endl; }
  virtual void SetOperation(OperationSys* sys) { mOperSystem = sys; }
  virtual void OutputSysName() { mOperSystem->OutputName(); }
};

int main(int argc, char* argv[])
{
  IOSSystem* iSys = new IOSSystem();
  iSys->SetName();
  IPhone* iPhone = new IPhone();
  iPhone->SetName();
  iPhone->SetOperation(iSys);

  HarmonySystem* hSys = new HarmonySystem();
  hSys->SetName();
  HwPhone* wPhone = new HwPhone();
  wPhone->SetName();
  wPhone->SetOperation(hSys);

  AndroidSystem* aSys = new AndroidSystem();
  aSys->SetName();
  MiPhone* mPhone = new MiPhone();
  mPhone->SetName();
  mPhone->SetOperation(aSys);

  iPhone->OutputName();
  iPhone->OutputSysName();

  wPhone->OutputName();
  wPhone->OutputSysName();

  mPhone->OutputName();
  mPhone->OutputSysName();

  return 0;
}

Additional Information

  • Abstraction: Define the behavior of a role and save a reference to an Implementor, which is generally an abstract class
  • Implementor: Define the required behaviors and attributes of a role, which is generally an interface or an abstract class
  • RefinedAbstraction: Refine Abstraction by referencing an Implementor
  • ConcreteImplementor: Implement methods and attributes defined by interfaces or abstract classes

19

20

Composite Pattern

The pattern describes the relationship between parts and the whole (Compose objects into a tree structure to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly). Usage scenarios:

  1. Represent a part-whole hierarchy of objects
  2. Make users ignore the difference between the composite object and the individual object, and the user will use all objects in the composite structure uniformly.
class Component
{
protected:
  string mName;

public:
  Component(string name) : mName(name) {}
  virtual ~Component() {}
  virtual void Operation() = 0;
  virtual void Add(Component *com) = 0;
  virtual void Remove(Component *com) = 0;
  virtual Component *Getchild(int index) = 0;
  virtual string GetName() { return mName; }
  virtual void ShowChilds() = 0;
};

class Leaf : public Component // The leaf structure.
{
public:
  Leaf(string name) : Component(name) {}
  void Operation() { cout << "name : " << mName << endl; }

  void Add(Component *com) {}
  void Remove(Component *com) {}
  Component *GetChild(int index) { return NULL; }
  void ShowChilds() {}
};

class Composite : public Component // The leaf structure.
{
private:
  vector<Component *> mComponents;

public:
  Composite(string name) : Component(name) {}
  ~Composite()
  {
    for (auto &it : mComponents)
    {
      cout << "---delete" << it->GetName() + "---" << endl;
      delete it;
      it = nullptr;
    }
    mComponents.clear();
  }
  void Operation() { cout << "I am " << mName << endl; }
  void Add(Component *com) { mComponents.push_back(com); }
  void Remove(Component *com)
{
    for (auto &it : mComponents)
    {
      for (auto it = mComponents.begin(); it != mComponents.end(); ++it)
      {
        if (*it != nullptr && (*it)->GetName() == com->GetName())
        {
          delete *it;
          *it = nullptr;
          mComponents.erase(it);
          break;
        }
      }
    }
  }
  Component *Getchild(int index)
{
    if ((size_t)index > mComponents.size())
      return nullptr;
    return mComponents[index - 1];
  }

  void ShowChilds()
{
    for (auto it = mComponents.begin(); it != mComponents.end(); ++it)
    {
      cout << (*it)->GetName() << endl;
    }
  }
};

Additional Information

  • Component: An abstract component role
  • Leaf: A leaf component, the smallest unit of traversal
  • Composite: A branch component that combines branch nodes and leaf nodes to form a tree structure

21

Decorator Pattern

The pattern dynamically adds some additional responsibilities to an object (to add some features to the object or make some modifications to the object being decorated). The decorator pattern is an alternative to the inheritance relationship, but multiple layers of decoration are more complex.

class Component {
public:
  virtual ~Component() {};
  virtual void Operate() = 0;
};

class ConcreteComponent : public Component
{
public:
  void Operate() override { cout << "do something" << endl;}
};

class Decorator : public Component
{
public:
  Decorator(Component* component) : mComponent(component) {}
  void Operate() override
{
    mComponent->Operate();
  }
private:
  Component* mComponent = nullptr;
};

class ConcreteDecorator1 : public Decorator {
public:
  ConcreteDecorator1(Component* component) : Decorator(component) {}

  void Operate() override {
    method1();
    Decorator::Operate();
  }
private:
  void method1() { cout << "method1 decoration" << endl; }
};

class ConcreteDecorator2 : public Decorator {
public:
  ConcreteDecorator2(Component* component) : Decorator(component) {}

  void Operate() override {
    method2();
    Decorator::Operate();
  }
private:
  void method2() { cout << "method2 decoration" << endl; }
};

int main()
{
  Component* component = new ConcreteComponent();
  component = new ConcreteDecorator1(component);
  component = new ConcreteDecorator2(component);
  component->Operate();
  delete component;
}

Additional Information

  • Component: An abstract component
  • ConcreteComponent: The object to be decorated
  • Decorator: Its attributes must contain a private variable that points to the Component.
  • Concrete Decorator (ConcreteDecoratorA, ConcreteDecoratorB)

22

Facade Pattern

The facade pattern requires that the communication between the outside and the inside of a subsystem must be carried out through a unified object. The facade pattern provides a high-level interface that makes the subsystem easier to use. Simply put, the facade object is the only way for the outside world to access the interior of the subsystem. No matter how chaotic the interior of the system is, as long as there is a facade object, the facade looks good.

class A
{
public:
void DoSomething() { cout << "class A is doing something" << endl; }
};

class B
{
public:
void DoSomething() { cout << "class B is doing something" << endl; }
};

class C
{
public:
void DoSomething() { cout << "class C is doing something" << endl; }
};

class Facade
{
public:
Facade()
{
    a = make_shared<A>();
    b = make_shared<B>();
    c = make_shared<C>();
}

void DoSomething()
{
    a->DoSomething();
    b->DoSomething();
    c->DoSomething();
}

private:
shared_ptr<A> a;
shared_ptr<B> b;
shared_ptr<C> c;
};

int main()
{
    Facade facade;
    facade.DoSomething();
}

Additional Information

  • Facade: This role will delegate all requests from the client to the corresponding subsystem. The role has no actual business logic, just a delegate class.
  • Subsystem: The subsystem does not know the existence of the facade. The facade is just another client for subsystems.

23

Flyweight Pattern

The flyweight pattern is an important implementation of pooling (using shared objects can effectively support a large number of fine-grained objects). The flyweight pattern aims to use sharing technology to ensure the sharing of fine-grained objects.

The flyweight pattern divides fine-grained object information into two parts: intrinsic state (the shared information of the object is stored inside the Flyweight and does not change with the environment) and extrinsic state (the label that the object depends on changes with the environment).

class Flyweight
{
public:
  // The flyweight must accept intrinsic state.
  Flyweight(const string& extrinsic) : mExtrinsic(extrinsic) {}
  virtual void Operate() = 0;
  string GetIntrinsic() { return mIntrinsic; }
  void SetIntrinsic(const string& extrinsic) { mIntrinsic = extrinsic; }

protected:
  const string mExtrinsic; // Extrinsic state

private:
  string mIntrinsic; // Intrinsic state
};

class ConcreteFlyweight1 : public Flyweight
{
public:
  ConcreteFlyweight1(const string& extrinsic) : Flyweight(extrinsic) {}
  void Operate() override { cout << "ConcreteFlyweight1: " << mExtrinsic << endl; }
};

class ConcreteFlyweight2 : public Flyweight
{
public:
  ConcreteFlyweight2(const string& extrinsic) : Flyweight(extrinsic) {}
  void Operate() override { cout << "ConcreteFlyweight2: " << mExtrinsic << endl; }
};

class FlyweightFactory
{
public:
  static shared_ptr<Flyweight> GetFlyweight(const string& extrinsic)
  {
    shared_ptr<Flyweight> flyweight;
    if (mPool.find(extrinsic) != mPool.end())
    {
      flyweight = mPool[extrinsic];
    }
    else
    {
      flyweight = make_shared<ConcreteFlyweight1>(extrinsic);
      mPool.emplace(extrinsic, flyweight);
    }
    return flyweight;
  }

private:
  static unordered_map<string, shared_ptr<Flyweight>> mPool; 
};

unordered_map<string, shared_ptr<Flyweight>> FlyweightFactory::mPool{};

int main()
{
  FlyweightFactory::GetFlyweight("extrinsic1");
  FlyweightFactory::GetFlyweight("extrinsic2");
  shared_ptr<Flyweight> flyweight = FlyweightFactory::GetFlyweight("extrinsic2");
  flyweight->SetIntrinsic("intrinsic2");
  flyweight->Operate();
  return 0;
}

Additional Information

  • Flyweight: Define the extrinsic and intrinsic states of an object
  • ConcreteFlyweight: Implement services defined by Abstraction
  • unsharedConcreteFlyweight: There are no extrinsic state or security requirements (thread safety) for objects that cannot use sharing technology. It generally does not appear in the FlyweightFactory.
  • FlyweightFactory: Construct a pool container and provide methods for obtaining objects from the pool

24

Proxy Pattern

The proxy pattern provides a proxy for other objects to control access to this object.

In design mode, it can be divided into normal proxy and forced proxy:

  • Normal Proxy: The client can only access the proxy role (rather than the real role).
  • Forced Proxy: Search for proxy roles from real roles. Direct access to real roles is not allowed.
class Subject
{
public:
  virtual void Request() = 0;
};

class RealSubject : public Subject
{
public:
  void Request() override { cout << "RealSubject is doing something" << endl;}
};

class Proxy : public Subject
{
public:
  Proxy(Subject* subject) : mSubject(subject) {}
  void Request() override 
{ 
    Before();
    mSubject->Request();
    After();
  }

private:
  void Before() { cout << "preparing ..." << endl; }
  void After() { cout << "Finishing ..." << endl; }

  Subject* mSubject;
};

int main()
{
  Subject* realSubject = new RealSubject();
  Proxy proxy(realSubject);
  proxy.Request();
  return 0;
}

Additional Information

  • Subject: An abstract subject role
  • RealSubjuect: A specific subject role and the specific executor of business logic
  • Proxy: A proxy subject role (delegate class, proxy class). It is responsible for the application of real roles. (You can also do preprocessing and after-treatment before and after the real role is processed.)

25

Behavioral Pattern

Chain of Responsibility

The chain of responsibility pattern allows multiple objects to process the request, avoiding the coupling relationship between the sender and receiver. Link these objects into a chain and pass the request along the chain until an object handles it.

In practical applications, there is generally an encapsulation class to encapsulate the responsibility pattern (to replace the Client class and directly return to the first processor in the chain). The setting of the specific chain does not require high-level module relationships.

class Handler
{
public:
  virtual ~ Handler() {}
  void HandleRequest(int32_t requestLevel)
{
    if (GetHandlerLevel() == requestLevel)
    {
      DoSomething();
    }
    else
    {
      if (mNextHandler)
      {
        mNextHandler->HandleRequest(requestLevel);
      }
      else
      {
        cout << "can not find request handler" << endl;
      }
    }
  }
  void SetNextHandler(Handler* handler)
{
    mNextHandler = handler;
  }
  virtual int32_t GetHandlerLevel() = 0;
  virtual void DoSomething() = 0;

private:
  Handler* mNextHandler;
};

class ConcreteHandler1 : public Handler
{
public:
  int32_t GetHandlerLevel() override { return 1; }
  void DoSomething() override { cout << "ConcreteHandler1 is doing something" << endl;}
};

class ConcreteHandler2 : public Handler
{
public:
  int32_t GetHandlerLevel() override { return 2; }
  void DoSomething() override { cout << "ConcreteHandler2 is doing something" << endl;}
};

class ConcreteHandler3 : public Handler
{
public:
  int32_t GetHandlerLevel() override { return 3; }
  void DoSomething() override { cout << "ConcreteHandler3 is doing something" << endl;}
};

int main()
{
  Handler* handler1 = new ConcreteHandler1();
  Handler* handler2 = new ConcreteHandler2();
  Handler* handler3 = new ConcreteHandler3();
  handler1->SetNextHandler(handler2);
  handler2->SetNextHandler(handler3);
  handler1->HandleRequest(4);
  delete handler1;
  delete handler2;
  delete handler3;
  return 0;
}

Additional Information

  • Handler: An abstract handler
  • ConcreteHandler: A concrete handler

26

Command Pattern

The command pattern encapsulates a request as an object, thereby letting us parameterize other objects with different requests. It supports Undo and Redo functions for request queuing or request logging.

class Receiver
{
public:
    virtual void DoSomething() = 0;
};

class ConcreteReceiver1 : public Receiver
{
public:
    void DoSomething() override { std::cout << "ConcreteReceiver1 is doing something" << std::endl; }
};

class ConcreteReceiver2 : public Receiver
{
public:
    void DoSomething() override { std::cout << "ConcreteReceiver2 is doing something" << std::endl; }
};

class Command
{
public:
    Command(const std::shared_ptr<Receiver>& receiver) : mReceiver(receiver) {}
    virtual void Execute() = 0;

protected:
    std::shared_ptr<Receiver> mReceiver;
};

class ConcreteCommand1 : public Command
{
public:
    ConcreteCommand1(const std::shared_ptr<Receiver>& receiver) : Command(receiver) {}

    void Execute() override { mReceiver->DoSomething(); }
};

class ConcreteCommand2 : public Command
{
public:
    ConcreteCommand2(const std::shared_ptr<Receiver>& receiver) : Command(receiver) {}

    void Execute() override { mReceiver->DoSomething(); }
};

class Invoker
{
public:
    void SetCommand(const std::shared_ptr<Command>& command) { mCommand = command; }
    void Action() { mCommand->Execute(); }

private:
    std::shared_ptr<Command> mCommand;
};

int main()
{
    std::shared_ptr<Receiver> receiver1(new ConcreteReceiver1());
    std::shared_ptr<Command> command1(new ConcreteCommand1(receiver1));

    Invoker invoker;
    invoker.SetCommand(command1);
    invoker.Action();

    return 0;
}

Note

  • Receiver: A working role, and the command is passed here and should be executed.
  • Command: All the commands that need to be executed are declared here.
  • Invoker: It accepts commands and executes them.

27

This way, there is no relationship between the invoker and the receiver. When the invoker implements the function, it only needs to call the execute method of the Command abstract class. This implements class decoupling without specifying which receiver executes.

This way, the subclasses of Command can be easily extended (extensible), and the invoker and the high-level module Client do not cause serious code coupling.

Iterator Pattern

The iterator pattern provides a way to access the elements in a container object without exposing the internal details of the object.

The iterator pattern is now in decline because, with the development of modern programming languages and the enrichment of standard libraries, there are fewer scenarios using the iterator pattern.

In C++, you can use the iterators provided by the STL library. For example, you can use the iterators of the vector container to traverse the elements in the container. In addition, C++ supports custom iterators. Developers can implement their own iterators based on specific needs. Custom iterators must implement corresponding iterator interfaces, such as begin, end, and operator++, overload the dereference operator (*), and arrow operator (->).

Mediator Pattern

The mediator pattern encapsulates a series of object interactions with a mediator. The mediator promotes loose coupling by keeping objects from referring to each other explicitly.

If multiple objects depend on each other, the mediator role is added to cancel the association or dependency of multiple objects and reduce the coupling of objects. Correspondingly, the mediator will expand significantly, and the more colleague classes there are, the more complex the logic of mediators will be.

class Mediator;
class Colleague
{
public:
    Colleague(const shared_ptr<Mediator>& mediator) : mMediator(mediator) {};
protected:
    shared_ptr<Mediator> mMediator;
};
class ConcreteColleague1 : public Colleague
{
public:
    ConcreteColleague1(const shared_ptr<Mediator>& mediator) : Colleague(mediator) {}
    void SelfMethod() { cout << "ConcreteColleague1 Handle its own business logic " << endl; }
    void DepMethod() { cout << "ConcreteColleague1 Delegate to mediator " << endl; }
};
class ConcreteColleague2 : public Colleague
{
public:
    ConcreteColleague2(const shared_ptr<Mediator>& mediator) : Colleague(mediator) {}
    void SelfMethod() { cout << "ConcreteColleague2 Handle its own business logic " << endl; }
    void DepMethod() { cout << "ConcreteColleague2 Delegate to mediator " << endl; }
};
class Mediator
{
public:
    shared_ptr<ConcreteColleague1> GetC1() { return mConcreteColleague1; }
    void SetC1(const shared_ptr<ConcreteColleague1>& concreteColleague1) { mConcreteColleague1 = concreteColleague1; }
    shared_ptr<ConcreteColleague2> GetC2() { return mConcreteColleague2; }
    void SetC2(const shared_ptr<ConcreteColleague2>& concreteColleague2) { mConcreteColleague2 = concreteColleague2; }
    virtual void DoSomething1()=0;
    virtual void DoSomething2()=0;
protected:
    shared_ptr<ConcreteColleague1> mConcreteColleague1;
    shared_ptr<ConcreteColleague2> mConcreteColleague2;
};
class ConcreteMediator : public Mediator
{
public:
    void DoSomething1() override 
{
        mConcreteColleague1->SelfMethod();
        mConcreteColleague2->SelfMethod();
    }
    void DoSomething2() override
{
        mConcreteColleague2->DepMethod();
        mConcreteColleague2->DepMethod();
    }
};

int main()
{
    shared_ptr<ConcreteMediator> mediator(new ConcreteMediator);
    shared_ptr<ConcreteColleague1> C1(new ConcreteColleague1(mediator));
    shared_ptr<ConcreteColleague2> C2(new ConcreteColleague2(mediator));

    mediator->SetC1(C1);
    mediator->SetC2(C2);
    // Call the operation of the mediator.
    mediator->DoSomething1();
    mediator->DoSomething2();

    return 0;
}

The colleague class uses the constructor to inject the mediator, and the mediator uses the get/set method to inject the colleague class because the colleague class must have a mediator, and the mediator can only have part of the colleague class.

Additional Information

  • Mediator: It defines a unified interface for communication between various colleague roles.
  • Concrete Mediator: It implements collaborative behavior by coordinating the various colleague roles, so it must rely on the various colleague roles.
  • Colleague: Each colleague role is aware of the mediator role and must collaborate through the mediator role when communicating with other colleague roles.

28

Memento Pattern

The memento pattern captures the internal state of an object and saves this state outside the object without breaking the encapsulation. This allows you to restore the state of an object to a previous state.

Usage Scenarios: Scenarios in which data needs to be saved and restored, scenarios in which rollback operations need to be provided, and scenarios in which replicas need to be monitored

class Memento
{
public:
    Memento() {}
    Memento(string state) :mState(state) {}
    const string& GetState() { return mState; }
    void SetState(const string& state) { mState = state; }
private:
    string mState;
};

class Originator
{
public:
    const string& GetState() { return mState; }
    void SetState(const string& state) { mState = state; }
    Memento CreateMemento() { return Memento(mState); }
    void Restore(Memento memento) { SetState(memento.GetState()); }
private:
    string mState;
};

class Caretaker
{
public:
    Memento& GetMemento() { return mMemento; }
    void SetMemento(Memento memento) { mMemento = memento; }
private:
    Memento mMemento;
};

int main()
{
    Originator originator;
    originator.SetState("state1");
    Caretaker caretaker;
    caretaker.SetMemento(originator.CreateMemento());
    originator.SetState("state2");
    originator.Restore(caretaker.GetMemento());
    cout << "current state: " << originator.GetState() << endl; 
    return 0;
}

Additional Information

  • Originator: It is responsible for recording the current internal state and creating and restoring memo data.
  • Memento: It is responsible for storing the internal state of the Originator object and providing the internal state required by the Originator when needed.
  • Caretaker: It is responsible for managing, saving, and serving memos.

29

Observer Pattern

The observer pattern is also known as the publisher-subscriber pattern. It defines a one-to-many dependency between objects. When the state of an object (observable object) changes, all objects (observers) that depend on it are notified and automatically updated.


class Observer {
public:
  virtual void Update(string &context) = 0;
};

class Observer1 : public Observer {
public:
  void Update(string &context) override { cout << "observer1 get message: " + context << endl; }
};

class Observer2 : public Observer {
public:
  void Update(string &context) override { cout << "observer2 get message: " + context << endl; }
};

class Observable {
public:
  virtual void AddObserver(Observer *observer) = 0;
  virtual void DeleteObserver(Observer *observer) = 0;
  virtual void NotifyObserver() = 0;
};

class Subject : public Observable {
public:
  string GetState() { return mContext; };
  void SetState(string context) { mContext = context; };
  void AddObserver(Observer *observer) override { mObserverList.push_back(observer); };
  void DeleteObserver(Observer *observer)override { mObserverList.remove(observer); };
  void NotifyObserver() override { for (const auto& it : mObserverList) it->Update(mContext); };
private:
  list<Observer *> mObserverList;
  string mContext;
};

void Client() {
  Subject *subject = new Subject;
  Observer *observer1 = new Observer1();
  Observer *observer2 = new Observer2();
  subject->AddObserver(observer1);
  subject->AddObserver(observer2);

  subject->SetState("I'm doing something");
  subject->NotifyObserver();
}

int main() {
  Client();
  return 0;
}

Returned Result

observer1 get message: I'm doing something

observer2 get message: I'm doing something

30

Extension: Based on the application scenarios, the observer pattern has different code implementations: synchronous blocking and asynchronous non-blocking implementations and in-process and cross-process implementations. Observer pattern application scenarios [7]

EventListener Pattern

It is also known as the callback pattern. It uses a callback function to process events. When an event occurs, it calls a predefined callback function to respond to the event. This pattern is commonly used in asynchronous programming, such as event-driven programming or GUI programming. Although the observer pattern and the eventlistener pattern have similar characteristics, their purpose and implementation are slightly different. The observer pattern is typically used to implement communication and collaboration between objects, while the Listener pattern is more focused on event handling and asynchronous programming.

State Pattern

The state pattern allows an object to alter its behavior when its internal state changes. It appears as if the object changed its class.

Usage Scenarios: Scenarios in which behavior changes with state changes and alternatives to conditional and branch judgment statements.

class State;
class Context
{
public:
    Context();
    void SetState(State* state);
    State* GetState1();
    State* GetState2();
    State* GetState();
    void Handle1();
    void Handle2();
private:
    State* mState1;
    State* mState2;
    State* mCurrentState;
};

class State
{
public:
    State(Context* context) : mContext(context) {}
    virtual void Handle1() = 0;
    virtual void Handle2() = 0;
protected:
    Context* mContext;
};
class ConcreteState1 : public State
{
public:
    ConcreteState1(Context* context) : State(context) {}
    void Handle1() override { cout << "ConcreteState1 is doing something" << endl; };
    void Handle2() override
{
        mContext->SetState(mContext->GetState2());
    }
};

class ConcreteState2 : public State
{
public:
    ConcreteState2(Context* context) : State(context) {}
    void Handle1() override { cout << "ConcreteState2 is doing something" << endl; };
    void Handle2() override
{
        mContext->SetState(mContext->GetState1());
    }
};

Context::Context() : mState1(new ConcreteState1(this)), mState2(new ConcreteState2(this)), mCurrentState(mState1) {}
void Context::SetState(State* state) { mCurrentState = state; }
State* Context::GetState1() { return mState1; }
State* Context::GetState2() { return mState2; }
State* Context::GetState() { return mCurrentState; }
void Context::Handle1() { mCurrentState->Handle1(); }
void Context::Handle2() { mCurrentState->Handle2(); }

int main()
{
    Context context;
    context.Handle1(); // do something
    context.Handle2(); // switch to state 2
    context.Handle1(); // do something
    context.Handle2(); // switch to state 1
    return 0;
}

Additional Information

  • State: An abstract state role. It is responsible for object state definition and encapsulating environment role to implement state switching.
  • ConcreteState: It needs to implement behavior management and state transition in the current state.
  • Context: It is responsible for switching specific states.

31

Strategy Pattern

The pattern defines a set of algorithms, encapsulates each algorithm, and makes them interchangeable.

Usage Scenarios: Multiple classes only differ in the algorithm or behavior. Algorithms need to be switched freely, and algorithm rules need to be masked.

class Strategy
{
public:
    virtual void DoSomething() = 0;
};
class ConcreteStrategy1 : public Strategy
{
public:
    void DoSomething() override { cout << "ConcreteStrategy1 is doing something" << endl; }
};
class ConcreteStrategy2 : public Strategy
{
public:
    void DoSomething() override { cout << "ConcreteStrategy2 is doing something" << endl; }
};
class Context
{
public:
    Context(Strategy* strategy) : mStrategy(strategy) {}
    void DoAnything() { mStrategy->DoSomething(); } 
private:
    Strategy* mStrategy;
};

int main()
{
    Strategy* strategy = new ConcreteStrategy1();
    Context context(strategy);
    context.DoAnything();
}

Additional Information

  • Context: An encapsulation role. It is responsible for shielding high-level modules from direct access to strategies and algorithms and encapsulating possible changes.
  • Strategy: An abstract strategy role. It is responsible for abstracting strategies and algorithms.
  • ConcreteStrategy: It implements operations in abstract strategies.

32

Template Pattern

The pattern defines the skeleton of an algorithm in an operation and defers some steps to subclasses. It allows subclasses to redefine certain steps of an algorithm without changing the structure of the algorithm.

The template method pattern is an application of the inheritance idea, but it is not completely equivalent to inheritance.

class AbstractClass
{
public:
    virtual void DoSomething() = 0;
    virtual void DoAnything() = 0;
    void TemplateMethod()
{
        DoSomething();
        DoAnything();
    }
};
class ConcreteClass1 : public AbstractClass
{
public:
    void DoSomething() override { cout << "ConcreteClass1 is doing something" << endl; }
    void DoAnything() override { cout << "ConcreteClass1 is doing anything" << endl; }
};
class ConcreteClass2 : public AbstractClass
{
public:
    void DoSomething() override { cout << "ConcreteClass2 is doing something" << endl; }
    void DoAnything() override { cout << "ConcreteClass2 is doing anything" << endl; }
};

int main()
{
    AbstractClass* class1 = new ConcreteClass1();
    AbstractClass* class2 = new ConcreteClass2();
    class1->TemplateMethod();
    class2->TemplateMethod();
}

Description: Methods in the template method pattern are divided into two types:

  • Basic methods, implemented by subclasses
  • Template method, a framework to implement the scheduling of basic methods and complete fixed logic

33

Visitor Pattern

The pattern encapsulates the operations that act on the elements of a data structure. It can define new operations that act on these elements without changing the data structure.

Usage Scenarios: Multiple objects with different interfaces need to be traversed (iterators can only access the same class and interface).

The visitor pattern is an extension of the iterator pattern. It can traverse different objects and then performs different operations.

class Visitor;
class Element {
public:
    virtual void Accept(Visitor& v) = 0;
};
class ConcreteElementA : public Element {
public:
    void Accept(Visitor& v) override;
    void OperationA();
};
class ConcreteElementB : public Element {
public:
    void Accept(Visitor& v) override;
    void OperationB();
};

class Visitor {
public:
    virtual void visit(ConcreteElementA& e) = 0;
    virtual void visit(ConcreteElementB& e) = 0;
};
class ConcreteVisitor1 : public Visitor {
public:
    void visit(ConcreteElementA& e) override
{
        cout << "visit "; 
        e.OperationA();
        cout << endl;
    }
    void visit(ConcreteElementB& e) override
{
        cout << "visit "; 
        e.OperationB();
        cout << endl;
    }
};

class ConcreteVisitor2 : public Visitor {
public:
    void visit(ConcreteElementA& e) override
{
        // Another operation to access the ConcreteElementA.
        cout << "visit "; 
        e.OperationA();
        cout << " in another way" << endl;
    }
    void visit(ConcreteElementB& e) override
{
        // Another operation to access the ConcreteElementB.
        cout << "visit "; 
        e.OperationB();
        cout << " in another way" << endl;
    }
};

void ConcreteElementA::Accept(Visitor& v) { v.visit(*this); }
void ConcreteElementA::OperationA() { cout << "ConcreteElementA"; }
void ConcreteElementB::Accept(Visitor& v) { v.visit(*this); }
void ConcreteElementB::OperationB() { cout << "ConcreteElementB"; }
class ObjectStructure
{
public:
    static Element* CreateElement()
{
        if (rand() % 100 > 50)
        {
            return new ConcreteElementA();
        }
        else
        {
            return new ConcreteElementB();
        }
    }
};
int main()
{
    ConcreteVisitor1 visitor1;
    ConcreteVisitor2 visitor2;
    for (int i=0; i < 10; i++)
    {
        Element* e = ObjectStructure::CreateElement();
        e->Accept(visitor1);
        e->Accept(visitor2);
    }
}

Additional Information

  • Visitor: An abstract visitor. It declares which elements the visitor can access.
  • ConcreteVistor: It affects what visitors should do after visitors access a class.
  • Element: An abstract role. It declares the type of visitor that is accepted for access.
  • ConcreteElement: It implements the accept method.
  • ObjectStructure: The element producer, generally contained in a number of containers (vector, map) with different classes and interfaces.

34

References (in Chinese)

Disclaimer: The views expressed herein are for reference only and don't necessarily represent the official views of Alibaba Cloud.

0 1 0
Share on

Alibaba Cloud Community

876 posts | 198 followers

You may also like

Comments