Common design patterns

Posted by shibobo12 on Thu, 24 Feb 2022 17:51:14 +0100

1. Memory model: you can have a look Inheritance and polymorphism Memory model

2. Pattern design principles:

#1: Dependency Inversion Principle

  • High level modules should not rely on low-level modules, but both should rely on abstraction;
  • Abstract should not rely on concrete implementation, but concrete implementation should rely on abstraction;

The automatic driving system is a high level, and the automobile manufacturer is the bottom layer. They should not depend on each other. Instead, an automatic driving industry standard should be abstracted, and both the high-level and the low-level rely on it; In this way, the changes of the two sides are decoupled; The automatic driving system and the automobile production factory are all concrete internships, they all should rely on the automatic driving profession standard (Abstract).

#2. Open and closed principle

  • A class should be open to extension and closed to modification

#3. Interface oriented programming

  • The variable type is not declared as a specific concrete class, but as an interface
  • The client program does not need to know the specific type of the object, but only the interface of the object.
  • Reduce the dependency of each part of the system, so as to realize the type design scheme with high cohesion and loose coupling.

#4. Packaging change point

  • Separate the stable point from the change point, expand and modify the change point, and separate the realization levels of the stable point and the change point;

#5. Principle of single responsibility

  • A class should have only one reason for its change;

#6. Richter substitution principle

  • The subtype must be able to replace its parent type; It mainly occurs when the subclass overrides the implementation of the parent class, and the original program using the parent type may have errors; Override the parent method but fail to implement the responsibility of the parent method;

#7. Interface isolation principle

  • Customers should not be forced to rely on methods they do not use
  • It is generally used to handle that a class has many interfaces, and these interfaces involve many responsibilities;

#8 object composition is better than class inheritance

  • High inheritance coupling and low combination coupling;

I Singleton mode

No matter how many times a class creates an object, it can always get an instance of an object of that type

Commonly used: such as log module and database module

Hungry Han style singleton: the instance object has been generated before obtaining the instance object. It must be thread safe

There is a disadvantage: in the actual project, the constructor may be called when the instance is, because the constructor may do many things, such as loading configuration in the constructor

class Singleton
{
    public:
        static Singleton* getInstance() //#3. Method to obtain the unique instance object of the class
        {
            return &initstate;
        }
    private:
        static Singleton instance; //#2. Define a unique class instantiation object
        Singleton(){} //#1. Privatize constructors and delete their copy structures and assignment overloads
        Singleton(const Singleton& ) = delete;
        Singleton& operator=(const Singleton&) = delete;
        
};
Singleton Singleton::instance;

int main()
{
    Singleton *p1 = Singleton::getInstance();
    Singleton *p2 = Singleton::getInstance();
    Singleton *p3 = Singleton::getInstance();
    return 0;
}

Lazy singleton: a unique instance object that is not generated until it is first acquired

Example 1: not thread safe

class Singleton
{
    public:
        static Singleton* getInstance() //#3. Method to obtain the unique instance object of the class
        {
            if (instance == nullptr)
            {
                instance = new Singleton();
            }
            return instance;
        }
    private:
        static Singleton *instance; //#2. Define a unique class instantiation object
        Singleton(){} //#1. Privatize constructors and delete their copy structures and assignment overloads
        Singleton(const Singleton& ) = delete;
        Singleton& operator=(const Singleton&) = delete;
        
};
Singleton* Singleton::instance = nullptr; //At the beginning of the data segment, it is just a pointer. Only when it is called for the first time, an object is new

int main()
{
    Singleton *p1 = Singleton::getInstance();
    Singleton *p2 = Singleton::getInstance();
    Singleton *p3 = Singleton::getInstance();
    return 0;
}

Example 2: the advantage of thread safe * volatile plus this is that once changed, other threads can see it. Do not cache

#include <iostream>
#include <mutex>
std::mutex mtx;

