生成式AI领域的最新成果都在这里!抢 QCon 展区门票 了解详情
写点什么

Swift 中的设计模式

  • 2015-09-13
  • 本文字数:11247 字

    阅读完需:约 37 分钟

设计模式

设计模式(Design Pattern)是对软件设计中普遍存在的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛等人(Erich Gamma,Richard Helm,Ralph Johnson 和John Vlissides 这四人提出的。也被称为:Gang of Four,GOF,四人帮)在1990 年代从建筑设计领域引入到计算机科学的。 设计模式并不能直接用于完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。

以上描述摘自维基百科。设计模式出现于1990 年代,那时主要的编程语言是Java 和C++ 这样的面向对象编程语言。随着计算机语言的发展,越来越多的现代语言的出现和流行,已经改变了设计模式出现时的计算机语言背景。虽然,现在Java 和C++ 依然流行,但Python、Scala、Groovy、Swift 等现代语言使用者越来越多。函数式编程语言(Lisp、Scheme、Haskell 等)也越来越来受到重视。程序员的视野大大拓展了,手中可使用的工具也更丰富了。

所以现在有一部分人质疑设计模式已经与时代发展脱节,认为设计模式是过时的产物。设计模式解决问题的方式,增加了不必要的复杂度。在一些现代语言中,设计模式解决的问题可以更好地被其他方式处理,有些问题甚至可能根本不存在。

这种质疑一定程度上是正确的。因为,我们现在拥有了更高级的语言工具。语言的选择是很重要的,因为语言的选择会影响我们看问题的方式。在使用过程式语言时,你可能为了方便读取数据,而“制造”某种类似于类(class)的数据结构,你可能会称这种设计模式为“封装“。但在面向对象编程语言中,你不会认为这是一个需要设计模式来解决的问题,因为语言本身已经为你解决了。在使用面向对象语言时,为了使操作可以在对象间传递,我们使用一个被称为命令模式(Command)的设计模式。但是,在拥有高阶函数的编程语言中,你可能没有意识到这是个问题。因为,你很自然地就将函数(即操作)作为参数传入到另一个函数中,而这就是函数式编程语言中高阶函数的概念。

从这点来看,随着我们所使用的编程语言的演化,我们遇到的问题也确实一直在改变。GOF 提出的23 种设计模式也许部分过时了。但我们遇到的问题并不会消失,设计模式的概念将一直存在:提炼普遍存在的问题,提出解决方案。这其实是一个抽象过程。现在已有的编程语言都存在表达的局限,即对某类问题抽象层次过低。所以,我们在使用任何编程语言时,都还是会遇到一些普遍存在的却没有被语言本身很好解决的问题。这时,我们就会使用到“设计模式”,即人们总结出来的解决方案:遇到问题A,用方案A;遇到问题B,用方案B。只不过问题会一直变化。现在我们不可以再使用那23 个模式来解决问题了,但是我们仍然需要总结出其它模式来解决新的问题。这种情况一直会持续到我们拥有“完美语言”的那一天。但现在看起来,这一天还没有到来的迹象。

Swift 中的设计模式

Swift 是一门多范式编程语言。除了支持面向对象编程范式之外,还支持函数式编程范式,和泛型编程。这使得 Swift 可以使用函数式编程的某些优秀工具解决我们在面向对象编程中遇到的困难。作为一门崭新的语言(2014 年发布),Swift 也同时吸收了很多其他语言的优秀特性。这使得与 GOF 首次提出 23 个设计模式时相比,我们现在看待 Swift 中设计模式相关问题的看法肯定已大大不同了。毕竟 GOF 提出设计模式主要是针对 Java 和 C++ 这种相对而言更传统的编程语言。在 Swift 中,一些模式已经被语言特性所吸收,你在使用 Swift 甚至察觉不出这类问题的存在;一些问题仍然存在,我们仍然需要某种设计模式,但实现起来会更为简便;当然,仍然会存在一些问题,Swift 也没有解决。

