Chapter 25: Protocol-Oriented Programming

大綱

Introducing protocol extensions

  • protocol extension跟protocol最大的差異就是可以進行內容的實作。

protocol TeamRecord {
  var wins: Int { get }
  var losses: Int { get }
  var winningPercentage: Double { get }
}

// protocol extensions
extension TeamRecord {
  // 替protocol新增變數,並實作內容
  var gamesPlayed: Int {
    return wins + losses
  }
}

Default implementations

  • 可以幫protocol本身的內容進行實作。

it differs in that winningPercentage is a member of the TeamRecord protocol itself whereas gamesPlayed isn’t. Implementing a member of a protocol in an extension creates a default implementation for that member.”

protocol TeamRecord {
  var wins: Int { get }
  var losses: Int { get }
  var winningPercentage: Double { get }
}

// Default implementations
// 直接幫宣告protocol本身的winningPercentage進行實作
extension TeamRecord {
  var winningPercentage: Double {
    return Double(wins) / Double(wins + losses)
  }
}

Understanding protocol extension dispatching

If a type defines a method or property in protocol extension, without declaring it in the protocol itself, static dispatching comes into play.

protocol WinLoss {
  var wins: Int { get }
  var losses: Int { get }
}

extension WinLoss {
  var winningPercentage: Double {
    return Double(wins) / Double(wins + losses)
  }
}

struct CricketRecord: WinLoss {
  var wins: Int
  var losses: Int
  var draws: Int

  // winningPercentage並沒有直接宣告WinLoss之中,而是宣告並實作在WinLoss的extension中
  var winningPercentage: Double {
    return Double(wins) / Double(wins + losses + draws)
  }
}

let miamiTuples = CricketRecord(wins: 8, losses: 7, draws: 1)
let winLoss: WinLoss = miamiTuples

miamiTuples.winningPercentage  // 0.5, 優先使用CricketRecord中已實作的方法
winLoss.winningPercentage  // 0.53, 使用WinLoss的extension中實作的方法

Type constraints

  • 透過type constraint可以讓你在某個protocol中去使用另外其他protocol的東西。

By using a type constraint on a protocol extension, you’re able to use methods and properties from another type inside the implementation of your extension.

protocol PostSeasonEligible {
  var minimumWinsForPlayoffs: Int { get }
}

// Type Constraints
extension TeamRecord where Self: PostSeasonEligible {
  var isPlayoffEligible: Bool {
   // TeamRecord中可以使用 PostSeasonEligible中宣告的minimumWinsForPlayoffs
    return wins > minimumWinsForPlayoffs
  }
}

protocol Tieable {
  var ties: Int { get }
}

// Type Constraints
extension TeamRecord where Self: Tieable {
  var winningPercentage: Double {
       // TeamRecord中可以使用 Tieable中宣告的ties
    return Double(wins) / Double(wins + losses + ties)
  }
}

struct RugyRecord: TeamRecord, Tieable {
  var wins: Int
  var losses: Int
  var ties: Int
}

let rugbyRecord = RugyRecord(wins: 8, losses: 7, ties: 1)
rugbyRecord.winningPercentage // 0.5

Programming to interfaces, not implementations

  • 继承本身存在的问题

    • 继承的另一个问题就是很多面向对象的编程语言都不支持多继承

    多用组合,少用继承。

Traits, mixins and multiple inheritance

  • Mixins 和 Traits 的概念1由此引入。

    • 通过继承,你定义你的类是什么。例如每条 Dog一个 Animal

    • 通过 Traits,你定义你的类能做什么。例如每个 Animal eat(),但是人类也可以吃,而且异世奇人(Doctor Who)也能吃鱼条和蛋挞,甚至即使是位 Gallifreyan(既不是人类也不是动物)。

    使用 Traits,重要的不是「是什么」,而是能「做什么」。

    继承描述了一个对象是什么,而 Traits 描述了这个对象能做什么。

  • Traits 很赞的一点就是它们并不依赖于使用到它们的对象本身的身份。Traits 并不关心类是什么,亦或是类是从哪里继承的:Traits 仅仅在类上定义了一些函数。

    异世奇人(Doctor Who)可以既是一位时间旅行者,同时还是一个外星人;而爱默·布朗博士(Dr Emmett Brown)既是一位时间旅行者,同时还属于人类;钢铁侠(Iron Man)是一个能飞的人,而超人(Superman)是一个能飞的外星人。

  protocol TieableRecord {
    var ties: Int { get }
  }

  protocol DivisionalRecord {
    var divisionalWins: Int { get }
    var divisionalLosses: Int { get }
  }

  protocol ScoreableRecord {
    var totalPoints: Int { get }
  }

  extension ScoreableRecord where Self: TieableRecord, Self: TeamRecord {
    var totalPoints: Int {
      return (2 * wins) + (1 * ties)
    }
  }

  // Traits,重要的不是「是什么」,而是能「做什么」
  struct NewHockeyRecord: TeamRecord, TieableRecord, DivisionalRecord, CustomStringConvertible, Equatable {
    var wins: Int
    var losses: Int
    var ties: Int
    var divisionalWins: Int
    var divisionalLosses: Int

    var description: String {
      return "\(wins) - \(losses) - \(ties)"
    }
  }

Simplicity

Why Swift is a protocol-oriented language

  • 先以Swift中Array當做例子,如何利用POP來定義出Array

    • Array is a struct means it’s a value type

      • it can’t be subclassed nor can it be a superclass.

    • Array will get numerous properties and methods common to every Collection, such as first, count or isEmpty

    • you can split() an Array or find the index(of:) an element, assuming the type of that element conforms to Equatable

// From the Swift standard library
public struct Array<Element> : RandomAccessCollection, MutableCollection {
  // ...
}

Key points

  • Protocol extensions let you write implementation code for protocols, and even write default implementations on methods required by a protocol.

  • Protocol extensions are the primary driver for protocol-oriented programming and let you write code that will work on any type that conforms to a protocol.

  • Type constraints on protocol extensions provide additional context and let you write more specialized implementations.

  • You can decorate a type with traits and mixins to extend behavior without requiring inheritance.

Last updated

Was this helpful?