C++之设计模式

设计模式

  • 简介
  • 单例模式
    • 饿汉模式
    • 懒汉模式
  • 工厂模式
    • 简单工厂模式
    • 工厂方法模式
    • 抽象工厂模式
  • 建造者模式
  • 代理模式

简介

设计模式是前辈们对代码开发经验的总结,是解决特定问题的⼀系列套路它不是语法规定,而是⼀套⽤来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决⽅案。

设计模式的六大原则

  • 单⼀职责原则(Single Responsibility Principle)

    (1)类的职责应该单一,一个⽅法只做⼀件事。职责划分清晰明了,每次改动到最小单位的方法或类。
    (2)使用建议:两个完全不⼀样的功能不应该放⼀个类中,⼀个类中应该是⼀组相关性很高的函数、数据的封装。
    (3)用例:网络聊天:网络通信 & 聊天,应该分割成为网络通信类 & 聊天类。

    在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。

    单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。

  • 开闭原则(Open Closed Principle)
    (1)对扩展开放,对修改封闭,即软件实体应尽量在不修改原有代码的情况下进行扩展。
    (2)使用建议:对软件实体的改动,最好用扩展而非修改的⽅式。
    (3)用例:超市卖货:商品价格—不是修改商品的原来价格,而是新增促销价格。

任何软件都需要面临一个很重要的问题,即它们的需求会随时间的推移而发生变化。因为变化,升级和维护等原因,如果需要对软件原有代码进行修改,可能会给旧代码引入错误,也有可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试,所以当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现使我们需要的。

抽象化是开闭原则的关键所在,我们可以为系统定义一个比较稳定的抽象层,将不同的实现行为移动至具体的实现层中去进行实现,如果我们此时需要对代码进行修改的话,比如新增某个功能,就不需要对抽象层的代码进行修改,而是将该类的实现添加到行为层就可以了,并不会修改已有的代码来达到实现新功能的需求,这样就达到开闭原则的要求了。

  • 里氏替换原则(Liskov Substitution Principle)
    (1)通俗点讲,就是只要父类能出现的地⽅,子类就可以出现,⽽且替换为子类也不会产生任何错误或异常;在继承类时,务必重写父类中所有的方法,尤其需要注意父类的protected方法,子类尽量不要暴露自己的public方法供外界调用。
    (2)使用建议:子类必须完全实现父类的方法,子类可以有自己的个性。覆盖或实现父类的方法时,输入参数可以被放大,输出可以缩小。
    (3)用例:跑步运动员类–会跑步,子类长跑运动员–会跑步且擅长长跑, 子类短跑运动员–会跑步且擅⻓短跑。

里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。

  • 依赖倒置原则(Dependence Inversion Principle)
    (1)高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象,其核心思想是:要面向接口编程,不要面向实现编程。
    (2)使用建议:每个类都尽量有抽象类,任何类都不应该从具体类派生。尽量不要重写基类的方法。结合里氏替换原则使用。
    (3)用例:奔驰车司机类–只能开奔驰; 司机类 – 给什么车,就开什么车; 开车的人:司机–依
    赖于抽象。

依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。

在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要对抽象层进行扩展,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下来扩展系统的功能,满足开闭原则的要求。

  • 迪米特法则(Law of Demeter)
    (1)如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用,其目的是降低类之间的耦合度,提高模块的相对独立性。
    (2)迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。
    (3)用例:老师让班长点名–⽼师给班长⼀个名单,班长完成点名勾选,返回结果,而不是班长点名,老师勾选。

迪米特法则运用到系统设计中时,要注意下面的几点:在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。

  • 接口隔离原则(Interface Segregation Principle)
    (1)使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
    (2)使用建议:接口设计尽量精简单⼀,但是不要对外暴露没有实际意义的接口。
    (3)用例:修改密码,不应该提供修改用户信息接口,而就是单⼀的最小修改密码接口,更不要暴露数据库操作。

接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:
(1)单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
(2)单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。

从整体上来理解六⼤设计原则,可以简要的概括为⼀句话,⽤抽象构建框架,⽤实现扩展细节,具体到每⼀条设计原则,则对应⼀条注意事项:

  • 单⼀职责原则告诉我们实现类要职责单⼀;
  • 里氏替换原则告诉我们不要破坏继承体系;
  • 依赖倒置原则告诉我们要⾯向接口编程;
  • 接⼝隔离原则告诉我们在设计接口的时候要精简单⼀;
  • 迪⽶特法则告诉我们要降低耦合;
  • 开闭原则是总纲,告诉我们要对扩展开放,对修改关闭。

