目录
- 六大原则
- 单例模式
- 工厂模式
- ①简单工厂模式
- ②工厂方法模式
- ③抽象工厂模式
- 建造者模式
- 代理模式
六大原则
单一责任原则(Single Responsibility Principle)
- 类的职责应该单一,一个方法只做一件事,职责划分清晰,每次改动到最小单位的方法或类
- 使用建议:两个完全不一样的功能不应该放在一个类中,一个类中应该是一组相关性很高的函数、数据的封装
- 用例:网络聊天类(❌)应该分割成网络通信类 + 聊天类
开闭原则(Open Closed Principle)
- 对扩展开放,对修改封闭(只添加新功能,不修改原有内容)
- 使用建议:对软件实体的改动,最好用扩展而非修改的方式
- 用例:超时卖货:商品价格—不是修改商品原来的价格,而是新增促销的价格
里氏替换原则(Liskov Substitution Principle)
- 凡事父类能够出现的地方,子类就可以出现,而且替换为子类也不会出现任何的错误或者异常
- 在继承类时,务必重写父类中的所有方法,尤其注意父类的protected方法,子类尽量不要暴露自己的public方法供外界调用
- 使用建议:子类无比完全实现父类的方法,还子类可以有自己的个性,覆盖或者实现父类的方法时,输入的参数可以被放大,输出也可以缩小
- 用例:跑步运动员类:会跑步, 子类长跑运动员-会跑步且擅长长跑,子类短跑运动员:会跑步且擅长短跑
依赖倒置原则(Dependence Inversion Principle)
- 高层模块不应该依赖底层模块,两者都应该依赖其抽象,不可分隔的原子逻辑就是低层的模式,原子逻辑组装成的就是高层模块
- 模块间依赖通过抽象(接口)发生,具体类之间不能直接依赖
- 使用建议:每一个类都尽量有抽象类,任何类都不应该从具体类派生。尽量不要重写基类的方法。结合里氏替换原则使用
- 用例:奔驰车司机 – 只能开奔驰,司机类:给什么车开什么车 : 开车的人 : 司机 – 依赖抽象
迪米特法则(Law of Demeter) 最少知道法则
- 尽量减少对象之间的交互,从而减少类之间的耦合。一个对象应该对其他对象有最少的了解,对类的低耦合提出了明确的要求:
只喝直接的朋友交流,朋友间也是有剧烈的。自己的就是自己的(如果一个方法放在本类中,既不增加类间关系,也不对本类造成负面影响,那就放置在本类中) - 用例:老师让班长点名,老师给班长名单,班长点名勾选,返回结果。老师只和班长交互,同学们只和班长交互
接口隔离原则
- 客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上
- 使用建议:接口设计尽量精简单一,但是不要对外暴露没有啥意义的接口
- 用例:修改密码,不应该提供用户信息接口,而是单一使用修改密码接口
总结:从整体上理解六大设计原则,可以简要概括为一句话,用抽象构建框架,用实现扩展细节,具体到每一条设计原则,则对应一条注意事项
单例模式
单例模式,顾名思义,就是只能由一个实例,那么我们就必须保证:该类不能被复制,该类不能被公开的创造。
单例模式又可以分为懒汉模式和饿汉模式
懒汉模式:对于一个进程来说,我们只有在第一次使用的时候才去创建。优点是节省进程加载的时间。缺点是在第一次使用的时候才去创建。
饿汉模式:在代码转化为进程时,先于main函数之前就已经创建好了,不需要使用的时候再去创建。优点是可以直接使用,该资源早早创建出来,影响进程加载的速度。
饿汉单例模式:
构造、拷贝构造、赋值私有化,不能被显示构造。
静态单例对象的指针,在程序初始化时完成创建。
提供一个获取单例对象的静态对象,可以在任意位置获取单例对象
其生命周期与程序的生命周期是相同的。这个同样适用于多线程的情况,没有线程安全问题。
/* 饿汉单例模式 以空间换时间 */
class Singleton{
public:static Singleton* getInstance() { return _eton; }int getData() { return _data; }
private:Singleton(int data = 99) : _data(data){}~Singleton(){};Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;private:static Singleton* _eton;int _data;
};
Singleton* Singleton::_eton =new Singleton;
懒汉单例模式:
构造、拷贝构造、赋值私有化,不能被显示构造。
静态单例对象的指针,在程序初始化时完成创建。
提供一个获取单例对象的静态对象,可以在任意位置获取单例对象
其生命周期与程序的生命周期是相同的。懒汉单例模式会有线程安全问题,可以通过两种方式解决。
方法一:加锁 + 双检查 (C++98)
class Singleton
{
public://3、提供一个全局访问点获取单例对象static Singleton* GetInstance(){//双检查if (_inst == nullptr){_mtx.lock();if (_inst == nullptr){_inst = new Singleton;}_mtx.unlock();}return _inst;}
private://1、将构造函数设置为私有,并防拷贝Singleton(){}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;//2、提供一个指向单例对象的static指针static Singleton* _inst;static mutex _mtx; //互斥锁
};//在程序入口之前先将static指针初始化为空
Singleton* Singleton::_inst = nullptr;
mutex Singleton::_mtx; //初始化互斥锁
方法二:C++11后在静态函数中直接构造单例对象
/* 懒汉单例模式 懒加载 -- 延时加载思想 -- 一个对象用的时候再实例化 */
// 这里介绍<Effective C++> 作者提出的一种更加优雅简便的单例模式 Meyers Singleton int C++
// C++11后是线程安全的class Singleton{
public:static Singleton& getInstance() {static Singleton _eton;return _eton;}int getData() { return _data; }
private:Singleton(int data = 99) : _data(data){}~Singleton() {};Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;int _data;
};
工厂模式
工厂模式是一种创建型的设计模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们创建对象不会对上层暴露创建逻辑,而是通过使用一个共同结构来指向新创建的对象,因此实现创建-使用的分离。
①简单工厂模式
简单工厂模式:简单工厂模式实现需要由一个工厂对象通过类型决定创建出来的制定产品类的实例。假设有个工厂可以生产水果,当客户需要产品时明确告知工厂生产哪种水果,工厂需要接收用户提供的类别信息,当新增产品的时候,工厂内部取添加新产品的生产方式
class Fruit{
public:virtual void name() = 0;
private:
};class Apple : public Fruit{
public:void name() override{std::cout << "I'm a apple" << std::endl;}
};class Banana : public Fruit{
public:void name() override {std::cout << "I'm a banana" << std::endl;}
};class FruitFactory {
public:static std::shared_ptr<Fruit> create(const std::string &name) {if (name == "苹果") {return std::make_shared<Apple>();} else {return std::make_shared<Banana>();}}
};int main() {std::shared_ptr<Fruit> fruit = FruitFactory::create("苹果");fruit->name();fruit = FruitFactory::create("香蕉");fruit->name();return 0;
}
这个模式的结构和管理产品对象的方式非常简单,但是它的扩展性非常差,当我们需要新增产品的时候,就需要去修改工厂类新增一个类型的产品创造逻辑,违背了开闭原则
②工厂方法模式
工厂方法模式:在简单的工厂模式下新增了多个工厂,多个产品,每个产品对应一个工厂。假设现在有A、B两种产品,则开两个工厂,工厂A主要负责生产产品A,工厂B主要生产产品B,用户只要知道产品的工厂名,而不需要知道具体的产品信息,工厂不需要接收客户的产品类别,只负责生产产品。
工厂方法模式不同于简单工厂模式的地方在于工厂方法模式把对象的创建过程放到里子类里。这样工厂父对象和产品父对象一样,可以是抽象类或者接口,只定义相应的规范或操作,不涉及具体的创建或实现细节。
/* 工厂方法模式 */
class Fruit{
public:virtual void name() = 0;
private:
};class Apple : public Fruit{
public:void name() override{std::cout << "I'm a apple" << std::endl;}
};class Banana : public Fruit{
public:void name() override {std::cout << "I'm a banana" << std::endl;}
};
class FruitFactory {
public:virtual std::shared_ptr<Fruit> createFruit() = 0;
};class AppleFactory : public FruitFactory {
public:virtual std::shared_ptr<Fruit> createFruit() override {return std::make_shared<Apple>();}
};class BananaFactory : public FruitFactory {
public:virtual std::shared_ptr<Fruit> createFruit() override {return std::make_shared<Banana>();}
};int main() {std::shared_ptr<FruitFactory> ff(new AppleFactory());std::shared_ptr<Fruit> fruit1 = ff->createFruit();fruit1->name();ff.reset(new BananaFactory());std::shared_ptr<Fruit> fruit2 = ff->createFruit();fruit2->name();return 0;
}
工厂方法模式每次增减一个产品时,都需要增加一个具体的产品类和工厂类,这使得系统中类的个数成倍的增加,在一定程度上增加了系统的耦合度
③抽象工厂模式
抽象工厂模式:工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必增加系统的开销,此时我们可以考虑将一些相关的产品组成一个产品族(位于不同产品等级结构中功能相互关联的产品组成的家族),由于一个工厂统一生产,这就是抽象工厂模式的基本思想。
#include <iostream>
#include <memory>/* 简单工厂模式 */
class Fruit{
public:virtual void name() = 0;
private:
};class Apple : public Fruit{
public:void name() override{std::cout << "I'm a apple" << std::endl;}
};class Banana : public Fruit{
public:void name() override {std::cout << "I'm a banana" << std::endl;}
};class Animal {public:virtual void name() = 0;
};class Lamp : public Animal {public:virtual void name() override { std::cout << "I'm a Lamp" << std::endl;}
};class Dog : public Animal {public:virtual void name() override {std::cout << "I'm a dog" << std::endl;}
};class Factory {public: 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:virtual std::shared_ptr<Fruit> getFruit(const std::string& name) override{if (name == "苹果") {return std::make_shared<Apple>();} else {return std::make_shared<Banana>();}}virtual std::shared_ptr<Animal> getAnimal(const std::string& name) override{return std::shared_ptr<Animal>();}
};class AnimalFactory : public Factory {public:virtual std::shared_ptr<Fruit> getFruit(const std::string& name) override {return std::shared_ptr<Fruit>();}virtual std::shared_ptr<Animal> getAnimal(const std::string& name) override {if (name == "山羊") {return std::make_shared<Lamp>();} else {return std::make_shared<Dog>();}}
};class FactoryProducer {public: static std::shared_ptr<Factory> create(const std::string &name) {if (name == "水果") {return std::make_shared<FruitFactory>();} else {return std::make_shared<AnimalFactory>();}}
};int main() {std::shared_ptr<Factory> ff = FactoryProducer::create("水果");std::shared_ptr<Fruit> fruit = ff->getFruit("苹果");fruit->name();fruit = ff->getFruit("香蕉");fruit->name();ff = FactoryProducer::create("动物");std::shared_ptr<Animal> animal = ff->getAnimal("山羊");animal->name();animal = ff->getAnimal("小狗");animal->name();return 0;
}
抽象工厂模式适用于生产多个工厂系列产品衍生的设计模式,增加新的产品等级结构复杂,需要对原有系统进行较大修改,甚至需要修改抽象层代码,违背了开闭原则
建造者模式
建造者模式是一种创建型的设计模式,使用多个简单对象一步一步构建成一个复杂的对象,能够将一个复杂的对象的构建与它的表示分离,提供一种创建对象的最佳方式。主要用于解决对象的构建过于复杂的问题。
1.建造者(Builder)角色:给出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体建造者(Concrete Builder)角色。具体建造者类必须实现这个接口所要求的方法:一个是建造方法,另一个是结果返还方法。此时就是米线店的员工,按照收银员的要求的去准备具体的套餐,放入适当的米线,凉菜和饮料。
2.具体建造者(Concrete Builder)角色:担任这个角色的是于应用程序紧密相关的类,它们在应用程序调用下创建产品实例。这个角色主要完成的任务包括:实现Builder角色提供的接口,一步一步完成创建产品实例的过程。在建造过程完成后,提供产品的实例。是具体的做某个套餐的员工。
3.指导者(Director)角色:担任这个角色的类调用具体建造者角色以创建产品对象。导演者并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者对象。是收银员,他知道我想要什么套餐,他会告诉里面的米线店员工去准备什么套餐。
4.产品(Product)角色:产品便是建造中的复杂对象。指导者角色是于客户端打交道的角色。导演者角色将客户端创建产品的请求划分为对各个零件的建造请求,再将这些请求委派给具体建造者角色。具体建造者角色是做具体建造工作的,但却不为客户端所知。就是最后的套餐,所有东西放到一起端过来。
建造者模式主要基于四个核心实现:
- 抽象产品类
- 具体产品类:一个具体的产品对象类
- 抽象Builder类:创建一个产品对象所需要的各个零部件的抽象接口
- 具体产品的Builder类:实现抽象接口,构建各个部件
- 指挥者Director类:统一组建过程,提供给调用者使用,通过指挥者来获取产品
#include <iostream>
#include <string>
#include <memory>/* 通过MacBook的构造理解建造者模式*/class Computer{public:Computer(){};void setBoard(const std::string &board) { _board = board; }void setDisplay(const std::string &display) { _display = display; }virtual void setOs() = 0;void showParamaters() {std::string param = "Computer Paramaters: \n";param += "\tBoard: " + _board + "\n";param += "\tDispaly: " + _display + "\n";param += "\tOs: " + _os + "\n";std::cout << param << std::endl;}protected:std::string _board;std::string _display;std::string _os;
};
class MacBook : public Computer{public:virtual void setOs() override {_os = "Mac OS x12";}
};class Builder {public:virtual void buildBoard(const std::string &board) = 0;virtual void buildDisplay(const std::string &display) = 0;virtual void buildOs() = 0;virtual std::shared_ptr<Computer> build() = 0;
};class MacBookBuilder : public Builder{public: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();}std::shared_ptr<Computer> build() {return _computer;}private:std::shared_ptr<Computer> _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:std::shared_ptr<Builder> _builder;
};int main() {Builder * builder = new MacBookBuilder();std::unique_ptr<Director> director(new Director(builder));director->construct("华硕主板", "三星显示器");std::shared_ptr<Computer> computer = builder->build();computer->showParamaters();return 0;
}
代理模式
代理模式指的是代理控制对其他对象的访问,也就是代理对象控制对原对象的引用。在某些情况下,一个对象不适合或者不能直接被引用访问,而代理对象可以在客户端和目标对象之间起到中介作用
代理模式的结构包括一个是真正的你要访问的目标对象(目标类)、一个是代理对象。目标对象与代理对象实现同一个接口,先访问代理类再通过代理类访问目标对象。代理模式一般分为静态代理、动态代理
- 静态代理指的是,在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时已经确定了代理要代理的是哪一个被代理类
- 动态代理指的是,在运行时才动态生成代理类,并将其与被代理类绑定。这意味着,在运行时才能确定代理类要代理的是哪个被代理类
以租房为例,租客租房,中间经过房屋中介向房东租房,使用代理模式实现
#include <iostream>/* 代理模式 */
class RentHouse {public:virtual void rentHouse() = 0;
};class Landlord : public RentHouse {public:void rentHouse() {std::cout << "房子租出去了" << std::endl;}
};class Intermediary : public RentHouse {public:void rentHouse() {std::cout << "发布招租启事" << std::endl;std::cout << "带人看房" << std::endl;_landload.rentHouse();std::cout << "负责租后维修" << std::endl;}private:Landlord _landload;
};int main() {Intermediary intermediary;intermediary.rentHouse();return 0;
}