C + + design pattern basis and basic principles of pattern design

1. Design mode Preface

1.1. pattern

A solution to a problem in a certain environment, including three basic elements – (problem, solution, environment).
That is: under certain circumstances, use a fixed routine to solve problems.

1.2. Design pattern

It is a summary of code design experience that is repeatedly used, known by most people, classified and catalogued. The purpose of using design patterns is to reuse the code, make the code easier to be understood by others and ensure the reliability of the code. There is no doubt that design patterns win more for themselves, others and the system; The design pattern makes the coding truly engineering;
Design pattern is the cornerstone of software engineering, just like the structure of a building.
The significance of learning design patterns
Improve professional quality and pay attention to the long-term development of students in the industry.

1.3. Classification of design patterns

Gang of Four's book "design patterns: elements of resulbel software" classifies design patterns into three types, a total of 23.
Creation mode: it is usually related to the creation of objects and involves the way of object instantiation. (5 modes in total)
Structural pattern: describes how to combine classes and objects to obtain a larger structure. (7 modes in total)
Behavioral pattern: used to describe how classes or objects interact and how responsibilities are assigned. (11 modes in total)

1.4. Specific classification

  • The creation mode is used to handle the creation process of objects. It mainly includes the following five design modes:

1. The purpose of Factory Method Pattern is to define a factory interface for creating product objects and postpone the actual creation to subclasses.
2. The purpose of Abstract Factory Pattern is to create a series of related or interdependent interfaces without specifying their specific classes.
3. The intention of Builder Pattern is to separate a complex construction from its representation, so that the same construction process can create different representations.
4. Prototype Pattern is to use prototype instances to specify the type of objects to be created, and create new objects by copying these prototypes.
5. Singleton Pattern ensures that a class has only one instance and provides a global access point to access it.

  • Structural patterns are used to deal with the combination of classes or objects. They mainly include the following 7 design patterns:

6. Proxy Pattern is to provide a proxy for other objects to control access to this object.
7. Decorator Pattern dynamically adds some additional responsibilities to an object. In terms of adding functionality, this pattern is more flexible than generating subclasses.
8. Adapter Pattern is to convert the interface of a class into another interface desired by the customer. So that those classes that cannot work together due to incompatible interfaces can work together.
9. Bridge Pattern is to separate the abstract part from the actual part so that they can change independently.

10. Composite Pattern is a hierarchical structure that combines objects into a tree structure to represent "part whole". Make users use single objects and combined objects consistently.
11. Facade Pattern is to provide a consistent interface for a group of interfaces in the subsystem. This pattern defines a high-level interface, which makes the subsystem easier to use.
12. Flyweight Pattern supports a large number of fine-grained objects efficiently in a shared way.

  • Behavioral patterns are used to describe how classes or objects interact and how to assign responsibilities. They mainly include the following 11 design patterns:

13. Template Method Pattern enables subclasses to redefine some specific steps of an algorithm without changing the structure of the algorithm.
14. Command Pattern encapsulates a request as an object, so that you can parameterize the client with different requests; Queue or log requests, and support revocable operations.
15. Chain of Responsibility Pattern, in which many objects are connected by each object's reference to its next home to form a chain. The request is passed on the chain until an object in the chain decides to process the request, which enables the system to dynamically reorganize the chain and assign responsibilities without affecting the client.
16. Strategy Pattern is to prepare a set of algorithms and encapsulate each algorithm so that they can be interchanged.
17. Mediator Pattern is to define a mediation object to encapsulate the interaction between a series of objects. The terminator makes each object do not need to call each other, so that its coupling is loose, and the interaction between them can be changed independently.
When the pattern of all objects depends on the observer, it is automatically notified of the change of the state of one or more objects.
19. Memo pattern is to capture the internal state of an object and save the state outside the object without destroying the encapsulation.
20. Visitor Pattern refers to an operation that acts on each element in an object structure. It enables you to define new operations that act on these elements without changing the class of each element.
21. State Pattern is the behavior of an object, which depends on its state.
22. Interpreter Pattern describes how to define a grammar for a simple language, how to represent a sentence in the language, and how to interpret these sentences.
23. Iterator Pattern provides a method to access each element in an aggregate object sequentially without exposing the internal representation of the object.