单例模式

单例模式指的就是一个类只能创建一个对象,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象同一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现方式,分别是饿汉模式懒汉模式

饿汉模式

饿汉模式实现原理就是程序启动时就会创建⼀个唯⼀的实例对象。 因为单例对象已经确定, 所以比较适用于多线程环境中, 多线程获取单例对象不需要加锁, 可以有效的避免资源竞争, 提高性能。

饿汉实现方式如下:

  • 将构造函数设置为私有,并将拷贝构造函数和赋值运算符重载函数设置为私有或删除,防止外部创建或拷贝对象。
  • 提供一个指向单例对象的static指针,并在程序入口之前完成单例对象的初始化。
  • 提供一个全局访问点获取单例对象。
class SingleTon
{
    public:
    // 提供一个获取单例对象的接口函数
    static SingleTon* GetInstance()
    {
        return _inst;
    }
    private:
    // 构造函数私有化
    SingleTon() {}
    // 析构函数私有化
    ~SingleTon() {}

    // 将拷贝构造函数以及运算符重载函数私有化或者delete
    SingleTon(const SingleTon&) = delete;
    SingleTon& operator=(const SingleTon&) = delete;
    static SingleTon* _inst; // 定义一个指向单例对象的static指针
};

// 在进入程序入口之前完成单例对象的初始化工作
SingleTon* SingleTon::_inst = new SingleTon;

线程安全相关问题:

  • 饿汉模式在程序运行主函数之前就完成了单例对象的创建,由于main函数之前是不存在多线程的,因此饿汉模式下单例对象的创建过程是线程安全的。
  • 后续所有多线程要访问这个单例对象,都需要通过调用GetInstance函数来获取,这个获取过程是不需要加锁的,因为这是一个读操作。
  • 当然,如果线程通过GetInstance获取到单例对象后,要用这个单例对象进行一些线程不安全的操作,那么这时就需要加锁了。

懒汉模式

懒汉模式在程序运行之前没有进行单例对象的创建,而是等到某个线程需要使用这个单例对象时再进行创建,也就是GetInstance函数第一次被调用时创建单例对象。

因此在调用GetInstance函数获取单例对象时,需要先判断这个static指针是否为空,如果为空则说明这个单例对象还没有创建,此时需要先创建这个单例对象然后再将单例对象返回;GetInstance函数第一次调用时需要对static指针进行写入操作,这个过程不是线程安全的,因为多个线程可能同时调用GetInstance函数,如果不对这个过程进行保护,此时这多个线程就会各自创建出一个对象。

class SingleTon
{
    public:
    static SingleTon* GetInstance()
    {
        // 双重判断
        if(_inst == nullptr)
        {
            _mtx.lock();
            if(_inst == nullptr)
            {
                _inst = new SingleTon;
                return _inst;
            }
            _mtx.unlock();
        }
    }
    private:
    // 将构造函数私有化
    SingleTon() {}
    // 将析构函数私有化
    ~SingleTon() {}

    // 将拷贝构造函数以及运算符重载函数私有化或者delete
    SingleTon(const SingleTon&) = delete;
    SingleTon& operator=(const SingleTon&) = delete;
    static SingleTon* _inst; // 定义一个指向单例对象的static指针
    static std::mutex _mtx; // 互斥锁
};

双重加锁的意义

通过上述代码我们可以看见,我们进行双重判断,锁也是在第一个if判断以后才进行使用的,原因就在于如果我们在if之前进行使用,当第一次调用GetInstance创建对象成功以后,后面进行调用依然会进入到GetInstance函数内部,依然会进行加锁解锁,这样就会使程序耗费大量的时间在加锁解锁上面,显然这是没有必要的,所以在这儿我们就使用双重判断的方式,避免了频繁地进行加锁解锁操作。

懒汉模式另一种实现方式

class SingleTon
{
    public:
    static SingleTon* GetInstance()
    {
        static SingleTon inst;
        return &inst;
    }
    private:
    // 构造函数私有
    SingleTon() {}
    // 析构函数私有
    ~SingleTon() {}

