关于设计模式
1. 如何阅读本文
略
2. 面向对象程序设计简介
2.1 面向对象程序设计基础
面向对象程序设计 (Object-Oriented Programming,缩写为 OOP)是一种范式,其基本理念是将 数据块 及 与数据相关的行为 封装成为特殊的、名为对象的 实体,同时对象实体的生成工作则是基于程序员给出的一系列 “蓝图”,这些 “蓝图”就是 类。
2.1.1 对象和类 - 对象是类的实例
- 成员变量 --> 数据 --> 状态
- 方法 --> 行为
2.1.1 层次结构
一些类可能会组织起来形成 类层次结构。
定义的父类被称为超类。继承它的类被称为子类。子类会继承其父类的状态和行为,其中只需定义不同于父类的属性或行为。
这种由各种类组成的金字塔就是 层次结构图。
如果展示类之间的关系比展示其内容更重要的话,那可对 UML 图中的类进行简化。
子类可以对从父类中继承而来的方法的行为进行重写。子类可以完全替换默认行为,也可以仅提供额外内容来对其进行加强。
2.2 面向对象程序设计
有四个要点:
2.2.1 抽象
抽象 是一种反映 真实世界对象 或 现象中特定内容 的模型,它能高精度地反映 所有与特定内容相关的 详细信息,同时忽略其他内容。
开发一款程序时,你的对象只需模拟真实对象的特定属性和行为即可,其他内容可以忽略
上图所示的 飞行模拟器 和 航班预订程序 Airplane 类所包含的信息差别很大
2.2.2 封装
封装是指一个对象对其他对象隐藏其部分状态和行为,而仅向程序其他部分暴露有限的接口的能力。
比如要启动一辆汽车,只暴露启动开关的 接口
, 而隐藏 打开引擎盖手动接线、转动曲轴和气缸并启动发动机的动力循环等细节。
封装某个内容意味着使用关键字 private 私有来对其进行修饰,这样仅有其所在类中的方法才能访问这些内容。还有一种限制程度较小的关键字 protected 保护 ,其所修饰的对象仅允许父?类访问其类中的成员。
接口机制 (通常使用 interface或 protocol关键字来声明)允许你定义对象之间的交互协议。
2.2.3 继承
继承是指在根据已有类创建新类的能力。
2.2.4 多态
多态 是指程序能够 检测 对象所属的实际类,并在当前上下文不知道其真实类型的情况下调用其实现的能力。
bag = [new Cat(), new Dog()];
foreach (Animal a : bag)
a.makeSound()
// 喵喵!
// 汪汪!
2.3 对象之间的关系
- 继承: 类 A 继承类 B 的接口和实现,但是可以对其进行扩展。对象 A 可被视为对象 B。类 A 依赖于类 B。
- 实现: 类 A 定义的方法由接口 B 声明。对象 A 可被视为对象 B。类 A 依赖于类 B。
- 依赖: **对类 B 进行修改会影响到类 A。**修改一个类可能造成另一个类的变化就存在依赖关系
- 关联: **对象 A 知道对象 B。类 A 依赖于类 B。**一个对象使用另一对象或与另一对象进行交互的关系。
class Professor is
field Student student // 2. 学生类是教授类的依赖
// 3. 教授能访问所有学生的成员,致依赖加强为关联
// ……
method teach(Course c) is // 1. 修改 Course(getKnowledge的函数名或添加参数) 代码会崩溃, 这就是依赖关系
// ……
this.student.remember(c.getKnowledge())
- 聚合:**对象 A 知道对象 B 且由 B 构成。类 A 依赖于类 B。**用于表示多个对象之间的 “一对多”、 “多对多”或 “整体对部分”的关系。通常在聚合关系中,一个对象 “拥有”一组其他对象,并扮演着容器或集合的角色。组件可以独立于容器存在,也可以同时连接多个容器。在 UML 图中,聚合关系使用一端是空心菱形,另一端指向组件的箭头来表示。
- 组合:**对象 A 知道对象 B、由 B 构成而且管理着 B 的生命周期。类 A 依赖于类 B。**组合是一种特殊类型的聚合,其中一个对象由一个或多个其他对象实例构成。组合与其他关系的区别在于组件仅能作为容器的一部分存在。在 UML 图中,组合与聚合关系的符号相同,但箭头起始处的菱形是实心的。
关系总结
3. 设计模式简介
3.1 什么是设计模式?
设计模式是软件设计中常见问题的典型解决方案。它们就像能根据需求进行调整的预制蓝图,可用于解决代码中反复出现的设计问题。
设计模式与方法或库的使用方式不同,你很难直接在自己的程序中套用某个设计模式。模式并不是一段特定的代码,而是解决特定问题的一般性概念。你可以根据模式来实现符合自己程序实际所需的解决方案。
人们常常会混淆模式和算法,因为两者在概念上都是已知特定问题的典型解决方案。但算法总是明确定义达成特定目标所需的一系列步骤,而模式则是对解决方案的更高层次描述。同一模式在两个不同程序中的实现代码可能会不一样。
3.1.1 模式包含哪些内容?
大部分模式的描述都会遵循特定的形式,以便在不同情况下使用。模式的描述通常会包括以下部分:
- 意图部分简要地描述问题和解决方案。
- 动机部分进一步解释问题并说明模式会如何提供解决方案。
- 结构部分展示模式的各个部分和它们之间的关系。
- 在不同语言中的实现提供流行编程语言的代码,让读者更好地理解模式背后的思想。
3.1.2 模式的分类
最基础的、底层的模式通常被称为惯用技巧。只能在一种编程语言中使用。
最通用的、高层的模式是架构模式。可以在任何编程语言中使用这类模式。
所有模式可以根据其意图或目的来分类。本书覆盖了三种主要的模式类别:
- 创建型模式提供创建对象的机制,增加已有代码的灵活性和可复用性。
- 结构型模式介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效。
- 行为模式负责对象间的高效沟通和职责委派。
3.2 为什么以及如何学习设计模式?
- 设计模式是针对软件设计中常见问题的工具箱,其中的工具就是各种经过实践验证的解决方案。即使你从未遇到过这些问题,了解模式仍然非常有用,因为它能指导你如何使用面向对象的设计原则来解决各种问题。
- 设计模式定义了一种让你和团队成员能够更高效沟通的通用语言。你只需说 “哦,这里用单例就可以了”,所有人都会理解这条建议背后的想法。只要知晓模式及其名称,你就无需解释什么是单例。
4. 软件设计原则
4.1 优秀设计的特征
在开始学习实际的模式前,让我们来看看软件架构的设计过程,了解一下需要达成目标与需要尽量避免的陷阱。
4.1.1 代码复用
代码复用是减少开发成本时最常用的方式之一。其意图非常明显:与其反复从头开发,不如在新对象中重用已有代码。
这个想法表面看起来很棒,但实际上要让已有代码在全新的上下文中工作,通常还是需要付出额外努力的。组件间紧密的耦合、对具体类而非接口的依赖和硬编码的行为都会降低代码的灵活性,使得复用这些代码变得更加困难。
使用设计模式是增加软件组件灵活性并使其易于复用的方式之一。但是有时,这也会让组件变得更加复杂。
4.1.2 扩展性
变化是程序员生命中唯一不变的事情。在设计程序架构时,所有有经验的开发者会尽量选择支持未来任何可能变更的方式。
4.2 设计原则
什么是优秀的软件设计?如何对其进行评估?你需要遵循哪些实践方式才能实现这样的方式?如何让你的架构灵活、稳定且易于理解?
有几个通用的软件设计原则可能会对解决这些问题有所帮助。本书中列出的绝大部分设计模式都是基于这些原则的。
4.2.1 封装变化的内容
找到程序中的变化内容并将其与不变的内容区分开。该原则的主要目的是将变更造成的影响最小化。
4.2.2 方法层面的封装
以计算税金为例。
修改前:
修改后:
4.2.3 面向接口进行开发,而不是面向实现
面向接口进行开发,而不是面向实现;依赖于抽象类型,而不是具体类。
如果无需修改已有代码就能轻松对类进行扩展,那就可以说这样的设计是灵活的。
可用另外一种更灵活的方式来设置对象之间的合作关系。
- 确定一个对象对另一对象的确切需求:它需执行哪些方法?
- 在一个新的接口或抽象类中描述这些方法。
- 让被依赖的类实现该接口。
- 现在让有需求的类依赖于这个接口,而不依赖于具体的类。你仍可与原始类中的对象进行互动,但现在其连接将会灵活得多。
以猫吃不同食物为例:
以公司-雇员为例:
修改前:公司Company类与具体雇员类紧密耦合。
修改后:归纳出几个与工作相关的方法,并且将其抽取为所有雇员的通用接口。此后,我们可在 公司类内应用多态机制,通过 雇员Employee接口来处理各类雇员对象。
修改后:声明一个抽象方法来获取雇员。每个具体公司都将以不同方式实现该方法,从而创建自己所需的雇员。修改后的 公司类将独立于各种雇员类。现在你可以对该类进行扩展,并在复用部分公司基类的情况下引入新的公司和雇员类型。对公司基类进行扩展时无需修改任何依赖于基类的已有代码。
4.3 组合优于继承
继承这件事通常只有在程序中已包含大量类,且修改任何东西都非常困难时才会引起关注。下面就是此类问题的清单。
- 子类不能减少超类的接口。你必须实现父类中所有的抽象方法,即使它们没什么用。
- 在重写方法时,你需要确保新行为与其基类中的版本兼容。这一点很重要,因为子类的所有对象都可能被传递给以超类对象为参数的任何代码,相信你不会希望这些代码崩溃的。
- 继承打破了超类的封装,因为子类拥有访问父类内部详细内容的权限。此外还可能会有相反的情况出现,那就是程序员为了进一步扩展的方便而让超类知晓子类的内部详细内容。
- 子类与超类紧密耦合。超类中的任何修改都可能会破坏子类的功能。
- 通过继承复用代码可能导致平行继承体系的产生。继承通常仅发生在一个维度中。只要出现了两个以上的维度,你就必须创建数量巨大的类组合,从而使类层次结构膨胀到不可思议的程度。
组合是代替继承的一种方法。继承代表类之间的 **“是”**关系 (汽车是交通工具),而组合则代表 **“有”**关系 (汽车有一个引擎)。
必须一提的是,这个原则也能应用于聚合 (一种更松弛的组合变体,一个对象可引用另一个对象,但并不管理其生命周期)。例如:一辆汽车上有司机,但是司机也可能会使用另一辆汽车,或者选择步行而不使用汽车。
示例:假如你需要为汽车制造商创建一个目录程序。该公司同时生产 汽车Car和 卡车Truck ,车辆可能是 电动车Electric或 汽油车Combustion ;所有车型都配备了 手动控制manual control或 自动驾驶Autopilot功能。
继承:
组合:
5. SOLID 原则
这五条原则是在罗伯特·马丁的著作 《敏捷软件开发:原则、模式与实践 36》中首次提出的。
SOLID 是让软件设计更易于理解、更加灵活和更易于维护的五个原则的简称。
与生活中所有事情一样,盲目遵守这些原则可能会弊大于利。在程序架构中应用这些原则可能会使其变得过于复杂。我对于是否真的有能够同时应用所有这五条原则的成功软件产品表示怀疑。有原则是件好事,但是也要时刻从实用的角度来考量,不要把这里的每句话当作放之四海皆准的教条。
5.1 S: 单一职责原则(Single Responsibility)
Single Responsibility Principle: 修改一个类的原因只能有一个。
尽量让每个类只负责软件中的一个功能,并将该功能完全封装 (你也可称之为隐藏)在该类中。
这条原则的主要目的是减少复杂度。你不需要费尽心机地去构思如何仅用 200 行代码来实现复杂设计,实际上完全可以使用十几个清晰的方法。
当程序规模不断扩大、变更不断增加后,真实问题才会逐渐显现出来。到了某个时候,类会变得过于庞大,以至于你无法记住其细节。查找代码将变得非常缓慢,你必须浏览整个类,甚至整个程序才能找到需要的东西。程序中实体的数量会让你的大脑堆栈过载,你会感觉自己对代码失去了控制。
还有一点:如果类负责的东西太多,那么当其中任何一件事发生改变时,你都必须对类进行修改。而在进行修改时,你就有可能改动类中自己并不希望改动的部分。
如果你开始感觉在同时关注程序特定方面的内容时有些困难的话,请回忆单一职责原则并考虑现在是否应将某些类分割为几个部分。
5.2 O: 开闭原则(Open/Closed)
Open/Closed Principle: 对扩展开放;对修改封闭
本原则的主要理念是在实现新功能时能保持已有代码不变。
通过关键字 final 可以限制一个类不再 “开放”(不能再被继承),如果某个类已做好了充分的准备并可供其他类使用的话(即其接口已明确定义且以后不会修改),那么该类就是封闭 (你可以称之为 完整 )的.
这条原则并不能应用于所有对类进行的修改中。如果你发现类中存在缺陷,直接对其进行修复即可,不要为它创建子类。子类不应该对其父类的问题负责。
5.3 L: 里氏替换原则(Liskov Substitution)
当你扩展一个类时,要能在不修改客户端代码的情况下将子类的对象作为父类对象进行传递(原理需要父类型参数的地方传子类参数也能用)。
这意味着子类必须保持与父类行为的兼容。在重写一个方法时,你要对基类行为进行扩展,而不是将其完全替换。
替换原则是用于预测子类是否与代码兼容,以及是否能与其超类对象协作的一组检查。这一概念在开发程序库和框架时非常重要,因为其中的类将会在他人的代码中使用——你是无法直接访问和修改这些代码的。
与有着多种解释方式的其他设计模式不同,替代原则包含一组对子类 (特别是其方法)的形式要求。让我们来仔细看看这些要求。
超类的不变量必须保留
- 子类方法的参数类型必须与其超类的参数类型相匹配或更加抽象
- 子类方法的返回值类型必须与超类方法的返回值类型或是其子类别相匹配(跟参数类型的要求相反)
- 子类中的方法不应抛出基础方法预期之外的异常类型
- 异常类型必须与基础方法能抛出的异常或是其子类别相 匹配 。这条规则源于一个事实:客户端代码的
<font size="-1" face="Menlo" color="inherit">try-catch</font>
代码块针对的是基础方法可能抛出的异常类型。因此,预期之外的异常可能会穿透客户端的防御代码,从而使整个应用崩溃。
- 异常类型必须与基础方法能抛出的异常或是其子类别相 匹配 。这条规则源于一个事实:客户端代码的
- 子类不应该加强其前置条件
- 子类不能削弱其后置条件
- 超类的不变量必须保留
- 子类不能修改超类中私有成员变量的值
5.4 I: 接口隔离原则(Interface Segregation)
客户端不应被强迫依赖于其不使用的方法。尽量缩小接口的范围,使得客户端的类不必实现其不需要的行为。
根据接口隔离原则,你必须将 “臃肿”的方法拆分为多个颗粒度更小的具体方法。**客户端必须仅实现其实际需要的方法。**否则,对于 “臃肿”接口的修改可能会导致程序出错,即使客户端根本没有使用修改后的方法。
继承只允许类拥有一个超类,但是它并不限制类可同时实现的接口的数量。因此,你不需要将大量无关的类塞进单个接口。你可将其拆分为更精细的接口,如有需要可在单个类中实现所有接口,某些类也可只实现其中的一个接口。
5.5 D: 依赖倒置原则(Dependency Inversion)
高层次的类不应该依赖于低层次的类。两者都应该依赖于抽象接口。抽象接口不应依赖于具体实现。具体实现应该依赖于抽象接口。
通常在设计软件时,你可以辨别出不同层次的类
- 低层次类:实现基础操作 (例如磁盘操作、传输网络数据和连接数据库等)
- 高层次类:包含复杂业务逻辑以指导低层次类执行特定操作
有时人们会先设计低层次的类,然后才会开发高层次的类。当你在新系统上开发原型产品时,这种情况很常见。由于低层次的东西还没有实现或不确定,你甚至无法确定高层次类能实现哪些功能。如果采用这种方式,业务逻辑类可能会更依赖于低层原语类。
依赖倒置原则建议改变这种依赖方式。
- 作为初学者,你最好使用业务术语来对高层次类依赖的低层次操作接口进行描述。例如,业务逻辑应该调用名为 openReport(file)的方法,而不是 openFile(x) 、 readBytes(n)和 closeFile(x)等一系列方法。这些接口被视为是高层次的
- 现在你可基于这些接口创建高层次类,而不是基于低层次的具体类。这要比原始的依赖关系灵活很多。
- 一旦低层次的类实现了这些接口,它们将依赖于业务逻辑层,从而倒置了原始的依赖关系
具体设计模式
1. 创建型模式
创建型模式提供了创建对象的机制,能够提升已有代码的灵活性和可复用性。
1.1 工厂方法/Factory Method
亦称:虚拟构造函数、Virtual Constructor、Factory Method
工厂方法 是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
看一个C++的例子:
它的结构总结起来就是下面的图:
1.2 抽象工厂/Abstract Factory
抽象工厂是一种创建型设计模式, 它能创建一系列相关的对象,而无需指定其具体类
看一个C++的例子:
它的结构总结起来就是下面的图:
1.3 生成器/Builder
亦称:建造者模式、Builder
生成器是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相同的创建 代码生成不同类型和形式的对象。
生成器模式建议将对象构造代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象中。该模式会将对象构造过程划分为一组步骤,比如 buildWalls 创建墙壁和 buildDoor 创建房门创建房门等。每次创建对象时,你都需要通过生成器对象执行一系列步骤。重点在于你无需调用所有步骤,而只需调用创建特定对象配置所需的那些步骤即可。
看一个C++的例子:
它的结构总结起来就是下面的图:
1.4 原型/Prototype
亦称:克隆、Clone、Prototype
原型是一种创建型设计模式,使你能够 复制已有对象,而又无需使代码依赖它们所属的类。
看一个C++的例子:
它的结构总结起来就是下面的图:
1.5 单例/Singleton
亦称:单件模式、Singleton
单例是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有,防止其他对象使用单例类的 new 运算符。
- 新建一个静态构建方法作为构造函数。该函数会“偷偷”调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象
它的结构总结起来就是下面的图:
2. 结构型模式
2.1 适配器/Adapter
亦称:封装器模式、Wrapper、Adapter
适配器是一种结构型设计模式,它能使接口不兼容的对象能够相互合作
适配器模式通过封装对象将复杂的转换过程隐藏于幕后。被封装的对象甚至察觉不到适配器的存在。例如,你可以使用一个将所有数据转换为英制单位 (如英尺和英里)的适配器封装运行于米和千米单位制中的对象。
适配器不仅可以转换不同格式的数据,其还有助于采用不同接口的对象之间的合作。它的运作方式如下:
- 适配器实现与其中一个现有对象兼容的接口。
- 现有对象可以使用该接口安全地调用适配器方法。
- 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象
它的结构总结起来就是下面的图:
2.2 桥接/Bridge
亦称:Bridge
桥接是一种结构型设计模式,可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构,从而能在开发时分别使用
它的结构总结起来就是下面的图:
2.3 组合/Composite
亦称:对象树、Object Tree、Composite
组合是一种结构型设计模式,你可以使用它将对象组合成树状结构,并且能像使用独立对象一样使用它们。
2.4 装饰/Decorator
亦称:装饰者模式、装饰器模式、Wrapper、Decorator
装饰是一种结构型设计模式,允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
2.5 外观/Facade
亦称:Facade
外观是一种结构型设计模式,能为程序库、框架或其他复杂类提供一个简单的接口
2.6 享元/Flyweight
亦称:缓存、Cache、Flyweight
享元是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能在有限的内存容量中载入更多对象。
<img src="./picture/08.Flyweights.png" width="360px" alt="xx" />
2.7 代理/Proxy
亦称:Proxy
代理是一种结构型设计模式,让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。
代理模式建议新建一个与原服务对象接口相同的代理类,然后更新应用以将代理对象传递给所有原始对象客户端。代理类接收到客户端请求后会创建实际的服务对象,并将所有工作委派给它.
代理与装饰的代码很像,那它们的区别在哪?
《java与模式》阎宏编著:
装饰器模式应当为所装饰的对象提供增强功能,而代理模式对所代理对象的使用施加控制,并不提供对象本身的增强功能
chatGPT:
- 目的不同 :装饰模式的主要目的是增强对象的功能,而代理模式的主要目的是控制对对象的访问。
- 结构不同 :装饰模式通常涉及多个装饰者类的组合,而代理模式通常涉及一个代理类和一个具体主题类。
- 实现方式 :装饰模式通过组合来动态添加功能,代理模式通过引用来控制访问。
- 使用场景不同 :装饰模式适用于需要动态添加功能的情况,而代理模式适用于需要控制对对象的访问的情况
3. 行为模式
3.1 责任链 / Chain of Responsibility
亦称:职责链模式、命令链、CoR、Chain of Command、Chain of Responsibility
责任链是一种行为设计模式,允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者
C++举例(核心代码):
3.2 命令 / Command
亦称:动作、事务、Action、Transaction、Command
命令是一种行为设计模式,它可将请求转换为一个包含与请求相关的所有信息的独立对象。该转换让你能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作
优秀的软件设计通常会将关注点进行分离,而这往往会导致软件的分层。最常见的例子:一层负责用户图像界面;另一层负责业务逻辑。GUI 层负责在屏幕上渲染美观的图形,捕获所有输入并显示用户和程序工作的结果。当需要完成一些重要内容时 (比如计算月球轨道或撰写年度报告),GUI 层则会将工作委派给业务逻辑底层.
3.3 迭代器 / Iterator
亦称:Iterator
迭代器是一种行为设计模式,让你能在不暴露集合底层表现形式(列表、栈和树等)的情况下遍历集合中所有的元素.
集合是编程中最常使用的数据类型之一。尽管如此,集合只是一组对象的容器而已。
3.4 中介者 / Mediator
亦称:调解人、控制器、Intermediary、Controller、Mediator
中介者是一种行为设计模式,能让你减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作.
中介者模式建议你停止组件之间的直接交流并使其相互独立。这些组件必须调用特殊的中介者对象,通过中介者对象重定向调用行为,以间接的方式进行合作。最终,组件仅依赖于一个中介者类,无需与多个其他组件相耦合。
C++举例(核心代码):
3.5 备忘录 / Memento
亦称:快照、Snapshot、Memento
备忘录是一种行为设计模式,允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态
C++举例(核心代码):
3.6 观察者 / Observer
亦称:事件订阅者、监听者、Event-Subscriber、Listener、Observer
观察者是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个“观察”该对象的其他对象
C++举例(核心代码):
3.7 状态 / State
亦称:State
状态是一种行为设计模式,让你能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样
状态模式与有限状态机的概念紧密相关. 其主要思想是程序在任意时刻仅可处于几种有限的状态中。在任何一个特定状态中,程序的行为都不相同,且可瞬间从一个状态切换到另一个状态。不过,根据当前状态,程序可能会切换到另外一种状态,也可能会保持当前状态不变。这些数量有限且预先定义的状态切换规则被称为转移。
状态机通常由众多条件运算符 ( if或 switch )实现,可根据对象的当前状态选择相应的行为。 “状态”通常只是对象中的一组成员变量值。即使你之前从未听说过有限状态机,你也很可能已经实现过状态模式。下面的代码应该能帮助你回忆起来。
C++举例(核心代码):
3.8 策略 / Strategy
亦称:Strategy
策略是一种行为设计模式,它能让你定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。
C++举例(核心代码) – 与策略模式很像(UML图相同), 都将一些工作委托给其他对象:
3.9 模板方法 / Template Method
亦称:Template Method
模板方法是一种行为设计模式,它在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。
C++举例(核心代码):
3.10 访问者 / Visitor
亦称:Visitor
访问者是一种行为设计模式,它能将算法与其所作用的对象隔离开来。
访问者模式建议将新行为放入一个名为访问者的独立类中,而不是试图将其整合到已有类中。现在,需要执行操作的原始对象将作为参数被传递给访问者中的方法,让方法能访问对象所包含的一切必要数据。
C++举例(核心代码):
结语
世界上还有许多其他的模式。希望本文提到的22中典型模式和能作为读者获取程序设计超能力的起点