12/08/2018, 14:55

Introducing Protocol-Oriented Programming in Swift 3 part I

Theo tài liệu: Protocol Giả sử bạn viết 1 game đua xe, và bạn có thể chạy ô tô, xe máy hoặc cưỡi máy bay để đua . Cách tiếp cận phổ biến sẽ là: sử dụng thiết kế hướng đối tượng, đóng gói tất cả logic bên trong 1 đối tượng được kế thừa cho những thứ có tính chất tương đồng (ví dụ vehicle). Cách ...

Theo tài liệu: Protocol Giả sử bạn viết 1 game đua xe, và bạn có thể chạy ô tô, xe máy hoặc cưỡi máy bay để đua . Cách tiếp cận phổ biến sẽ là: sử dụng thiết kế hướng đối tượng, đóng gói tất cả logic bên trong 1 đối tượng được kế thừa cho những thứ có tính chất tương đồng (ví dụ vehicle). Cách thiết kế này vẫn hoạt động, nhưng có 1 số nhược điểm. vd: nếu bạn thêm vào chim bướm bay ở background, hoặc thứ gì đó muốn chia sẻ game logic, sẽ ko có 1 cách nào tốt đẹp để tách các thành phần chức năng của vehicle thành thứ gì đó tái sử dụng được. Và đây là lúc protocol làm nên chuyện. Protocol rất mạnh mẽ và có thể thanh đổi cách bạn viết code. Trong tutorial này, bạn sẽ khám phá những cách mà bạn có thể create&use protocols, cũng như cách sử dụng protocol-oriented programming patterns để khiến cho code dễ mở rộng hơn. Bạn cũng sẽ thấy cách mà Swift team sử dụng protocol extensions để cải thiện thư viện chuẩn của Swift.

Getting Started

Tạo 1 playground mới với tên SwiftProtocols và viết đoạn code sau vào:

protocol Bird {
 var name: String { get }
 var canFly: Bool { get }
}

protocol Flyable {
 var airspeedVelocity: Double { get }
}

Chúng ta ở đây đơn giản define 1 protocol bird với 2 thuộc tính name và canFly, cùng với 1 protocol Flyable với thuộc tính airspeedVelocity Trước khi sử dụng Protocol, bạn có thể sẽ bắt đầu với Flyable là class cơ sở, sau đó Bird cũng như các thứ có thể bay khác như máy bay, bướm,.. kế thừa lại Flyable. Tuy nhiên ở đây, mọi thứ đều bắt đầu bằng Protocol, điều này cho phép bạn đóng gọi cá khái niệm chức năng theo cách mà ko cần 1 class cơ sở. Bạn sẽ thấy rằng mọi thứ sẽ linh hoạt hơn khi bạn bắt đầu định nghĩa thêm các kiểu thực thể khác tiếp theo đây.

Defining Protocol-Conforming Types

Thêm 1 struct như sau vào playground:

struct FlappyBird: Bird, Flyable {
  let name: String
  let flappyAmplitude: Double
  let flappyFrequency: Double
  let canFly = true
 
  var airspeedVelocity: Double {
    return 3 * flappyFrequency * flappyAmplitude
  }
}

Chúng ta define 1 struct FlappyBird mà conform với cả 2 protocols Bird và Flyable, airspeedVelocity được tính toán dựa trên flappyFrequency và flappyAmplitude, và nó có thể bay. Thêm tiếp 2 struct vào playground:

struct Penguin: Bird {
  let name: String
  let canFly = false
}
 
struct SwiftBird: Bird, Flyable {
  var name: String { return "Swift (version)" }
  let version: Double
  let canFly = true
 
  // Swift is FASTER every version!
  var airspeedVelocity: Double { return version * 1000.0 }
}

Peguin cũng là chim nhưng lại không thể bay, như vậy rõ ràng nếu bạn để Bird kế thừa Flyable, sẽ không hề hợp lý ở đây. Sử dụng Protocols cho phép bạn xác định các functional components và có bất kỳ 1 đối tượng liên quan nào phù hợp với chúng. Hiện tại bận vẫn sẽ thấy 1 số dư thừa, mỗi loại Bird phải khai báo xem nó có thể bay hay không, mặc dù đã có 1 khái niệm về Flyable trong hệ thống.