class Singleton
{
    public:
        static Singleton* getInstance() //#3. Method to obtain the unique instance object of the class
        {
            if (instance == nullptr)
            {
                /*
                1.Open up memory
                2.Call construct
                3.Assign value to instance
                There is a thread safety problem: maybe thread 2 came in when thread 1 was opening up memory.
                It is also possible that when thread 1 is assigned a value, thread 2. It's possible, so it's not thread safe
                */
               std::lock_guard<std::mutex> lock(lock); //So lock it
               if(instance == nullptr)
               {
                   instance = new Singleton();
               }
                
            }
            return instance;
        }
    private:
        static Singleton *volatile instance; //#2. Define a unique class instantiation object
        Singleton(){} //#1. Privatize constructors and delete their copy structures and assignment overloads
        Singleton(const Singleton& ) = delete;
        Singleton& operator=(const Singleton&) = delete;
        
};
Singleton *volatile Singleton::instance = nullptr; //At the beginning of the data segment, it is just a pointer. Only when it is called for the first time, an object is new

int main()
{
    Singleton *p1 = Singleton::getInstance();
    Singleton *p2 = Singleton::getInstance();
    Singleton *p3 = Singleton::getInstance();
    return 0;
}

2, Simple Factory

Factory mode: it mainly encapsulates the creation of objects and returns the created objects by passing in different identifiers. The customer does not need to be responsible for the new object and understand the object creation process

Disadvantages: the interface for creating object instances is not closed and cannot be closed for modification

class Car
{
    public:
        Car(std::string name): _name(name){}
        virtual void show() = 0;
    protected:
        std::string _name;
};

class BMW:public Car
{
    public:
        BMW(std::string name) :Car(name){}
        void show() 
        {
            std::cout << "Got a BMW:" << _name<<std::endl;
        }
};
class Audi:public Car
{
    public:
        Audi(std::string name) :Car(name){}
        void show() 
        {
            std::cout << "Got an Audi:" << _name << std::endl;
        }
};

int main()
{
    //We shouldn't create classes here. We need to remember too many class names
    Car *p1 = new BMW("x1"); 
    Car *p2 = new Audi("A6"); 
    return 0;
}

Change to simple factory:

#include <iostream>
#include <mutex>
#include <string>
#include <memory>
class Car
{
    public:
        Car(std::string name): _name(name){}
        virtual void show() = 0;
    protected:
        std::string _name;
};

class BMW:public Car
{
    public:
        BMW(std::string name) :Car(name){}
        void show() 
        {
            std::cout << "Got a BMW:" << _name<<std::endl;
        }
};
class Audi:public Car
{
    public:
        Audi(std::string name) :Car(name){}
        void show() 
        {
            std::cout << "Got an Audi:" << _name << std::endl;
        }
};

enum CarType
{
    Bmw,Audic
};
//Create factory interface
class SimpleFactory
{
    public:
        Car* createCar(CarType ct)
        {
            switch (ct)
            {
            case Bmw:
                return new BMW("x1");
                break;
            case Audic:
                /* code */
                return new Audi("A6"); 
                break;
            default:
                std::cerr << "The parameters passed in to the factory are incorrect:" << ct << std::endl;
                break;
            }
            return nullptr;
        }
};


int main()
{
    std::unique_ptr<SimpleFactory> factory(new SimpleFactory());
    std::unique_ptr<Car> p1(factory->createCar(Bmw));
    p1->show();
    return 0;
}

However, the above interfaces do not conform to the pattern design (opening and closing principle) when creating each class. To add a new object, you need to override swith

3, Factory method

Since the creation process is complicated, you want to hide these details:

For example, connection pool and thread pool;

Hide the real type of object;

Object creation will have many parameters to determine how to create;

Create objects with complex dependencies;  

The Factory base class provides a pure virtual function (creating products) and defines derived classes (factories of specific products) to create corresponding products. Different products can be created in different factories, and existing factories and product modifications can be closed.

In fact, many products are related and belong to a product cluster. They should not be created in different factories. This does not conform to the actual logic of product object creation. Second, there are too many factory classes and it is difficult to maintain

class Car
{
    public:
        Car(std::string name): _name(name){}
        virtual void show() = 0;
    protected:
        std::string _name;
};

class BMW:public Car
{
    public:
        BMW(std::string name) :Car(name){}
        void show() 
        {
            std::cout << "Got a BMW:" << _name<<std::endl;
        }
};
class Audi:public Car
{
    public:
        Audi(std::string name) :Car(name){}
        void show() 
        {
            std::cout << "Got an Audi:" << _name << std::endl;
        }
};


