圣诞大减价开始了!

装饰

也被称为:包装器

意图

装饰是一种结构设计模式,通过将对象放置在包含行为的特殊包装器对象中,可以将新行为附加到对象。

装饰器设计模式

问题

假设您正在开发一个通知库,该库允许其他程序将重要事件通知给用户。

库的初始版本是基于通知人类,该类只有几个字段,一个构造函数和一个发送方法。该方法可以接受来自客户端的消息参数,并将消息发送到通过构造函数传递给通知器的电子邮件列表。作为客户端的第三方应用程序应该创建并配置一次notifier对象,然后在每次重要事件发生时使用它。

在应用Decorator模式之前的库结构

程序可以使用notifier类将重要事件的通知发送到预定义的电子邮件集。

在某种程度上,您意识到库的用户期望的不仅仅是电子邮件通知。他们中的许多人希望收到关于关键问题的短信。其他人希望在Facebook上收到通知,当然,企业用户也希望收到Slack的通知。

实现其他通知类型后的库结构

每种通知类型都实现为通知器的子类。

这能有多难?你延长了通知人类,并将额外的通知方法放入新的子类中。现在客户机应该实例化所需的通知类,并将其用于所有进一步的通知。

但随后有人合理地问您,“为什么不能同时使用几种通知类型呢?如果你的房子着火了,你可能希望通过各种渠道得到通知。”

您试图通过创建特殊的子类来解决这个问题,这些子类将几个通知方法组合在一个类中。然而,很快就会发现这种方法会极大地膨胀代码,不仅是库代码,还有客户端代码。

创建类组合后的库结构

子类的组合爆炸。

您必须找到其他方法来构造通知类,以便它们的数量不会意外地打破一些吉尼斯记录。

解决方案

当您需要更改对象的行为时,首先想到的是扩展类。但是,继承有几个需要注意的严重警告。

  • 继承是静态的。不能在运行时更改现有对象的行为。只能将整个对象替换为从不同子类创建的另一个对象。
  • 子类只能有一个父类。在大多数语言中,继承不允许一个类同时继承多个类的行为。

克服这些警告的方法之一是使用聚合作文而不是继承.这两种选择的工作方式几乎相同:一个对象有一个引用另一个对象并委托它做一些工作,而继承则是对象本身能够从它的超类继承行为来完成这项工作。

使用这种新方法,您可以轻松地将链接的“helper”对象替换为另一个对象,从而在运行时改变容器的行为。对象可以使用各种类的行为,具有对多个对象的引用,并将各种工作委托给它们。聚合/组合是许多设计模式(包括Decorator)背后的关键原则。188bet平台电子游戏关于这一点,让我们回到模式讨论。

继承与聚合

继承与聚合

“Wrapper”是Decorator模式的另一个昵称,它清楚地表达了模式的主要思想。一个包装器对象是否可以与某些对象链接目标对象。包装器包含与目标相同的方法集,并将接收到的所有请求委托给目标。但是,包装器可以通过在将请求传递给目标之前或之后执行某些操作来更改结果。

什么时候简单的包装器成为真正的装饰器?如前所述,包装器实现了与包装对象相同的接口。这就是为什么从客户的角度来看,这些对象是相同的。使包装器的引用字段接受该接口后面的任何对象。这将允许您在多个包装器中覆盖一个对象,将所有包装器的组合行为添加到该对象中。

在我们的通知示例中,让我们将简单的电子邮件通知行为留在base中通知人类,但将所有其他通知方法转换为装饰器。

Decorator模式的解决方案

各种通知方法成为装饰器。

客户端代码需要将一个基本的通知器对象包装成一组与客户端首选项匹配的装饰器。生成的对象将被构造为堆栈。

应用程序可能会配置复杂的通知装饰器堆栈

应用程序可能会配置复杂的通知装饰器堆栈。

堆栈中的最后一个装饰器将是客户端实际使用的对象。因为所有装饰器都实现了与基本通知器相同的接口,所以其余的客户端代码不会关心它是与“纯”通知器对象还是与被装饰的对象一起工作。

我们可以将相同的方法应用于其他行为,如格式化消息或组合收件人列表。客户端可以使用任何自定义装饰器来装饰对象,只要它们遵循与其他装饰器相同的接口。

