type
Post
status
Published
date
Apr 1, 2022
slug
design-patterns-cpp-example
summary
常用设计模式极简实现, 有很多不严谨的地方, 但是很容易懂
tags
CS
develop
category
技术分享
icon
password

前言

设计模式是软件开发过程中一系列解决方案, 学习设计模式有利于提升开发效率, 也能提高多人协作时的沟通效率.
大部分设计模式要解决的, 是软件结构中那些不稳定的关系, 通过特定的模式, 让软件在拓展功能或重构时能仅作尽量小程度的改动就达成目的.
小部分设计模式则解决一些性能问题, 比如单例模式等.
在学习设计模式的过程中, 应时刻结合如下八大设计原则理解模式要解决的问题, 有利于加深理解.
  • 八大设计原则
    • DIP: 依赖倒置原则
      • 高层模块(稳定)不应该依赖于低层模块(变化), 二者都应该依赖于抽象(稳定)
      • 抽象(稳定)不应依赖于实现细节(变化), 实现细节应依赖于抽象(稳定)
    • OCP: 开放封闭原则
      • 对扩展开放, 对更改封闭
      • 类模块应该是可扩展的, 但不可修改
    • SRP: 单一职责原则
      • 一个类应该仅有一个引起它变化的原因
      • 变化的方向隐含着类的责任
    • LSP: Liskov替换原则
      • 子类必须能够替换它们的基类(就是IS-A的另一种表达)
      • 继承表达类型抽象
    • ISP: 接口隔离原则
      • 不应该强迫客户程序依赖它们不用的方法
      • 接口应该小而完备
    • “组合&聚合优于继承”
      • 类继承通常为“白箱复用”, 对象组合通常为“黑箱复用”
      • 继承在某种程度上破坏了封装性, 子父类耦合度高, 父类向子类暴露过多
      • 对象组合只要求被组合的对象具有良好定义的接口, 耦合度低
    • “封装变化点”
      • 使用封装来创建对象之间的分界层, 让设计者可以在分界层的一侧进行修改, 而不会对另一侧产生不良的影响, 从而实现层次间的松耦合
    • “针对接口编程, 而非针对实现”
      • 不将变量类型声明为某个特定的具体类, 而是声明为某个接口
      • 客户程序无需获知对象的具体类型, 只需要知道对象所具有的接口
      • 减少系统中各部分的依赖关系, 从而实现“高内聚, 松耦合”的类型设计方案
下面是以伪C++实现的常用设计模式示例, 都是比较简短易懂的demo, 设计了相应的场景, 讲解每个类的实现, 并有一段简单的测试代码.

模板方法

关键词: 行为模式, 组件协作, 继承

场景

实现一个运算模板, 完成将一个整形输入 int a 先加2再乘2的运算.

实现

Template类是抽象的模板类, 其中实现了加2的实方法, 乘2的虚方法以及运算结束之后的虚拓展方法.
Concrete1通过加法实现了乘2, 完成了模板中所有纯虚方法的实现, 成为一个具体类, Concrete2亦然, 实现方式为左移一位.
//[抽象类]模板,示例为实现加2和乘2两种运算 class Template { public: int AddTwoMulTwo(int a) { //调用实方法 int ans = this->AddTwo(a); //调用下游实现的方法 ans = this->MulTwo(ans); //调用拓展方法,若下游不实现相当于什么都不做 this->Hook(); return ans; } virtual ~Template() { } protected: //已实现的实方法:加2 int AddTwo(int a) { return a + 2; } //未实现的纯虚方法,交给下游实现:乘2 virtual int MulTwo(int a) = 0; //可不实现的虚方法,方便拓展,下游可选择是否实现 virtual void Hook() { } }; //[具体类]乘2的实现1 class Concrete1 :public Template { protected: int MulTwo(int a) { return a + a; } }; //[具体类]乘2的实现2 class Concrete2 :public Template { protected: int MulTwo(int a) { return a << 1; } void Hook() { cout << "在步骤之间充当拓展操作\n"; } };