    // 将拷贝构造函数以及运算符重载函数私有化或者delete
    SingleTon(const SingleTon&) = delete;
    SingleTon& operator=(const SingleTon&) = delete;
};

上面这种方式也可用于懒汉模式的创建:

  • 由于实际只有第一次调用GetInstance函数时才会定义这个静态的单例对象,这也就保证了全局只有这一个唯一实例。
  • 并且这里单例对象的定义过程是线程安全的,因为现在的C++标准保证多线程初始化static变量不会发生数据竞争,可以视为原子操作。
  • 该方法属于懒汉模式,因为局部静态变量不是在程序运行主函数之前初始化的,而是在第一次调用GetInstance函数时初始化的。

这种方式的缺点就在于:

  • 单例对象定义在静态区,因此太大的单例对象不适合使用这种方式。
  • 单例对象创建在静态区后没办法主动释放。

饿汉模式和懒汉模式对比

  • 饿汉模式的优点就是简单,但是它的缺点也比较明显。饿汉模式在程序运行主函数之前就会创建单例对象,如果单例类的构造函数中所做的工作比较多(初始化动作多,还会伴随这一些IO行为,如读取配置文件),就会导致程序迟迟无法进入主函数,在外部看来就好像是程序卡住了。
  • 此外,如果有多个单例类需要创建单例对象,并且它们之间的初始化存在某种依赖关系,比如单例对象A的创建必须在单例对象B之后,此时饿汉模式也会存在问题,因为我们无法保证这多个单例对象中的哪个对象先创建。
  • 而懒汉模式就能很好的解决上述饿汉模式的缺点,因为懒汉模式并不是一开始就完成单例对象的创建,因此不会导致程序迟迟无法进入主函数,并且懒汉模式中各个单例对象创建的顺序是由各个单例类中的GetInstance函数第一次被调用的顺序决定,因此是可控制的。

懒汉模式的缺点就是,在编码上比饿汉模式复杂,在创建单例对象时需要考虑线程安全的问题。

工厂模式

工厂模式是⼀种创建型设计模式, 它提供了⼀种创建对象的最佳方式。在工厂模式中,我们创建对象时不会对上层暴露创建逻辑,而是通过使用⼀个共同结构来指向新创建的对象,以此实现创建–使用的分离。

简单工厂模式

简单工厂模式实现由⼀个工厂对象通过类型决定创建出来指定产品类的实例。假设有个工厂能⽣产出水果,当客户需要产品的时候明确告知工厂生产哪类水果,工厂需要接收用户提供的类别信息,当新增产品的时候,工行内部去添加新产品的生产方式。
在这里插入图片描述

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

class Fruit
{
    public:
    Fruit() {}

    virtual void show() = 0;
};

class Apple : public Fruit
{
    public:
    Apple() {}

    void show()
    {
        std::cout << "我是一个苹果!!!" << std::endl;
    }
};

class Banana : public Fruit
{
    public:
    Banana() {}

    void show()
    {
        std::cout << "我是一个香蕉!!!" << std::endl;
    }
};

class FruitFactory
{
    public:
    FruitFactory() {}

    static std::shared_ptr<Fruit> Create(const std::string& name)
    {
        if (name == "苹果")
        {
            return std::make_shared<Apple>();
        } 
        else if (name == "香蕉"){
            return std::make_shared<Banana>();
        }

        return std::shared_ptr<Fruit>();
    }
};

int main()
{
    std::shared_ptr<Fruit> fruit = FruitFactory::Create("苹果");
    fruit->show();
    fruit = FruitFactory::Create("香蕉");
    fruit->show();
    return 0;
}

简单工厂模式可以通过参数控制可以生产任何产品,他的优点就在于:

  • 简单粗暴,直观易懂;
  • 使用一个工厂⽣产同⼀等级结构下的任意产品。

缺点:

  • 所有东西生产在⼀起,产品太多会导致代码量庞大;
  • 开闭原则遵循(开放拓展,关闭修改)的不是太好,要新增产品就必须修改工厂方法。

工厂方法模式

在简单工厂模式下新增多个工厂,多个产品,每个产品对应⼀个工厂。假设现在有A、B 两种产品,则开两个工厂,工厂A 负责⽣产产品 A,工厂B 负责生产产品 B,用户只知道产品的工厂名,而不知道具体的产品信息,工厂不需要再接收客户的产品类别,而只负责生产产品。

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

