Tìm hiểu về Generic
Bài đăng này đã không được cập nhật trong 6 năm
Generic programming là 1 cách để viết function và kiểu dữ liệu trong khi đưa ra những giả định về loại dữ liệu đang được dùng. Generics swift viết code không cần xác định về loại dữ liệu cụ thể, cho phép trừu tượng hóa để tạo ra code clean hơn, ít lỗi hơn.
1. Tổng quan
VD ta có hàm cộng 2 số như sau:
func addInt(x: Int, y: Int) -> Int {
return x + y
}
func addDouble(x: Double, y: Double) -> Double {
return x + y
}
let a = addInt(x: 1, y: 2)
let b = addDouble(x: 1.0, y: 2.0)
Hàm addInt() và addDouble() là khác nhau nhưng nội dung trong hàm giống nhau là return x+y . Nó là 2 hàm nhưng code bên trong lại lặp lại. Generics có thể được dùng để gộp 2 hàm này làm 1 và xóa redundant code. Ngoài ra, có thể bạn không nhận ra. Nhưng 1 số cấu trúc phổ biến mà ta hay sử dụng như Array, Dictionary hay Optional đều là genric type.
Array
let numbers = [1, 2, 3]
let a = numbers[0]
Array là 1 generic type. Generic type yêu cầu 1 tham số phải được xác định. Khi bạn tạo 1 insatance và chỉ định 1 kiểu tham số sẽ cho phép xác định kiểu cụ thể của instance. Để thấy rõ hơn về generic của Array ta có thể khai báo như sau:
var numbers : Array<Int> = []
numbers.append(1)
numbers.append(2)
numbers.append(3)
let a = numbers[0]
Bạn sẽ bị lỗi như sau nếu insert vào number 1 kiểu khác Cannot convert value of type ‘String’ to expected argument type ‘Int’. Hàm append của Array được gọi là generic method.
Dictionary
Dictionary cũng là kiểu generic
let codes = ["English": "en", "Japan": "jp",]
let code = codes["English"]
Optinal
Cách tạo 1 kiểu optional string với full syntax
let optionalName = Optional<String>.some("Ozawa")
if let name = optionalName {}
Check kiểu của name sẽ là String. Đó là optional binding (if-let) là 1 generic transformation của sorts. Nó truyền vào 1 generic value của kiểu T? và trả ra kiểu T. Điều đó có nghĩa là bạn có thể dùng if let với tất cả các kiểu.
2. Generic Data Structure
Ta thử tạo 1 Queue là data structures kiểu giống list hoặc stack.
struct Queue<Element> {
}
Queue là 1 generic type với 1 type argument là Element. 1 cách gọi khác Queue là generic over type Element. Nếu ta khai báo Queue<Int> hay Queue<String> thì các kiểu dữ liệu trong nó sẽ là Int hoặc String. Ta định nghĩa 1 property và 2 method cho nó:
fileprivate var elements: [Element] = []
mutating func enqueue(newElement: Element) {
elements.append(newElement)
}
mutating func dequeue() -> Element? {
guard !elements.isEmpty else { return nil }
return elements.remove(at: 0)
}
Sau đó, ta khởi tạo và chạy thử. Kiểm tra các giá trị in ra màn hình.
var q = Queue<Int>()
q.enqueue(newElement: 5)
q.enqueue(newElement: 2)
q.dequeue() //5
q.dequeue() //2
q.dequeue() //nil
3. Generic Function
Ta muốn viết 1 hàm truyền vào 1 dictionary và trả ra là 1 list key-value của dictionary. Generic function sẽ giúp ta làm việc đó
func pairs<Key, Value>(from dictionary: [Key: Value]) -> [(Key, Value)] {
return Array(dictionary)
}
let somePairs = pairs(from: ["min": 199, "max": 299])
// [("max", 299), ("min", 199)]
let morePairs = pairs(from: [1: "Swift", 2: "Generics", 3: "Rule"])
// [(2, "Generics"), (3, "Rule"), (1, "Swift")]
Ta đã có thể tạo 1 hàm có khả năng trả về nhiều kiểu dữ liệu khác nhau với nhiều cách gọi khác nhau.
4. Constraining a Generic Type
Ta muốn lấy ra 1 giá trị ở giữa của 1 list đã được sort. Ta khai báo như sau:
func mid<T>(array: [T]) -> T? {
guard !array.isEmpty else { return nil }
return array.sorted()[(array.count - 1) / 2]
}
Ta sẽ bị error. Để sort có thể work được, element của array cần được so sánh. Ta cần khai báo element của array kiểu Comparable
func mid<T: Comparable>(array: [T]) -> T? {
guard !array.isEmpty else { return nil }
return array.sorted()[(array.count - 1) / 2]
}
mid(array: [3, 5, 1, 2, 4]) // 3
Ta đã thêm 1 type constraint vào generic type T. Chỉ cần gọi hàm của 1 array với element Comparable và sorted() sẽ luôn chạy. Tương tự như ở phần 1, ta muốn cộng 2 số với nhiều kiểu dữ liệu khác nhau. Ta có kiểu Summable
func add<T: Summable>(x: T, y: T) -> T {
return x + y
}
let a = add(x: 1, y: 2) // 3
let b = add(x: 1.0, y: 2.0) // 3
let c = add(x: "A", y: "B") //AB
5. Extending a Generic Type
Ta cũng có thể extend Generic Data Structure và viết thêm các hàm extend cho nó
extension Queue {
func peek() -> Element? {
return elements.first
}
}
q.enqueue(newElement: 5)
q.enqueue(newElement: 2)
q.peek() // 5
6. Subclassing a Generic Type
Ta có 1 generic class
class Box<T> {
}
Ta muốn extend box nhưng vẫn giữ là generic để có thể truyền vào nhiều kiểu dữ liệu khác nhau. Nhưng ta cũng muốn có 1 subclass đặc biệt để biết những item cụ thể trong box. Ta có thể làm như sau:
class Gift<T>: Box<T> {
func wrap() {
print("Wrap")
}
}
class Pizza {
}
class PizzaBox: Gift<Pizza> {
override func wrap() {
print("Wrap pizza")
}
}
class Shoe {
}
class ShoeBox: Box<Shoe> {
}
Ta chạy thử và xem giá trị
let box = Box<Pizza>()
let gift = Gift<Pizza>()
let shoeBox = ShoeBox()
let pizzaBox = PizzaBox()
gift.wrap() // Wrap
pizzaBox.wrap() // Wrap pizza
7. Enumerations With Associated Values
Dưới đây là generic enum với 2 value: 1 là value trả về, 2 là error
enum Result<Value> {
case success(Value), failure(Error)
}
Ngoài ra ta muốn khai báo 1 error cụ thể cho từng trường hợp. Ta đã định nghĩa 1 error enumeration type và 1 hàm chia 2 số Int:
enum MathError: Error {
case divisionByZero
}
func divide(_ x: Int, by y: Int) -> Result<Int> {
guard y != 0 else {
return .failure(MathError.divisionByZero)
}
return .success(x / y)
}
let a = divide(10, by: 2) // .success(5)
let b = divide(10, by: 0) // .failure(MathError.divisionByZero)
All rights reserved