Back

C++设计模式-创建型模式

推荐看👉常用设计模式有哪些? (refactoringguru.cn)

设计模式描述:

用特定的套路解决现实问题,每一个设计模式对应一个法子

分类

根据意图或目的分类

  • 创建型模式: 提供创建对象机制,增加已有的代码灵活性和可复用性
  • 结构型模式: 介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。
  • 行为模式: 负责对象间的高效沟通和职责委派。

创建型模式

simple_factory_pattern(简单工厂)

Factory Method(工厂方法)

在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。

要解决的问题:

当增加一个新类,和其他类低耦合

解决方案:

增加一个子类的抽象类,所有的子类继承于抽象类,并且写父类的实现

#include <iostream>

class product
{
public:
    virtual ~product() {}
};

class phone : public product
{
public:
    phone()
    {
        std::cout << "make phone" << std::endl;
    }
};

class tv : public product
{
public:
    tv()
    {
        std::cout << "make tv" << std::endl;
    }
};

class factory
{
public:
    virtual ~factory() {}
    virtual product* make_product() = 0; 
};

class phone_factory : public factory
{
public:
    virtual product* make_product()
    {
        return new phone();
    }
};

class tv_factory : public factory
{
public:
    virtual product* make_product()
    {
        return new tv();
    }
};

int main()
{
    factory* factory1 = new phone_factory();
    product* product1 = factory1->make_product();

    factory* factory2 = new tv_factory();
    product* product2 = factory2->make_product();

    delete factory1;
    delete product1;
    delete factory2;
    delete product2;
    
    return 0;
}

Abstract Factory(抽象工厂模式)

随着业务的发展,更多的类需要创建,比如美国工厂,欧洲工厂,亚洲工厂,南美工厂,每个地区工厂又需要创建各自的产品

问题:

每个地区生产的产品拥有一个风格,不同地区的产品混杂一起对顾客不友好

保持核心代码不修改。

解决:

首先, 抽象工厂模式建议为系列中的每件产品明确声明接口 (例如phone、 tv)。 然后, 确保所有产品变体都继承这些接口。 例如, 所有风格的椅子都实现 phone接口; 所有风格的咖啡桌都实现 tv接口, 以此类推。

#include <iostream>

// 食草动物.
class herbivore
{
public:
    virtual ~herbivore() {}
};

// 食肉动物.
class carnivore
{
public:
    virtual ~carnivore() {}
    virtual void eat(herbivore* h) = 0;
};

// 角马.
class wildebeest : public herbivore
{
};

// 野牛.
class bison : public herbivore
{
};

// 狮子.
class lion : public carnivore
{
public:
    void eat(herbivore*)
    {
        std::cout << "lion eat wildebeest" << std::endl;
    }
};

// 狼.
class wolf : public carnivore
{
public:
    void eat(herbivore*)
    {
        std::cout << "wolf eat bison" << std::endl;
    }
};

// 抽象工厂.
class factory
{
public:
    virtual ~factory() {}
    // 创建食草动物.
    virtual herbivore* create_herbivore() = 0;
    // 创建食肉动物.
    virtual carnivore* create_carnivore() = 0;
};

// 非洲工厂.
class africa_factory : public factory
{
public:
    virtual herbivore* create_herbivore()
    {
        return new wildebeest();
    }

    virtual carnivore* create_carnivore()
    {
        return new lion();
    }
};

// 美洲工厂.
class america_factory : public factory
{
public:
    virtual herbivore* create_herbivore()
    {
        return new bison();
    }

    virtual carnivore* create_carnivore()
    {
        return new wolf();
    }
};

// 动物世界(客户端).
class animal_world
{
public:
    animal_world(factory* f)
    {
        _herbivore = f->create_herbivore(); 
        _carnivore = f->create_carnivore();
    }

    void start()
    {
        _carnivore->eat(_herbivore);
    }

private:
    herbivore* _herbivore;
    carnivore* _carnivore;
};

int main()
{
    factory* factory1 = new africa_factory();
    animal_world* world1 = new animal_world(factory1);
    world1->start();

    factory* factory2 = new america_factory();
    animal_world* world2 = new animal_world(factory2);
    world2->start();

    delete factory1;
    delete world1;
    delete factory2;
    delete world2;

    return 0;
}