class Fruit
{
    public:
    Fruit () {}

    virtual void show() = 0;
};

class Apple : public Fruit
{
    public:
    Apple() {}

    void show()
    {
        std::cout << "我是一个苹果!!!" << std::endl;
    }
};

class Banana : public Fruit
{
    public:
    Banana() {}

    void show()
    {
        std::cout << "我是一个香蕉!!!" << std::endl;
    }
};

class FruitFactory
{
    public:
    FruitFactory() {}

    virtual std::shared_ptr<Fruit> Create() = 0;

};

class AppleFactory : public FruitFactory
{
    public:
    AppleFactory() {}

    std::shared_ptr<Fruit> Create()
    {
        return std::make_shared<Apple>();
    }
};

class BananaFactory : public FruitFactory
{
    public:
    BananaFactory() {}

    std::shared_ptr<Fruit> Create()
    {
        return std::make_shared<Banana>();
    }
};

int main()
{
    std::shared_ptr<Fruit> fruit = nullptr;
    std::shared_ptr<FruitFactory> factory(new  AppleFactory());
    fruit = factory->Create();
    fruit->show();

    factory.reset(new BananaFactory());
    fruit = factory->Create();
    fruit->show();

    return 0;
}

工厂方法模式的优点就在于:

  • 减轻了工厂类的负担,将某类产品的⽣产交给指定的工厂来进行;
  • 开闭原则遵循较好,添加新产品只需要新增产品的工厂即可,不需要修改原先的工厂类。

缺点:

  • 对于某种可以形成⼀组产品族的情况处理较为复杂,需要创建大量的工厂类,在⼀定程度上增加了系统的耦合度。

抽象工厂模式

工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产⼀类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。此时,我们可以考虑将⼀些相关的产品组成⼀个产品族(位于不同产品等级结构中功能相关联的产品组成的家族),由同⼀个工厂来统一生产,这就是抽象工厂模式的基本思想。

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

class Fruit 
{
    public:
    Fruit() {}

    virtual void Show() = 0;
};

class Apple : public Fruit
{
    public:
    Apple() {}

    void Show()
    {
        std::cout << "我是一个苹果!!!" << std::endl;
    }
};

class Banana : public Fruit
{
    public:
    Banana() {}

    void Show()
    {
        std::cout << "我是一个香蕉!!!" << std::endl;
    }
};

class Animal
{
    public:
    Animal() {}

    virtual void Voice() = 0;
};

class Dog : public Animal
{
    public:
    Dog() {}

    void Voice()
    {
        std::cout << "汪汪汪!!!" << std::endl;
    }
};

class Lamp : public Animal
{
    public:
    Lamp() {}

    void Voice()
    {
        std::cout << "咩咩咩!!!" << std::endl;
    }
};

class Factory
{
    public:
    Factory() {}

    virtual std::shared_ptr<Fruit> GetFruit(const std::string& name) = 0;
    virtual std::shared_ptr<Animal> GetAnimal(const std::string& name) = 0;
};

class FruitFactory : public Factory
{
    public:
    FruitFactory() {}

    std::shared_ptr<Animal> GetAnimal(const std::string& name)
    {
        return std::shared_ptr<Animal>();
    }

    std::shared_ptr<Fruit> GetFruit(const std::string& name)
    {
        if (name == "苹果")
        {
            return std::make_shared<Apple>();
        }
        else if (name == "香蕉")
        {
            return std::make_shared<Banana>();
        }

        return std::shared_ptr<Fruit>();
    }
};

class AnimalFactory : public Factory
{
    public:
    AnimalFactory() {}

    std::shared_ptr<Fruit> GetFruit(const std::string& name)
    {
        return std::shared_ptr<Fruit>();
    }

    std::shared_ptr<Animal> GetAnimal(const std::string& name)
    {
        if(name == "小狗")
        {
            return std::make_shared<Dog>();
        }
        else if (name == "小羊")
        {
            return std::make_shared<Lamp>();
        }
        
        return std::shared_ptr<Animal>();
    }
};

class FactoryProducer
{
    public:
    FactoryProducer() {}

    static std::shared_ptr<Factory> GetFactory(const std::string& name)
    {
        if (name == "水果")
        {
            return std::make_shared<FruitFactory>();
        }
        else if (name == "动物")
        {
            return std::make_shared<AnimalFactory>();
        }

        return std::shared_ptr<Factory>();
    }
};

