C++设计模式
设计模式(Design Pattern)是解决软件设计中常见问题的可重用方案,它是对代码设计经验的总结,提供了可复用的最佳实践。设计模式不是具体的代码,而是一种模板或指导原则,帮助开发者编写更灵活、可维护和可扩展的代码。接下来将会介绍常用的几种设计模式。
1.单例模式
单例模式(Singleton Pattern)是一种常用的设计模式,主要适用于需要确保系统中一个类只有一个实例,并提供一个全局访问点的场景。比如,配置文件管理类,日志器类,数据库连接池等等。
单例模式分为两种,一种是懒汉模式,一种饿汉模式。
饿汉模式:是指,像一个饥饿的流浪汉一样,迫不及待地得到我们的实例对象。也就是会在程一开始的时候就会创建好对象。
单例模式具有的特点,首先就是全局只有一个实例化对象,所以要求我们不能够直接在使用单例类的构造函数创建对象,当使用者需要使用的时候,我们同时使用GetInstence函数获取已经创建好的单例对象的指针,提供给全局使用。
class Config
{
public:static Config* GetInstence(){return _pconfig;}void ShowConfig(){cout << "path:/home/conf" <<endl;}
private://构造函数,拷贝构造,赋值重载运算符,私有化,提供 GetInstence 来对单例对象的获取Config(int x = 0) :_x(x){}Config(Config& tmp) = delete;Config& operator=(Config& tmp) = delete;
private:int _x;static Config* _pconfig;
};
//主函数之前就已经创建好了Config对象
Config* Config::_pconfig = new Config();
懒汉模式:顾名思义就是,像一个懒惰的流浪汉一样,只有在用到对象的时候,才会去实例化,而且全部仅会实例化一个,往后再需要单例对象的时候,只需要返回已经创建好的单例对象的指针,提供给全局使用。
class Config
{
public:static Config* GetInstence(){if(_config==nullptr)//降低锁竞争{unique_lock<mutex> u_lock(_mutex);//防止多执行流竞争if (_config == nullptr){_config = new Config(); }}return _config;}void ShowConfig(){cout << "path:/home/conf" <<endl;}private://构造函数私有化Config() {}Config(Config& obj) = delete;Config operator=(Config& obj) = delete;
private://类内部声明static Config* _config;static mutex _mutex;
};
//类外部定义
mutex Config::_mutex;
Config* Config::_config = nullptr;
2.工厂模式
工厂模式是一种创建型设计模式,其核心思想是将对象的创建逻辑与使用逻辑分离,通过统一的接口动态创建对象,提升代码的灵活性和可维护性。工厂模式一般分为两种一种是普通工厂模式,一种是抽象工厂模式。
工厂模式最核心的价值就是,将一类对象的实例化统一化,工厂化。从而将对象的创建和使用进行解耦。
普通工厂模式:
class Fruit
{
public:virtual void show() {}
};class Apple: public Fruit
{
public:virtual void show(){cout << "this a Apple" << endl;}
};class Banana : public Fruit
{
public:virtual void show(){cout << "this a Banana" << endl;}
};class FruitFactory
{
public:static shared_ptr<Fruit> CreateFruit(const string &name){if (name == "apple"){return make_shared<Apple>();}else if (name == "banana"){return make_shared<Banana>();}else return nullptr;}
};int main()
{FruitFactory fruit_fa;shared_ptr<Fruit> apple = fruit_fa.CreateFruit("apple");shared_ptr<Fruit> banana = fruit_fa.CreateFruit("banana");apple->show();banana->show();return 0;
}
工厂方法模式:
区别于简单工厂模式,工厂方法模式是将各个产品使用不同的子类来进行创建,使⽤多个⼯⼚分别⽣产指定的固定产品,好处上,减轻了⼯⼚类的负担,将某类产品的⽣产交给指定的⼯⼚来进⾏,开闭原则遵循较好,添加新产品只需要新增产品的⼯⼚即可,不需要修改原先的⼯⼚类。但是缺点也很明显,对于某种可以形成⼀组产品族的情况处理较为复杂,需要创建⼤量的⼯⼚类.
开闭原则:简单来说就是,尽可能的少对代码进行修改,推荐使用,继承,增加类或者方法的方式来对增加新的功能。对比简单工厂模式,我们也能够发现,如果现在需要新增一个产品,就需要修改工厂类的条件判断分支,以达到新增产品的目的。这就是不遵循开闭原则的。
// 工厂方法模式
class Fruit
{
public:virtual void show() {}
};class Apple: public Fruit
{
public:virtual void show(){cout << "this a Apple" << endl;}
};class Banana : public Fruit
{
public:virtual void show(){cout << "this a Banana" << endl;}
};class Factory
{
public:virtual shared_ptr<Fruit> Create() = 0;
};// 具体的工厂
class BananFactory : public Factory
{
public:virtual shared_ptr<Fruit> Create(){return make_shared<Banana>();}
};class AppleFactory : public Factory
{
public:virtual shared_ptr<Fruit> Create(){return make_shared<Apple>();}
};int main()
{//实例化出具体的工厂再生产出具体的产品Factory* bananfactory = new BananFactory();shared_ptr<Fruit> banana = bananfactory->Create();banana->show();Factory* applefactory = new AppleFactory();shared_ptr<Fruit> apple = applefactory->Create();apple->show();return 0;
}
⼯⼚⽅法模式每次增加⼀个产品时,都需要增加⼀个具体产品类和⼯⼚类,这会使得系统中类的个数成倍增加,在⼀定程度上增加了系统的耦合度。
抽象工厂模式:
⼯⼚⽅法模式通过引⼊⼯⼚等级结构,解决了简单⼯⼚模式中⼯⼚类职责太重的问题,但由于⼯⼚⽅法模式中的每个⼯⼚只⽣产⼀类产品,可能会导致系统中存在⼤量的⼯⼚类,势必会增加系统的开销。此时,我们可以考虑将⼀些相关的产品组成⼀个产品族(位于不同产品等级结构中功能相关联的产品组成的家族),由同⼀个⼯⼚来统⼀⽣产,这就是抽象⼯⼚模式的基本思想。
//抽象工厂模式//水果产品
class Fruit
{
public:virtual void show() {}
};class Apple: public Fruit
{
public:virtual void show(){cout << "this a Apple" << endl;}
};class Banana : public Fruit
{
public:virtual void show(){cout << "this a Banana" << endl;}
};//动物产品
class Animal
{
public:virtual void show() = 0;
};class Dog : public Animal
{
public:virtual void show(){cout << "this a Banana" << endl;}
};class Sheep : public Animal
{
public:virtual void show(){cout << "this a Sheep" << endl;}
};//抽象工厂
class Factory
{
public:virtual shared_ptr<Fruit> CreateFruit(const string& name) = 0;virtual shared_ptr<Animal> CreateAnimal(const string& name) = 0;
};//根据一类产品开创建合适的工厂class FruitFactory : public Factory //统一生产水果
{
public:virtual shared_ptr<Animal> CreateAnimal(const string& name) {return shared_ptr<Animal>();}virtual shared_ptr<Fruit> CreateFruit(const string& name){if (name == "Apple") {return make_shared<Apple>();}else if (name == "Banana") {return make_shared<Banana>();}return shared_ptr<Fruit>();}
};class AnimalFactory : public Factory //统一生产动物
{
public:virtual shared_ptr<Fruit> CreateFruit(const string& name) {return shared_ptr<Fruit>();}virtual shared_ptr<Animal> CreateAnimal(const string& name){if (name == "Dog"){return make_shared<Dog>();}else if (name == "Sheep"){return make_shared<Sheep>();}return shared_ptr<Animal>();}
};int main()
{Factory* animal_factory = new AnimalFactory();shared_ptr<Animal> dog = animal_factory->CreateAnimal("Dog");shared_ptr<Animal> sheep= animal_factory->CreateAnimal("Sheep");dog->show();sheep->show();Factory* fruitl_factory = new FruitFactory();shared_ptr<Fruit> apple = fruitl_factory->CreateFruit("Apple");shared_ptr<Fruit> banana = fruitl_factory->CreateFruit("Banana");apple->show();banana->show();return 0;
}
抽象⼯⼚模式适⽤于⽣产多个⼯⼚系列产品衍⽣的设计模式,增加新的产品等级结构复杂,需要对原有系统进⾏较⼤的修改,甚⾄需要修改抽象层代码,违背了“开闭原则”。
3.建造设模式
建造者模式是⼀种创建型设计模式, 使⽤多个简单的对象⼀步⼀步构建成⼀个复杂的对象,能够将⼀个复杂的对象的构建与它的表⽰分离,提供⼀种创建对象的最佳⽅式。主要⽤于解决对象的构建过于复杂的问题。
建造者模式中会有几个核心的角色,来组成整个模式的运行:
- 抽象产品类:⼀个抽象的产品对象类。
- 具体产品类:⼀个具体的产品对象类。
- 抽象Builder类:创建⼀个产品对象所需的各个部件的抽象接⼝。
- 具体产品的Builder类:实现抽象接⼝,构建各个部件。
- 指挥者Director类:统⼀组建过程,提供给调⽤者使⽤,通过指挥者来构造产品。
//抽象复杂产品
class Computer
{
public:using Computerptr = std::shared_ptr<Computer>;Computer(const string& name):_name(name) {}void SetBoard(string board) { _board = board; }void SetDisplay(string display) { _display = display; }virtual void SetOs() = 0;void show() {cout << _name << "-" << _board << "-" << _display << "-" << _os << endl;}
protected:string _board;string _display;string _os;string _name;
};//具体复杂产品
class MacBook :public Computer
{
public:MacBook(const string& name):Computer(name){}virtual void SetOs(){_os = "Max Os X12";}
};//抽象建造者,应该包含构造对象,所有的基础建造步骤,作为抽象建造者类的纯虚函数
class Builder {
public:using ptr = std::shared_ptr<Builder>;virtual void buildBoard(const std::string& board) = 0;virtual void buildDisplay(const std::string& display) = 0;virtual void buildOs() = 0;virtual Computer::Computerptr build() = 0;
};//具体建造者,应该确定构造对象,所有的基础建造步骤,并且能够将需要构造的对象创建出来,构造步骤的纯虚函数完成重写
class MacbookBuilder:public Builder
{
public:MacbookBuilder(const string& name) :_computer(new MacBook(name)) {}virtual void buildBoard(const std::string& board){_computer->SetBoard(board);}virtual void buildDisplay(const std::string& display) {_computer->SetDisplay(display);}virtual void buildOs(){_computer->SetOs();}virtual Computer::Computerptr build(){return _computer;}
private:Computer::Computerptr _computer;
};//指挥者(调用者),负责使用外部提供的建造者,创建目标对象
class Director
{
public: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()
{//1.调用者创建复杂对象建造起//2.创建指挥者使用建造者创建对象Builder* builder = new MacbookBuilder("MacBool air 13");Director director(builder);director.construct("Interboard","Redmidisplay");builder->build()->show();return 0;
}
4.代理模式
代理模式指代理控制对其他对象的访问, 也就是代理对象控制对原对象的引⽤。在某些情况下,⼀个
对象不适合或者不能直接被引⽤访问,⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤。
代理模式的结构包括⼀个是真正的你要访问的对象(⽬标类)、⼀个是代理对象。⽬标对象与代理对象实现同⼀个接⼝,先访问代理类再通过代理类访问⽬标对象。代理模式分为静态代理、动态代理:
静态代理指的是,在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时就已经确定了代理类要代理的是哪个被代理类。
动态代理指的是,在运⾏时才动态⽣成代理类,并将其与被代理类绑定。这意味着,在运⾏时才能
确定代理类要代理的是哪个被代理类。
以租房为例,房东将房⼦租出去,但是要租房⼦出去,需要发布招租启⽰, 带⼈看房,负责维修,这些⼯作中有些操作并⾮房东能完成,因此房东为了图省事,将房⼦委托给中介进⾏租赁。 静态代理模式实现:
//被代理抽象类(出租屋)
class RentHouse
{
public:virtual void renthouse() = 0;
};//被代理对象(房东)
class Landlord : public RentHouse
{
public:virtual void renthouse(){cout << "将房子出租" << endl;}
};//代理着(中介)
class Intermediary : public RentHouse
{
public:virtual void renthouse(){std::cout << "发布招租启⽰\n";std::cout << "带人看房\n";_landlord.renthouse();std::cout << "负责租后维修\n";}
private:Landlord _landlord;
};int main()
{Intermediary inter;inter.renthouse();return 0;
}
5.适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它的核心目标是让原本不兼容的接口能够协同工作。它通过创建一个中间层(适配器),将一个类的接口转换成另一个接口,使得原本无法直接交互的类可以一起使用。
比如:我们有一个vector<int> ,我们使用对vector<int>的push_back()和pop_back(),来封装Stack<int>的push()和pop(),这种设计方式就是适配器模式,STL中也是使用的这种设计模式。
template<class T>
class Stack
{
public:Stack(){}void push(T& val){_arr.push_back(val);}void pop(){_arr.pop_back();}T top(){return _arr.back();}bool empty(){return _arr.empty();}
private:vector<T> _arr;
};int main()
{Stack<int> st;for (int i = 0; i <= 10; i++){st.push(i);}while (!st.empty()){cout<<st.top()<<" ";st.pop();}return 0;
}
6.装饰模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它的核心目标是让原本不兼容的接口能够协同工作。它通过创建一个中间层(适配器),将一个类的接口转换成另一个接口,使得原本无法直接交互的类可以一起使用。
比如:奶茶店有普通奶茶,也有椰果奶茶,也有红豆奶茶,但是椰果奶茶就是普通奶茶加上椰果的装饰,红豆奶茶就是普通奶茶加上红豆的装饰。
//抽象奶茶类
class MilkTea
{
public:virtual int price() = 0;
};//具体奶茶类
class BaseMikeTea : public MilkTea
{
public:BaseMikeTea(int p) :_price(p) {}int price(){return _price;}
private:int _price;
};//抽象装饰器类(加料奶茶)
class MaterialsMikeTea
{
public:MaterialsMikeTea(shared_ptr<BaseMikeTea> basemilktea):_basemilktea(basemilktea){}virtual int price() = 0;
protected:shared_ptr<BaseMikeTea> _basemilktea;
};//具体装饰器(椰果奶茶)
class CoconutMileTea : public MaterialsMikeTea
{
public:CoconutMileTea(shared_ptr<BaseMikeTea> basemilktea) :MaterialsMikeTea(basemilktea) {}virtual int price(){return _basemilktea->price() + 5;}
};//具体装饰器(红豆奶茶)
class RedbeanMileTea : public MaterialsMikeTea
{
public:RedbeanMileTea(shared_ptr<BaseMikeTea> basemilktea) :MaterialsMikeTea(basemilktea) {}virtual int price(){return _basemilktea->price() + 4;}
};int main()
{//普通奶茶shared_ptr<BaseMikeTea> basemilktea(new BaseMikeTea(10));cout << "普通奶茶:¥" << basemilktea->price() << endl;//红豆奶茶RedbeanMileTea redbeanmiketea(basemilktea);cout << "红豆奶茶:¥" << redbeanmiketea.price() << endl;//椰果奶茶CoconutMileTea cocountmiketea(basemilktea);cout << "椰果奶茶:¥" << cocountmiketea.price() << endl;return 0;
}
7.策略模式
策略模式是一种行为型设计模式,它通过定义一系列可互换的算法,并将每个算法封装在独立的类中,使它们能够相互替换。这种模式让算法的变化独立于使用它的客户端,从而提升代码的灵活性和可维护性。
特点就是,提供一个抽象的基类策略,后续的具体的策略通过继承的方式实现。仍然更好的遵守开闭原则。
8.模板方法模式
模板方法模式(Template Method Pattern)是一种行为型设计模式,它通过定义一个算法的骨架(模板),将某些步骤的具体实现延迟到子类中,从而在不改变算法结构的前提下,允许子类重定义部分步骤。这种模式的核心思想是封装不变部分,扩展可变部分。
比如比如我们可以冲泡一个饮品,可以奶茶,也可也是咖啡,整体的步骤都是,冲洗杯子,加入奶茶、咖啡,加水,搅拌。主要的区别就是加的东西不一样。但是流程是一样的,我们只要将不一样的地方抽离出来,就可以实现多种功能,但是又不改变步骤。
class Recipe {
public:// 模板方法(final 确保子类不能修改流程)virtual void prepareRecipe() final {boilWater();brew();pourInCup();addCondiments();hook(); // 可选钩子方法}// 固定步骤(所有子类共用)void boilWater() { std::cout << "烧水..." << std::endl; }void pourInCup() { std::cout << "倒入杯子..." << std::endl; }// 抽象步骤(子类必须实现)virtual void brew() = 0;virtual void addCondiments() = 0;// 钩子方法(可选步骤,子类可选择覆盖)virtual void hook() {}
};class Coffee : public Recipe {
public:void brew() override {std::cout << "冲泡咖啡粉..." << std::endl;}void addCondiments() override {std::cout << "加糖和牛奶..." << std::endl;}void hook() override {std::cout << "额外步骤:搅拌咖啡..." << std::endl;}
};class Tea : public Recipe {
public:void brew() override {std::cout << "浸泡茶叶..." << std::endl;}void addCondiments() override {std::cout << "加柠檬..." << std::endl;}
};int main()
{Recipe* coffee = new Coffee();Recipe* tea = new Tea();std::cout << "制作咖啡:" << std::endl;coffee->prepareRecipe();std::cout << "\n制作茶:" << std::endl;tea->prepareRecipe();delete coffee;delete tea;return 0;
}
9.设计模式六大原则:
单⼀职责原则(Single Responsibility Principle);
特点:类的职责应该单一,一个方法只做⼀件事。职责划分清晰了,每次改动到最⼩单位的方法或类。
使用建议:两个完全不⼀样的功能不应该放⼀个类中,⼀个类中应该是⼀组相关性很⾼的函
数、数据的封装。
⽤例:网络聊天:网络通信 & 聊天,应该分割成为网络通信类 & 聊天类。
开闭原则(Open Closed Principle);
特点:对扩展开放,对修改封闭。
使用建议:对软件实体的改动,最好用扩展而非修改的方式。
用例:超时卖货:商品价格---不是修改商品的原来价格,而是新增促销价格。
里氏替换原则(Liskov Substitution Principle);
特点:通俗点讲,就是只要父类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常。在继承类时,务必重写父类中所有的方法,尤其需要注意父类的protected方法,子类尽量不要暴露自己的public方法供外界调用。
使用建议:子类必须完全实现父类的方法,孩⼦类可以有自己的个性。覆盖或实现父类的方法时,输入参数可以被放大,输出可以缩小。
用例:跑步运动员类-会跑步,子类长跑运动员-会跑步且擅长长跑, 子类短跑运动员-会跑步且擅长短跑。
依赖倒置原则(Dependence Inversion Principle)。
特点:高层模块不应该依赖低层模块,两者都应该依赖其抽象. 不可分割的原子逻辑就是低层模式,原子逻辑组装成的就是高层模块。模块间依赖通过抽象(接口)发生,具体类之间不直接依赖。
使用建议:每个类都尽量有抽象类,任何类都不应该从具体类派生。尽量不要重写基类的方
法。结合里氏替换原则使用。
用例:奔驰车司机类--只能开奔驰; 司机类 -- 给什么车,就开什么车; 开车的⼈:司机--依
赖于抽象。
迪米特法则(Law of Demeter),⼜叫“最少知道法则”;
特点:尽量减少对象之间的交互,从而减小类之间的耦合。⼀个对象应该对其他对象有最少的了解。
使用建议:对类的低耦合提出了明确的要求:只和直接的朋友交流, 朋友之间也是有距离的。自己的就是自己的(如果⼀个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中)。
用例:老师让班长点名--老师给班长⼀个名单,班长完成点名勾选,返回结果,而不是班长点名,老师勾选。
接口隔离原则(Interface Segregation Principle);
特点:客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。
使⽤建议:接口设计尽量精简单⼀,但是不要对外暴露没有实际意义的接口。
⽤例:修改密码,不应该提供修改用户信息接口,而就是单⼀的最小修改密码接口,更不要暴露数据库操作。
从整体上来理解六大设计原则,可以简要的概括为⼀句话,用抽象构建框架,用实现扩展细节,具体到每⼀条设计原则,则对应⼀条注意事项:
- 单⼀职责原则 告诉我们实现类要职责单⼀;
- 里氏替换原则 告诉我们不要破坏继承体系;
- 依赖倒置原则 告诉我们要面向接口编程;
- 接口隔离原则 告诉我们在设计接口的时候要精简单⼀;
- 迪米特法则 告诉我们要降低耦合;
- 开闭原则是总纲,告诉我们要对扩展开放,对修改关闭。