设计模式分类
设计模式可以分为三种类型:创建型设计模式、结构型设计模式和行为型设计模式。
创建型设计模式:这些模式涉及到对象的创建机制,包括简单工厂模式、工厂方法模式、抽象工厂模式、单例模式、建造者模式和原型模式。
结构型设计模式:这些模式涉及到类和对象的组合,包括适配器模式、桥接模式、组合模式、装饰器模式、外观模式、享元模式和代理模式。
行为型设计模式:这些模式涉及到对象之间的通信和交互,包括责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。
本文是对结构型设计模式中的装饰器、组合设计模式的一个总结。每个设计模式的定义都比较晦涩,可以直接看代码理解。
设计模式的设计原则
依赖倒置:高层模块不应该依赖低层模块,两者都应该依赖抽象; 抽象不应该依赖具体实现,具体实现应该依赖于抽象; (记住依赖抽象就好了)。
开放封闭:一个类应该对扩展(组合和继承)开放,对修改关闭;
面向接口:不将变量类型声明为某个特定的具体类,而是声明为某个接口;
客户程序无需获知对象的具体类型,只需要知道对象所具有的接口;
减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案;(记住只暴露接口,只调用接口)。
封装变化点:将稳定点和变化点分离,扩展修改变化点;让稳定点和变化点的实现层次分离;
单一职责:一个类应该仅有一个引起它变化的原因; (就是变化点不要太多)。
里氏替换:子类型必须能够替换掉它的父类型;主要出现在子类覆盖父类实现,原来使用父类型的程序可能出现错误;覆盖了父类方法却没有实现父类方法的职责;( 就是子类可以覆盖父类的方法,但是得保证父类必要的功能)。
接口隔离:不应该强迫客户依赖于它们不用的方法;
一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责;
客户端不应该依赖它不需要的接口。
一个类对另一个类的依赖应该建立在最小的接口上。
组合优于继承:继承耦合度高,组合耦合度低;
装饰器模式
动态的给对象添加一些额外的责任,就增加功能来说,装饰比生成子类更为灵活。
用一个菜品计算成本(包括食物和各种调料)的例子说明这个设计模式:
在餐馆需要给食物计算成本,比如面条和加的各种调料:
#include <iostream> #include <string> using namespace std; // 食品类 class Food { protected: string des; double price; public: virtual double cost() = 0; string getDes() { return des; } void setDes(string des) { this->des = des; } double getPrice() { return price; } void setPrice(double price) { this->price = price; } }; // 面条类 class Noodles : public Food { public: double cost() override { return getPrice(); } }; // 中式面条类 class ChineseNoodles : public Noodles { public: ChineseNoodles() { setDes("中式面条"); setPrice(25.00); } }; // 装饰器类 class Decorator : public Food { protected: Food* desFood; public: Decorator(Food* desFood) { this->desFood = desFood; } double cost() override { cout << desFood->getDes() << "价格:" << desFood->getPrice() << " 配料如下:" << getDes() << " 价格:" << getPrice() << " 总价" << (getPrice() + desFood->cost()) << endl; return getPrice() + desFood->cost(); } }; // 孜然类 class Cumin : public Decorator { public: Cumin(Food* desFood) : Decorator(desFood) { setDes("孜然"); setPrice(2.00); } }; // 胡椒类 class Peper : public Decorator { public: Peper(Food* desFood) : Decorator(desFood) { setDes("胡椒"); setPrice(3.00); } }; int main() { // 先定义一个被装饰者,返回对象要为最顶层的对象,这样被装饰者才能接受 Food* noodles = new ChineseNoodles(); // 定义一个装饰者对象 Food* cumin = new Cumin(noodles); // 输出为:中式面条价格:25配料如下:孜然价格:2总价27 cout << "-----------面条+孜然------------------------" << endl; cumin->cost(); cout << "-----------面条+胡椒------------------------" << endl; Food* peper = new Peper(noodles); peper->cost(); cout << "-----------面条+胡椒+孜然------------------------" << endl; peper = new Peper(cumin); cout << "面条+胡椒+孜然价格:" <<peper->cost(); delete cumin; delete noodles; delete peper; return 0; }
“面条+胡椒+孜然”的例子日志打印比较乱,是由于装饰器类cost打印有嵌套,所以日志打印比较乱。
结构:
- 被装饰者抽象接口(Food):提供基础功能方法,和装饰方法接口。
- 具体的被装饰者(Noodles):继承抽象类,实现方法接口。
- 装饰者公共类(Decorator):实现统一的装饰方法。
- 具体的修饰者类:定制化装饰者属性。
使用场景:在软件开发过程中,有时想用一些现存的组件(已经定义好的对象)。这些组件可能只是完成一些核心功能。但在不改变其架构的情况下,可以动态地扩展其功能。所以这些都可以采用装饰模式来实现。
特点:
装饰者设计模式是通过组合+继承的形式实现的。
装饰类和被装饰类可以独立发展,不会相互耦合。
装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
继承和组合的区别:
继承是is A;
组合是has A;