//Factory method
class Factory
{
    public:
        virtual Car* createCar(std::string name) = 0;
};
//BMW factory
class BWMFactory:public Factory
{
    public:
        Car* createCar(std::string name){
            return new BMW(name);
        }
};
//Audi factory
class AudiFactory:public Factory
{
    public:
        Car* createCar(std::string name){
            return new Audi(name);
        }
};

int main()
{
    std::unique_ptr<Factory> bmwfty(new BWMFactory());
    std::unique_ptr<Factory> audifty(new AudiFactory());
    std::unique_ptr<Car> p1(bmwfty->createCar("X1"));
    std::unique_ptr<Car> p2(audifty->createCar("A6"));
    p1->show();
    p2->show();
    return 0;
}

4, Abstract factory

Combination of factory methods

Put the interface functions created by all products belonging to a product cluster that are associated into an abstract factory, and the derived class (factory of specific products) should be responsible for creating all products in the product cluster

#include <iostream>
#include <mutex>
#include <string>
#include <memory>
//Series 1
class Car
{
    public:
        Car(std::string name): _name(name){}
        virtual void show() = 0;
    protected:
        std::string _name;
};

class BMW:public Car
{
    public:
        BMW(std::string name) :Car(name){}
        void show() 
        {
            std::cout << "Got a BMW:" << _name<<std::endl;
        }
};
class Audi:public Car
{
    public:
        Audi(std::string name) :Car(name){}
        void show() 
        {
            std::cout << "Got an Audi:" << _name << std::endl;
        }
};

//Series 2
class Light
{
    public:
        virtual void show() = 0;
};
class BmwLight:public Light
{
    public:
        void show() {  std::cout << "BMW light!" << std::endl; }
};
class AudiLight:public Light
{
    public:
        void show() {  std::cout << "Audi light!" << std::endl; }
};

//Abstract factory (provide unified creation of product objects for a set of related product clusters)
class AbstractFactory
{
    public:
        virtual Car* createCar(std::string name) = 0; //Factory method to create cars
        virtual Light* createCarLight() = 0;//Factory method to create car lights
};
//BMW factory
class BWMFactory:public AbstractFactory
{
    public:
        Car* createCar(std::string name){
            return new BMW(name);
        }
        Light* createCarLight()
        {
            return new BmwLight();
        }
};
//Audi factory
class AudiFactory:public AbstractFactory
{
    public:
        Car* createCar(std::string name){
            return new Audi(name);
        }
        Light* createCarLight()
        {
            return new AudiLight();
        }
};

int main()
{
    std::unique_ptr<AbstractFactory> bmwfty(new BWMFactory());
    std::unique_ptr<AbstractFactory> audifty(new AudiFactory());
    std::unique_ptr<Car> p1(bmwfty->createCar("X1"));
    std::unique_ptr<Car> p2(audifty->createCar("A6"));
    std::unique_ptr<Light> l1(bmwfty->createCarLight());
    std::unique_ptr<Light> l2(audifty->createCarLight());
    p1->show();
    p2->show();
    l1->show();
    l2->show();
    return 0;
}

5, Agent mode

Client Assistant proxy boss (delegate class includes all functions)

Proxy mode: control the access permission of the actual object (boss) through the proxy class

//Without permission management


class VideoSite
{
    public:
        virtual void freeMovie()    = 0; //Free movie
        virtual void vipMovie()     = 0; //vip movie
        virtual void ticketMovie()  = 0; //Need tickets for movies
};

class FixBugVideoSite:public VideoSite
{
    public:
        virtual void freeMovie()
        {
            std::cout << "Watch movies for free" <<std::endl;
        }
        virtual void vipMovie()
        {
            std::cout << "vip Watch movies" <<std::endl;
        }
        virtual void ticketMovie()
        {
            std::cout << "Need tickets to watch movies" <<std::endl;
        }
};


int main()
{
    //No agent mode is like this, and then each common need to make judgment
    VideoSite *p1 = new FixBugVideoSite();
    p1->freeMovie();
    p1->vipMovie();
    p1->ticketMovie();
    return 0;
}

