Introducing Protocol-Oriented Programming in Swift 3 part II
Bài đăng này đã không được cập nhật trong 7 năm
Theo tài liệu: Protocol và tiếp theo từ Phần trước
Effects on the Swift Standard Library
Bạn đã thấy protocol extensions là 1 trong những cách rất hay để customize và mở rộng mọi thứ. Và bạn sẽ còn ngạc nhiên hơn nữa khi thấy Swift team sử dụng protocols để cải tiến Swift standard library. Thêm đoạn code sau vào cuối playground của bạn:
let numbers = [10, 20, 30, 40, 50, 60]
let slice = numbers[1...3]
let reversedSlide = slice.reversed()
let answer = reversedSlide.map { $0 * 10 }
print(answer)
Ví dụ này nhìn rất là đơn giản, và thậm chí bạn có thể đoán được answer sẽ được in ra màn hình ntn. Nhưng bạn sẽ ngạc nhiên với những types liên quan. Ví dụ như slice
, không phải là 1 Array
của các số integers mà là 1 ArraySlice<Int>
. Kiểu wrapper
đặc biệt này hoạt động như một Cách nhìn
(view into
) vào mảng ban đầu , và tránh được việc phân bổ bộ nhớ tốn kém mà có thể bổ sung nhanh chóng. tương tự, reversedSlice
cũng thực chất là ReversedRandomAccessCollection<ArraySlice<Int>>
tức cũng là một wrapper type view in to the original array
. May thay, các thiên tài =)) phát triển thư viện chuẩn đã định nghĩa method map
như 1 extension đối với protocol Sequence
và tất cả các kiểu collection wrappers (có khoảng hàng chục loại) để conform protocol đó. Điều đó cho phép bạn gọi map
ở trên Array
cũng đơn giản như trên ReversedRandomAccessCollection
mà ko thấy gì khác biệt. Và chúng ta sẽ học tập cái design pattern
rất quan trọng này trong những phần sau.
Off to the Races
Ở phần trước, chúng ta đã định nghĩa 1 số loại Bird
, bây h hãy add thêm vào 1 thứ hoàn toàn khác biệt:
class Motorcycle {
init(name: String) {
self.name = name
speed = 200
}
var name: String
var speed: Double
}
Class này hoàn toàn không liên quan gì đến chim bướm hay những thứ biết bay mà bạn đã định nghĩa trước đó. Nhưng nếu như bạn muốn xe máy chạy đua với chym cánh cụt thì sao? Đây là lúc ta ghép lại các mảnh ghép mà đã phân tích trước đó.
Bringing it Together
Đã đến lúc thống nhất tất cả các kiểu riêng lẻ vào 1 protocol chung cho cuộc đua (racing). Bạn có thể làm điều này mà ko cần phải quay lại và thay đổi các model đã đc define trước đó. Thuật ngữ thường được sử dụng cho cách làm này đc gọi là retroactive modeling
. Add đoạn code sau vào playground:
protocol Racer {
var speed: Double { get } // speed is the only thing racers care about
}
extension FlappyBird: Racer {
var speed: Double {
return airspeedVelocity
}
}
extension SwiftBird: Racer {
internal var speed: Double {
return airspeedVelocity
}
}
extension Penguin: Racer {
var speed: Double {
return 42 // full waddle speed
}
}
extension UnladenSwallow: Racer {
var speed: Double {
return canFly ? airspeedVelocity : 0
}
}
extension Motorcycle: Racer {}
let racers: [Racer] = [UnladenSwallow.african,
UnladenSwallow.european,
UnladenSwallow.unknown,
Penguin(name: "King Penguin"),
SwiftBird(version: 3.0),
FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0),
Motorcycle(name: "Giacomo")]
Trong đoạn code này, đầu tiên, bạn define 1 protocol là Racer
rồi sau đó, bạn bắt tất cả các types khác conform theo nó. 1 số types - như Motorcycle
- thì conform 1 cách ko đáng kể (trivially) và đơn giản. Một số khác như UnladenSwallow
cần 1 chút logic. Cuối cùng bạn đã có 1 loạt các types mà cùng conform với protocol Racer
. Với tất cả các types đó, bạn đã tạo ra 1 array chứa các racers.
Top Speed
Cùng viết 1 function mà tính toán tốc độ cao nhất của các racers:
func topSpeed(of racers: [Racer]) -> Double {
return racers.max(by: { $0.speed < $1.speed })?.speed ?? 0
}
topSpeed(of: racers) // 3000
function này sử dụng hàm max
của standard library để tìm ra racer có speed lớn nhất và return. Bạn sẽ return 0 nếu như user truyền vào 1 array rỗng. Có vẻ như đó là Swift 3 FTW =))
Making it more generic
Có 1 vấn đề ở đây: giả sử như bạn muốn tìm racer có tốc độ cao nhất trong 1 phần của array racers
. Thử thêm đoạn code sau vào playground:
topSpeed(of: racers[1...3]) // ERROR
Swift sẽ báo lỗi: "it cannot subscript a value of type [Racer] with an index of type CountableClosedRange". Như vậy khi lấy ra 1 phần của mảng racers, cái chúng ta nhận đc cũng là 1 kiểu wrapper. Để giải quyết vấn đề này, chúng ta sẽ viết lại hàm topSpeed
mà ko dành cho 1 mảng cụ thể nữa mà dành cho 1 protocol phổ biến:
func topSpeed<RacerType: Sequence>(of racers: RacerType) -> Double
where RacerType.Iterator.Element == Racer {
return racers.max(by: { $0.speed < $1.speed })?.speed ?? 0
}
Nhìn function này thể hơi đáng sợ 1 chút với <>
nên hãy chia nhỏ nó ra và cùng xem xét: RacerType
là generic type
của function này và nó có thể bất cứ kiểu nào, miễn là nó conforms lại protocol Sequence
. Mệnh đề where
chỉ ra rằng mỗi 1 element ở trong kiểu tập hợp RacerType
phải conform protocol Racer
. Tất cả Sequence
types có 1 associated type
với tên là Iterator
mà có thể loop qua tất cả các kiểu của Element
. Body thực sự method cũng vẫn chỉ như trước đó. Và method topSpeed
đã hoạt động với bất kỳ kiểu Sequence
nào bao gồm trong array racers
.
topSpeed(of: racers[1...3]) // 42
Make it More Swifty
Để làm cho mọi thứ có vẻ hướng đối tượng và có tính đóng gói hơn, bạn có thể extend Sequence
type và viết như sau:
extension Sequence where Iterator.Element == Racer {
func topSpeed() -> Double {
return self.max(by: { $0.speed < $1.speed })?.speed ?? 0
}
}
racers.topSpeed() // 3000
racers[1...3].topSpeed() // 42
Bây giờ bạn đã có 1 method dễ dàng sử dụng nhắc lệnh mà có thể thực thi với mọi loại sequences của racers.
Protocol Comparators
Thêm đoạn code sau vào cuối playground:
protocol Score {
var value: Int { get }
}
struct RacingScore: Score {
let value: Int
}
Với 1 Score
protocol, bạn có thể viết code để xử lý tất cả các kiểu scores theo cùng 1 cách. Tuy nhiên, với việc có nhiều types khác nhau, như RacingScore
bạn sẽ ko muốn nó lẫn lộn với StyleScores
hay CutenessScores
. Bạn thực sự muốn socres là comparable
để bạn có thể biết được ai cao điểm nhất. Trước swift 3, bạn sẽ cần phải add global operator functions
để conform những protocol này. Bây giờ bạn chỉ cần define 1 số static method như sau:
protocol Score: Equatable, Comparable {
var value: Int { get }
}
struct RacingScore: Score {
let value: Int
static func ==(lhs: RacingScore, rhs: RacingScore) -> Bool {
return lhs.value == rhs.value
}
static func <(lhs: RacingScore, rhs: RacingScore) -> Bool {
return lhs.value < rhs.value
}
}
Bạn đã đóng gói hoàn toàn logic của RacingScore
trong nơi. Bây giờ bạn có thể so sánh scores, và thậm chí sử dụng các phép toán như
greater-than-or-equal-to` mà bạn ko cần phải defined 1 cách rõ ràng.
RacingScore(value: 150) >= RacingScore(value: 130) // true
All rights reserved