int main()
{
    std::shared_ptr<Factory> fruit_factory = FactoryProducer::GetFactory("水果");
    std::shared_ptr<Fruit> fruit = fruit_factory->GetFruit("苹果");
    fruit->Show();
    fruit = fruit_factory->GetFruit("香蕉");
    fruit->Show();

    std::shared_ptr<Factory> animal_factory = FactoryProducer::GetFactory("动物");
    std::shared_ptr<Animal> animal = animal_factory->GetAnimal("小狗");
    animal->Voice();
    animal = animal_factory->GetAnimal("小羊");
    animal->Voice();

    return 0;
}

抽象工厂模式将产品族的依赖与约束关系放到抽象工厂中,便于管理;他可以实现职责解耦,用户不需要关心一堆自己不关心的细节,由抽象工厂来负责组件的创建,而且切换产品族容易,只需要增加一个具体工厂实现,客户端选择另一个套餐就可以了。

建造者模式

建造者模式是⼀种创建型设计模式, 使用多个简单的对象⼀步⼀步构建成⼀个复杂的对象,能够将⼀个复杂的对象的构建与它的表示分离,提供⼀种创建对象的最佳方式。主要用于解决对象的构建过于复杂的问题。

建造者模式主要基于四个核心类进行实现:

  • 产品(Product):代表最终构建的复杂对象,包含多个部件和属性。产品类定义了对象的结构和行为,通常包括多个属性和方法。
  • 建造者(Builder):定义创建产品的各个部件的抽象接口。建造者接口提供了构建不同部分的方法,允许灵活地创建不同类型的产品。
  • 具体建造者(ConcreteBuilder):实现建造者接口,具体构建和装配产品的各个部件。每个具体建造者负责创建特定类型的产品,同时维护构建过程的状态。
  • 指挥者(Director):负责管理建造过程,调用建造者的方法以生成产品。指挥者定义构建的顺序,确保产品的构建过程符合预期。
#include <iostream>
#include <memory>
#include <string>

// 创建一个抽象的产品类
class Computer
{
    public:
    using ptr = std::shared_ptr<Computer>;
    Computer() {}

    void SetBoard(const std::string& board)
    {
        _board = board;
    }

    void SetDisplay(const std::string& display)
    {
        _display = display;
    }

    virtual void SetOs() = 0;

    std::string ToString()
    {
        std::string computer = "Computer:{\n";
        computer += "\tboard=" + _board + ",\n"; 
        computer += "\tdisplay=" + _display + ",\n"; 
        computer += "\tOs=" + _os + ",\n"; 
        computer += "}\n";
        return computer;
    }

    protected:
    std::string _board;
    std::string _display;
    std::string _os;
};

// 创建一个具体的产品类
class MacBook : public Computer
{
    public:
    using ptr = std::shared_ptr<MacBook>;

    MacBook() {}

    void SetOs()
    {
        _os = "Max Os X12";
    }
};

// 创建一个抽象的建造者类
class Builder
{
    public:
    using ptr = std::shared_ptr<Builder>;

    Builder() {}

    virtual void BuildBoard(const std::string board) = 0;
    virtual void BuildDisplay(const std::string display) = 0; 
    virtual void BuildOs() = 0;
    virtual Computer::ptr Build() = 0;
};

// 创建一个具体的建造者类
class MacBookBuilder : public Builder
{
    public:
    using ptr = std::shared_ptr<MacBookBuilder>;
    MacBookBuilder(): _computer(new MacBook()) {}

    void BuildBoard(const std::string board)
    {
        _computer->SetBoard(board);
    }

    void BuildDisplay(const std::string display)
    {
        _computer->SetDisplay(display);
    }

    void BuildOs()
    {
        _computer->SetOs();
    }

    Computer::ptr Build()
    {
        return _computer;
    }

    private:
    Computer::ptr _computer;
};

// 创建一个指挥者类
class Director
{
    public:
    using ptr = std::shared_ptr<Director>;

    Director(Builder* builder): _builder(builder) {}

    void Construct(const std::string& board, const std::string& display)
    {
        _builder->BuildBoard(board);
        _builder->BuildDisplay(display);
        _builder->BuildOs();
    }
    private:
    Builder::ptr _builder;
};