Use proxy mode for permission management:

#include <iostream>
#include <mutex>
#include <string>
#include <memory>


class VideoSite
{
    public:
        virtual void freeMovie()    = 0; //Free movie
        virtual void vipMovie()     = 0; //vip movie
        virtual void ticketMovie()  = 0; //Need tickets for movies
};
 
class FixBugVideoSite:public VideoSite  //Delegate class
{
    public:
        virtual void freeMovie()
        {
            std::cout << "Watch movies for free" <<std::endl;
        }
        virtual void vipMovie()
        {
            std::cout << "vip Watch movies" <<std::endl;
        }
        virtual void ticketMovie()
        {
            std::cout << "Need tickets to watch movies" <<std::endl;
        }
};

//Proxy class proxy class
class FreeVideoSiteProxy: public VideoSite
{
    public:
        FreeVideoSiteProxy()
        {
            pVideo = new FixBugVideoSite();
        }
        ~FreeVideoSiteProxy()
        {
            delete pVideo;
        }
        virtual void freeMovie()  {
            //The real object is accessed through the proxy method of the movie object
            pVideo->freeMovie(); 
        }
        virtual void vipMovie()   {
            std::cout << "VIP Movie, your identity doesn't deserve to see! Please go to improve your identity!" << std::endl;
        }
        virtual void ticketMovie(){
            std::cout << "Movies with coupons are not suitable for poor people! If you really want to pretend to be forced, please charge!" << std::endl;
        }
    private:
        VideoSite *pVideo; //new FixBugVideoSite()
};

//Proxy class proxy class
class VipVideoSiteProxy: public VideoSite
{
    public:
        VipVideoSiteProxy()
        {
            pVideo = new FixBugVideoSite();
        }
        ~VipVideoSiteProxy()
        {
            delete pVideo;
        }
        virtual void freeMovie()  {
            //The real object is accessed through the proxy method of the movie object
            pVideo->freeMovie(); 
        }
        virtual void vipMovie()   {
            pVideo->vipMovie();
        }
        virtual void ticketMovie(){
            std::cout << "Movies with coupons are not suitable for poor people! If you really want to pretend to be forced, please charge!" << std::endl;
        }
    private:
        VideoSite *pVideo; //new FixBugVideoSite()
};

void watchMovice(std::unique_ptr<VideoSite> &ptr)
{
    ptr->freeMovie();
    ptr->vipMovie();
    ptr->ticketMovie();
}

int main()
{
    std::unique_ptr<VideoSite> p1(new VipVideoSiteProxy());
    watchMovice(p1);
    
    return 0;
}

6, Decorator mode decorator

It is mainly to add the functions of existing classes. However, another way to add the function of existing classes is to add a new subclass

For example: add subclasses

In order to enhance the function of existing classes, the function expansion can be completed by implementing subclasses and rewriting interfaces. But looking at the figure above, each brand adds two functions, that is, six categories. inappropriate

For example: decorator

Decorator 1

#include <iostream>
#include <mutex>
#include <string>
#include <memory>

class Car
{
    public:
        virtual void show() = 0;
};

class Bmw : public Car
{
    public:
        void show() { std::cout << "BMW, only basic configuration" ; }
};

class Audi : public Car
{
    public:
        void show() { std::cout << "Audi, only basic configuration" ; }
};

class Benz : public Car
{
    public:
        void show() { std::cout << "Mercedes Benz, only basic configuration" ; }
};

//Base class of decorator 
class CarDecorator: public Car
{
    public:
        CarDecorator(Car *p):pCar(p) { }
    private:
        Car *pCar;
};
//If the base class has no public function, you can omit the direct inheritance of Car
// class ConcreateDecorator01:public CarDecorator {}  
//Trim timing Cruise
class ConcreateDecorator01:public Car
{
    public:
        ConcreateDecorator01(Car *p) :pCar(p) {}
        void show()
        {
            pCar->show();
            std::cout << ",Cruise control " ;
        }
    private:
        Car *pCar;
};

class ConcreateDecorator02:public Car
{
    public:
        ConcreateDecorator02(Car *p) :pCar(p) {}
        void show()
        {
            pCar->show();
            std::cout << ",Automatic brake" ;
        }
    private:
        Car *pCar;
};

