Design pattern (C + +)

Design mode (I)

1. Singleton mode

definition

The singleton pattern is that a class can only be instantiated once, more precisely, a class with only one instantiated object.

  • This class can only have one instance (only create a new object when the pointer is null, otherwise return the original object);
  • It must create the instance itself (the constructor is private, and then accessed indirectly through the getInstance method);
  • It must provide this instance to the whole system itself (getInstance is a static member function).

Note: if it is a multithreaded environment, it needs to be protected with a mutex when creating an instance.

#include <iostream>
#include <mutex>
class Singleton
{
public:
    // Get the function of the singleton object. Note that the instance object must be global
    static Singleton *getInstance(int num)
    {
        // m_mutex.lock();
        if (instance == nullptr)
        {
            std::cout << "new instance!" << std::endl;
            instance = new Singleton(num);
        }
        // m_mutex.unlock();
        return instance;
    }
    // Strictly speaking, objects in singleton mode can neither be assigned nor copied
    Singleton(Singleton &other) = delete;
    void operator=(const Singleton &) = delete;
    int getNum() const
    {
        return this->num;
    }

private:
    // Private constructor. The constructor here cannot be used directly
    Singleton(int num)
    {
        this->num = num;
    }
    // Private field
    int num;
    // Static method to facilitate global access to instances
    static Singleton *instance;
    // static std::mutex m_mutex;
};

// The initial value is nullptr
Singleton *Singleton::instance = nullptr;
// std::mutex Singleton::m_mutex;


int main()
{
    Singleton *s1 = Singleton::getInstance(1);
    std::cout << s1->getNum() << std::endl;
    // No new instances will be created thereafter
    Singleton *s2 = Singleton::getInstance(2);
    std::cout << s2->getNum() << std::endl;
}

result:

new instance!
1
1

2. Factory mode

definition

Define an interface for creating objects, but let subclasses decide which class to instantiate. The factory method pattern delays the instantiation of a class to its subclasses.

There are four kinds of members: abstract factory, concrete factory, abstract product and concrete product.

Take "factories producing mobile phones and computers" as an example:

#include <iostream>
// Abstract product
class AbstractProduct
{
public:
    AbstractProduct() {}
    virtual ~AbstractProduct() {}
    virtual void getInfo() const = 0;
};

// Specific products 1
class Computer : public AbstractProduct
{
public:
    Computer(int price) : price(price)
    {
        getInfo();
    }
    virtual void getInfo() const override
    {
        std::cout << "computer:$" << price << std::endl;
    }

private:
    int price;
};

// Specific products 2
class MobilePhone : public AbstractProduct
{
public:
    MobilePhone(int price) : price(price)
    {
        getInfo();
    }
    virtual void getInfo() const override
    {
        std::cout << "mobile phone:$" << price << std::endl;
    }

private:
    int price;
};

// Abstract factory
class AbstractFactory
{
public:
    virtual AbstractProduct *getProduct(int) const = 0; // Return the product base class pointer, and use polymorphism to get different products
    virtual ~AbstractFactory() {}
};

// Specific factory 1
// Each time a new product is introduced, a corresponding factory (override subclass) is created
// If the construction method is relatively simple, the abstract factory and the concrete factory can also be combined and implemented with switch case statements
class ComputerFactory : public AbstractFactory
{
public:
    AbstractProduct *getProduct(int price) const override
    {
        return new Computer(price);
    }
};

// Specific plant 2
class MobilePhoneFactory : public AbstractFactory
{
public:
    AbstractProduct *getProduct(int price) const override
    {
        return new MobilePhone(price);
    }
};

int main()
{
    AbstractFactory *fac = nullptr;

    fac = new ComputerFactory();
    fac->getProduct(1000);
    delete fac;

    fac = new MobilePhoneFactory();
    fac->getProduct(500);
    delete fac;
}

result:

computer:$1000
mobile phone:$500

3. Abstract factory mode

definition

Provides an interface to create a series of related or interdependent objects without specifying their specific classes.

Different from the General Factory pattern, each concrete factory of the abstract factory pattern can produce a variety of products. These products generally do not belong to the same category, but they are interdependent, complementary and closely related.

Take the "factory producing mobile phones, computers and their supporting USB" as an example:

#include <iostream>
// Abstract product class 1 (here is the production electronic product ontology)
class AbstractProductA
{
public:
    AbstractProductA() {}
    virtual ~AbstractProductA() {}
    virtual void getInfo() const = 0;
};

// Abstract product class 2 (here is the USB supporting the production of electronic products)
class AbstractProductB
{
public:
    AbstractProductB() {}
    virtual ~AbstractProductB() {}
    virtual void getInfo() const = 0;
};

// Specific product 1 (belonging to class A)
class Computer : public AbstractProductA
{
public:
    Computer(int price) : price(price)
    {
        getInfo();
    }
    virtual void getInfo() const override
    {
        std::cout << "computer:$" << price << std::endl;
    }

private:
    int price;
};

// Specific product 2 (belonging to class A)
class MobilePhone : public AbstractProductA
{
public:
    MobilePhone(int price) : price(price)
    {
        getInfo();
    }
    virtual void getInfo() const override
    {
        std::cout << "mobile phone:$" << price << std::endl;
    }

private:
    int price;
};

// Specific product 3 (belonging to class B)
class ComputerUSB :public AbstractProductB
{
public:
    ComputerUSB(int price):price(price)
    {
        getInfo();
    }
    virtual void getInfo() const override
    {
        std::cout << "computer USB:$" << price << std::endl;
    }

private:
    int price;
};

// Specific product 4 (belonging to class B)
class MobilePhoneUSB :public AbstractProductB
{
public:
    MobilePhoneUSB(int price):price(price)
    {
        getInfo();
    }
    virtual void getInfo() const override
    {
        std::cout << "mobile phone USB:$" << price << std::endl;
    }

private:
    int price;
};


// Abstract factory
class AbstractFactory
{
public:
    virtual AbstractProductA *getProductA(int) const = 0 ; // Return the product base class pointer, and use polymorphism to get different products
    virtual AbstractProductB *getProductB(int) const = 0 ; // It can produce multiple supporting products
    virtual ~AbstractFactory() {}
};

// Specific factory 1
// Each time a new product is introduced, a corresponding factory (override subclass) is created
class ComputerFactory : public AbstractFactory
{
public:
    AbstractProductA *getProductA(int price) const override
    {
        return new Computer(price);
    }
    AbstractProductB *getProductB(int price) const override
    {
        return new ComputerUSB(price);
    }
};

// Specific plant 2
class MobilePhoneFactory : public AbstractFactory
{
public:
    AbstractProductA *getProductA(int price) const override
    {
        return new MobilePhone(price);
    }
    AbstractProductB *getProductB(int price) const override
    {
        return new MobilePhoneUSB(price);
    }
};

int main()
{
    AbstractFactory *fac = nullptr;

    fac = new ComputerFactory();
    std::cout<<"produce a computer:"<<std::endl;
    fac->getProductA(1000);
    fac->getProductB(50);
    delete fac;

    std::cout<<"produce a mobile phone:"<<std::endl;
    fac = new MobilePhoneFactory();
    fac->getProductA(500);
    fac->getProductB(30);
    delete fac;
}

4. Prototype mode

definition

Use the prototype instance to specify the type of object to be created, and create a new object by copying the prototype.

Prototype pattern indicates the type of object to be created by giving a prototype object, and then creates more objects of the same type by copying the prototype object. The prototype pattern can be exempted from considering shallow copy and deep copy.

Take "get graphics with different colors and shapes" as an example:

#include <iostream>
#include <cstring>
// Base class, you need to define the Clone method as a virtual method
class Shape
{
public:
    Shape(int centerX, int centerY, char *_color) : centerX(centerX), centerY(centerY), color(_color)
    {
        
    }
    virtual Shape *Clone() const = 0;
    virtual void getAddr() const = 0;
    virtual ~Shape()
    {
        
    }

protected:
    int centerX, centerY;
    char *color;
};

class Circle : public Shape
{
public:
    Circle(int centerX, int centerY, char *_color, int radius) : Shape(centerX, centerY, _color), radius(radius)
    {
        std::cout << "Circle:(" << centerX << ',' << centerY << ")," << radius << ',' << _color << std::endl;
    }
    // Basic operation of clone
    Shape *Clone() const override
    {
        return new Circle(*this);
    }
    void getAddr() const override
    {
        std::cout << &centerX << ' ' << &centerY << ' ' << &radius << ' ' << &color << std::endl;
    }

private:
    int radius;
};