int main()
{
    Builder* builder = new MacBookBuilder();
    std::unique_ptr<Director> dt(new Director(builder));
    dt->Construct("英特尔主板", "VOC显示器");
    Computer::ptr computer = builder->Build();
    std::cout << computer->ToString();
    return 0;
}

建造者模式的优缺点
优点:

  • 解耦:建造者模式将复杂对象的构建与表示分离,使得构建过程与产品的表示相互独立,降低了模块间的耦合度。
  • 灵活性:可以通过更换具体建造者来灵活地构建不同类型的产品,而无需修改指挥者或客户端代码。
  • 可读性和可维护性:通过清晰的接口和结构,增加了代码的可读性,便于后续的维护和扩展。
  • 支持复杂对象的构建:适合构建复杂对象,特别是那些具有多个部件和装配顺序要求的对象。
  • 步骤控制:指挥者可以控制构建的步骤和顺序,确保复杂对象按照预期的方式构建。

缺点:

  • 复杂性:对于简单对象的构建,使用建造者模式可能显得过于复杂,增加了不必要的代码和结构。
  • 类的数量增加:需要创建多个具体建造者类和产品类,可能导致类的数量增加,增加了系统的复杂性。
  • 构建过程固定:一旦定义了指挥者的构建过程,可能不够灵活,难以适应一些动态变化的需求。

代理模式

代理模式指代理控制对其他对象的访问, 也就是代理对象控制对原对象的引用。在某些情况下,⼀个对象不适合或者不能直接被引用访问,而代理对象可以在客户端和目标对象之间起到中介的作用;代理模式提供了额外的功能,如延迟加载、访问控制、日志记录等。

代理模式的结构包括⼀个是真正的你要访问的对象(目标类)、⼀个是代理对象。目标对象与代理对象实现同⼀个接⼝,先访问代理类再通过代理类访问目标对象。代理模式分为静态代理动态代理

  • 静态代理:在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时就已经确定了代理类要代理的是哪个被代理类。
  • 动态代理:在运行时才动态⽣成代理类,并将其与被代理类绑定。这意味着,在运行时才能确定代理类要代理的是哪个被代理类。

主要组成成分

  • 抽象主题(Subject):定义了真实对象和代理对象的共同接口。
  • 真实主题(Real Subject):实现了抽象主题接口,代表实际的业务逻辑。
  • 代理(Proxy):持有对真实主题的引用,并实现了抽象主题接口。代理可以在访问真实主题之前或之后添加额外的功能。

以租房为例,房东将房子租出去,但是要租房子出去,需要发布招租启示, 带人看房,负责维修,这些工作中有些操作并非房东能完成,因此房东为了图省事,将房子委托给中介进行租赁。 代理模式实现:

#include <iostream>
#include <string>


class RentHouse
{
    public:
    RentHouse() {}

    virtual void rentHouse() = 0;
};

// 创建一个房东类,负责将房子租出去
class LandLord : public RentHouse
{
    public:
    LandLord() {}

    void rentHouse()
    {
        std::cout << "把房子租出去" << std::endl;
    }
};

// 创建一个中介类,对租房子的功能进行加强,实现租房以外的其他功能
class Intermediary
{
    public:
    void rentHouse()
    {
        std::cout << "发布租房启示" << std::endl;
        std::cout << "带人看房" << std::endl;
        _landlord.rentHouse();
        std::cout << "负责房子的售后服务" << std::endl;
    }
    private:
    LandLord _landlord;
};

int main()
{
    Intermediary intermediary;
    intermediary.rentHouse();
    return 0;
}

代理模式的优缺点
优点:

  • 控制访问:代理可以控制对真实对象的访问,增加安全性。
  • 延迟加载:可以在需要时才创建真实对象,节省资源。
  • 增强功能:可以在访问真实对象前后添加额外的功能,如日志记录、缓存等。

缺点:

  • 增加复杂性:引入代理可能会增加系统的复杂性,尤其是在管理代理和真实对象时。
  • 性能开销:代理可能会引入额外的性能开销,尤其是在处理大量请求时。