另外一个有趣的相反的问题是:Swift 是否引起了一些新问题,需要一些新的设计模式来解决呢?相对于一些实验性编程语言(如 Lisp,Haskell)而言,作为一门工业语言,Swift 的语言设计其实是保守的,吸收的大都是其他语言成熟的优秀特性。本质上,Swift 仍然主要还是一门面向对象编程语言。函数式编程,泛型编程等新的语言特性的引入,都只是一种改进,还谈不上是革命性的变化。这使得 Swift 虽然显得保守,但也风险可控,伴随剧烈革命而引入新的问题的可能性被降低了。当然,最终结论仍然需要大规模的业界应用实践之后才能得出。

这篇文章主要会涉及 Swift 改善的那一部分。具体而言,是指 Swift 消除了哪些设计模式,使哪些设计模式的实现简化了。

函数式编程

Swift 支持函数式编程范式。程序员可以使用 Swift 写出函数式风格的代码。函数式编程是一种以数学函数为程序语言建模的核心的编程范式。在函数式编程中,函数是核心概念,是“头等公民”,函数被赋予了更多职责,拥有更多灵活的使用方式。这一章可以看到使用函数式编程范式,可以消除一些面向对象编程中使用到的设计模式。

高阶函数

高阶函数,指可以将其他函数作为参数或者返回结果的函数。由于高阶函数,我们发现 GOF 设计模式中命令模式(Command)在 Swift 中消失了。

命令模式使用对象封装一系列操作(命令),使得操作可以重复使用,也易于在对象间传递。由于Swift 仍然主要是一门面向对象编程语言,我们仍然可以使用Swift 实现一个经典的命令模式。实现命令模式的目的只是和之后使用高阶函数的方案对比:

复制代码
// Receiver
class Light {
func turnOn() {
println("The light is on")
}
func turnOff() {
println("The light is off")
}
}
// Abstract Command
protocol Command {
func execute()
}
// Concrete Command
class FlipUpCommand: Command {
private let light: Light
init(light: Light) {
self.light = light
}
func execute() {
light.turnOn()
}
}
class FlipDownCommand: Command {
private let light: Light
init(light: Light) {
self.light = light
}
func execute() {
light.turnOff()
}
}

以上例程中,灯(Light)是命令(Command)的操作对象(Receiver)。我们定义了命令的协议,同时我们实现两个具体的命令操作:FlipUpCommand 和 FlipDownCommand。它们分别使灯亮,和使灯灭。

复制代码
// Invoker
class LightSwitch {
var queue: [Command] = []
func addCommand(command: Command) {
queue.append(command)
}
func execute() {
for command in queue {
command.execute()
}
}
}
// Client
class Client {
static func pressSwitch() {
let lamp = Light()
let flipUpCommand = FlipUpCommand(light: lamp)
let flipDownCommand = FlipDownCommand(light: lamp)
let switcher = LightSwitch()
switcher.addCommand(flipUpCommand)
switcher.addCommand(flipDownCommand)
switcher.addCommand(flipUpCommand)
switcher.addCommand(flipDownCommand)
switcher.execute()
}
}
// Use
Client.pressSwitch()

这段则代码显示了如何使用命令模式。

在函数式编程中,由于存在高阶函数。我们可以直接将一个函数作为参数传给另外一个函数。所以,使用类包裹函数在对象间传递这件事情就显得多余了。以下代码显示如何使用高阶函数达到命令模式相同的效果:

复制代码
// Invoker
class LightSwitchFP {
{1}
var queue: Array<(Light) -> ()> = []
{1}
func addCommand(command: (Light) -> ()) {
queue.append(command)
}
{1}
func execute(light: Light) {
for command in queue {
command(light)
}
}
}
{1}
// Client
class ClientFP {
{1}
static func pressSwitch() {
let lamp = Light()
let flipUp = { (light: Light) -> () in light.turnOn() }
let flipDown = { (light: Light) -> () in light.turnOff() }
{1}
let lightSwitchFP = LightSwitchFP()
lightSwitchFP.addCommand(flipUp)
lightSwitchFP.addCommand(flipDown)
lightSwitchFP.addCommand(flipUp)
lightSwitchFP.addCommand(flipDown)
{1}
lightSwitchFP.execute(lamp)
}
}
{1}
// Use
ClientFP.pressSwitch()
{1}
{1}