class Square : public Shape
{
    Square(int centerX, int centerY, char *_color, int x) : Shape(centerX, centerY, _color), x(x)
    {
        std::cout << "Square:(" << centerX << ',' << centerY << ")," << x << ',' << _color << std::endl;
    }
    Shape *Clone() const override
    {
        return new Square(*this);
    }
    void getAddr() const override
    {
        std::cout << &centerX << ' ' << &centerY << ' ' << &x << ' ' << &color << std::endl;
    }

private:
    int x;
};

int main()
{
    Shape *shape1 = new Circle(2, 2, "red", 3);
    Shape *shape2 = shape1->Clone();
    // As you can see, calling the clone method can avoid the problem of shallow copy
    shape1->getAddr();
    shape2->getAddr();
    delete shape1;
    delete shape2;
}

result:

Circle:(2,2),3,red
0xd714d8 0xd714dc 0xd714e8 0xd714e0
0xd71528 0xd7152c 0xd71538 0xd71530

5. Builder mode

definition

Separate the construction of a complex object from its representation, so that the same construction process can create different representations.

There are four kinds of members: Abstract generator (the abstract interface for realizing the construction method and assembly method), concrete generator (realizing the specific construction method and assembly method of each part), product and Supervisor (isolating the production process of customers and objects and controlling the production process of product objects).

The following is an example of "a chef who cooks two different dishes":

#include <iostream>
#include <utility>
#include <string>
typedef std::pair<std::string,int> formula; // menu

// Product category (here is food)
class Dish
{
public:
    // In builder mode, the traditional constructor form is not required
    Dish(){}
    void setMeat(formula meat)
    {
        this->meat = meat;
    }
    void setVegetable(formula vegetable)
    {
        this->vegetable = vegetable;
    }
    void getInfo()
    {
        std::cout << meat.first << ':' << meat.second << 'g' << std::endl;
        std::cout << vegetable.first << ':' << vegetable.second << 'g' << std::endl;
    }

private:
    formula meat;
    formula vegetable;
};

// Abstract builder class
class AbstractBuilder
{
public:
    AbstractBuilder()
    {
        this->dish = new Dish();
    }
    virtual void putMeat() = 0;
    virtual void putVegetable() = 0;
    virtual Dish* getDish() = 0;
    virtual ~AbstractBuilder(){}
protected:
    Dish *dish;
};

// Specific builder class 1 (recipe 1)
class ConcreteBuilderA :public AbstractBuilder
{
public:
    void putMeat () override
    {
        this->dish->setMeat(std::make_pair("pork",50));
    }
    void putVegetable () override
    {
        this->dish->setVegetable(std::make_pair("onion",100));
    }
    Dish* getDish() override
    {
        return this->dish;
    }
};

// Specific builder class 2 (recipe 2)
class ConcreteBuilderB :public AbstractBuilder
{
public:
    void putMeat () override
    {
        this->dish->setMeat(std::make_pair("beef",100));
    }
    void putVegetable () override
    {
        this->dish->setVegetable(std::make_pair("potato",200));
    }
    Dish* getDish() override
    {
        return this->dish;
    }
};

// The supervisor class is responsible for encapsulating the construction process and returning the construction results
class Director
{
public:
    Director(){}
    void setBuilder(AbstractBuilder *_Builder)
    {
		this->builder = _Builder;
	}
    // Core encapsulation code
    Dish* constructor()
    {
        this->builder->putMeat();
        this->builder->putVegetable();
        return this->builder->getDish();
    }
private:
	AbstractBuilder *builder;
};

int main()
{
    AbstractBuilder *builder;
	Director *director = new Director();
    Dish *dish;

    // Designated builder A
    std::cout<<"dish1:"<<std::endl;
    builder = new ConcreteBuilderA();
    director->setBuilder(builder);
    dish = director->constructor();
    dish->getInfo();

    // Designated builder B
    std::cout<<"dish2:"<<std::endl;
    builder = new ConcreteBuilderB();
    director->setBuilder(builder);
    dish = director->constructor();
    dish->getInfo();

    delete builder;
    delete director;
    delete dish;
}

result:

dish1:
pork:50g
onion:100g
dish2:
beef:100g
potato:200g

Keywords: C++ Design Pattern Cpp

Added by Crysma on Mon, 27 Dec 2021 12:31:54 +0200