真实的模拟

Decorator模式的示例

你可以通过穿多件衣服来获得一种综合效果。

穿衣服就是使用装饰师的一个例子。冷的时候,你会把自己裹在毛衣里。如果你穿毛衣还是觉得冷,你可以在外面穿件夹克。如果下雨,你可以穿上雨衣。所有这些衣服都“延伸”了你的基本行为,但不是你的一部分,你可以在不需要的时候轻松脱下任何一件衣服。

结构

Decorator设计模式的结构 Decorator设计模式的结构
  1. 组件声明包装器和被包装对象的公共接口。

  2. 具体的成分是被包装的对象的类。它定义了基本的行为,可以由装饰器更改。

  3. 基地装饰类具有用于引用包装对象的字段。字段的类型应该声明为组件接口,这样它就可以包含具体组件和装饰器。基本装饰器将所有操作委托给包装对象。

  4. 具体的修饰符定义可以动态添加到组件的额外行为。具体装饰器覆盖基本装饰器的方法,并在调用父方法之前或之后执行它们的行为。

  5. 客户端可以在多层装饰器中包装组件,只要它通过组件接口与所有对象一起工作。

伪代码

在本例中,装饰模式允许您独立于实际使用该数据的代码来压缩和加密敏感数据。

结构的Decorator模式示例

加密和压缩装饰器示例。

应用程序用一对装饰器包装数据源对象。这两个包装器都改变了数据写入磁盘和从磁盘读取数据的方式:

  • 就在数据之前写入磁盘, decorator加密并压缩。原始类在不知道更改的情况下将加密和受保护的数据写入文件。

  • 就在数据之后从磁盘读取,它经过相同的decorator,这些decorator解压和解码它。

装饰器和数据源类实现相同的接口,这使得它们在客户端代码中都是可互换的。

//组件接口定义了可以被decorator修改的操作。interface DataSource is method writeData(data) method readData():data //具体组件为//操作提供默认实现。在一个程序中,这些//类可能有几种变体。类FileDataSource实现DataSource是构造函数FileDataSource(文件名){…} method writeData(data) is //将数据写入文件。method readData():data is //从文件中读取数据。//基本装饰器类遵循与//其他组件相同的接口。该类的主要目的是为所有具体装饰器定义包装接口。//包装代码的默认实现可能包括//用于存储包装组件的字段和//初始化它的方法。DataSource构造函数DataSourceDecorator(source: DataSource) is wrap = source //基本装饰器简单地将所有工作委托给//包装组件。 Extra behaviors can be added in // concrete decorators. method writeData(data) is wrappee.writeData(data) // Concrete decorators may call the parent implementation of // the operation instead of calling the wrapped object // directly. This approach simplifies extension of decorator // classes. method readData():data is return wrappee.readData() // Concrete decorators must call methods on the wrapped object, // but may add something of their own to the result. Decorators // can execute the added behavior either before or after the // call to a wrapped object. class EncryptionDecorator extends DataSourceDecorator is method writeData(data) is // 1. Encrypt passed data. // 2. Pass encrypted data to the wrappee's writeData // method. method readData():data is // 1. Get data from the wrappee's readData method. // 2. Try to decrypt it if it's encrypted. // 3. Return the result. // You can wrap objects in several layers of decorators. class CompressionDecorator extends DataSourceDecorator is method writeData(data) is // 1. Compress passed data. // 2. Pass compressed data to the wrappee's writeData // method. method readData():data is // 1. Get data from the wrappee's readData method. // 2. Try to decompress it if it's compressed. // 3. Return the result. // Option 1. A simple example of a decorator assembly. class Application is method dumbUsageExample() is source = new FileDataSource("somefile.dat") source.writeData(salaryRecords) // The target file has been written with plain data. source = new CompressionDecorator(source) source.writeData(salaryRecords) // The target file has been written with compressed // data. source = new EncryptionDecorator(source) // The source variable now contains this: // Encryption > Compression > FileDataSource source.writeData(salaryRecords) // The file has been written with compressed and // encrypted data. // Option 2. Client code that uses an external data source. // SalaryManager objects neither know nor care about data // storage specifics. They work with a pre-configured data // source received from the app configurator. class SalaryManager is field source: DataSource constructor SalaryManager(source: DataSource) { ... } method load() is return source.readData() method save() is source.writeData(salaryRecords) // ...Other useful methods... // The app can assemble different stacks of decorators at // runtime, depending on the configuration or environment. class ApplicationConfigurator is method configurationExample() is source = new FileDataSource("salary.dat") if (enabledEncryption) source = new EncryptionDecorator(source) if (enabledCompression) source = new CompressionDecorator(source) logger = new SalaryManager(source) salary = logger.load() // ...