测试

通过Template *的多态指针, 分别调用两种模板实现, 可以看到, 不同的模板都可以正常工作, 并且在实现模板的时候, 加2的方法不用由下游重复实现, 并且保留了可拓展性.
//多态指针 Template* _tmp; this->_tmp = new Concrete1(); int result1 = this->_tmp->AddTwoMulTwo(3); //result1 = 10 this->_tmp = new Concrete2(); int result2 = this->_tmp->AddTwoMulTwo(3); //result2 = 10 //并且控制台输出字符串:"在步骤之间充当拓展操作"

策略模式

关键词: 行为模式, 组件协作, 组合

场景

实现一个整数运算策略, 可以由客户端切换为加法策略or减法策略.

实现

Strategy类是抽象的整数运算策略, 保证了该运算方法有两个整形输入和一个整形输出.
AddStrategy为加法策略, 将整数运算方法实现为加法, 而SubStrategy则实现为减法.
//[抽象类]整数运算策略 class Strategy { public: virtual int DoOperation(int a, int b) = 0; virtual ~Strategy() { } }; //[具体类]加法策略,继承整数运算策略 class AddStrategy :public Strategy { public: int DoOperation(int a, int b) { return a + b; } }; //[具体类]减法策略,继承整数运算策略 class SubStrategy :public Strategy { public: int DoOperation(int a, int b) { return a - b; } };

测试

客户端可以自由切换策略完成不同的运算.
值得注意的是, 该模式与模板方法有些相似(我写的示例有些偷懒), 但他们是有很明显的区别的, 模板方法主要依赖继承, 完成对模板的使用, 而策略模式则主要依赖组合, 继承仅仅是为了使外部接口一致.
//多态指针 Strategy* _strategy; //策略选择 void setStrategy(Strategy* strategy) { this->_strategy = strategy; } //客户端测试逻辑 void test() { //选择加法策略 setStrategy(new AddStrategy()); //调用加法策略 int result = this->_strategy->DoOperation(3, 2); //result应为3+2=8 //选择减法策略 setStrategy(new SubStrategy()); //调用减法策略 int result = this->_strategy->DoOperation(3, 2); //result应为3-2=1 }

观察者模式

关键词: 行为模式, 组件协作

场景

类中发生的某些变化时, 要将信息 msg 通知到多个外部类

实现

Observer是观察者接口, 需要订阅消息的类都需要实现这个接口.
Observer1Observer2是简陋的观察者实现, 没写出具体观察者方的订阅机制.
Publisher是发布者接口, 内含最基本的三个方法: 订阅+退订+通知
Publisher1是一个具体的发布者, 实现了订阅(将观察者添加到订阅列表), 退订(将观察者从订阅列表中移除)和通知(依次遍历订阅列表, 逐个将变化告知观察者)
//[抽象类]观察者接口 class Observer { public: //由具体观察者实现,发布者调用 virtual void Update(string message) = 0; virtual ~Observer() { } }; //[具体类]观察者1号 class Observer1 :public Observer { public: void Update(string message) { cout << "1号收到" << message << endl; } void Other() { //...其他业务逻辑 } }; //[具体类]观察者2号 class Observer2 :public Observer { public: void Update(string message) { cout << "2号收到" << message << endl; } void Other() { //...其他业务逻辑 } }; //[抽象类]发布者接口 class Publisher { public: //添加观察者, "订阅" virtual void Attach(Observer* observer) = 0; //移除观察者, "退订" virtual void Detach(Observer* observer) = 0; //调用所有订阅者的Update, "通知" virtual void Notify() = 0; virtual ~Publisher() { }; }; //[具体类]发布者1号,这里只用一个发布者举例,多个的区别就是通知的来源不同而已 class Publisher1 : public Publisher { private: List<Observer*> m_observerList; public: void Attach(Observer* observer) { m_observerList.add(observer); } void Detach(Observer* observer) { m_observerList.remove(observer); } virtual void Notify() { List<Observer*>::Iterator it = m_observerList.begin(); while (it != m_observerList.end()) { (*it)->Update("msg"); ++it; } } };