使用高阶函数的版本中,负责集中调度命令的 LightSwitchFP 类有一个接受命令的函数 addCommand。由于 Swift 支持高阶函数,这个函数无需接受一个携带命令函数的 Command 对象,而是直接接受表示命令的函数。这样更为直接自然。所以,命令模式在 Swift 这样拥有高阶函数的编程语言中,就显得多余了。

一等函数

一等函数,进一步扩展了函数的使用范围,使得函数成为语言中的“头等公民”。这意味函数可在任何其他语言构件(比如变量)出现的地方出现。可以说,一等函数是更严格的高阶函数。

策略模式(Strategy)定义了一系列算法,将每个算法封装起来,并且使它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。

下面,我们使用Swift 讨论一下一等函数对策略模式的影响。我们先用Swift 实现传统的策略模式:

复制代码
protocol Strategy {
func compute(first: Int, second: Int) -> Int
}
class Add: Strategy {
func compute(first: Int, second: Int) -> Int {
return first + second
}
}
class Multiply: Strategy {
func compute(first: Int, second: Int) -> Int {
return first * second
}
}
class Context {
let strategy: Strategy
init(strategy: Strategy) {
self.strategy = strategy
}
func use(first: Int, second: Int) {
strategy.compute(first, second: second)
}
}
let context = Context(strategy: Add())
context.use(1, second: 2)

类似于命令模式,策略模式中的策略对象主要用于封装操作(函数),不同的是策略模式中的策略对象封装的是不同的算法。这些算法实现了相同的接口,在这个例子中,接口是用 Strategy 协议表示的。我们使用两个实现了 Strategy 协议的具体类:AddMultiply分别封装两个简单的算法。Context 对象,用于对算法进行配置选择,它有一个 Strategy 类型的实例变量:strategy。通过配置 Context 的 strategy 具体类型,可以使用不同的算法。

然后我们再看看如果存在一等函数,策略模式是否可以得到化简:

复制代码
let add = { (first: Int, second: Int) -> Int in
return first + second
}
let multiply = { (first: Int, second: Int) -> Int in
return first * second
}
class ContextFP {
let strategy: (Int, Int) -> Int
init(strategy: (Int, Int) -> Int) {
self.strategy = strategy
}
{1}
func use(first: Int, second: Int) {
strategy(first, second)
}
}
{1}
let fpContext = FPContext(strategy: multiply)
fpContext.use(1, second:2)
{1}
{1}

由于 Swift 的函数都是一等函数,使得我们可以把函数作为参数传给另外一个函数。这无需在使用对象来封装算法。函数可以成为封装算法的载体,这样更为直接自然。例子中,ContextFP 的构造器的传参就是函数类型。给予构造器代表不同算法的函数,就配置了不同的算法。

函数也可以作为类的实例变量。这样在类中,直接维护代表算法的函数也成为可能。从类型声明可以看出,ContextFP 中的实例变量 strategy 就是一个函数。

一等函数的概念使得函数获得了更高的地位,使得函数的灵活性大大增加。在很多场景下直接使用函数会是更直接自然的选择。面向对象编程范式,赋予了对象更高的地位。但是,如果给予函数“正常”一些的地位,可以简化不少问题。设计模式中的不少模式存在都是由于函数的使用限制,需要使用在使用类包裹函数。类似的例子还有模版方法模式(Template method)。

柯里化函数

柯里化函数(Curried Function),是指接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,该函数返回一个接受余下参数的新函数。这个名词来源于逻辑学家:Haskell Curring。编程语言 Haskell 也取自这位逻辑学家的名字。

抽象工厂模式(Abstract Factory)提供了一种方式,可以将一组具有同一主题的单独的工厂封装起来。在正常使用中,客户端程序需要创建抽象工厂的具体实现,然后使用抽象工厂作为接口来创建这一主题的具体对象。客户端程序不需要知道(或关心)它从这些内部的工厂方法中获得对象的具体类型,因为客户端程序仅使用这些对象的通用接口。抽象工厂模式将一组对象的实现细节与他们的一般使用分离开来。

函数柯里化可以用于制造一系列相关的函数。这和抽象工厂模式的目的类似。仍然如前例,先使用Swift 实现经典的抽象工厂模式。首先,我们给出一个产品族,包括两个牌子,两种设备类型的四个组合:

复制代码
// Abstract Product
protocol Product {
var brand: String { get }
var name: String { get }
}
protocol Phone: Product { }
protocol Pad: Product { }
// Concrete Product
class ApplePhone: Phone {
var brand: String {
return "Apple"
}
var name: String
init(name: String) {
self.name = name
}
}
class SamsungPhone: Phone {
var brand: String {
return "Samsung"
}
var name: String
init(name: String) {
self.name = name
}
}
class ApplePad: Pad {
var brand: String {
return "Apple"
}
var name: String
init(name: String) {
self.name = name
}
}
class SamsungPad: Pad {
var brand: String {
return "Samsung"
}
var name: String
init(name: String) {
self.name = name
}
}

然后,我们需要抽象工厂模式,来为创建这些同一主题的产品提供易于使用,方便扩展的方法:

复制代码
// Abstract Factory
protocol AbstractFactory {
func createPhone() -> Phone
func createPad() -> Pad
}
// Concrete Factory
class AppleFactory: AbstractFactory {
func createPhone() -> Phone {
return ApplePhone(name: "iPhone 6S")
}
func createPad() -> Pad {
return ApplePad(name: "iPad Air 2")
}
}
class SamsungFactory: AbstractFactory {
func createPhone() -> Phone {
return SamsungPhone(name: "Note 5")
}
func createPad() -> Pad {
return SamsungPad(name: "Note")
}
}

再看看如何使用抽象工厂:

复制代码
let appleFactory: AbstractFactory = AppleFactory()
appleFactory.createPad()
let samsungFactory: AbstractFactory = SamsungFactory()
samsungFactory.createPhone()

对于这个抽象工厂实现来说,虽然加入一个新的设备类型会有很大问题,需要改变抽象工厂的接口,但如果只增加品牌就很好办了。只需在实现一个具体品牌的工厂即可。

然后,我们使用柯里化函数来实现一个工厂方法。这个函数接受创建产品所需要各种参数,返回具体的产品。但我们实际上不会使用这个工厂方法直接创建产品。而是使用柯里化,让这个工厂方法充当一个函数工厂,创建一批可以作为产品工厂的方法。然后,我们可以使用这批产品工厂方法创建具体的产品。

复制代码
func createProductWithType(type: String)(brand: String)(name: String) -> Product? {
switch (type, brand) {
case ("Phone", "Apple"):
return ApplePhone(name: name)
case ("Pad", "Apple"):
return ApplePad(name: name)
case ("Phone", "Samsung"):
return SamsungPhone(name: name)
case ("Pad", "Samsung"):
return SamsungPad(name: name)
default:
return nil
}
}
let createApplePhone = createProductWithType("Phone")(brand: "Apple")
let createSamsungPhone = createProductWithType("Phone")(brand: "Samsung")
let createApplePad = createProductWithType("Pad")(brand: "Apple")
let createSamsungPad = createProductWithType("Pad")(brand: "Samsung")
// Use
let applePhone = createApplePhone(name: "iPhone 6S")
println(applePhone)

与抽象工厂模式相比,使用柯里化函数充当函数工厂,我们可以更轻松地基于一些条件创建一系列工厂方法。

Swift 的语言特性

扩展

扩展(Extension)扩展是一种向已有的类,枚举或者结构体添加新功能的方法。扩展和 Objective-C 中的类别(Category)类似,但是与 Objective-C 中的分类不同的是,Swift 中的扩展没有名字。

适配器模式(Adapter)将一个类的接口转接成用户所期待的。适配器使得因接口不兼容而不能在一起工作的类工作在一起。通常适配器有两种实现方法:一种使用继承,创建新类,并继承原有类,同时实现需要兼容的接口;另一种使用组合,创建新类,维护原有类的实例对象,并实现需要兼容的接口。无论哪种方法我们都需要创建一个新的被称为适配器的类。

扩展使得我们多了一种为类增添方法的手段。适配器模式的实质是使得一个已经存在的类,适配另外一个接口。实现接口其实就是实现接口所定义的方法。如果拥有扩展这种工具,就无需适配器模式这种笨重的做法。

下面先试着完成适配器的经典实现。首先,我们写一套配合完美的圆木桩和圆洞。如果圆木桩的直径小于圆洞的直径,毫无疑问,我们可以把圆木桩打入圆洞。

