Bài 27: Generics trong swift - Lập trình Swift cơ bản

Đăng bởi: Admin | Lượt xem: 1778 | Chuyên mục: Swift


Generics trong ngôn ngữ lập trình Swift chính là một tính năng mà chúng ta đều sử dụng hằng ngày. Nếu như lần đầu sử dụng Generics, lập trình viên thường nhầm lẫn về việc nên và cần sử dụng cái gì? Tại sao phải sử dụng? Khi nào sử dụng và sử dụng như thế nào cho hợp lý?. Vì vậy, ở bài viết này chúng tôi sẽ đưa ra một số giải thích kèm ví dụ để lập trình viên có thể hiểu hơn về Generic.

Vấn đề

Ở ví dụ dưới đây, tôi muốn có một hàm có 1 mảng với hai loại hình vuông và hình chữ nhật và loại bỏ những phần tử có diện tích nhỏ hơn 100.  
struct Rectangle: Equatable {

    let width: Double

    let height: Double

    func area() -> Double {

        return width * height

    }

}

struct Square: Equatable {

    let length: Double

    func area() -> Double {

        return length * length

    }

}

Cách thứ nhất

Bạn có thể giải quyết vấn đề theo nhiều cách khác nhau và cách đầu tiên là không sử dụng generic thay vào đó chúng ta sử dụng Any.
func filterSmallShapes(_ shapes: [Any]) -> [Any]

Để thực hiện, chúng ta cần truyền loại hình dạng, gọi hàm tính diện tích và so sánh nó với 100.

func filterSmallShapes(_ shapes: [Any]) -> [Any] {

    return shapes.filter {

        if let square = $0 as? Square {

            return square.area() > 100

        } else if let rectangle = $0 as? Rectangle {

            return rectangle.area() > 100

        } else {

            fatalError("Unhandled shape")

        }

    }

}
Tuy nhiên, nhược điểm của cách làm này là:
  • Có thể bị run time crash khi bạn truyền vào kiểu khác Rectangle và Square.
  • Lặp lại so sánh ( cụ thể ở ví dụ này là so sánh với 100).
  • Nếu như bạn muốn thêm các dạng khác như tròn, tam giác, ngũ giác, lục giác,.. hàm chắc chắn sẽ bị phình ra.
  • Hàm trả về mảng Any, nghĩa là chúng ta cần cast nó sang kiểu khác sau này.

Cách thứ hai

Ở cách thứ hai này, bạn hãy thử sử dụng protocol để chúng ta không gặp phải những vấn đề, nhược điểm như cách thứ nhất. 
protocol Sizeable {
    func area() -> Double
}

extension Rectangle: Sizeable {}

extension Square: Sizeable {}

func filterSmallShapes(_ shapes: [Sizeable]) -> [Sizeable] {
    return shapes.filter { $0.area() > 100 }

}
Chúng ta đã loại bỏ đi những nhược điểm của phương án thứ nhất nhưng hiện tại chúng ta vẫn chỉ nhận được mảng Sizable, tức là điều này cũng giống như cách thứ nhất khi nhận được mảng Any.

Cách thứ ba

Và đây chính là cách giải quyết cho một số vấn đề vướng mắc mà ta gặp phải ở cách thứ 1 và cách thứ 2. Lập trình viên có thể tách thành 2 hàm khác nhau như ở ví dụ dưới đây.
func filterSmallShapes(_ shapes: [Rectangle]) -> [Rectangle] {

    return shapes.filter { $0.area() > 100 }

}

func filterSmallShapes(_ shapes: [Square]) -> [Square] {

    return shapes.filter { $0.area() > 100 }

}
Tuy nhiên, sau khi chạy xong đôi khi chúng ta vẫn gặp phải một số vấn đề như: hàm và logic bị lặp lại khi bạn thêm nhiều kiểu hình dáng.

Sử dụng Generic

Đầu tiên, bạn cần yêu cầu Swift tạo ra các phiên bản khác nhau giống như cách thứ ba bằng việc cung cấp cùng 1 chức năng nào đó mà nó có thể sử dụng.
func filterSmallShapes<Shape: Sizeable>(_ shapes: [Shape]) -> [Shape] {

    return shapes.filter { $0.area() > 100 }

}
Phần thân hàm, lập trình viên có thể code giống theo với cách thứ 2. Và chỉ việc thay đổi định nghĩa của hàm. Chúng ta đã giới thiệu một kiểu placeholder giữa <> mà chúng ta đã gọi là Shape. Loại placeholder này có một số ràng buộc được đặt theo nó trong đó chúng tôi đang nói rằng nó phải là một loại phù hợp với Sizable, điều này được biểu thị bằng cách viết Sizable sau dấu :
Bài tiếp theo: Access control trong swift >>
vncoder logo

Theo dõi VnCoder trên Facebook, để cập nhật những bài viết, tin tức và khoá học mới nhất!