Swift解决方案:装饰器模式

Swift Solutions是涵盖设计模式的一系列文章。 在每篇文章中,我们讨论该模式是什么,何时应用以及如何以Swifty方式实现它。

在Swift解决方案的此迭代中,我们将在Swift中实现Decorator Pattern。 该模式提供了一种向应用程序添加功能的有用方法,并且是每个开发人员都可以在其套件中使用的出色工具。

我们将在方法上做到周密,以免材料发粘。 我们将在Swift中定义,说明和编码模式。 最终,您将了解该模式是什么,何时应用以及如何编码。 让我们直接进入定义!

限定

装饰器模式可动态修改核心对象的行为,而无需更改其现有的类定义。

装饰器模式有两个主要组成部分:一个将修改其行为的核心对象,以及一个在核心对象中带来更改的行为的装饰器对象。

这两个组件可以共同实现两个主要目标:

  1. 行为修改
  2. …动态实现

行为修改

该模式的目的是在不修改其现有类实现的情况下更改核心对象的工作方式。 这是通过用装饰器包装核心对象来实现的,装饰器可以增强核心对象的默认行为。 装饰器从字面上完全“装饰”(修改)了它们包装的对象。

装饰器增强了核心对象中已经存在的行为。 它们与核心对象共享相同的接口,但提供不同的实现。 结果,客户端无法区分是使用装饰器还是使用核心对象:它们在两者上调用完全相同的函数/属性。 此共享接口允许装饰器充当中间人,拦截对核心对象的调用并为客户端提供自定义行为。

动态修改

装饰器不只是简单地修改行为,而是动态地修改行为。 换句话说,它们更改了对象在运行时(而不是编译时)的工作方式。

编译时行为修改很简单:只需要​​直接更改对象的类定义或子类并覆盖其默认实现即可。 但是,由于以下原因,可能不希望使用这些选项:

  1. 可以将核心对象类声明为final ,这可以防止子类化。
  2. 该类可以在第三方框架中定义,对它的任何更改都将在下一次框架更新时丢失。
  3. 可能存在许多所需的修改,并且可以将几种行为链接在一起以产生复杂的组合。 为每种可能的组合创建一个子类将导致类爆炸。
  4. 对现有类的更改可能会导致意外的副作用,从功能中断到崩溃。 当继承脆弱且未完全理解的旧版代码库时,尤其如此。

所有这些缺点使动态方法成为有吸引力的替代方法。 我们没有将具有不同行为的核心对象的多个变体硬编码,而是将每个行为分成了自己的专用包装器类。

然后,通常在用户输入期间,将这些专用包装器在运行时应用于核心对象。 用户选择他们的自定义项,然后我们应用包装器以产生所需的结果。 这是一种灵活的方法,因为行为的组合是通过根据需要增量应用包装器来实现的。

说明

我们将使用赛车游戏作为插图。 在此游戏中,玩家在每次比赛前选择车辆。 选择后,他们可以自定义车辆的轮胎,变速箱,发动机等。 这些自定义通过更改其统计信息来影响车辆的行驶方式。 在我们的示例中,统计信息将是速度和牵引力。

玩家可以选择更换零件以更好地适应他们的喜好。 例如,玩家可以选择将默认轮胎更改为越野轮胎,这些轮胎的抓地力更好,但速度却有所降低。

实行

让我们使用赛车插图来实现该模式。

 protocol Transporting { 
func getSpeed() -> Double
func getTraction() -> Double
}

我们创建了一个Transporting协议,该协议要求符合条件的车辆对象才能为其提供速度和牵引力统计信息。

 // Core Component 
final class RaceCar: Transporting {
private let speed = 10.0
private let traction = 10.0

func getSpeed() -> Double {
return speed
}

func getTraction() -> Double {
return traction
}
}

在这里,我们创建一个核心对象: RaceCar类。 您可以想象一场赛车游戏会配备几种不同类型的汽车,但是出于本示例的考虑,我们将坚持使用其中一种。

赛车内置了默认的速度和牵引力统计信息。这些属性标记为私有,因此我们的应用无法直接查看或修改这些统计信息。 相反,赛车符合Transporting ,它允许我们的应用使用getSpeed()getTraction()检索车辆统计信息。

使用赛车类

 let raceCar = RaceCar() 
let defaultSpeed = raceCar.getSpeed()
let defaultTraction = raceCar.getTraction()

虽然汽车可以按原样使用,但我们希望为用户提供自定义驾驶体验的功能。 这将在不更改现有类定义或不依赖子类的情况下实现(毕竟, RaceCar被标记为final!)。 这是用装饰工来调整我们的车辆的绝佳机会!

