# iOS开发Swift实战之模板模式

语言：zh-CN
原文：[iOS开发Swift实战之模板模式](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao51>)
中文：[中文 HTML](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao51>)
English: [English HTML](<https://allluckly.cn/en/journal/2016-06-29-swift-in-practice-the-template-pattern/>)
发布于: 2016-06-29T00:00:00+08:00
内容更新于: 2016-06-29T00:00:00+08:00
署名：Sindri

以商品数据和计算逻辑为例，分析代码对元组结构的依赖，再用类型与协议组织可复用的处理方式，讨论如何通过抽象减少参数形式和业务方法之间的耦合。

原文署名Sindri，Bison 编辑收录。

[原文所列来源](<http://www.jianshu.com/p/76d38ceb1ad3>)

**阅读前** 这是 2015–2017 年间的技术记录，本次整理未重新运行验证示例。涉及的 API、依赖和工具版本请结合发表日期阅读。

![1](<https://allluckly.cn/images/blog/tuogao/tougao51/1.png>)

> 设计模式（Design pattern）是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。GoF提出了23种设计模式，本系列将使用Swift语言来实现这些设计模式

编辑：[Bison](<https://allluckly.cn/>)  
 投稿：[Sindri的小巢](<http://www.jianshu.com/p/76d38ceb1ad3>)

## 概述

模板设计模式是最基础的设计模式之一，在网上被称作`模板方法模式`，但实际运用时这种设计模式却不仅仅局限于方法。因此笔者对于模板设计模式的理解定义如下：

> 【模板设计模式】将常见的方法以及对象成员进行封装，创建了一个实现一组抽象方法以及抽象对象成员作为对象实现使用的模板

## 坏味道的代码

在Swift中有个有趣的数据结构——`元组Tuples`，现在有这么一段代码。其中传入的每一个元祖表示某个商品的数据类型：

```
func calculateTax(product: (String, Double, Int)) -> Double {
    return product.1 * 0.17
}

func calculateStockValue(products: [(String, Double, Int)]) -> Double {
    return products.reduce(0.0){
        $0 + $1.1 * Double($1.2)
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    let products = [
        ("Pizazz", 99.8, 10),
        ("Hamburger", 25.6, 8),
        ("Beef", 19.9, 30) ]

    print("The tax of \(products[0].1) is \(calculateTax(product[0]))")
    print("The stock value of all products is \(calculateStockValue(products))")
} 上面的方法通过传入一个或者多个元组用来计算增值税（17%）以及商品存货总额。毫无疑问，对于稍有经验的开发者来说，都能说出这段代码有太大的`坏味道`了：两个方法过度的依赖于传入的元组结构，这意味着几乎无法复用这些方法
```

![1](<https://allluckly.cn/images/blog/tuogao/tougao51/2.jpg>)

## 模板设计模式

在开发语言从面向过程发展到面向对象的过程中，高级语言以及开发框架早已利用各种设计模式进行了封装，我们在不知不觉中享受着这种封装带来的便利却不自知。上方代码中的`坏味道`主要因为参数类型以及参数值受到限制，为了避免这种`坏味道`出现，通常使用`模板设计模式`来解决过高的耦合度。

![1](<https://allluckly.cn/images/blog/tuogao/tougao51/3.jpg>)

在Swift中，模板设计模式通过使用`class`和`struct`两种数据类型作为类对象的创建结构模板，用来创建不同内容的具体对象。因此改变之后的代码如下：

```
func calculateTax(product: Product) -> Double {
    return product.price * 0.17
}

func calculateStockValue(products: [Product]) -> Double {
    return products.reduce(0){
        return $0 + $1.price * Double($1.stock)
    }
}

class Product {
    var name: String
    var price: Double
    var stock: Int

    init(name: String, price price: Double, stock stock: Int) {
        self.name = name
        self.price = price
        self.stock = stock
    }
}
```

通过以`class`结构为模板，我们可以创建各种不同属性的商品。虽然在定义`class`结构的过程中增加了一些额外的工作，但一来这使得我们的代码更加符合`OOP`的编程思想，二来解除了方法和元组之间的高度耦合。但这样的代码仍然存在另一个耦合问题——方法和类之间的耦合。如果有一天，项目需要面对全球市场，但是在不同的国家税种不同、税率也不同，这段代码也就有了另外的`坏味道`。因此我们需要把这两个方法同样放到`class`结构中成为方法模板

```
class Product {
    var name: String
    var price: Double
    var stock: Int

    init(name: String, price price: Double, stock stock: Int) {
        self.name = name
        self.price = price
        self.stock = stock
    }

    private func calculateTax() -> Double {
        return price * 0.17
    }

    private func calculateStockValue() -> Double {
        return price * Double(stock)
    }

    func log() {
        print("The tax of \(name) is \(calculateTax())")
        print("The stock value of product is \(calculateStockValue())")
    }
}

class Product_Japan: Product {
    override private calculateTax() -> Double {
        return price * 0.05
    }
}
```

将方法抽象封装到父类中，并在子类中重写具体实现，这是`模板方法模式`的典型做法

![1](<https://allluckly.cn/images/blog/tuogao/tougao51/4.jpg>)

为了计算不同地区的税率要实现多个这样的子类的做法带来的代码量也是十分可观的，而且当税率成了一个可改变的因素时，方法可以改变成为接收税率进行计算：

```
func calculateTax(ratio: Double) -> Double {
    return price * ratio
}
```

## 总结

> 模式优点

- 将不变的行为抽离封装起来，去除了重复代码
- 通过子类重写父类实现，可以扩展新的行为

> 模式缺点

- `class`和`struct`都能利用模板模式来解除高耦合，但两者在传递时采用不同的方式，这容易引发错误
- 容易造成子类的数量暴增，导致代码设计更加抽象

---

好文推荐：[分分钟解决iOS开发中App启动广告的功能](<https://allluckly.cn/lblaunchimagead/LBLaunchImageAd>)