singletonton_pattern(单例模式)

保证一个类只有一个实例, 并提供一个访问该实例的全局节点

问题:

同时解决了两个问题, 但违反了单一职责原则

解决:

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。

  • 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

#include <iostream>

class singleton
{
public:
    singleton() = default;
    singleton(const singleton&) = delete;
    singleton& operator=(const singleton&) = delete;

    static singleton& get_instance()
    {
        static singleton s;
        return s;
    }

    void print()
    {
        std::cout << "Hello world" << std::endl;
    }
};

int main()
{
    singleton::get_instance().print();
    singleton::get_instance().print();
    return 0;
}

prototype_pattern(原型模式)

使你能够复制已有对象, 而又无需使代码依赖它们所属的类。

问题:

如果你有一个对象, 并希望生成与其完全相同的一个复制品, 你该如何实现呢? 首先, 你必须新建一个属于相同类的对象。 然后, 你必须遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。

不错! 但有个小问题。 并非所有对象都能通过这种方式进行复制, 因为有些对象可能拥有私有成员变量, 它们在对象本身以外是不可见的。

直接复制还有另外一个问题。 因为你必须知道对象所属的类才能创建复制品, 所以代码必须依赖该类。 即使你可以接受额外的依赖性, 那还有另外一个问题: 有时你只知道对象所实现的接口, 而不知道其所属的具体类, 比如可向方法的某个参数传入实现了某个接口的任何对象。

解决:

原型模式将克隆过程委派给被克隆的实际对象。 模式为所有支持克隆的对象声明了一个通用接口, 该接口让你能够克隆对象, 同时又无需将代码和对象所属类耦合。 通常情况下, 这样的接口中仅包含一个 克隆方法。

所有的类对 克隆方法的实现都非常相似。 该方法会创建一个当前类的对象, 然后将原始对象所有的成员变量值复制到新建的类中。 你甚至可以复制私有成员变量, 因为绝大部分编程语言都允许对象访问其同类对象的私有成员变量。

支持克隆的对象即为原型。 当你的对象有几十个成员变量和几百种类型时, 对其进行克隆甚至可以代替子类的构造。

其运作方式如下: 创建一系列不同类型的对象并不同的方式对其进行配置。 如果所需对象与预先配置的对象相同, 那么你只需克隆原型即可, 无需新建一个对象。

可以类比有丝分裂:由于工业原型并不是真正意义上的自我复制, 因此细胞有丝分裂 (还记得生物学知识吗?) 或许是更恰当的类比。 有丝分裂会产生一对完全相同的细胞。 原始细胞就是一个原型, 它在复制体的生成过程中起到了推动作用。

#include <string.h>
#include <iostream>
#include <string>
// #include <vector>

class resume
{
public:
    resume() {}
    virtual ~resume() {}
    virtual resume* clone() { return nullptr; }
    virtual void set(char*) {}
    virtual void show() {}

protected:
    char* _name = nullptr;
};

class resume_a : public resume
{
public:
    resume_a(const char* str)
    {
        if (str == nullptr)
        {
            _name = new char[1];
            _name[0] = '\0';
        }
        else
        {
            _name = new char[strlen(str) + 1];
            strcpy(_name, str);
        }
    }

    ~resume_a()
    {
        if (_name != nullptr)
        {
            delete [] _name;
            _name = nullptr;
        }
    }

    resume_a(const resume_a& other)
    {
        if (_name != nullptr)
        {
            delete [] _name;
            _name = nullptr;
        }
        _name = new char[strlen(other._name) + 1];
        strcpy(_name, other._name);
    }

    virtual resume_a* clone()
    {
        return new resume_a(*this);
    }

    virtual void show()
    {
        std::cout << "resume_a name: " << _name << std::endl;
    }
};

int main()
{
    resume* r1 = new resume_a("A");
    resume* r2 = r1->clone();
    r1->show();
    delete r1;
    r2->show();
    delete r2;
    return 0;
}

builder_pattern(生成器模式)

使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。

问题:

假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。 这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中; 甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。 例如, 我们来思考如何创建一个 房屋House对象。 建造一栋简单的房屋, 首先你需要建造四面墙和地板, 安装房门和一套窗户, 然后再建造一个屋顶。 但是如果你想要一栋更宽敞更明亮的房屋, 还要有院子和其他设施 (例如暖气、 排水和供电设备), 那又该怎么办呢?

最简单的方法是扩展 房屋基类, 然后创建一系列涵盖所有参数组合的子类。 但最终你将面对相当数量的子类。 任何新增的参数 (例如门廊类型) 都会让这个层次结构更加复杂。

另一种方法则无需生成子类。 你可以在 房屋基类中创建一个包括所有可能参数的超级构造函数, 并用它来控制房屋对象。 这种方法确实可以避免生成子类, 但它却会造成另外一个问题。 通常情况下, 绝大部分的参数都没有使用, 这使得对于构造函数的调用十分不简洁。 例如, 只有很少的房子有游泳池, 因此与游泳池相关的参数十之八九是毫无用处的。

解决:

生成器模式建议将对象构造代码从产品类中抽取出来, 并将其放在一个名为生成器的独立对象中。 该模式会将对象构造过程划分为一组步骤, 比如 build­Walls创建墙壁和 build­Door创建房门创建房门等。 每次创建对象时, 你都需要通过生成器对象执行一系列步骤。 重点在于你无需调用所有步骤, 而只需调用创建特定对象配置所需的那些步骤即可。

当你需要创建不同形式的产品时, 其中的一些构造步骤可能需要不同的实现。 例如, 木屋的房门可能需要使用木头制造, 而城堡的房门则必须使用石头制造。

在这种情况下, 你可以创建多个不同的生成器, 用不同方式实现一组相同的创建步骤。 然后你就可以在创建过程中使用这些生成器 (例如按顺序调用多个构造步骤) 来生成不同类型的对象。

例如, 假设第一个建造者使用木头和玻璃制造房屋, 第二个建造者使用石头和钢铁, 而第三个建造者使用黄金和钻石。 在调用同一组步骤后, 第一个建造者会给你一栋普通房屋, 第二个会给你一座小城堡, 而第三个则会给你一座宫殿。 但是, 只有在调用构造步骤的客户端代码可以通过通用接口与建造者进行交互时, 这样的调用才能返回需要的房屋。 主管 你可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。 主管类可定义创建步骤的执行顺序, 而生成器则提供这些步骤的实现。

严格来说, 你的程序中并不一定需要主管类。 客户端代码可直接以特定顺序调用创建步骤。 不过, 主管类中非常适合放入各种例行构造流程, 以便在程序中反复使用。

此外, 对于客户端代码来说, 主管类完全隐藏了产品构造细节。 客户端只需要将一个生成器与主管类关联, 然后使用主管类来构造产品, 就能从生成器处获得构造结果了。

#include <iostream>
#include <string>
#include <vector>

// 产品.
class product
{
public:
    void add(const std::string& product_name)
    {
        _product_name_vec.emplace_back(product_name);
    }

    void show()
    {
        for (auto& name : _product_name_vec)
        {
            std::cout << name << std::endl;
        }
    }

private:
    std::vector<std::string> _product_name_vec;
};

// 抽象建造者.
class builder
{
public:
    virtual ~builder() {}
    virtual void build_ricenoodles() = 0;
    virtual void build_cooldish() = 0;
    virtual void build_drink() = 0;
    virtual product* get_product() = 0;
};

// 建造者A.
class builder_a : public builder
{
public:
    builder_a()
    {
        _product = new product;
    }

    ~builder_a()
    {
        delete _product;
    }

    virtual void build_ricenoodles()
    {
        _product->add("ricenoodles a");
    }

    virtual void build_cooldish()
    {
        _product->add("cooldish a");
    }

    virtual void build_drink()
    {
        _product->add("drink a");
    }

    virtual product* get_product()
    {
        return _product;
    }

private:
    product* _product;
};

// 导演.
class director
{
public:
    void construct(builder* b)
    {
        b->build_ricenoodles();
        b->build_cooldish();
        b->build_drink();
    }
};

int main()
{
    director* d = new director();
    builder* b = new builder_a();
    d->construct(b);
    product* p = b->get_product();
    p->show();

    delete b;
    delete d;

    return 0;
}