代理模式的分类

  • 远程代理 (Remote Proxy):用于控制对远程对象的访问,通常用于网络编程中,例如通过网络调用远程服务。
  • 虚拟代理 (Virtual Proxy):用于延迟加载资源,避免不必要的开销。只有在真正需要时才创建真实对象,如大文件或复杂对象的加载。
  • 保护代理 (Protection Proxy):用于控制对对象的访问权限,增强安全性。可以根据不同用户的权限来决定是否允许访问某些方法或数据。
  • 缓存/缓冲代理 (Cache Proxy):用于缓存频繁访问的数据,以减少计算或网络请求的开销。例如,缓存数据库查询结果或API响应。
  • 智能引用代理 (Smart Reference Proxy):用于管理对象的生命周期,例如引用计数或智能指针。可以确保对象在不再需要时被正确释放,避免内存泄漏。
  • 写时代理 (Write Proxy):用于延迟写入操作,直到需要时才执行。可以用于优化性能,尤其在处理大量数据时。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/947655.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云效流水线使用Node构建部署前端web项目

云效流水线实现自动化部署 背景新建流水线配置流水线运行流水线总结 背景 先来看看没有配置云效流水线之前的部署流程&#xff1a; 而且宝塔会经常要求重新登录&#xff0c;麻烦的很 网上博客分享了不少的配置流程&#xff0c;这一篇博客的亮点就是不仅给出了npm命令构建&…

pycharm如何拉取一个git项目,然后,修改后再上传到自建的项目中?

以chattts为例 https://github.com/2noise/ChatTTS.git 1.建一个虚拟环境&#xff0c;用于项目使用 2.pychar&#xff4d;新建工程 &#xff13;.忽略 提示 勾选&#xff0c;新建远程仓库 设置账号和密码 设置git路径&#xff0c;一般是正确的&#xff0c;点测试即可 &…

(五)开机自启动以及scp工具文件传输小问题

文章目录 程序开机自启动先制作一个可执行程序第一种 通过命令行实现程序开机自启动第二种 通过 Linux 系统镜像实现程序开机自启动 scp工具文件传输小问题 程序开机自启动 原因&#xff1a;做成产品后&#xff0c;用户直接开机使用&#xff0c;总不能在开机执行程序后才可以使…

供需平台信息发布付费查看小程序系统开发方案

供需平台信息发布付费查看小程序系统主要是为了满足个人及企业用户的供需信息发布与匹配需求。 一、目标用户群体 个人用户&#xff1a;寻找兼职工作、二手物品交换、本地服务&#xff08;如家政、维修&#xff09;等。 小微企业&#xff1a;推广产品和服务&#xff0c;寻找合…

中建海龙:科技助力福城南产业片区绿色建筑发展

在快速发展的城市化进程中&#xff0c;绿色建筑以其环保、节能、可持续的特点日益受到重视。作为建筑工业化领域的领军企业&#xff0c;中建海龙科技有限公司&#xff08;简称“中建海龙”&#xff09;凭借其卓越的科技实力和创新举措&#xff0c;在推动绿色建筑发展方面做出了…

OJ随机链表的复制题目分析

题目内容&#xff1a; 138. 随机链表的复制 - 力扣&#xff08;LeetCode&#xff09; 分析&#xff1a; 这道题目&#xff0c;第一眼感觉非常乱&#xff0c;这是正常的&#xff0c;但是我们经过仔细分析示例明白后&#xff0c;其实也并不是那么难。现在让我们一起来分析分析…

动态规划回文串问题系列一>回文子串

题目&#xff1a; 解析&#xff1a; 注意&#xff1a;字串和子数组差不多 状态表示&#xff1a; 状态转移方程&#xff1a; 初始化&#xff1a; 填表顺序&#xff1a; 返回值&#xff1a; 返回dp表里true的个数

万里数据库GreatSQL监控解析

GreatSQL是MySQL的一个分支&#xff0c;专注于提升MGR&#xff08;MySQL Group Replication&#xff09;的可靠性及性能。乐维监控平台可以有效地监控GreatSQL&#xff0c;帮助用户及时发现并解决潜在的性能问题。 通过在GreatSQL服务器上安装监控代理&#xff0c;收集数据库性…

君正T41交叉编译ffmpeg、opencv并做h264软解,利用君正SDK做h264硬件编码

目录 1 交叉编译ffmpeg----错误解决过程&#xff0c;不要看 1.1 下载源码 1.2 配置 1.3 编译 安装 1.3.1 报错&#xff1a;libavfilter/libavfilter.so: undefined reference to fminf 1.3.2 报错&#xff1a;error: unknown type name HEVCContext; did you mean HEVCPr…