1.5. Basic principles of design mode

Ultimate goal: high cohesion, low coupling

  1. OCP, open for extension, closed for modification principle
    Class changes are made by adding code, not modifying the source code.
#include <iostream>
using namespace std;
/*
1. Opening and closing principle, 
    It is open to extension and closed to modification
    Adding functions is realized through code, not modifying source code
    For example, in the calculator below, each function is a separate class, so that the source code can be added without moving when extending the function
*/

// negative cases 
// The calculation method of calculator, when extended operations such as extension remainder, cannot rewrite the function

// Write an abstract class
class AbstractCaculate{
public:
    virtual void setOperatorNumber(int a, int b) = 0;
    virtual int getResult() = 0;
};

class PlusCaculate : public AbstractCaculate{
private:
    int mA, mB;
public:
    virtual void setOperatorNumber(int a, int b){
        this->mA = a;
        this->mB = b;
    }
    virtual int getResult(){
        return this->mA + this->mB;
    }
};

class MultiCaculate : public AbstractCaculate{
private:
    int mA, mB;
public:
    virtual void setOperatorNumber(int a, int b){
        this->mA = a;
        this->mB = b;
    } 
    virtual int getResult(){
        return this->mA * this->mB;
    }
};

// If you need to add functions to calculation, you only need to define a class below to implement the new functions

int main(int argc, char const *argv[])
{
    AbstractCaculate* caculator = new PlusCaculate();
    caculator->setOperatorNumber(3,5);
    cout<< "res = "<<caculator->getResult()<<endl;
    delete caculator; // Note that every time you create a new object, you must use delete to destroy it
    
    return 0;
}
  1. Single responsibility principle (SRP)
    The responsibility of a class should be single. Only one function is provided externally, and there should be only one reason for the change of a class.

  2. Dip (dependency inversion principle)
    Rely on abstraction (Interface) instead of concrete implementation (class), that is, programming for interface.

// 04_ Rely on the principle of inversion cpp


#include <iostream>

using namespace std;

// banking business
class AbstractWorker{
    public:
    virtual void doBussiness() = 0;
};


// Underlying business
class doSaveWorker: public AbstractWorker{
    public:
    virtual void doBussiness(){
        cout<<"Deposit business.."<<endl;
    }
};

class doPayWorker: public AbstractWorker{
    public:
    virtual void doBussiness(){
        cout<<"Payment business.."<<endl;
    }
};

class doTransferWorker: public AbstractWorker{
    public:
    virtual void doBussiness(){
        cout<<"Transfer business"<<endl;
    }
};

// The specific implementation of middle-level business is simple
void doNewBusiness(AbstractWorker* worker){
    worker->doBussiness();
}

int main(int argc, char const *argv[])
{

    // Similar to polymorphism, the parent class points to the child class
    AbstractWorker* worker = new doTransferWorker();
    doNewBusiness(worker);
    delete worker; // Be sure to delete your new object


    cout<<"hello"<<endl;
    return 0;
}

  1. Interface isolation principle (ISP)
    Client programs should not be forced to rely on interface methods they do not need. An interface should only provide one external function, and all operations should not be encapsulated in one interface.
    The following code is written according to this interface isolation principle

  2. Liskov Substitution Principle (LSP)
    Any place where an abstract class appears can be replaced by its implementation class. In fact, it is a virtual mechanism, which realizes object-oriented function at the language level.

class Abstract{ // Abstract classes only define functions, but do not implement them
public:
    virtual void hello() = 0;
};

class B : public Abstract{
public:
    virtual void hello(){
        // Concrete implementation
    }
};
  1. Give priority to the use of combination rather than inheritance principle (car, composite / Aggregate Reuse Principle)
    If inheritance is used, any transformation of the parent class may affect the behavior of the child class.
    If you use object composition, you reduce this dependency.
