装修办法
装修者办法、装修器办法、Wrapper、Decorator
目的
装修办法是一种结构型规划办法, 答应你经过将方针放入包括行为的特别封装方针中来为原方针绑定新的行为。
问题
假定你正在开发一个提供告诉功用的库, 其他程序可运用它向用户发送关于重要事情的告诉。
库的开始版本根据 告诉器
Notifier类, 其中只要很少的几个成员变量, 一个构造函数和一个 send
发送办法。 该办法能够接纳来自客户端的音讯参数, 并将该音讯发送给一系列的邮箱, 邮箱列表则是经过构造函数传递给告诉器的。 作为客户端的第三方程序仅会创立和装备告诉器方针一次, 然后在有重要事情发生时对其进行调用。
程序能够运用告诉器类向预界说的邮箱发送重要事情告诉。
尔后某个时刻, 你会发现库的用户期望运用除邮件告诉之外的功用。 许多用户会期望接纳关于紧急事情的手机短信, 还有些用户期望在微信上接纳音讯, 而公司用户则期望在 QQ 上接纳音讯。
每种告诉类型都将作为告诉器的一个子类得以完成。
这有什么难的呢? 首先扩展 告诉器
类, 然后在新的子类中加入额定的告诉办法。 现在客户端要对所需告诉办法的对应类进行初始化, 然后运用该类发送后续一切的告诉音讯。
但是很快有人会问: “为什么不一起运用多种告诉办法呢? 如果房子着火了, 你大概会想在一切途径中都收到相同的音讯吧。”
你能够尝试创立一个特别子类来将多种告诉办法组合在一起以解决该问题。 但这种办法会使得代码量迅速膨胀, 不仅仅是程序库代码, 客户端代码也会如此。
子类组合数量爆破。
你有必要找到其他办法来规划告诉类的结构, 否则它们的数量会在不经意之间打破吉尼斯纪录。
解决计划
当你需求更改一个方针的行为时, 第一个跳入脑际的主意就是扩展它所属的类。 但是, 你不能忽视承继或许引发的几个严重问题。
- 承继是静态的。 你无法在运行时更改已有方针的行为, 只能运用由不同子类创立的方针来替代当时的整个方针。
- 子类只能有一个父类。 大部分编程语言不答应一个类一起承继多个类的行为。
其中一种办法是用聚合或组合 , 而不是承继。 两者的作业办法几乎如出一辙: 一个方针包括指向另一个方针的引证, 并将部分作业委派给引证方针; 承继中的方针则承继了父类的行为, 它们自己能够完结这些作业。
你能够运用这个新办法来轻松替换各种衔接的 “小帮手” 方针, 然后能在运行时改动容器的行为。 一个方针能够运用多个类的行为, 包括多个指向其他方针的引证, 并将各种作业委派给引证方针。 聚合 (或组合) 组合是许多规划办法背面的关键准则 (包括装修在内)。 记住这一点后, 让咱们持续关于办法的评论。
承继与聚合的比照
封装器是装修办法的别称, 这个称谓明确地表达了该办法的主要思维。 “封装器” 是一个能与其他 “方针” 方针衔接的方针。 封装器包括与方针方针相同的一系列办法, 它会将一切接纳到的请求委派给方针方针。 但是, 封装器能够在将请求委派给方针前后对其进行处理, 所以或许会改动终究结果。
那么什么时候一个简略的封装器能够被称为是实在的装修呢? 正如之前提到的, 封装器完成了与其封装方针相同的接口。 因而从客户端的视点来看, 这些方针是完全一样的。 封装器中的引证成员变量能够是遵循相同接口的任意方针。 这使得你能够将一个方针放入多个封装器中, 并在方针中增加一切这些封装器的组合行为。
比如在音讯告诉示例中, 咱们能够将简略邮件告诉行为放在基类 告诉器
中, 但将一切其他告诉办法放入装修中。
将各种告诉办法放入装修。
客户端代码有必要将根底告诉器放入一系列自己所需的装修中。 因而终究的方针将形成一个栈结构。
程序能够装备由告诉装修构成的杂乱栈。
实践与客户端进行交互的方针将是终究一个进入栈中的装修方针。 由于一切的装修都完成了与告诉基类相同的接口, 客户端的其他代码并不在意自己到底是与 “朴实” 的告诉器方针, 仍是与装修后的告诉器方针进行交互。
咱们能够运用相同办法来完结其他行为 (例如设置音讯格局或许创立接纳人列表)。 只要一切装修都遵循相同的接口, 客户端就能够运用任意自界说的装修来装修方针。
实在国际类比
穿上多件衣服将取得组合性的效果。
穿衣服是运用装修的一个例子。 觉得冷时, 你能够穿一件毛衣。 如果穿毛衣还觉得冷, 你能够再套上一件夹克。 如果遇到下雨, 你还能够再穿一件雨衣。 一切这些衣物都 “扩展” 了你的根本行为, 但它们并不是你的一部分, 如果你不再需求某件衣物, 能够方便地随时脱掉。
装修办法结构
- 部件 (Component) 声明封装器和被封装方针的共用接口。
- 详细部件 (Concrete Component) 类是被封装方针所属的类。 它界说了根底行为, 但装修类能够改动这些行为。
- 根底装修 (Base Decorator) 类拥有一个指向被封装方针的引证成员变量。 该变量的类型应当被声明为通用部件接口, 这样它就能够引证详细的部件和装修。 装修基类会将一切操作委派给被封装的方针。
- 详细装修类 (Concrete Decorators) 界说了可动态增加到部件的额定行为。 详细装修类会重写装修基类的办法, 并在调用父类办法之前或之后进行额定的行为。
- 客户端 (Client) 能够运用多层装修来封装部件, 只要它能运用通用接口与一切方针互动即可。
伪代码
在本例中, 装修办法能够对敏感数据进行紧缩和加密, 然后将数据从运用数据的代码中独立出来。
加密和紧缩装修的示例。
程序运用一对装修来封装数据源方针。 这两个封装器都改动了从磁盘读写数据的办法:
- 当数据即将被写入磁盘前, 装修对数据进行加密和紧缩。 在原始类对改动毫无发觉的情况下, 将加密后的受维护数据写入文件。
- 当数据刚从磁盘读出后, 相同经过装修对数据进行解压和解密。
装修和数据源类完成同一接口, 然后能在客户端代码中相互替换。
装修办法适合应用场景
如果你期望在无需修改代码的情况下即可运用方针, 且期望在运行时为方针新增额定的行为, 能够运用装修办法。
装修能将事务逻辑组织为层次结构, 你可为各层创立一个装修, 在运行时将各种不同逻辑组合成方针。 由于这些方针都遵循通用接口, 客户端代码能以相同的办法运用这些方针。
如果用承继来扩展方针行为的计划难以完成或许底子不可行, 你能够运用该办法。
许多编程语言运用 final
终究关键字来约束对某个类的进一步扩展。 复用终究类已有行为的仅有办法是运用装修办法: 用封装器对其进行封装。
完成办法
- 保证事务逻辑可用一个根本组件及多个额定可选层次表明。
- 找出根本组件和可选层次的通用办法。 创立一个组件接口并在其中声明这些办法。
- 创立一个详细组件类, 并界说其根底行为。
- 创立装修基类, 运用一个成员变量存储指向被封装方针的引证。 该成员变量有必要被声明为组件接口类型, 然后能在运行时衔接详细组件和装修。 装修基类有必要将一切作业委派给被封装的方针。
- 保证一切类完成组件接口。
- 将装修基类扩展为详细装修。 详细装修有必要在调用父类办法 (总是委派给被封装方针) 之前或之后执行自身的行为。
- 客户端代码负责创立装修并将其组合成客户端所需的办法。
装修办法优缺点
-
你无需创立新子类即可扩展方针的行为。
-
你能够在运行时增加或删去方针的功用
-
你能够用多个装修封装方针来组合几种行为。
-
单一责任准则。 你能够将完成了许多不同行为的一个大类拆分为多个较小的类。
-
在封装器栈中删去特定封装器比较困难。
-
完成行为不受装修栈次序影响的装修比较困难。
-
各层的初始化装备代码看上去或许会很糟糕。
代码示例
- Golang 装修办法讲解和代码示例 – 掘金 ()