class ConcreateDecorator03:public Car
{
    public:
        ConcreateDecorator03(Car *p) :pCar(p) {}
        void show()
        {
            pCar->show();
            std::cout << ",Road deviation" ;
        }
    private:
        Car *pCar;
};


int main()
{
    Car *p1 = new ConcreateDecorator01(new Bmw());
    p1->show();
    delete p1;
    
    return 0;
}

Decoration 2:

#include <iostream>
#include <mutex>
#include <string>
#include <memory>

class Car
{
    public:
        virtual void show() = 0;
};

class Bmw : public Car
{
    public:
        void show() { std::cout << "BMW, basic configuration" ; }
};

class Audi : public Car
{
    public:
        void show() { std::cout << "Audi, basic configuration" ; }
};

class Benz : public Car
{
    public:
        void show() { std::cout << "Mercedes Benz, basic configuration" ; }
};

//Base class of decorator 
class CarDecorator: public Car
{
    public:
        CarDecorator(std::unique_ptr<Car> &ptr):pCar(std::move(ptr)) { }
        virtual void show()= 0;
    protected:
        std::unique_ptr<Car> pCar;
};

//Trim timing Cruise
class ConcreateDecorator01:public  CarDecorator
{
    public:
        ConcreateDecorator01(std::unique_ptr<Car> &ptr) :CarDecorator(ptr) {}
        void show()
        {
            pCar->show();
            std::cout << ",Cruise control " ;
        }

};

class ConcreateDecorator02:public  CarDecorator
{
    public:
        ConcreateDecorator02(std::unique_ptr<Car> &ptr) :CarDecorator(ptr) {}
        void show()
        {
            pCar->show();
            std::cout << ",Automatic brake" ;
        }

};

class ConcreateDecorator03:public  CarDecorator
{
    public:
        ConcreateDecorator03(std::unique_ptr<Car> &ptr) :CarDecorator(ptr) {}
        void show()
        {
            pCar->show();
            std::cout << ",Road deviation" ;
        }

};


int main()
{
    std::unique_ptr<Car> p(new Bmw());
    std::unique_ptr<Car> p1(new ConcreateDecorator01(p));
    std::unique_ptr<Car> p2(new ConcreateDecorator02(p1));
    p2->show();
    std::cout << std::endl;
    
    return 0;
}

7. Adapter mode: make incompatible interfaces work together

The code example above:

#include <iostream>
#include <mutex>
#include <string>
#include <memory>
#include <string>

class VGA //VGA interface class
{
    public:
        virtual void play() = 0;
};

//TV01 indicates a projector that supports VGA interface
class TV01:public VGA
{
    public:
        void play() {  std::cout << "adopt VGA Interface connection adaptation playback" << std::endl; } 
};

class HDMI //HDMI interface class
{
    public:
        virtual void play() = 0;
};

//TV02 represents a projector that supports HDML interface
class TV02:public HDMI
{
    public:
        void play() {  std::cout << "adopt HDMI Interface connection adaptation playback" << std::endl; } 
};

//Computer
class Computer //Only VGA interface is supported
{
    public:
        //Since the computer only supports VGA interface, the parameters of this method can only support the pointer of VGA interface
        void playVideo(std::unique_ptr<VGA> pVga)
        {
            std::cout << "VGA Interface computer";
            pVga->play();
        }
};

int main()
{
    
    std::unique_ptr<VGA> tv01(new TV02());
    Computer computer;
    computer.playVideo(std::move(tv01));
    return 0;
    
}

Interface incompatibility: TV02 is a projector with HDMI interface

Solution 1: change a computer with HDMI interface, also known as code refactoring, but the old interface in the actual project has too many places to use this interface. It's expensive to replace them all

Solution 2: buy a converter that can convert VGA signal into HDMI signal. This is called adding adapter class

#include <iostream>
#include <mutex>
#include <string>
#include <memory>
#include <string>

class VGA //VGA interface class
{
    public:
        virtual void play() = 0;
};

//TV01 indicates a projector that supports VGA interface
class TV01:public VGA
{
    public:
        void play() {  std::cout << "adopt VGA Interface connection adaptation playback" << std::endl; } 
};

