装饰器模式
官方定义: 动态地给⼀个对象增加⼀些额外的职责。就增加功能而言,装饰器模式比生成子类更为灵活。 —— 《设计模式》GoF
通俗解释: 装饰器是为了给对象增加额外职责而产生的, 有点粉刷的意思, 房子已经存在了, 在房子的表面加上一层粉刷. (而且它的优势是相较于继承而言的, 相比直接继承, 装饰器更加灵活, 耦合度更低)
应对与 ”过度的采取类继承扩展对象的功能“ 的情况
继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致子类数量的膨胀。
(继承下来就是实实在在的一份负担, 代码的膨胀, 类的膨胀.) 但是很多时候其实我们没有必要去继承, 完全可以使用组合的方式来根据需求动态的实现对象功能的扩展, 以解决子类数量膨胀的问题. 使得功能扩展所带来的影响,代价最小,没必要说来一个新的功能扩展就新创建一个类。
这个类图其实很奇怪, 我刚看见的时候就在想,为啥又是继承, 又是组合的, 继承是为了复用之前的框架, 接口, 裸机 你只有有了之前的房子,才能粉刷装饰吧, 继承体现的是最初的裸机, 接口, 组合完成动态的修饰, 装饰。
这个组合还是组合抽象类本身,绝对看的让人很迷,组合的虽然是抽象类本身, 但是抽象类是一个接口,它可以代表它的一切派生类, 这样便使得这个组合对象可扩展性很强.
模式要点:
通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。
Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。
记忆技巧: 继承复用接口,组合复用实现细节,动态组合
代码场景: 工资计算, 基本员工的工资是天数*100, 经理员工是基本工资 + 加成add. 老板更是在经理员工的基础上增加10倍加成
实现代码如下:
#include <iostream> using namespace std; //此处我没有再写componet类了, 想写的兄弟可以添加喔 class Context { public: int day;//天数 int add;//加成 }; //分发奖金: 普通员工仅仅只是day * 100 //经理 还有加成 //老总等等加成更加牛逼, 在经理的基础上再10倍加成 //基础员工工资 //DecoratorClass装饰器基类 class BaseWocker { public: BaseWocker(BaseWocker* _base) : base(_base) { } virtual int CalculateSlary(Context& ctx) { //计算基本工资 return ctx.day * 100; } virtual ~BaseWocker() {} protected: BaseWocker* base; }; //继承复用接口 //BaseWocker继承下来的是裸机, 最初框架, 接口 //组合扩展细节. 运算. class Manager : BaseWocker { public: Manager(BaseWocker* _base = nullptr) : BaseWocker(_base) { } virtual int CalculateSalary(Context& ctx) { int basesalary = base->CalculateSlary(ctx); //...在组合对象的基础上进行扩展运算 return basesalary + ctx.add; } }; //Decorator class Boss : public BaseWocker { public: Boss(BaseWocker* _base) : BaseWocker(_base) { } virtual int CalculateSalary(Context& ctx) { int basesalary = base->CalculateSlary(ctx); //...在组合对象的基础上进行扩展运算 return (basesalary + (ctx.add * 10)); } };
如下是大佬写的一份代码: 大家也可以赏析一下:
//业务操作, Component抽象基类 class Stream{ public: virtual char Read(int number)=0; virtual void Seek(int position)=0; virtual void Write(char data)=0; virtual ~Stream(){} }; //主体类具体的Component类 class FileStream: public Stream{ public: virtual char Read(int number){ //读文件流 } virtual void Seek(int position){ //定位文件流 } virtual void Write(char data){ //写文件流 } }; class NetworkStream :public Stream{ public: virtual char Read(int number){ //读网络流 } virtual void Seek(int position){ //定位网络流 } virtual void Write(char data){ //写网络流 } }; class MemoryStream :public Stream{ public: virtual char Read(int number){ //读内存流 } virtual void Seek(int position){ //定位内存流 } virtual void Write(char data){ //写内存流 } }; //扩展操作 //扩展操作采取的是装饰器类, has a 的特征 DecoratorStream: public Stream{ protected: Stream* stream;//... DecoratorStream(Stream * stm):stream(stm){ } }; //具体的decorator类 class CryptoStream: public DecoratorStream { public: CryptoStream(Stream* stm):DecoratorStream(stm){ } virtual char Read(int number){ //额外的加密操作... stream->Read(number);//读文件流 } virtual void Seek(int position){ //额外的加密操作... stream::Seek(position);//定位文件流 //额外的加密操作... } virtual void Write(byte data){ //额外的加密操作... stream::Write(data);//写文件流 //额外的加密操作... } }; class BufferedStream : public DecoratorStream{ Stream* stream;//... public: BufferedStream(Stream* stm):DecoratorStream(stm){ } //... };
工厂方法模式
官方定义: 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。——《设计模式》GoF
通俗解释:简单来说就是在工厂基类中定义一个抽象接口, 将整个接口的实例化延迟到一个具体的子类工厂(具体工厂),解耦合. ---- 之后对于工厂的依赖都是依赖稳定的工厂基类
这个类图感觉不算特别完善, 最好可以在Factory下面在继承具体的Factory感觉比较好.
实现代码如下:
//抽象产品类 class Operator { public: Operator(int l, int r) : lhs(l) , rhs(r) { } //抽象操作 virtual int Operation() = 0; virtual ~Operator() {} protected: int lhs; int rhs; }; //具体产品类 class AddOperator : public Operator{ public: AddOperator(int l, int r) : Operator(l, r) { } virtual int Operation() { return lhs + rhs; } }; class SubOperator : public Operator{ public: SubOperator(int l, int r) : Operator(l, r) { } virtual int Operation() { return lhs - rhs; } }; class MulOperator : public Operator{ public: MulOperator(int l, int r) : Operator(l, r) { } virtual int Operation() { return lhs * rhs; } }; class DivOperator : public Operator{ public: DivOperator(int l, int r) : Operator(l, r) { } virtual int Operation() { if (rhs == 0) { cerr << "zero div" << endl; return 0; } return lhs / rhs; } }; //工厂基类 class OperatorFactory { public: //创建对象的接口, 实例化延迟到子类, 工厂方法FactoryMethod virtual Operator* CreateMethod(int, int) = 0; }; //具体工厂 class AddOperatorFactory : public OperatorFactory { public: virtual Operator* CreateMethod(int lhs, int rhs) override { return new AddOperator(lhs, rhs); } }; class SubOperatorFactory : public OperatorFactory { public: virtual Operator* CreateMethod(int lhs, int rhs) { return new SubOperator(lhs, rhs); } }; class MulOperatorFactory : public OperatorFactory { public: virtual Operator* CreateMethod(int lhs, int rhs) { return new MulOperator(lhs, rhs); } }; class DivOperatorFactory : public OperatorFactory { public: virtual Operator* CreateMethod(int lhs, int rhs) { return new DivOperator(lhs, rhs); } }; int main() { int l; int r; char op; while (1) { cout << "请输入lhs, rhs: " << endl; cin >> l >> r; cout << "请输入操作方法op: " << endl; cin >> op; OperatorFactory* factory = nullptr; //创建工厂 switch (op) { case '+': { factory = new AddOperatorFactory(); } break; case '-': { factory = new SubOperatorFactory(); } break; case '*': { factory = new MulOperatorFactory(); } break; case '/': { factory = new DivOperatorFactory(); } break; } //生产产品运行Operaction cout << factory->CreateMethod(l, r)->Operation() << endl; } getchar(); return 0; }
要点:
Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
Factory Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。
使用场景:
解决创建过程比较复杂,希望对外隐藏这些细节;
- 比如连接池,线程池;
- 隐藏对象真实类型;
- 对象创建会有很多参数来决定如何创建;
- 创建对象有复杂的依赖关系;
本质记忆技巧: 延迟到子类进行创建实现
抽象工厂模式
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。——《设计模式》GoF
通俗解释:本质上来讲, 无论是抽象工厂模式, 还是简工厂方法模式,均是属于对象创建模式. 抽象工厂模式相对于工厂方法而言核心差别在于它是负责相关联的一系列的产品, 对象的创建. 不再是单个产品的创建, 而是系列产品的创建, 两者差别仅此而已.
一个抽象工厂下面的两个具体工厂生产的两种产品是搭配产品。好比如说:瓶身与瓶盖两个产品的加工厂. 产品相互依赖,配套. 可以进行扩展, 比如说瓶子也有种类,矿泉水瓶,可口可乐瓶子,芬达瓶子等等. 这个瓶身瓶盖一定是配套生产的. (对应一系列相关,相互依赖的对象)
//产品抽象类A class IProductA { public: //对于产品的操作 virtual void Operation() = 0; virtual ~IProductA() {} }; //产品抽象类B class IProductB { public: //对于产品的操作 virtual void Operation() = 0; virtual ~IProductB() {} }; //抽象工厂 A 瓶身, B 瓶盖 class IFactory { public: virtual IProductA* FactoryMethodA() = 0;//创建产品A virtual IProductB* FactoryMethodB() = 0;//创建产品B virtual ~IFactory() {} }; //具体的产品 class PingSheng : public IProductA { public: virtual void Operation() { cout << "我是瓶身" << endl; } }; class PingGai : public IProductB { public: virtual void Operation() { cout << "我是瓶盖" << endl; } }; //具体的工厂 class PingZiFactory : public IFactory { virtual IProductA* FactoryMethodA() { return new PingSheng(); } virtual IProductB* FactoryMethodB() { return new PingGai(); } };
记忆技巧: 配套产品生产
适配器模式
定义: 将一个类的接口转换为用户需要的另一个接口。Adapter使得原本由于接口不兼容不能在一起工作的那些类可以一起工作。 ——《设计模式》GoF
通俗解释:简单来说就是适配接口, 本来一个类的接口是和用户需要的接口不统一的, 但是现在需要使用这个类的一些功能, 于是可以抽象出来一个中间的适配器类, 进行组合这个需要使用的类对象,并且适配接口,进而实现接口不兼容的类功能, 接口也可以使用.
说到适配器模式,我还想说所有学习C++的大家,只要学了STL, 容器适配器学的还不错的,仿写过stack,queue两个容器适配器的实现的,你们都已经使用了适配器模式了. 只是当时还不知道自己用的是适配器模式. 想想好像确实就是适配新的接口.
push_back-----> push pop_back ----> pop 针对stack而言
场景引入, 简单实现一下适配器模式
//目标接口, 新接口. class ITarget { public: virtual void Request() = 0; }; //需要复用的老旧接口 class Adaptee { public: virtual void RequestOne() = 0; virtual void RequestTwo() = 0; }; //具体的旧类型, 老旧的库, 需要适配复用其中的Request class Old : public Adaptee { public: virtual void RequestOne() { cout << "处理需求1" << endl; } virtual void RequestTwo() { cout << "处理需求2" << endl; } }; //接口的不一致,需要产生一个Adapter接口适配器类, 适配接口 //适配ITarget目标类接口 //继承目标类适配接口 class Adapter : public ITarget { protected: Adaptee* adaptee;//组合复用老旧接口 public: virtual void Request() { adaptee->RequestOne(); adaptee->RequestTwo(); } }; class Client { private: ITarget* target; public: void Click() {// 客户点击, 目标执行需求 target->Request(); } };
要点总结:
Adapter模式主要应用于 "希望复用一些现成类, 但是接口又与复用环境不一致的情况", 在遗留代码复用, 类库迁移方面非常有用
适配的方式使用的是对象组合的方式, 更加符合松耦合的设计原则
原来的接口是稳定的,新的外来的需求是变化的,那么可以通过继承原来的接口,让原来的接口继续保持稳定,在子类通过组合的方式来扩展功能。 继承原来的接口(用户需要使用的接口, 复用其他类对象的接口,子类中组合扩展)
记忆技巧: 继承使得接口转换匹配, 组合复用已有类功能
代理模式
官方定义:为其他对象提供⼀种代理以控制(隔离, 使用接口)对这对象的访问。
——《设计模式》GoF
通俗解释:代理完成职责,代理一个具体类完成职责(接口职责, 相同的接口), 常见的代理, 婚庆代理, 代理商.
代理层出现的原因,背景,优势.
在有些系统中,为了某些对象的纯粹性,只进行了功能相关封装(稳定点),后期添加了其他功能 需要对该对象进行额外操作(变化点),为了隔离变化点(也就是不直接在稳定点进行修改,这样 会让稳定点也变得不稳定),可以抽象一层代理层
首先解释一下纯粹性:纯粹性指的是接口中仅仅只是实现功能,而不需要进行权限判断,引用计数等等其他的额外操作
代理类是一个中介层, 并不做具体的业务细节实现。而是介于客户类和具体的委托代理类之间的中间层. 作用在于对委托代理类使用前后的一些处理扩展, 让真正的RealSubject类保持纯粹 真正的业务功能其实还是委托代理类实现的。代理类是做中间处理和扩展的. (对已有对象的额外操作, 放在代理层)
简单实现:
class ISubject { public: virtual void Handle() = 0; virtual ~ISubject() {} }; //真实的Subject对象 //是一个很纯粹的类. 很稳定. class RealSubject : public ISubject { public: virtual void Handle() { //纯粹的功能实现 } }; //代理类. 扩展变化 class Proxy1 : public ISubject { public: Proxy1(ISubject* subject) : _subject(subject) { } //实现功能扩展. virtual void Handle() { //在访问RealSubject之前的一些操作 //if (是否不可以访问) return; _subject->Handle(); //访问玩之后的一些操作 ++count;//++访问计数等等 } private: ISubject* _subject; static int count; }; int Proxy1::count = 0; //代理模式2扩展处理 //在分布式系统中出现的,skynet actor等 // 在分布式系统当中 skynet actor class Proxy2 : public ISubject { public: virtual void Handle() { // 在访问 RealSubject 之前做一些处理 // 发送到数据到远端 网络处理 同步非阻塞 ntyco c协程 //IResult * val = rpc->call("RealSubject", "Handle"); // 在访问 RealSubject 之后做一些处理 } private: /*void callback(IResult * val) { // 在访问 RealSubject 之后做一些处理 }*/ };
模式要点:
远程代理(隐藏⼀个对象存在不同的地址空间的事实),虚代理(延迟加载lazyload),保护代理(在代理前后做额外操作,权限管理,引用计数等)
在分布式系统中,actor模型(skynet)等常用的设计模式
本质:控制对象访问, 代理RealSubject, 处理对RealSubject访问前后的扩展需求. 权限验证, 引用计数等
好了各位老铁们,设计模式到此结束了.感谢各位兄弟们从一到现在的陪伴
设计模式的学习确实是有点虚无的. 特别是我们这些没啥工作经验的,学完之后根本不知道咋用到实际项目中实现重构项目的设计,持续重构形成自己的设计模式。 我们也没有经历过没有任何设计原则的Bad Smell Code的维护,所以体会是没有实践过的前辈们深入的
但是我觉得提早了解和具备遵循设计原则这样的大思想是我们需要具备的,不一定强行套入设计模式,可以先是我们的代码遵循设计原则。 遵循设计原则的代码往往更容易重构和维护
IT技术路漫长久远。。 ---- 感谢各位大佬的支持,让我们继续加油,工作的前辈升职加薪,学习的友友学业有成,争取保研