本文主要介绍设计模式的设计原则,常见的设计模式:单例模式、工厂模式、抽象工厂模式
建造者模式、代理模式等。
7大设计原则
单一职责原则
- 类的职责应该尽量的单一,一个方法只做一件事。
使用建议:类中的方法功能应该尽量近似,否则就拆分成俩个类。
用例:聊天通信,拆分成聊天类和通信类。
开闭原则
- 尽可能是对外扩展
使用建议:类要修改,尽可能是扩展功能,而不是修改。便于出错回滚。
用例:超市促销,不是覆盖原有的价格,而是在价格旁再打上一个新价格。
里氏替换原则:
- 基类能出现的地方,都能用子类替换
- 对子类重写基类全部的方法,子类不对外提供自己的方法。
使用建议:子类重写父类的方法,并且放大输出。
用例:有一个员工类,俩个基类分别的长期工和临时工,都重写了父类的方法。
依赖倒置原则
- 高层模块不应该依赖于底层模块
使用建议:每个类都有抽象类,不继承于具体类。继承于抽象类。
用例:司机---什么车都能开,小车司机---只能开小车 。如果继承出子类,那么小车司机就违背了依赖倒置原则。
迪⽶特法则:最少知道原则
- 一个类对自己类依赖越少越好。
使用建议:降低不同类间的耦合度,增加的方法不会影响本类。
举例:班主点名,老师在点名册打勾。就违背了最小知道原则,应该由班长完成点名和打勾任务。
接⼝隔离原则
- 接口尽量都是需要的,不需要用到的接口,不暴露对外。
使用建议:接口设置尽量单一,对外只提供有用接口。
举例:修改密码界面,只提供密码即可,不提供用户名等资料。
这7大原则总结下来就是抽象实现框架,具体填充扩展细节,接口尽量单一,类间耦合度低。
常见的设计模式
单例模式
类在全局中只有一份实例,并且提供一个全局的访问点,一次创建,可以提供给所有程序调用。比如服务器的配置文件,程序就会通过单例对象,对配置文件读取。
使用场景:
- 服务器配置文件
- 日志器,防止文件被重入
单例模式根据创建的时机,划分成饿汉模式、懒汉模式
饿汉模式
在程序运行时,就创建好。
- 优点:遇到单例时,直接就可以调用,不需要消耗时间创建。
- 缺点:启动慢、浪费空间。
设计思路:类中的全局变量,会在main函数开始前就被创建。
为了实现单例,需要私有析构和构造函数,防止在堆和栈上直接创建。禁掉拷贝构造和赋值重载。
//饿汉模式
class Singleton{
public:
Singleton(const Singleton& ) =delete;
Singleton& operator=(const Singleton& )=delete;
//获取单例
static Singleton &GetInstance(){
return _eton;
}
private:
static Singleton _eton;
Singleton(){
std::cout<<"构造"<<std::endl;
}
~Singleton(){}
};
Singleton Singleton::_eton;
懒汉模式
在第一次调用这个类的时候创建
- 优点:启动快,自主选择单例创建的时机,节省内存。
- 缺点:创建慢,效率低。
懒汉模式的普通写法:判断单例是否为空,为空就加锁,在判断是否为空 就创建,返回单例。
懒汉的现代写法:C++11后,静态成员的静态变量是局部变量,只有在第一次调用函数的时候才创建。同样需要私有构造、拷贝,禁掉 拷贝构造和赋值重载
//懒汉模式:只有首次调用的时候创建:采用类中静态函数的局部变量
class Singleton{
public:
static Singleton& GetInstance(){
static Singleton _eton;
return _eton;
}
Singleton(const Singleton& ) =delete;
Singleton& operator=(const Singleton& )=delete;
private:
Singleton(){
std::cout<<"构造"<<std::endl;
}
~Singleton(){}
};
工厂模式
工程模式是设计类设计模式。提供一种创建类的最佳方法。创建对象不会暴露上传逻辑,需要创建什么就往工厂放。是一种由共同结构体指向新创建的对象。实现创建和使用的分离。
简单工厂模式
由一个工厂类传入参数,动态决定返回哪一个子类的指针,而接收指针是用抽象的父类。
比如有一个抽象的水果类,继承出苹果和香蕉。
有一个工厂类,传入你需要的类型,就能返回指定水果的指针
class Fruit
{
public:
Fruit() {}
// 设置为纯虚函数
virtual void show() = 0;
};
class Apple : public Fruit
{
public:
Apple() {}
void show() override
{
std::cout << "Apple" << std::endl;
}
};
class Banana : public Fruit
{
public:
Banana() {}
void show() override
{
std::cout << "Banana" << std::endl;
}
};
class Factory
{
public:
static std::shared_ptr<Fruit> BuildFruit(const std::string& name)
{
if (name == "Apple")
return std::make_shared<Apple>();
else if (name == "Banana")
{
return std::make_shared<Banana>();
}
return std::shared_ptr<Fruit>();
}
};
int main()
{
std::shared_ptr<Fruit> apple = Factory::BuildFruit("Apple");
apple->show();
std::shared_ptr<Fruit> banana = Factory::BuildFruit("Banana");
banana->show();
return 0;
}
优点:获取对象简单,代码简单
缺点:违反开闭原则
工厂方法模式
简单工厂下的扩展,一个产品对于一个工厂。假设现在有俩个产品,A产品,B产品,A工厂和B工厂。A工厂负责生成产品A,B工厂生成产品B。用户不需要知道具体的产品名,只需要往对于的工厂里取数据。
设计:抽象产品A ,B。派生出具体产品A和B。抽象工厂,拍派生出具体工厂A,B。
class Fruit
{
public:
Fruit() {}
// 设置为纯虚函数
virtual void show() = 0;
};
class Apple : public Fruit
{
public:
Apple() {}
void show() override
{
std::cout << "Apple" << std::endl;
}
};
class Banana : public Fruit
{
public:
Banana() {}
void show() override
{
std::cout << "Banana" << std::endl;
}
};
class FruitFactory{
public:
virtual std::shared_ptr<Fruit> BuildFruit()=0;
};
class AppleFactory :public FruitFactory
{
public:
std::shared_ptr<Fruit> BuildFruit() override
{
return std::make_shared<Apple>();
}
};
class BananaFactory :public FruitFactory
{
public:
std::shared_ptr<Fruit> BuildFruit() override
{
return std::make_shared<Banana>();
}
};
int main()
{
std::shared_ptr<FruitFactory> factory(new AppleFactory());
auto apple=factory->BuildFruit();
apple->show();
factory.reset(new BananaFactory());
auto banana=factory->BuildFruit();
banana->show();
return 0;
}
优点:方便生成对象
缺点:违反开闭原则,需要为每一个类都配置一个工厂,代码冗余
抽象工厂模式
工厂方法解决了简单工厂工厂类职责太重的问题,但是需要为每一个类都创建一个对象,会存在大量的开销。一种方法是将相同的类归于一个族,一个工厂来负责生成一个家族的产品。
下面就以水果家族和动物家族演示抽象工厂的创建
class Fruit
{
public:
Fruit() {}
// 设置为纯虚函数
virtual void show() = 0;
};
class Apple : public Fruit
{
public:
Apple() {}
void show() override
{
std::cout << "Apple" << std::endl;
}
};
class Banana : public Fruit
{
public:
Banana() {}
void show() override
{
std::cout << "Banana" << std::endl;
}
};
class Animal
{
public:
virtual void show() = 0;
};
class Dog : public Animal
{
public:
void show() override
{
std::cout << "Dog" << std::endl;
}
};
class Lamp : public Animal
{
public:
void show() override
{
std::cout << "Lamp" << std::endl;
}
};
class Factory
{
public:
virtual std::shared_ptr<Fruit> BuildFruit(const std::string &name) = 0;
virtual std::shared_ptr<Animal> BuildAnimal(const std::string &name) = 0;
};
class FruitFactory : public Factory
{
public:
std::shared_ptr<Animal> BuildAnimal(const std::string &name) override
{
return std::shared_ptr<Animal>();
}
std::shared_ptr<Fruit> BuildFruit(const std::string &name) override
{
if (name == "Apple")
return std::make_shared<Apple>();
else if (name == "Banana")
return std::make_shared<Banana>();
return std::shared_ptr<Fruit>();
}
};
class AnimalFactory : public Factory
{
public:
std::shared_ptr<Fruit> BuildFruit(const std::string &name) override
{
return std::shared_ptr<Fruit>();
}
std::shared_ptr<Animal> BuildAnimal(const std::string &name) override
{
if (name == "Dog")
return std::make_shared<Dog>();
else if (name == "Lamp")
return std::make_shared<Lamp>();
return std::shared_ptr<Animal>();
}
};
class FactoryProduce{
public:
static std::shared_ptr<Factory> Build(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> factor=FactoryProduce::Build("动物");
auto dog=factor->BuildAnimal("Dog");
dog->show();
std::shared_ptr<Factory> fruit_factor=FactoryProduce::Build("水果");
auto apple=fruit_factor->BuildFruit("Apple");
apple->show();
return 0;
}
优点:开销相对较小,创建产品也相对简单。
缺点:扩展复杂,既要扩展家族,也要往具体的工厂添加。
适用场景:
- (属于同一产品族)一起创建时需要大量的重复代码
建造者模式
建造者模式是一种建造型的设计模式。将复杂的对象分解成简单的步骤一步一步组装出对象。
做到复杂对象的构建与表示分离。
建造者的模式的五个核心步骤
- 抽象产品类
- 构建具体产品类
- 抽象builder类:创建一个产品对象所需要的接口
- 具体产品建造者类: 实现各个结果,构建部件
- 指挥者类:统一组装,通过指挥者构建产品
class Computer
{
public:
Computer() {}
void set_dispaly(const std::string &display)
{
_display = display;
}
void set_board(const std::string &board)
{
_board = board;
}
virtual void set_os() = 0;
void showPara()
{
std::string s = "笔记本参数:\n";
s += "\t 显示器:" + _display+"\n";
s += "\t 主板:" + _board+"\n";
s += "\t 操作系统:" + _os;
std::cout<<s<<std::endl;
}
protected:
std::string _display;
std::string _board;
std::string _os;
};
class MacBook : public Computer
{
public:
void set_os() override
{
_os = "Mac-Os";
}
};
// 建造零部件抽象类
class Builder
{
public:
virtual void BuildDisplay(const std::string &display) = 0;
virtual void BuildBoard(const std::string &board) = 0;
virtual void BuildOs() = 0;
virtual std::shared_ptr<Computer> Build() = 0;
};
// 具体类
class BuidlerMacBook : public Builder
{
public:
BuidlerMacBook() : _cmp(new MacBook) {}
void BuildDisplay(const std::string &display) override
{
_cmp->set_dispaly(display);
}
void BuildBoard(const std::string &board) override
{
_cmp->set_board(board);
}
void BuildOs()
{
_cmp->set_os();
}
std::shared_ptr<Computer> Build() override
{
return _cmp;
}
private:
std::shared_ptr<Computer> _cmp;
};
//指挥者
class Dirctor{
public:
Dirctor(Builder* builder)
:_builder(builder)
{
}
void Construct(const std::string &display,const std::string &board)
{
_builder->BuildDisplay(display);
_builder->BuildBoard(board);
_builder->BuildOs();
}
private:
std::shared_ptr<Builder> _builder;
};
int main()
{
Builder *build=new BuidlerMacBook();
//指挥者
std::unique_ptr<Dirctor> dirctor(new Dirctor(build));
dirctor->Construct("sangxing","CHERRY");
auto max=build->Build();
max->showPara();
return 0;
}
- 优点:创建与表示分离
- 缺点:产品发生变化,需要更改builder类
适用场景:复杂对象,有多个步骤组装的产品
代理模式
代理模式控制对原对象的引用。一般情况下,一个对象不适合被直接访问,或者原对象无法满足需求的时候,创建一个中介,隔离原对象和客户端。这个中介就是代理模式。
下面以房东出租房间。中介发布出租,带人看房等写一份代理模式的例子
#include<string>
#include <iostream>
/*通过租房来学习代理模式
代理模式:代理者与被代理者有相同的接口,但是是加强版的更加详细
*/
class RentHouse{
public:
virtual void rent()=0;
};
class Landlord:public RentHouse{
public:
void rent() override{
std::cout<<"房东出租房间"<<std::endl;
}
};
class Intermediary:public Landlord{
public:
void rent() override{
std::cout<<"中介发布公告"<<std::endl;
std::cout<<"中介带人看房"<<std::endl;
_landlord.rent();
std::cout<<"中介完成交易"<<std::endl;
};
private:
Landlord _landlord;
};
int main()
{
Intermediary intermediary;
intermediary.rent();
return 0;
}
优点:
- 职责明确,只需要考虑自己应该做的事情。
- 高扩展性,真实对象改变不影响代理。
- 安全访问,代理模式隔断访问,提供安全的接口。
缺点:
- 增加系统复杂度。代理冗余
- 请求速度慢:请求要先经过中介,最后到达真实对象。
适用场景:
- 当对象被控制访问或者需要对原对象补充访问等。