#include <iostream>
#include <vector>
using namespace std;
class AbstractCar{
    public:
    virtual void run() = 0;
};

class Dazhong: public AbstractCar{
private:
    
public:
    virtual void run(){
        cout<<"Mass departure, Confused"<<endl;
    }
};

class Benchi: public AbstractCar{
private:
    
public:
    virtual void run(){
        cout<<"Benz, Confused"<<endl;
    }
};

class Person{
private:
    AbstractCar* car;
public:
    ~Person(){
        if(this->car != NULL){
            delete this->car;
        }
    }
    void setCar(AbstractCar* car){
        if(this->car != NULL){
            delete this->car;
            cout<<"The car has been returned.."<<endl;
        } // Prevent you from driving another car before you return the last one
        this->car = car;
    }
    void Doufeng(){
        this->car->run();
    }
};

// Inherit and combine, and use combination first
int main(int argc, char const *argv[])
{
    Person * p1 = new Person(); // Create a person
    p1->setCar(new Benchi()); // The man borrowed a car
    p1->Doufeng();  // Take this car for a ride
    p1->setCar(new Dazhong()); // While picking up a car, the last car has been returned
    p1->Doufeng(); // I'm going for a ride
    delete p1; // Return the car and delete the person

    return 0;
}

  1. LOD (Law of Demeter) minimum principle
    An object should know as little as possible about other objects, so as to reduce the coupling between various objects and improve the maintainability of the system. For example, in a program, when each module calls each other, it usually provides a unified interface to implement. In this way, other modules do not need to know the internal implementation details of another module, so that when the internal implementation of a module changes, it will not affect the use of other modules. (black box principle)
#include <iostream>
#include <vector>
using namespace std;
// Minimum knowledge principle
class AbstractBuilding{
public:
    virtual string getQuality() = 0;
    virtual void sale() = 0;
};

class BuildingA: public AbstractBuilding{
public:
    string mQulity;
    BuildingA(){
        mQulity = "superior quality";
    }
    virtual void sale(){
        cout<<"properties for sale A Sell "<<mQulity<<endl;
    }
    virtual string getQuality(){
        return mQulity;
    }
};

class BuildingB: public AbstractBuilding{
public:
    string mQulity;
    BuildingB(){
        mQulity = "Low quality";
    }
    virtual void sale(){
        cout<<"properties for sale B Sell "<<mQulity<<endl;
    }
    virtual string getQuality(){
        return mQulity;
    }
};

// Intermediary class
class Mediator{
public:
    Mediator(){
        AbstractBuilding* bu = new BuildingA;
        buildings.push_back(bu);
        bu = new BuildingB;
        buildings.push_back(bu);
        bu = new BuildingB;
        buildings.push_back(bu);
    }
    ~Mediator(){
        for(vector<AbstractBuilding*>::iterator it = buildings.begin(); 
            it!=buildings.end(); it++){
                if(*it!=NULL){
                    delete *it;
                }
            }
    }
    vector<AbstractBuilding*> buildings;
    // External interface
    AbstractBuilding* findMyBu(string quality){
        for(vector<AbstractBuilding*>::iterator it = buildings.begin(); 
            it!=buildings.end(); it++){
                if((*it)->getQuality() == quality){
                    return *it;
                }else{
                    return NULL;
                }
            }
    }
};

int main(int argc, char const *argv[])
{
    // BuildingA* build = new BuildingA();
    // If (build - > mqulity = = "high quality")
    //     build->sale();
    // delete build;
    
    Mediator * Medi = new Mediator();
    AbstractBuilding* building = Medi->findMyBu("superior quality");
    if(building == NULL){
        cout<<"There is no high-quality house"<<endl;
    }else{
        building->sale();
    }
    return 0;
}

Keywords: C++ Design Pattern

Added by ecaandrew on Tue, 08 Mar 2022 21:24:18 +0200