Generic Protocols with Associated Type

Chúng ta đã biết về protocol và thường xuyên sử dụng chúng. Nhưng bạn đã khi nào nghe nói về generic protocol hay chưa? Trong bài viết này, mình sẽ giới thiệu về generic protocol và dùng nó với associated type. Trước khi đi sau hơn về generic protocols, bạn nên làm quen với đoạn code dưới đây.

struct GenericStruct<T> {
 var property: T?
}

Bạn có thể set type cho T hoặc để cho swift tự suy dựa trên giá trị

let explictStruct = GenericStruct<Bool>() 
// T is Bool 
let implicitStruct = GenericStruct(property: "An")
// T is String

Normal Protocol

Đầu tiên để biết rõ hơn về generic protocols, ta sẽ tạo một protocol với property với kiểu String.

Design Protocol

protocol NormalProtocol {
 var property: String { get set }
}

Design Class and Conform

class NormalClass: NormalProtocol {
 var property: String = "Bob"
}

Tuy nhiên, NormalProtocol chỉ làm việc với String. Nếu như bạn muốn property là một kiểu khác thì sẽ như thế nào? Đã đến lúc ta cần biết về Protocol Associated Types

Introducing Protocol Associated Types

Trong generic protocols, để tạo ra một thứ gì đó giống như <T> trong Generics, bạn cần phải dùng associatedtype

protocol GenericProtocol {
 associatedtype myType
 var anyProperty: myType { get set }
}

Bây giờ, bất cứ class, struct nào muốn conform GenericProtocol phải implement anyProperty. Tuy nhiên, anyproperty type lại không được định nghĩa. Do đó, class hoặc struct conform GenericProtocol phải định nghĩa nó một cách ngầm hoặc rõ ràng. Đầu tiên ta tạo một class SomeClass conform GenericProtocol. Ta phải define myType theo 2 cách mà ta đã nói ở trên

Define Associated Type Implicitly

Bạn có thể define myType dựa trên giá trị được set của anyproperty.

class SomeClass: GenericProtocol {
 var anyProperty: myType = "An"
}

Bây giờ myType đã được định nghĩa kiểu String dựa trên "An". Tuy nhiên bạn có thể cho Swift đoán nhiều hơn như ví dụ dưới đây

class SomeClass: GenericProtocol {
 var anyProperty = "An" // myType is "String"
}

Define Associated Type Explicitly

Bạn có thể định nghĩa associated type(myType) bằng cách sử dụng typealias.

class SomeClass: GenericProtocol {
 typealias myType = String
 var anyProperty: myType = "An"
}

Nếu bạn muốn định nghĩa associated type là myType, bạn có thể sử dụng typealias hoặc bạn có thể định nghĩa myType ngầm như chúng ta đã thấy ở trên Ta hãy thử myType với kiểu Int thay thế cho kiểu String cho các ví dụ trước đó

struct SomeStruct: GenericProtocol {
 var anyProperty = 1996
}

Bạn đã định nghĩa ngầm cho myType là kiểu Int dựa trên giá trị 1996.

Protocol Extension và Type Constraints

Design Extension

extension GenericProtocol {
 static func introduce() {
  print("I'm An")
 }
}

Ta có thể sử dụng hàm introduce với các class và struct conform GenericProtocol

SomeClass.introduce() // I'm An
SomeStruct.introduce() // I'm An 

Introducing Where Clause

Đừng lo lắng nếu bạn chưa bao giờ sử dụng where. Nó chỉ là một cách ngắn hơn để viết một câu lệnh else-if mà thôi.

extension GenericProtocol where myType == String {
 func introduce(){
  print("I'm An")
 }
}

where clause ở phía trên, nếu myType là String thì ta mới xử lý, nếu không thì sẽ bỏ qua . Nếu bạn còn nhớ, SomeClassmyType là kiểu String và SomeStructmyType là kiểu Int.

let someClassInstance = SomeClass().introduce() // "I'm An"

Tiếp theo là SomeStruct

let someStructInstance = SomeStruct() // Error 

Multiple Where Conditions with Self

Bạn có thể add thêm nhiều where clause để cho extension được cụ thể hơn. Tất cả những gì bạn cần làm là add thêm nhiều điều kiện ở phía sau.

extension GenericProtocol where type == String, Self == SomeClass {
 func introduce(){
  print("I'm An") 
 }
}

Theo đoạn code ở trên thì chỉ có SomeClass có method introduce()

Override Associated Type

Kiểu của myType có thể được định nghĩa bởi class hoặc struct conform GenericeProtocol. Tuy nhiên, bạn cũng có thể định nghĩa trước associatedtype ngay trong protocol đó

protocol GenericProtocol {
 associatedtype myType
 var anyProperty: myType { get set }
}

Ví dụ ta tạo một protocol với tên là Familiable. Nó chứa một associatedtype gọi là FamilyType. Bạn có thể định nghĩa trước cho nó là kiểu Int

protocol Familiable {
 associatedtype FamilyType = Int
 func getName() -> [FamilyType]
}

Adopt Type Pre-defined Protocol

class NumberFamily: Familiable {
 func getName() -> [FamilyType] {
  return [1, 2, 3]
 }
}

hoặc

class NumberFamily: Familiable {
 func getName() -> [Int] {
  return [1, 2, 3]
 }
}

Bây giờ nếu như ta tạo một instance thì

let numberFam = NumberFamily() // NumberFamily<Int>

Tuy nhiên, ta có thể override/change pre-defined type của protocol

Override Associated Type

Đầu tiên ta tạo một generic struct tên là NormalFamily conform Familiable. Nếu bạn muốn struct của bạn làm việc với String thì

struct NormalFamily<T: ExpressibleByStringLiteral>: Familiable  {
 func getName() -> [T] {
  return ["An", "Nhan"]
 }
}

Bây giờ nếu bạn muốn tạo một instance thì

let normalFam = NormalFamily() // NormalFamily<String>

Trong bài viết này mình đã hướng dẫn cho mọi người cách overide associatedtype và combine giữa protocol và generics. Mong là sẽ có ích đối với các bạn.


All Rights Reserved