Sublime Text4 4189 安装激活【 2025年1月3日 亲测可用】

-----------------测试时间2025年1月3日------------------- 下载地址 官方网址&#xff1a;https://www.sublimetext.com 更新日志&#xff1a;https://www.sublimetext.com/download V4189 64位&#xff1a;https://www.sublimetext.com/download_thanks?targetwin-x64 ....…

Zabbix5.0版本(监控Nginx+PHP服务状态信息)

目录 1.监控Nginx服务状态信息 &#xff08;1&#xff09;通过Nginx监控模块&#xff0c;监控Nginx的7种状态 &#xff08;2&#xff09;开启Nginx状态模块 &#xff08;3&#xff09;配置监控项 &#xff08;4&#xff09;创建模板 &#xff08;5&#xff09;用默认键值…

Java高频面试之SE-08

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本牛马baby今天又来了&#xff01;哈哈哈哈哈嗝&#x1f436; 成员变量和局部变量的区别有哪些&#xff1f; 在 Java 中&#xff0c;成员变量和局部变量是两种不同类型的变量&#xff0c;它们在作用域…

Linux(Centos 7.6)命令行快捷键

Linux(Centos 7.6)操作系统一般都是使用命令行进行管理&#xff0c;如何能高效的进行命令编辑与执行&#xff0c;需要我们记住一些常见的命令&#xff0c;也需要连接一些常见快捷键的使用&#xff0c;常见快捷键如下&#xff1a; 快捷键快捷键说明tab命令行补齐ctrlr快速查找之…

Geoserver修行记-后端调用WMS/WMTS服务无找不到图层Could not find layer

项目场景 调用geoserver地图服务WMS,找不到图层 我在进行地图服务调用的时候&#xff0c;总是提示我找不多图层 Could not find layer&#xff0c;重点是这个图层我明明是定义了&#xff0c;发布了&#xff0c;且还能够正常查看图层的wms的样式&#xff0c;但是在调用后端调用…

ip属地的信息准确吗?ip归属地不准确怎么办

在数字化时代&#xff0c;IP属地信息成为了我们日常生活中不可或缺的一部分。在各大社交媒体平台上&#xff0c;IP属地信息都扮演着重要的角色。然而&#xff0c;随着技术的不断进步和网络的复杂性增加&#xff0c;IP属地信息的准确性问题也日益凸显。那么&#xff0c;IP属地信…

nginx高可用集群搭建

本文介绍nginx高可用集群的搭建。利用keepalived实时检查nginx进程是否存活、keepalived的虚拟ip技术&#xff0c;达到故障转移的目的。终端用户通过访问虚拟ip&#xff0c;感知不到实际发生的故障。架构图如下&#xff1a; 0、环境 Ubuntu&#xff1a;22.04.2 ltsnginx: 1.…

UE5材质节点Distance

Distance可以计算两个物体间的距离&#xff0c;可以用来做过渡效果 当相机和物体距离3000的时候&#xff0c;就会渐渐从蓝过渡到红色&#xff0c;除以500是为了平滑过渡

CS·GO搬砖流程详细版

说简单点&#xff0c;就是Steam买了然后BUFF上卖&#xff0c;或许大家都知道这点&#xff0c;但就是一些操作和细节问题没那么明白。我相信&#xff0c;你看完这篇文章以后&#xff0c;至少会有新的认知。 好吧&#xff0c;废话少说&#xff0c;直接上实操&#xff01; 首先准…

【Cocos TypeScript 零基础 3.1】

目录 场景跳转 场景跳转 把新建好的TS文件与场景绑定 选中 场景 或 camera 拖进右边的 属性检查器 双击T文件,进入编辑 至于用什么IDE看个位朋友高兴 我这里有 VScode ,先用这个,老师也没有推荐 (老师也用的是这个) VScode UI 也有中文包,请自行上网搜索 打开创建的TS文件后…

分析服务器 systemctl 启动gozero项目报错的解决方案

### 分析 systemctl start beisen.service 报错 在 Linux 系统中&#xff0c;systemctl 是管理系统和服务的主要工具。当我们尝试重启某个服务时&#xff0c;如果服务启动失败&#xff0c;systemctl 会输出错误信息&#xff0c;帮助我们诊断和解决问题。 本文将通过一个实际的…