C++设计模式(中):结构型模式
本文章将重点讲解结构型模式,也会根据上一篇博客的方式,分别从是什么,为什么,实现步骤,优缺点以及具体的实现代码四步来讲解具体的设计模式

目录
1.前言
2.代理模式
3.桥接模式
4.享元模式
5.外观模式
6.组合模式
7.装饰者模式
8.适配器模式
9.结构体型模式总结
前言
继上一篇《C++设计模式(上):创建型模式》博客,本文章将重点讲解结构型模式,也会根据上一篇博客的方式,分别从是什么,为什么,实现步骤,优缺点以及具体的实现代码四步来讲解具体的设计模式

图1.结构型模式区分示意
代理模式
1.代理模式:为其他对象提供代理以控制对这个对象的访问( 代理是指具有与被代理的对象具有相同的接口的类),客户端必须过代理与被代理的目标类交互,而代理一般在交互的过程中进行某些处理。用于经常会在对象实现的基础上,增强额外的功能操作,所以通过预编译方式和运行期间动态代理实现程序功能的统一维护
2.代理模式实现步骤:
1.定义一个抽象功能类:声明具体需要实现的功能
2.定义一个具体功能类:代理类所代表的具体对象
3.定义代理类:含有对具体对象的引用
3.优点:具体类对象就是实现实际业务逻辑,不关心其他非本职责事务,通过代理完成事务操作,编程简介清晰,高扩展性
4.缺点:代理模式可能会造成请求的处理速度变慢(毕竟有一个代理对象,相当于间接操作)
5.代理模式实现代码:
class Subject { //抽象功能类public: virtual void BuyTicket() = 0;};class Ctrip : public Subject { //具体功能类public: //构造函数 Ctrip(Subject* sub) :m_sub(sub) {} void BuyTicket() { cout << "具体功能类" << endl; }private: Subject* m_sub;};class User :public Subject { //代理类public: void BuyTicket() { cout << "代理类" << endl; }};int main() { //定义一个Subject类型(抽象功能类)指针指向一个User类型(代理类)的地址 Subject* sub = new User; /*定义一个Ctrip类型(具体功能类)指针,指向一个Ctrip类型的地址,该地址由 代理类转换而成 */ Ctrip* proxy = new Ctrip(sub); proxy->BuyTicket(); //通过proxy调用BuyTicket函数,虽然是调用具体功能类,但是访问的是代理类地址 return 0;}桥接模式
1.桥接模式:将业务逻辑或一个大类拆分为不同的层次结构, 通过将继承改为组合的方式,从而能独立地进行开发,在桥接模式的层次结构中的第一层 (通常称为抽象部分) 将包含对第二层 (实现部分) 对象的引用。 抽象部分将能将一些对自己的调用转发给实现部分的对象。一般在处理跨平台应用、 支持多种类型的数据库服务器或与多个特定种类中使用 桥接模式
2.桥接模式实现步骤:
1.定义抽象类:提供高层控制逻辑,依赖实际底层对象
2.定义功能抽象类:拓展抽象类,定义实现的功能
3.定义实现抽象类:用于定义实现类的函数
4.定义具体实现类:实现实现抽象类的函数
3.优点:客户端代码不接触到平台详细信息,开闭原则,单一职责原则,抽象专注高层逻辑, 实现部分处理平台细节
4.缺点:对高内聚的类使用该模式可能会让代码更加复杂(高内聚:每个类或模块负责完成一个单一、明确定义的任务或职责)
5.桥接模式实现代码:
class Color { //定义抽象类 public: virtual void FillColor() = 0;};class Red : public Color { //定义功能抽象类 public: Red() :colorType("Red") {} void FillColor(){ cout << "填充颜色:" << colorType << endl; } private: string colorType;};class Blue : public Color{ //定义功能抽象类 public: Blue() :colorType("Blue") {} void FillColor(){ cout << "填充颜色:" << colorType << endl; } private: string colorType;};class Shape { //定义实现抽象类public: virtual void ShowShape() = 0; virtual void SetColor(Color* color) = 0;protected: virtual void DrawShape() = 0; virtual void DrawColor() = 0; Color* color;};class Circle :public Shape { //定义具体实现类 public: Circle() :shapeType("Circle") {} void ShowShape(){ DrawShape(); DrawColor(); } void SetColor(Color* color){ this->color = color; } private: void DrawShape(){ cout << "绘制:" << shapeType << endl; } void DrawColor(){ this->color->FillColor(); } string shapeType;};int main() { /* 通过定义具体实现类来调用功能抽象类 使用什么功能,就通过功能抽象类来实现不同的功能 */ Shape* shape = new Circle; shape->SetColor(new Red); shape->ShowShape(); shape->SetColor(new Blue); shape->ShowShape(); delete shape; return 0;}享元模式
1.享元模式:共享多个对象所共有的相同状态, 在有限的内存容量中载入更多对象。通过复用相同的对象来减少对象的创建数量,创建更小的对象组,并通过共享实现重用。通过归类,将对象的属性分为内蕴状态和外蕴状态:
1.内蕴状态:指对象直接管理的属性,是对象的私有数据,完全由对象自身管理
2.外蕴状态:对象的外部描述,是每个对象的可变部分,完全由调用者维护和管理
2.享元模式实现步骤:
1.定义享元类:定义实现一些的公共接口
2.定义享元工厂类:负责创建和管理享元角色
3.优点:节省大量内存
4.缺点:速度会变慢,代码会变得更加复杂
5.享元模式实现代码:
class Font { //定义享元类 private: string type; // 内蕴状态 public: Font(const std::string& type) : type(type) {} // 根据外蕴状态显示字体样式 void display(int size, bool bold, bool italic) { string style = bold ? "Bold" : "Normal"; style += " and " + (italic ? "Italic" : "Normal"); cout << "Font: " << type << ", Size: " << size << ", Style: " << style; }};class FontFactory { //享元工厂 private: std::map<std::string, Font*> fonts; public: Font* getFont(const std::string& type) { if (fonts.find(type) == fonts.end()) { Font* font = new Font(type); fonts[type] = font; } return fonts[type]; }};int main() { FontFactory factory; //使用享元工厂获取字体对象 Font* arial = factory.getFont("Arial"); arial->display(12, true, false); Font* times = factory.getFont("Times New Roman"); times->display(14, false, true); return 0;}外观模式
1.外观模式:定义外观类为包含许多函数的复杂类型提供一个简单的接口。 与直接调用原类型相比, 外观提供的功能比较有限, 却包含了客户端真正需要的功能。如果你的程序需要与包含几十种功能的复杂库整合, 但只需使用其中非常少的功能, 那么使用外观模式会非常方便
2.外观模式实现步骤:
1.定义外观类: 实现简单的调用接口
2.定义功能类:实现功能函数的类
3.优点:代码独立于复杂子系统,更直观方便
4.缺点:外观类可能成为与程序中所有类都具有耦合关系
5.外观模式实现代码:
class CSyntaxParser{ //定义功能类 public: void SyntaxParser(){ cout << "语法分析中......"; } };class CMidCode{ //定义功能类 public: void MidCode(){ cout << "生成中间代码中......"; }};class Facade{ //定义外观类 public: void Build(){ CSyntaxParser parser; CMidCode midcode; parser.SyntaxParser(); midcode.MidCode(); }};int main(){ Facade facade; facade.Build(); return 0;}组合模式
1.组合模式:分为叶子节点和组合节点,客户端通过接口统一管理所有对象,无论是叶子节点还是组合节点。组合节点通过其内部的叶子节点来实现接口,实现“整体-部分”的递归组合
2.组合模式实现步骤:
1.定义组合类:描述复杂元素的通用接口和方法
2.定义组合节点类:内含组合类,以及通过组合类对叶子节点类调用的函数
3.在组合节点类中定义添加和删除子元素的方法
4.定义叶子节点类:定义实现功能函数
3.优点:用多态和递归机制更方便地使用复杂树结构。符合开闭原则,添加新功能无需更改现有代码,可以在组合节点类中添加新元素, 使其成为对象树的一部分
4.缺点:功能差异较大的叶子节点类, 提供公共接口或许会有困难,代码可读性差
5.组合模式实现代码:
class Component { //定义组合类 public: virtual void operation() = 0; virtual ~Component() {}};class Leaf : public Component{ //定义叶子节点类 public: void operation() override{ cout << "Leaf is performing operation." << endl; }};class Composite : public Component{ //定义组合节点类 private: vector<Component*> children; public: void add(Component* component){ //添加元素函数 children.push_back(component); } void remove(Component* component){ //删除元素函数 children.erase(remove(children.begin(), children.end(), component), children.end()); } void operation() override{ for (Component* child : children) { child->operation(); } }};int main() { // 创建组合结构 Composite* composite = new Composite(); composite->add(new Leaf()); composite->add(new Leaf()); //使用组合结构 composite->operation(); // 清理分配的内存 delete composite; return 0;}装饰者模式
1.装饰者模式:又叫做包装模式,通过对客户端透明的方式来扩展对象的功能,是继承关系的一个替换方案。 把要添加的附加功能分别放在单独的类中,并让这个类包含它要装饰的对象。针对增加功能来说,如果存在继承的层次越来越深的情况,会不利与代码的维护和可读性,所以装饰者模式来完成,因为此模式比生成子类更为灵活
2.装饰者模式实现步骤:
1.定义抽象组件类:声明需要实现的组件操作函数
2.定义具体组件类:实现的组件操作函数
3.定义抽象装饰器类:声明对抽象组件接口调用的函数
4.定义具体装饰器类:实现抽象组件接口调用的函数
3.优点:易于扩展对象功能 ,通过装饰类的排列组合,可创造出很多不同行为的组合
4.缺点:代码量增大,增加程序复杂性。动态装饰时,多层装饰时会更复杂
5.装饰者模式实现代码:
class Component { //定义抽象组件接口类 public: virtual ~Component() {} virtual void operate() = 0;};class ConcreteComponent : public Component { //定义具体组件类 public: void operate() override { scout << "ConcreteComponent operate"; }};class Decorator : public Component { //定义抽象装饰器类 protected: unique_ptr<Component> component; public: Decorator(std::unique_ptr<Component> c) : component(std::move(c)) {} void operate() override { if (component) { component->operate(); } }};class ConcreteDecoratorA : public Decorator { //定义具体装饰器类 public: ConcreteDecoratorA(std::unique_ptr<Component> c) : Decorator(move(c)) {} void operate() override { Decorator::operate(); addedBehaviorA(); } private: void addedBehaviorA() { cout << "Added behavior A" << endl; }};int main() { //定义具体组件类对象 auto component = make_unique<ConcreteComponent>(); //定义具体装饰器类对象 auto decorator = make_unique<ConcreteDecoratorA>(move(component)); //通过装饰器类对象调用组件类 decorator->operate(); return 0;}适配器模式
1.适配器模式:将一个类的接口转换成另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。在软件开发中,有的时候系统的数据和行为都正确,但接口不符合,这时使用适配器模式
2.适配器模式实现步骤:
1.定义抽象适配器类:声明适配目标接口
2.定义具体的适配器类:实现适配的目标接口函数,一般成员内含被适配者对象
3.通过适配器的目标接口调用被适配者的函数,从而实现与对象交互
3.优点:开发新功能更便利,提高项目质量,新增的适配器类不需要对旧的类进行全面的测试,提高效率,降低成本
4.缺点:类适配器模式,由于多继承,可能会出现二义性,过多使用适配器模式,会导致代码阅读难度增大
5.适配器模式实现代码:
class MediaPlayer { //定义目标接口类 public: virtual ~MediaPlayer() {} virtual void play(std::string file) = 0;};class MediaAdapter { //被适配者类 public: void playVlc(std::string file) { cout << "Playing " << file << " using VLC media player." << endl; }};class MediaAdapterAdapter : public MediaPlayer { //定义适配器类 private: MediaAdapter *adaptee; public: MediaAdapterAdapter(MediaAdapter *adaptee) : adaptee(adaptee) {} void play(string file) override { string vlcFile = convertToVlc(file); adaptee->playVlc(vlcFile); } private: string convertToVlc(string filename){ return filename; }};int main() { //定义被适配的对象 MediaAdapter *media = new MediaAdapter(); //定义适配器 MediaPlayer *player = new MediaAdapterAdapter(media); //通过适配器调用被适配的对象media player->play("movie.mp4"); delete player; delete media; return 0;}结构体型模式总结
- 代理模式:通过代理类进行具体对象的访问
- 桥接模式:通过具体的实现类调用具体的功能类,来实现功能调用
- 享元模式:共享使用同一个类,类内含有不同的功能实现
- 外观模式:对复杂的函数接口进行整合为简单的接口
- 组合模式:分为组合节点和叶子节点,通过组合节点管理所有接口对象(叶子节点)
- 装饰者模式:添加功能时,在原本的功能上进行继承,实现新的功能
- 适配器模式:将不兼容的接口,定义适配器类来实现兼容