复制代码
protocol Circularity {
var radius: Double { get }
}
class RoundPeg: Circularity {
let radius: Double
init(radius: Double) {
self.radius = radius
}
}
class RoundHole {
let radius: Double
init(radius: Double) {
self.radius = radius
}
func pegFits(peg: Circularity) -> Bool {
return peg.radius <= radius
}
}

现在我们的工地出现了一根方木桩。我们仍然需要测试这个方木桩是否可以打入圆洞。由于木桩是否能被打入圆洞的测试要求木桩实现了 Circularity,这时我们就需要一个适配器,将 SquarePeg 适配为符合 Circularity 接口的类。适配的结果就是下面的 SquarePegAdaptor,它实现了 Circularity 接口。这样,我们可以判断它是否能打入圆洞。

复制代码
class SquarePeg {
let width: Double
init(width: Double) {
self.width = width
}
}
class SquarePegAdaptor: Circularity {
private let peg: SquarePeg
var radius: Double {
get {
return sqrt(pow(peg.width/2, 2) * 2)
}
}
init(peg: SquarePeg) {
self.peg = peg
}
}

检测结果:

复制代码
// Test if the square peg is fit with the hole
let hole = RoundHole(radius: 5.0)
for i in 5...10 {
let squarePeg = SquarePeg(width: Double(i))
let peg: Circularity = SquarePegAdaptor(peg: squarePeg)
let fit = hole.pegFits(peg)
println("width:\(i), fit:\(fit)")
}

然后,我们看看使用扩展是否能简化适配器模式:

复制代码
// Use extension
extension SquarePeg: Circularity {
var radius: Double {
get {
return sqrt(pow(width/2, 2) * 2)
}
}
}

使用扩展,我们无需像适配器模式那样,创建新类充当适配器。而只需要扩展原有类,为原有类添加一个方法,同时就实现了 Circularity 接口。在使用时,也更简单了,我们只需要直接使用原有类即可:

复制代码
for i in 5...10 {
let peg = SquarePeg(width: Double(i))
let fit = hole.pegFits(peg)
println("width:\(i), fit:\(fit)")
}

由于 Swift 提供了扩展(Extension)这一新的语言特性,使得处理接口转换这类原来需要适配器模式解决的问题时,更为简单了。

运算符重载

解释器模式(Interpreter)定义语言的文法,并建立一个解释器来解释语言中的句子。本质上说,解释器模式是一种语言扩展工具。如果你使用的语言不适合解决某类问题,可以使用解释器模式构建一些新的语法,甚至新的语言用于更为方便地解决特定领域的问题。这是在语言不提供语言级别的对自身扩展时的一种替代。

一些语言会禁止对语言本身进行扩展,例如Java。这可能是因为Java 的设计者认为,扩展语言需要专业的技能和良好的语法设计,这通常不是普通程序员可以胜任的。扩展语言这种特性常常会带来一些容易令人混淆的问题,很多描述这类技术的文档,也通常会告诫读者,“如果你不清楚自己在做什么事情,就最好不要使用这个功能”。但另一方面,语言禁止对本身的扩展也就限制了其使用范围,所以,Java 语言中才会出现解释器模式这种解决扩展语言自身功能的方案。当然不可避免的,这种解决方案会比有语言级别的支持复杂晦涩。

下面我们会讨论一种扩展语言语法的语言特性:运算符重载。看看有了运算符重载,解释器模式是否能得到简化。运算符重载是多态的一种。运算符(比如+,= 或==)被当作多态函数,他们的行为随着其参数类型的不同而不同。这意味着我们可以赋予一个运算符新的含义,使用在新的数据类型上,这就是一种扩展语言语法的特性。

我们仍然分别用解释器模式和运算符重载来解决同一个问题。两个例程的比较可以清晰地说明差别。问题的要求是实现复数的加法和乘法。先用解释器模式实现:

复制代码
import Foundation
protocol Expression {
func interprete(variables:Dictionary<String, Expression>) -> ComplexNumber
}
struct ComplexNumber: Expression {
let real: Double
let imaginary: Double
init(real: Double, imaginary: Double) {
self.real = real
self.imaginary = imaginary
}
func interprete(variables:Dictionary<String, Expression>) -> ComplexNumber {
return self
}
}
struct Plus: Expression {
let leftOperand: Expression
let rightOperand: Expression
init(left: Expression, right: Expression) {
self.leftOperand = left
self.rightOperand = right
}
func interprete(variables:Dictionary<String, Expression>) -> ComplexNumber {
let left = leftOperand.interprete(variables)
let right = rightOperand.interprete(variables)
return ComplexNumber(real: left.real + right.real, imaginary: left.imaginary + right.imaginary)
}
}
struct Multiply: Expression {
let leftOperand: Expression
let rightOperand: Expression
init(left: Expression, right: Expression) {
self.leftOperand = left
self.rightOperand = right
}
func interprete(variables:Dictionary<String, Expression>) -> ComplexNumber {
let left = leftOperand.interprete(variables)
let right = rightOperand.interprete(variables)
let resultReal = left.real * right.real - left.imaginary * right.imaginary
let resultImaginary = left.real * right.imaginary + left.imaginary * right.real
return ComplexNumber(real: resultReal, imaginary: resultImaginary)
}
}
struct Variable: Expression {
let name: String
init(name: String) {
self.name = name
}
func interprete(variables: Dictionary<String, Expression>) -> ComplexNumber {
if let variable = variables[name] {
return variable.interprete(variables)
}
return ComplexNumber(real: 0, imaginary: 0)
}
}

然后,我们实现解释器。为了使实现更简单,问题更聚焦,我们只实现后缀表达式形式的复数加法和乘法。这是因为对于后缀表达式,我们只需借助栈(Stack)对表达式进行一次遍历,就可以计算出结果,这大大降低了算法复杂度。而对中缀表达式的解释,我们通常也会先将它转化为后缀表达式,再做解释,计算出结果。鉴于这个例子里主要是展示解释器模式,而不是讲解解释器实现,所以,我们仅实现最简单的后缀加法和乘法。

复制代码
struct Stack<M> {
var items = Array<M>()
mutating func push(item: M) {
items.append(item)
}
mutating func pop() -> M {
return items.removeLast()
}
}
struct Evaluator: Expression {
let syntaxTree: Expression
init(expression: String) {
var expressionStack = Stack<Expression>()
let tokens = expression.componentsSeparatedByString(" ")
for token in tokens {
if token == "+" {
let subExpression = Plus(left: expressionStack.pop(), right: expressionStack.pop())
expressionStack.push(subExpression)
} else if token == "*" {
let subExpression = Multiply(left: expressionStack.pop(), right: expressionStack.pop())
expressionStack.push(subExpression)
} else {
expressionStack.push(Variable(name: token))
}
}
syntaxTree = expressionStack.pop()
}
func interprete(variables: Dictionary<String, Expression>) -> ComplexNumber {
return syntaxTree.interprete(variables)
}
}

最后,看看如何使用解释器模式:

复制代码
let expression = "w x *"
let sentence = Evaluator(expression: expression)
var variables = Dictionary<String, Expression>()
variables["w"] = ComplexNumber(real: 1, imaginary: 2)
variables["x"] = ComplexNumber(real: 2, imaginary: 4)
sentence.interprete(variables)

由于 Swift 支持运算符重载,我们可以更简单地使用语言已经提供的特性完成上面例程中相同功能。Swift 支持以运算符作为函数名。所以,对于 Swift 中已经实现的运算符,我们只需要重载两个函数,使运算符可以应用于新的类型:复数类型,即可。

复制代码
func + (left: ComplexNumber, right: ComplexNumber) -> ComplexNumber {
let resultReal = left.real + right.real
let resultImaginary = left.imaginary + right.imaginary
return ComplexNumber(real: resultReal, imaginary: resultImaginary)
}
func * (left: ComplexNumber, right: ComplexNumber) -> ComplexNumber {
let resultReal = left.real * right.real - left.imaginary * right.imaginary
let resultImaginary = left.real * right.imaginary + left.imaginary * right.real
return ComplexNumber(real: resultReal, imaginary: resultImaginary)
}

完成运算符重载之后,我们就可以像将两个整数相加一样,使用加法运算符将两个复数相加:

复制代码
let first = ComplexNumber(real: 1, imaginary: 2)
let second = ComplexNumber(real: 2, imaginary: 4)
first + second
first * second