class HDMI //HDMI interface class
{
    public:
        virtual void play() = 0;
};

//TV02 represents a projector that supports HDML interface
class TV02:public HDMI
{
    public:
        void play() {  std::cout << "adopt HDMI Interface connection adaptation playback" << std::endl; } 
};

//Since the computer (VGA interface) and projector (HDMI) cannot be directly connected, you need to add an adapter class
class VGAToHDMIdapter:public VGA
{
    public:
        VGAToHDMIdapter(HDMI *p):pHdmi(std::move(p)) {}
        void play()
        {
            std::cout << "Through the conversion head,";
            pHdmi->play();
        }
    private:
        std::unique_ptr<HDMI> pHdmi;
};


//Computer
class Computer //Only VGA interface is supported
{
    public:
        Computer(VGA *p):pVga(std::move(p)){}
        //Since the computer only supports VGA interface, the parameters of this method can only support the pointer of VGA interface
        void playVideo()
        {
            std::cout << "VGA Interface computer";
            pVga->play();
        }
    private:
        std::unique_ptr<VGA> pVga;
};

int main()
{
    Computer computer(new VGAToHDMIdapter(new TV02()));
    computer.playVideo();
    return 0;

}

8. Observer listener (publish subscribe mode)

Behavior pattern: the main concern is the communication between objects

The main concern is the one to many relationship of objects, that is, multiple objects depend on one object. When the state of the object changes, other objects can receive corresponding notifications

#include <iostream>
#include <mutex>
#include <string>
#include <memory>
#include <string>
#include <unordered_map>
#include <list>

class Observer //Observer abstract class
{
    public:
        //Interface for processing messages
        virtual void handle(int msgid) = 0;
};

class Observer1 : public Observer
{
    public:
        //Interface for processing messages
        void handle(int msgid) {
            switch (msgid)
            {
            case 1:
                std::cout << "observer1 recv 1 msg!" << std::endl;
                break;
            case 2:
                std::cout << "observer1 recv 2 msg!" << std::endl;
                break;
            
            default:
                std::cout << "observer1 recv unknow msg!" << std::endl;
                break;
            }
        }
};

class Observer2 : public Observer
{
    public:
        //Interface for processing messages
        void handle(int msgid) {
            switch (msgid)
            {
            case 2:
                std::cout << "observer2 recv 2 msg!" << std::endl;
                break;
            
            default:
                std::cout << "observer2 recv unknow msg!" << std::endl;
                break;
            }
        }
};

class Observer3 : public Observer
{
    public:
        //Interface for processing messages
        void handle(int msgid) {
            switch (msgid)
            {
            case 1:
                std::cout << "observer3 recv 1 msg!" << std::endl;
                break;
            case 3:
                std::cout << "observer3 recv 3 msg!" << std::endl;
                break;
            
            default:
                std::cout << "observer3 recv unknow msg!" << std::endl;
                break;
            }
        }
};

class Suhject
{
    public:
        //Subscribe to objects
        void addObserver(Observer* obser, int msgid)
        {
            _subMap[msgid].push_back(obser);
        }
        //The subject detects a change and notifies the corresponding observer object to handle the event
        void dispatch(int msgid)
        {
            auto it = _subMap.find(msgid);
            if(it != _subMap.end())
            {
                for(Observer *pObser : it->second) //Fetch the subscribed object
                {
                    pObser->handle(msgid);
                }
            }
        }
    private:
        std::unordered_map<int, std::list<Observer*>> _subMap; 
};


int main()
{
    Suhject subject;
    Observer *p1 = new Observer1();
    Observer *p2 = new Observer2();
    Observer *p3 = new Observer3();

    subject.addObserver(p1,1);
    subject.addObserver(p1,2);
    subject.addObserver(p2,2);
    subject.addObserver(p3,1);
    subject.addObserver(p3,3);

    int msgid = 0;
    for(;;)
    {
        std::cout << "input id:" ;
        std::cin >> msgid;
        if(msgid == -1) break;
        subject.dispatch(msgid);
    }

    delete p1;
    delete p2;
    delete p3;

    return 0;

}

 

Topics: C++