适用性

当您需要能够在运行时为对象分配额外的行为而不破坏使用这些对象的代码时,请使用Decorator模式。

Decorator允许您将业务逻辑构建为层,为每一层创建一个Decorator,并在运行时使用该逻辑的各种组合组合对象。客户端代码可以以相同的方式处理所有这些对象,因为它们都遵循一个公共接口。

当不方便或不可能使用继承扩展对象的行为时,请使用该模式。

许多编程语言都有最后关键字,可用于防止类的进一步扩展。对于最后一个类,重用现有行为的唯一方法是使用Decorator模式用自己的包装器包装类。

如何实施

  1. 确保您的业务域可以表示为具有多个可选层的主组件。

  2. 找出哪些方法对于主要组件和可选层都是通用的。创建一个组件接口并在那里声明这些方法。

  3. 创建一个具体的组件类,并在其中定义基本行为。

  4. 创建一个基本装饰器类。它应该有一个字段用于存储对包装对象的引用。该字段应声明为组件接口类型,以允许链接到具体组件和装饰器。基本装饰器必须将所有工作委托给包装对象。

  5. 确保所有类都实现了组件接口。

  6. 通过从基本装饰器扩展它们来创建具体的装饰器。具体装饰器必须在调用父方法之前或之后执行其行为(总是委托给包装对象)。

  7. 客户端代码必须负责创建装饰器,并按照客户端需要的方式组合它们。

利与弊

  • 你可以扩展一个对象的行为,而不需要创建一个新的子类。
  • 您可以在运行时从对象中添加或删除职责。
  • 您可以通过将一个对象包装到多个装饰器中来组合几个行为。
  • 单一责任原则.您可以将一个实现了许多可能的行为变体的单一类划分为几个较小的类。
  • 很难从包装器堆栈中删除特定的包装器。
  • 很难实现一个装饰器,使其行为不依赖于装饰器堆栈中的顺序。
  • 层的初始配置代码可能看起来非常难看。

与其他模式的关系

  • 适配器更改现有对象的接口,而装饰在不改变对象接口的情况下增强对象。此外,装饰支持递归组合,当您使用适配器

  • 适配器为被包装的对象提供不同的接口,代理为它提供相同的接口装饰为它提供增强的接口。

  • 责任链而且装饰具有非常相似的类结构。这两种模式都依赖于递归组合来通过一系列对象传递执行。然而,有几个关键的区别。

    天哪处理程序可以彼此独立地执行任意操作。它们还可以在任何时候停止进一步传递请求。另一方面,各种各样修饰符可以扩展对象的行为,同时保持它与基本接口的一致。此外,不允许装饰器破坏请求的流程。

  • 复合而且装饰具有相似的结构图,因为两者都依赖递归组合来组织不限数量的对象。

    一个装饰就像复合但只有一个子组件。还有一个显著的区别:装饰向包装对象添加额外的职责,而复合只是“总结”了其子公司的结果。

    但是,这些模式也可以相互配合:您可以使用装饰对象中特定对象的行为复合树。

  • 大量使用的设计复合而且装饰能经常受益于使用吗原型.应用该模式可以克隆复杂的结构,而不是从头重新构造它们。

  • 装饰允许您更改对象的皮肤,而策略让你改头换面。

  • 装饰而且代理有相似的结构,但意图非常不同。这两种模式都建立在组合原则之上,其中一个对象应该将一些工作委托给另一个对象。区别在于a代理的组合通常单独管理其服务对象的生命周期,而修饰符始终由客户端控制。

代码示例

c#中的装饰器c++中的装饰器Go中的装饰器Java中的装饰器PHP中的装饰器Python中的装饰器Ruby中的装饰器Rust中的装饰师Swift的装饰师TypeScript中的装饰器

Baidu
map