可以看到运算符重载使得扩展运算符的使用范围变得更容易了。这也很容易理解,解释器模式其实就是对语言扩展自身语法这一功能的缺失而设计的设计模式。再次设计很有时候需要绕过很多障碍,很容易比语言的原生支持更为复杂繁琐。

总结

这篇短文中,我们回顾了设计模式的概念,并讨论了在现代编程语言背景下,设计模式相关问题的一些变化。然后使用 Swift 语言演示了 5 个设计模式和新的语言特性之间的关系。这 5 个设计模式和语言特性分别是:高阶函数和命令模式,一等函数和策略模式,柯里化函数和抽象工厂模式,扩展和适配器模式,运算符重载和解释器模式。

这 5 个例子展示了设计模式在现代编程语言中的变化:要么被语言特性更好的代替了;要么变得更为简单了。

参考文档


感谢丁晓昀对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。

2015-09-13 18:255084

评论

发布
暂无评论
发现更多内容

Java面试必备的集合源码详解,小白勿进

Java 程序员 后端

Java面试java基础,java基础语法菜鸟教程,腾讯Java社招面试

Java 程序员 后端

Java面试常问问题,java入门电子书百度网盘,直接上干货

Java 程序员 后端

Java面试官,springboot教程百度云,Java编程教程视频

Java 程序员 后端

Java面试知识点总结宝典助你通关,Java校招笔试面试题目

Java 程序员 后端

Java面试知识点总结,mongodb权威指南pdf网盘,SpringBoot

Java 程序员 后端

Java菜鸟入门教程,硅谷之火pdf百度网盘,吃透这份阿里P8纯手打Java面经

Java 程序员 后端

Java面试基础知识点,mysql编程入门教程,Java知识总结

Java 程序员 后端

Java进阶教程,极客网盘破解版吾爱破解,卑微打工人

Java 程序员 后端

Java编程方法论pdf,kalilinux新手教程,Java实习面试经验汇总

Java 程序员 后端

Java这些高端技术只有你还不知道,正在准备面试

Java 程序员 后端

Java通用流行框架大全,迈向java面试突击课百度云,来来来

Java 程序员 后端

Java重点知识点,极客时间破解蓝奏云,顺利通过阿里Java岗面试

Java 程序员 后端

Java面试回忆录,任小龙java笔记百度云,Java社招面经分享

Java 程序员 后端

Java面试带答案,合买咕泡学院vip,全面展示线程内存模型的能力

Java 程序员 后端

Java面试常见笔试题,java基础入门传智播客图书,被逼无奈开始狂啃底层技术

Java 程序员 后端

Java面试常问问题,图灵科技学院,十分钟带你看懂Netty如何实现C-S

Java 程序员 后端

Java自学视频百度云,尚硅谷大数据百度云,Java编程教学视频

Java 程序员 后端

Java软件开发面试题,从paxos到zookeeper网盘,已拿到offer

Java 程序员 后端

架构训练营-模块二

Geek_9de3de

架构实战营

Java面试手写算法,极客时间数据结构算法地图,Java岗面试12家大厂成功跳槽

Java 程序员 后端

Java编程教程入门,java使用教程第五版答案,美团优选面试题Java

Java 程序员 后端

Java自学宝典下载免费,java框架ssh和ssm百度,Java常见编程笔试题

Java 程序员 后端

Java零基础自学书籍,尚硅谷spring视频,BAT大厂面试总结

Java 程序员 后端

Java重点知识点总结,java开发教程百度云,Java零基础入门书籍

Java 程序员 后端

Java银行面试题目及答案,java基础菜鸟教程容器类,疯狂涨知识

Java 程序员 后端

Java通用流行框架大全,Java校招面试问题大全

Java 程序员 后端

Java编程入门自学,牛客网在线编程,Java基础入门视频教程

Java 程序员 后端

Java编程百度云,java实用教程第五版百度云,字节跳动资深面试官亲述

Java 程序员 后端

Java进阶之光,java工程师视频教程,王者笔记!

Java 程序员 后端

Java面试技巧和注意事项,java开发实例教程,阿里巴巴Java编程笔试题

Java 程序员 后端

Swift中的设计模式_移动_郭麟_InfoQ精选文章