测试

客户端通过Attach方法将观察者逐个添加到订阅列表后, 在有变化时调用Notify即可使观察者得到信息, 观察者可以正确地打印字符, 表明其已收到信息.
订阅列表是可以更新的, 观察者如果不再需要被通知, 可以退出订阅列表.
void Publisher1::test() { Observer* observer1 = new Observer1(); Observer* observer2 = new Observer2(); Attach(observer1); Attach(observer2); Notify(); //输出两行字符串: "1号收到" "2号收到" Detach(observer1); Notify(); //输出一行字符串:"2号收到" }

装饰模式

关键词: 结构模式, 单一职责, 组合

场景

存在一个 Shape 接口, 其的实现为各种基础形状(e.g.square). 现 Shape 类有两个拓展方向:

实现

Shape是组件接口, 不同的图形用不同的方式实现ShowInfo函数
Square是组件的具体实现, 只是简单输出“square”
ShapeDecorator是装饰接口, 重载原有方法, 方便拓展. 注意, 成员变量中有一个组件接口指针, 这才是装饰的对象, 继承只是为了使外部接口一致.
RedShape具体装饰类, 拓展原有方法, 使其输出“red”+“图形”
ShadowShape具体装饰类, 拓展原有方法, 使其输出“图形”+" with shadow"
//[抽象类]组件接口 class Shape { public: //由具体组件实现 virtual void ShowInfo() = 0; virtual ~Shape() { } }; //[具体类]组件1,以square为例 class Square :public Shape { public: void ShowInfo() { cout << "square"; } }; //[抽象类]装饰接口 class ShapeDecorator : public Shape { protected: Shape* m_shape; public: //构造函数 ShapeDecorator(Shape* shape) :m_shape(shape) { } void ShowInfo() { this->m_shape->ShowInfo(); } }; //[具体类]装饰1,以前缀red为例 class RedShape : public ShapeDecorator { public: //构造函数 RedShape(Shape* sh) :ShapeDecorator(sh) { } //装饰 void ShowInfo() { cout << "red "; this->m_shape->ShowInfo(); } }; //[具体类]装饰2,以后缀with shadow为例 class ShadowShape : public ShapeDecorator { public: //构造函数 ShadowShape(Shape* sh) :ShapeDecorator(sh) { } //装饰 void ShowInfo() { this->m_shape->ShowInfo(); cout << " with shadow"; } };

测试

通过调用装饰方法, 在原始对象的基础上“包装”, 实现其原有的功能, 并且可以自由组合拓展, 将通过继承实现拓展的编译时依赖优化为通过组合实现拓展的运行时依赖.
Shape* shape = new Square(); shape.ShowInfo(); cout << endl; //输出字符串 "square" shape = new RedShape(shape); shape.ShowInfo(); cout << endl; //输出字符串 "red square" shape = new ShadowShape(shape); shape.ShowInfo(); cout << endl; //输出字符串 "red square with shadow"

桥模式

关键词: 结构模式, 单一职责

场景

设计一个应用, 既要在PC平台工作, 也要在Linux平台工作, 均需要实现GUI

实现

Implementation是“实现”接口, WinImp是“Win平台的实现”, LinuxImp是“Linux平台的实现”
Abstraction是具体的“抽象接口”, 看起来有些像装饰模式, 但Abdstraction并不用继承基类, 其与Implementation是纯粹的组合关系, 这是两个平等的不同维度的变化, 并不能像装饰模式那样嵌套.
GUIAbs是具体的GUI拓展, 其不需要依赖具体的实现, 只需依赖抽象而稳定的Implementation接口.
//[抽象类]实现(不是"具体实现"的实现) class Implementation { public: virtual void Show() = 0; virtual ~Implementation() { } }; //[具体类]底层Win实现 class WinImp : public Implementation { public: void Show() { cout << "at Win"; } }; //[具体类]底层Linux实现 class LinuxImp : public Implementation { public: void Show() { cout << "at Linux"; } }; //[具体类]抽象基类(不是"抽象类"的抽象) class Abstraction { protected: Implementation* m_implementation; public: Abstraction(Implementation* imp) :m_implementation(imp) { } virtual void Show() { this->m_implementation->Show(); } virtual ~Abstraction() { } }; //[具体类]GUI拓展 class GUIAbs : public Abstraction { public: GUIAbs(Implementation* imp) :Abstraction(imp) { } void Show() { cout << "GUI "; this->m_implementation->Show(); } };

测试

不同的底层实现被“抽象”组合后都能正确的在各自平台运行.
Implementation* imp1 = new WinImp(); Implementation* imp2 = new LinuxImp(); Abstraction* abs = new GUIAbs(imp1); abs->Show(); //输出字符串: "at Win" abs = new GUIAbs(imp2); abs->Show(); //输出字符串 "GUI at Linux"

工厂方法

关键词: 创建模式, 对象创建

场景

将客户端与产品的new解耦(实现“多态new”), 可在外部切换产品工厂, 以便切换要创建的产品却不修改客户端的代码(运行时依赖)

实现

Product是产品接口, 需要多态new的产品都需要实现这个接口, 保证接口一致, 在客户端可重用.
CandyCoffee为具体的产品, 用各自的方式实现了产品的功能. Factory是工厂接口, 具体的产品需要依托具体的工厂来创建, 而具体的工厂均需要实现这个接口.
CandyFactoryCoffeeFactory是具体的工厂, 封装了各自的产品的new方法, 但是都实现了同样的接口, 实现了“多态new”.
//[抽象类]产品接口 class Product { public: virtual void Taste() = 0; virtual ~Product() { } }; //[具体类]产品1:糖果 class Candy :public Product { public: void Taste() { cout << "sweet" << endl; } }; //[具体类]产品2:咖啡 class Coffee :public Product { public: void Taste() { cout << "bitter" << endl; } }; //[抽象类]工厂接口 class Factory { public: virtual Product* CreateProduct() = 0; virtual ~Factory() { } }; //[具体类]糖果工厂 class CandyFactory : public Factory { public: Product* CreateProduct() { return new Candy(); } }; //[具体类]咖啡工厂 class CoffeeFactory : public Factory { public: Product* CreateProduct() { return new Coffee(); } };

测试

不同的工厂由于实现了相同的接口, 当外部传入不同的工厂对象, 客户端都可以用同样的代码调用创建方法, 创建出对应的对象, 而不需要修改并重新编译客户端的代码.
// 实际使用中客户端的工厂一般由外部传入,实现客户端与产品创建的new之间的解耦 Factory* factory = new CandyFactory(); Product* product = factory->CreateProduct(); product->Taste(); //输出字符串:"sweet" factory = new CoffeeFactory(); product = factory->CreateProduct(); product->Taste(); //输出字符串: "bitter"

抽象工厂

关键词: 创建模式, 对象创建

场景

在使用工厂模式的基础上, 有多个系列的产品需要生产, 系列内部的产品之间相互关联.

实现

ProductFoodProductTool是产品接口, 表示两种不同的产品类型.
Factory是工厂接口, 各个系列工厂都需要实现这个接口.
ChildrensFoodChildrensTool是同一个系列的具体产品, 而ChildrensFactory是这个系列的工厂, 内部实现了这个系列所有产品的创建方法.
AdultFood, AdultToolAdultFactory的含义和Childrens系列相似.
//[抽象类]产品类型1:食物接口 class ProductFood { public: virtual void Taste() = 0; virtual ~ProductFood() { } }; //[抽象类]产品类型2:工具接口 class ProductTool { public: virtual void Use() = 0; virtual ~ProductTool() { } }; //[抽象类]工厂接口 class Factory { public: virtual ProductFood* CreateFood() = 0; virtual ProductTool* CreateTool() = 0; virtual ~Factory() { } }; //[具体类]产品系列1:儿童食物 class ChildrensFood :public ProductFood { public: void Taste() { cout << "sweet for child" << endl; } }; //[具体类]产品系列1:儿童工具 class ChildrensTool : public ProductTool { public: void Use() { cout << "child use toy" << endl; } }; //[具体类]产品系列1:儿童工厂 class ChildrensFactory :public Factory { public: ProductFood* CreateFood() { return new ChildrensFood(); } ProductTool* CreateTool() { return new ChildrensTool(); } }; //[具体类]产品系列2:成人食物 class AdultFood :public ProductFood { public: void Taste() { cout << "bitter for adult" << endl; } }; //[具体类]产品系列2:成人工具 class AdultTool : public ProductTool { public: void Use() { cout << "adult use money" << endl; } }; //[具体类]产品系列2:成人工厂 class AdultFactory :public Factory { public: ProductFood* CreateFood() { return new AdultFood(); } ProductTool* CreateTool() { return new AdultTool(); } };

测试

应用不同的系列工厂, 可以创建不同系列的产品. 从同一个工厂生产出来的产品之间必然相互匹配
Factory* f = new ChildrensFactory(); ProductFood* food = f->CreateFood(); ProductTool* tool = f->CreateTool(); food->Taste(); //输出字符串: "sweet for child" tool->Use(); //输出字符串: "child use toy" f = new AdultFactory(); food = f->CreateFood(); tool = f->CreateTool(); food->Taste(); //输出字符串: "bitter for adult" tool->Use(); //输出字符串: "adult use money"

原型模式

关键词: 创建模式, 对象创建

场景

要频繁地创建一个初始化很复杂的对象

实现

Prototype是原型接口, 亟待复制的对象需要实现这个接口.
Car是一个具体对象, 有自己的成员变量和函数, 它继承了Prototype接口, 实现了可供委派的克隆方法.
Factory是具体的工厂, 内部包含着一个已经完成初始化的对象, 亦即用来克隆的原型, 工厂内部有Create方法, 但其并不负责对象创建, 而是将创建委派给对象自身.
//[抽象类]原型接口 class Prototype { public: virtual Prototype* Clone() = 0; virtual ~Prototype() { } }; //[具体类]具体的对象 class Car :public Prototype { public: int speed; Car(int num) :speed(num) { } //可供委派的克隆方法 Prototype* Clone() { return new Car(*this); } }; //[具体类]工厂 class Factory { public: Prototype* prototype; //相当于工厂预设的汽车速度是5 Factory() { prototype = new Car(5); } //将具体的创建委派给原型对象自身 Prototype* Create() { return prototype->Clone(); } };

测试

客户端将创建委派给工厂, 而工厂则委派给对象本身, 从而直接创建完成初始化的对象, 而不需要对对象的细节进行初始化.
Factory* factory; Car car1 = factory.Create(); cout << car1 << endl; //输出字符串: "5" factory.prototype = new Car(7); car1 = factory.Create(); cout << car1 << endl; //输出字符串: "7"

单例模式

关键词: 创建模式, 对象性能

场景

只需一个全局对象就可满足需求或者必须有且仅有一个对象

实现

Setting是单例类, 这里偷懒写的线程不安全的串行版
//[具体类]单例类,线程不安全的串行版本 class Setting { private: //禁止调用者构造对象 Setting(); Setting(const Setting&); Setting& operator=(const Setting&) = delete; static Setting* m_instance; public: static Setting* GetInstance() { if (m_instance == NULL) { m_instance = new Setting(); } return m_instance; } void Set() { cout << "use setting" << endl; } };

测试

调用GetInstance, 若无对象, 将创建并返回对象; 若有对象, 则直接返回对象.
Setting* setting = Setting::GetInstance(); setting->Set(); //输出字符串:"use setting"

享元模式

关键词: 结构模式, 对象性能

场景

存在多个可以共享使用的对象

实现

Character是具体的可被分享的对象, CharacterFactory是享元工厂, 客户端可以通过享元工厂得到这些可被分享的对象.
//[具体类]享元对象 class Character { public: char c; Character(char ch) : c(ch) { } void Print() { cout << "shared " << this->c << endl; } }; //[具体类]享元工厂 class CharacterFactory { public: unordered_map<char, Character*>mp; Character* GetChar(char c) { if (mp.count(c) != 0) { return mp[c]; } Character* character = new Character(c); mp[c] = character; return character; } };

测试

客户端调用享元工厂来“创建”对象, 如果对象已存在, 工厂会直接返回对象.
享元模式是对象级别的共享, 单例模式是类级别的共享.
CharacterFactory* cf = new CharacterFactory(); Character* charA = cf->GetChar('A'); charA->Print(); //输出字符串:"shared A" Character* charB = cf->GetChar('B'); charB->Print(); //输出字符串:"shared B" Character* charA1 = cf->GetChar('A'); //检验是否是同一个对象 if (charA == charA1) { cout << "you are using the same obj" << endl; } //输出字符串:"you are using the same obj"

门面模式

关键词: 结构模式, 接口隔离, 组合

场景

构造一个厨房类, 使客户可以在外部关闭厨房内的设备, 但不需要知道厨房内部的细节.

实现

Kettle, ToasterRefrigerator是具体的系统内部结构, Kitchen是供外部调用的“封装”.
Kitchen整个对象系统提供了一个新接口Off, 使用户可以透明地调用所有内部结构的TurnOff方法.
//[具体类]内部结构1 class Kettle { void TurnOff() { cout << "Kettle turn off" << endl; } }; //[具体类]内部结构2 class Toaster { void TurnOff() { cout << "Toaster turn off" << endl; } }; //[具体类]内部结构3 class Refrigerator { void TurnOff() { cout << "Refrigerator turn off" << endl; } }; //[具体类]门面 class Kitchen { protected: Kettle* kettle; Toaster* toaster; Refrigerator* refrigerator; public: //构造和析构懒得写了( void Off() { this->kettle->TurnOff(); this->toaster->TurnOff(); this->refrigerator->TurnOff(); } };

测试

客户端只看得到Kitchen这个门面, 三个内部结构类被门面封装成一个系统, 只通过Kitchen的接口服务客户端, 客户并不能穿过门面操作系统内部结构.
Kitchen* kitchen = new Kitchen(); kitchen->Off(); //输出字符串: //"Kettle turn off" //"Toaster turn off" //"Refrigerator turn off"

代理模式

关键词: 结构模式, 接口隔离, 组合

场景

为一个对象设置代理, 使调用该对象的方法前后可以做一些其他工作.

实现

Graphic是对象要实现的接口, Image是对象本身.
ImageProxy是代理对象, 实现了和对象相同的接口, 可以替换对象.
//[抽象类]被代理对象对应接口 class Graphic { virtual void Draw() = 0; virtual ~Graphic() { } }; //[具体类]被代理对象 class Image :public Graphic { public: void Draw() { cout << "Draw image" << endl; } }; //[具体类]代理 class ImageProxy :public Graphic { private: Image* image; public: void Draw() { if (this->image == null) { this->image = new Image(); } cout << "[Proxy]" << endl; this->image->Draw(); } };

测试

客户端调用对象本身的方法和通过代理对象调用对象的方法的步骤相同, 既代理这个过程对客户端透明, 代理对象可以在方法前后实现一些不为外部所知所累的功能.
Graphic* ip = new ImageProxy(); ip->Draw(); //输出字符串:"[Proxy]Draw image"

适配器模式

关键词: 结构模式, 接口隔离, 组合

场景

将旧的对象放到新的环境中去使用, 但旧的接口与新的环境并不兼容

实现

Adaptee 是旧的对象, Adapter 内部包含了旧的对象, 对其进行了封装, 改装了新的接口.
//[具体类]old对象 class Adaptee { public: void OldOperation() { cout << "old behavior" << endl; } }; //[具体类]适配器(组合实现) class Adapter { private: Adaptee* oldObj; public: Adapter(Adaptee* adaptee) :oldObj(adaptee) { } void NewOperation() { cout << "do sth" << endl; this->oldObj->OldOperation(); cout << "do sth" << endl; } };

测试

客户端可以调用适配器获得新的行为.
适配器模式和代理模式的区别在于: 适配器模式提供新的接口, 代理模式在原有接口内部进行修改, 提供相同的接口.
Adaptee* adaptee = new Adaptee(); Adapter* adapter = new Adapter(adaptee); adapter->NewOperation(); //输出字符串: //"do sth" //"old behavior" //"do sth"

状态模式

关键词: 行为模式, 状态变化

场景

上下文有两个状态 OpenClose , 要求上下文能自由切换状态.

实现

State是状态接口, 具体的状态需要实现这个接口.
Connection是上下文, 其将与状态有关的工作都委派给内部的状态对象.
OpenStateCloseState是具体状态, 状态相互之间可见, 可互相切换.
//[抽象类]状态接口 class State { public: virtual void Open(Connection* con) = 0; virtual void Close(Connection* con) = 0; virtual ~State() { } }; //[具体类]上下文 class Connection { private: State* state; public: //默认状态设为关闭 Connection() { this->state = new CloseState(); } void SetState(State* st) { this->state = st; } void Open() { state->Open(this); } void Close() { state->Close(this); } }; //[具体类]具体状态1 class OpenState :public State { public: void Open(Connection* con) { cout << "connection is already open" << endl; } void Close(Connection* con) { cout << "close the connection" << endl; con->SetState(new CloseState()); } }; //[具体类]具体状态2 class CloseState :public State { public: void Open(Connection* con) { cout << "open the connection" << endl; con->SetState(new OpenState()); } void Close(Connection* con) { cout << "connection is already closed" << endl; } };

测试

初始状态为Close, 调用Open可切换到Open状态, 重复调用会提示已经处于该状态, 同理可调用Close切换到Close状态.
Connection* connection = new Connection(); connection->Open(); //输出字符串:"open the connection" connection->Open(); //输出字符串:"connection is already open" connection->Close(); //输出字符串:"close the connection" connection->Close(); //输出字符串:"connection is already closed"

组合模式

关键词: 结构模式, 数据结构, 组合

场景

将对象组合成树形, 使客户端对对单个对象的调用和对组合对象的调用具有一致性.

实现

Graphic是组件接口, 具体的单个对象和组合对象都需要实现这个统一的接口, 后续客户端将通过这个接口来调用对象.
CircleSquare是具体的单个对象. Image是组合对象, 内部可包含多个对象或组合对象, 形成“树”结构.
//[抽象类]Component接口 class Graphic { public: virtual void Add(Graphic* component) = 0; virtual void Draw() = 0; virtual ~Graphic() { } }; //[具体类]Leaf对象1 class Circle :public Graphic { public: void Draw() { cout << "Draw circle" << endl; } }; //[具体类]Leaf对象2 class Square :public Graphic { public: void Draw() { cout << "Draw square" << endl; } }; //[具体类]Composite对象 class Image :public Graphic { protected: list<Graphic*>graphic; public: void Draw() { cout << "Draw image" << endl; for (auto& g : graphic) { g->Draw(); } } void Add(Graphic* component) { this->graphic.push_back(component); } };

测试

客户端构造对象树, 然后直接将树的根节点当作单个对象调用, 组合模式下对象会自行完成递归调用
Image* image = new Image(); image->Add(new Circle()); image->Add(new Square()); Image* picture = new Image(); picture->Add(image); picture->Add(new Image()); picture->Draw(); //输出字符串: //"Draw image" //"Draw circle" //"Draw square" //"Draw image"

Sources

Ubuntu搭建RSSHub记录[读书笔记]编译器_Go语言底层原理剖析 第一章