Extending Protocols With Default Implementations

Với Protocol extensions, bạn có thể define default behavior cho 1 protocol. Thêm đoạn code sau ngay bên dưới Bird protocol definition:

extension Bird {
 // Flyable birds can fly!
 var canFly: Bool { return self is Flyable }
}

Nó định nghĩa 1 mở rộng của Bird mà set giá trị default của canFly sẽ là true nếu như loại chim đó cũng conform cả Flyable, và false nếu ko conform. Nói 1 cách khác, bất kỳ 1 loại 'Bird' nào cũng sẽ ko cần phải khai báo 1 cách rõ ràng về canFly nữa. extension protocol Vì vậy, hãy xoá hết sạch tất cả những dòng khai báo canFly ở các struct đi.

Why not base classes?

Protocol extensions và default implements có vẻ giống với việc sử dụng base class hoặc thậm chí abstract classes trong các ngôn ngữ khác, nhưng lại cung cấp nhiều lợi thế quan trọng trong Swift:

  • Bởi vì các loại (types) có thể phù hợp với nhiều giao thức, chúng có thể được trang trí với nhiều default behavior từ nhiều Protocol khác nhau. Không giống như việc đa kế thừa, Protocol extensions không hề nhồi nhét thêm vào bất kỳ 1 trạng thái nào.
  • Protocol có thể phù hợp với Classes, Structs hoặc Enums. Trong khi Base Classes và kế thừa bị giới hạn trong class types.

Nói cách khác, protocol extensions cung cấp khả năng define default behavior cho value types chứ ko chỉ classes. Thêm đoạn định nghĩa về enum sau vào playground và bạn sẽ thấy:

enum UnladenSwallow: Bird, Flyable {
  case african
  case european
  case unknown
 
  var name: String {
    switch self {
      case .african:
        return "African"
      case .european:
        return "European"
      case .unknown:
        return "What do you mean? African or European?"
    }
  }
 
  var airspeedVelocity: Double {
    switch self {
    case .african:
      return 10.0
    case .european:
      return 9.9
    case .unknown:
      fatalError("You are thrown from the bridge of death!")
    }
  }
}

Với bất kỳ 1 value type nào, tất cả những gì bạn cần là defined chính xác các properties của các Protocol mà bạn conform. Ở đây UnladenSwallow conforms cả Bird và Flyable, và nó cũng được set default là "có thể bay".

Overriding Default Behavior

Bạn đã để UnladenSwallow tự động get giá trị true cho canFly, tuy nhiên nếu bạn muốn UnladenSwallow.unknow trả về false cho canFly, bạn có thể override default implementation:

extension UnladenSwallow {
 var canFly: Bool {
   return self != .unknown
 }
}

Và bây giờ chỉ có .african và .european là sẽ trả về true cho canFly. Test điều này bằng cách thêm đoạn code sau vào playground:

UnladenSwallow.unknown.canFly  // false
UnladenSwallow.african.canFly  // true
Penguin(name: "King Penguin").canFly  // false

Extending Protocols

Bạn có thể sử dụng các protocols từ thư viện chuẩn (standard library) và cũng có thể định nghĩa default behavior. Sửa lại để protocol Bird conform protocol CustomStringConvertible:

protocol Bird: CustomStringConvertible {

Conforming to CustomStringConvertible nghĩa là types của bạn phải có 1 description property để nó có thể giống với 1 String. Điều có nghĩa là bạn sẽ phải add property đó vào mỗi 1 kiểu Bird mà bạn đang có? Dĩ nhiên là không, và ta sẽ làm điều đó vẫn với protocol extensions:

extension CustomStringConvertible where Self: Bird {
 var description: String {
   return canFly ? "I can fly" : "Guess I’ll just sit here :["
 }
}

Phần extension sẽ khiến cho canFly thể hiện giá trị của description. thêm đoạn code sau vào playground để thấy được điều đó:

UnladenSwallow.african
0