iOS设计模式之模版方法

123 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情

  • 本文主要介绍iOS设计模式之模版方法,之前我们说的模式,都是在拓展对象的行为的同时,对对象进行最少的修改甚至不作修改。而模版方法则是父类调用子类,把一些步骤放到子类中执行

1. 什么是模版方法

举个例子,比如我们炒菜的过程通常有些特定的步骤,比如洗菜,切菜,倒油,炒菜,装盘,上桌。每个步骤,都可以在其范围变化。对于某种特定的食品,烹饪步骤甚至可以进一步一般化。比如你不管做哪种三明治,都有些一般步骤。每种三明治可以在面包,肉和调味上选用有所不同,增加了一些步骤,但是制作中仍然基本上依照一般步骤
标准房屋建造方案中可提供几个扩展点, 允许潜在房屋业主调整成品房屋的部分细节。 每个建造步骤 (例如打地基、 建造框架、 建造墙壁和安装水电管线等) 都能进行微调, 这使得成品房屋会略有不同。
模版方法模式是面向对象软件设计中一种非常简单的设计模式。其基本思想是在抽象类的一个方法中定义“标准”算法。在这个方法中调用的基本操作由子类重载予以实现。这个方法被称为“模版”,因为方法定义的算法缺少一些特有的操作。抽象类和具体子类的关系--抽象类定义模版,子类重载基本操作以提供独特操作供模版方法调用

模版方法模式:定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模版方法使子类可以重定义算法的默写特定步骤而不改变该算法的结构。

2. 什么时候使用模版方法

  • 需要一次性实现算法的不变部分,并将可变的行为留给子类来实现
  • 子类的共同行为应该被提取出来放到公共类中,避免代码重复。现有代码的差别应该被分离为新的操作。然后用一个调用这些新操作的模版方法来替换这些不同的代码
  • 需要控制子类的扩展,可以定义一个在特定点调用“钩子“(hook)操作的模版方法。子类可以通过钩子操作的实现在这些点拓展功能。钩子操作给出了默认行为,子类可对其扩展,默认行为通常什么都不做。子类可以重载这个方法,为模版算法提供附加操作

3. 模版方法的一些注意的点

模版方法和委托(适配器)在框架中比较常见,都是通过算法来制定对象的一些行为,那么它们有什么区别呢?

模版方法委托(适配器)
父类定义一个一般算法,但缺少某些特定/可选的信息或算法,它通过这些缺少的信息或算法得到一个算法“食谱”的作用委托(适配器)与预先定义好的委托接口一起定义一个特定算法
缺少的信息由子类通过继承来提供特定算法由任何对象通过对象组合来提供
  • 保证模版方法正常工作

对于一些必须要实现的方法,可以要求子类必须进行重栽。我么在测试阶段可以通过抛出异常NSException的语句。

  • 向模版方法中增加额外的步骤 我们可以在特定步骤添加方法,而且者额外步骤的默认实现是空操作。必要时子类可以扩展父类中的方法叫做“钩子”,默认的钩子方法什么也不做。

4. 代码展示

import XCTest


/// The Abstract Protocol and its extension defines a template method that
/// contains a skeleton of some algorithm, composed of calls to (usually)
/// abstract primitive operations.
///
/// Concrete subclasses should implement these operations, but leave the
/// template method itself intact.
protocol AbstractProtocol {

    /// The template method defines the skeleton of an algorithm.
    func templateMethod()

    /// These operations already have implementations.
    func baseOperation1()

    func baseOperation2()

    func baseOperation3()

    /// These operations have to be implemented in subclasses.
    func requiredOperations1()
    func requiredOperation2()

    /// These are "hooks." Subclasses may override them, but it's not mandatory
    /// since the hooks already have default (but empty) implementation. Hooks
    /// provide additional extension points in some crucial places of the
    /// algorithm.
    func hook1()
    func hook2()
}

extension AbstractProtocol {

    func templateMethod() {
        baseOperation1()
        requiredOperations1()
        baseOperation2()
        hook1()
        requiredOperation2()
        baseOperation3()
        hook2()
    }

    /// These operations already have implementations.
    func baseOperation1() {
        print("AbstractProtocol says: I am doing the bulk of the work\n")
    }

    func baseOperation2() {
        print("AbstractProtocol says: But I let subclasses override some operations\n")
    }

    func baseOperation3() {
        print("AbstractProtocol says: But I am doing the bulk of the work anyway\n")
    }

    func hook1() {}
    func hook2() {}
}

/// Concrete classes have to implement all abstract operations of the base
/// class. They can also override some operations with a default implementation.
class ConcreteClass1: AbstractProtocol {

    func requiredOperations1() {
        print("ConcreteClass1 says: Implemented Operation1\n")
    }

    func requiredOperation2() {
        print("ConcreteClass1 says: Implemented Operation2\n")
    }

    func hook2() {
        print("ConcreteClass1 says: Overridden Hook2\n")
    }
}

/// Usually, concrete classes override only a fraction of base class'
/// operations.
class ConcreteClass2: AbstractProtocol {

    func requiredOperations1() {
        print("ConcreteClass2 says: Implemented Operation1\n")
    }

    func requiredOperation2() {
        print("ConcreteClass2 says: Implemented Operation2\n")
    }

    func hook1() {
        print("ConcreteClass2 says: Overridden Hook1\n")
    }
}

/// The client code calls the template method to execute the algorithm. Client
/// code does not have to know the concrete class of an object it works with, as
/// long as it works with objects through the interface of their base class.
class Client {
    // ...
    static func clientCode(use object: AbstractProtocol) {
        // ...
        object.templateMethod()
        // ...
    }
    // ...
}


/// Let's see how it all works together.
class TemplateMethodConceptual: XCTestCase {

    func test() {

        print("Same client code can work with different subclasses:\n")
        Client.clientCode(use: ConcreteClass1())

        print("\nSame client code can work with different subclasses:\n")
        Client.clientCode(use: ConcreteClass2())
    }
}

执行结果

Same client code can work with different subclasses:

AbstractProtocol says: I am doing the bulk of the work

ConcreteClass1 says: Implemented Operation1

AbstractProtocol says: But I let subclasses override some operations

ConcreteClass1 says: Implemented Operation2

AbstractProtocol says: But I am doing the bulk of the work anyway

ConcreteClass1 says: Overridden Hook2


Same client code can work with different subclasses:

AbstractProtocol says: I am doing the bulk of the work

ConcreteClass2 says: Implemented Operation1

AbstractProtocol says: But I let subclasses override some operations

ConcreteClass2 says: Overridden Hook1

ConcreteClass2 says: Implemented Operation2

AbstractProtocol says: But I am doing the bulk of the work anyway

5. 总结

模版方法模式是代码复用的一项基本技术。模版方法在框架设计中非常重要,因为他是抽出共同行为放入框架类中的手段。相比于继承,子类重写父类方法,模版方法是父类调用子类方法。