添加装饰器

 // Abstract Decorator 
class TireDecorator: Transporting {
// 1
private let transportable: Transporting

init(transportable: Transporting) {
self.transportable = transportable
}

// 2
func getSpeed() -> Double {
return transportable.getSpeed()
}

func getTraction() -> Double {
return transportable.getTraction()
}
}

以上实现了以下目的:

  1. 创建对包装对象的私有引用,该引用在初始化时设置。 所有装饰器都拥有对其装饰对象的引用。
  2. 实现Transporting协议方法。 由于这是一个抽象装饰器,因此我们仅默认使用包装对象的实现返回值。

让我们看看实际修饰行为的具体装饰器是什么样的:

 class OffRoadTireDecorator: Transporting { 
private let transportable: Transporting

init(transportable: Transporting) {
self.transportable = transportable
}

func getSpeed() -> Double {
return transportable.getSpeed() - 3.0
}

func getTraction() -> Double {
return transportable.getTraction() + 3.0
}
}

OffRoadTireDecorator是一种包装材料,可将越野轮胎添加到车辆中,从而导致统计数据发生变化。 它分两步完成:

  1. 它通过调用其包装对象的getSpeed()getTraction()方法来计算基本牵引力和速度。
  2. 检索默认统计信息后,将对其进行相应的扩充,并将新统计信息返回给调用方。

这两个步骤就是大多数装饰器的工作方式。 他们依赖装饰对象的实现,并以某种方式在此之上构建。 在这种情况下,将检索默认统计信息,然后扣除速度并增加牵引力。

使用装饰器

 ''// Create Race Car 
let defaultRaceCar = RaceCar()
defaultRaceCar.getSpeed() // 10
defaultRaceCar.getTraction() // 10

// Modify Race Car
let offRoadRaceCar = OffRoadTireDecorator(transportable: defaultRaceCar)
offRoadRaceCar.getSpeed() // 7
offRoadRaceCar.getTraction() // 13

如预期的那样,将包装器应用于defaultRaceCar ,其速度下降到7,而牵引力增加到预期的13。

装饰装潢

装饰器的强大功能是它们还可以包装其他装饰器:

 class ChainedTireDecorator: Transporting { 
private let transportable: Transporting

init(transportable: Transporting) {
self.transportable = transportable
}

func getSpeed() -> Double {
return transportable.getSpeed() - 1.0
}

func getTraction() -> Double {
return transportable.getTraction() * 1.1
}
}

在这里,我们创建第二个装饰器。 它的工作是在轮胎打滑的环境中为轮胎添加链条。 这以速度为代价将牵引力提高了10%。

我们将继续进行升级,以升级越野赛车,使其轮胎上也装有链条。

装饰一个装饰器

 // Double Decorators 
let chainedTiresRaceCar = ChainedTireDecorator(transportable: offRoadRaceCar)
chainedTiresRaceCar.getSpeed() // 6
chainedTiresRaceCar.getTraction() // 14.3

由于链条轮胎将牵引力提高了一个百分点,因此包裹物体的顺序有所不同。 如果我们将链条轮胎添加到默认赛车中,则牵引力只会增加1点(从10点增加到11点)。 但是,在我们当前的实现中,链条轮胎将牵引力从13增加到14.3。 就像数学方程式一样,我们应用运算的顺序也会影响结果。

应用

在总结之前,下面是装饰器模式何时特别有用的简要列表:

  • 核心对象已(或将被弃用),需要包装器以提供新功能并将其替换到系统中
  • 核心对象不能或不应直接修改
  • 避免修改易于破坏的遗留代码(或第三方框架)
  • 避免修改标记为final的类,因为可能有根本原因不应将其子类化
  • 防止类爆炸

在寻求模式时,一定要知道有一个明显的缺点:它使调试变得非常困难。 这是由于它对对象应用更改的动态方式-使我们不得不依赖运行时调试工具,而不是方便的编译器时间错误检查。

结论

让我们最后简要总结一下我们从装饰器模式中学到的知识:

  • 提供通过包装程序更改对象行为的动态方式。
  • 装饰器与核心对象共享一个基本类型,从而使它们在我们的系统中无法区分(并且可以互换)。 这允许使用而不破坏客户端代码。
  • 可以应用多个装饰器来创建复杂的行为链。
  • 装饰器的应用顺序可能会更改最终输出。

这是我们已经介绍过的更复杂的模式之一,因此,如果您最终做到了,那就对不起! 感谢您在整个过程中进行标记,我期待着一起介绍我们的下一个模式!

最初于 2017 年9月2日 发布在 swiftcraft.io 上。