Introducing Protocol-Oriented Programming in Swift 3 part I
Bài đăng này đã không được cập nhật trong 3 năm
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.
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
All rights reserved