Protocol swift ios
Bài này mình viết nhằm mở rộng cho bài Swift 2: Protocol trước đó. Các bạn có thể nên xem trước về Protocol để tiếp tục theo dõi bài viết này.
Protocol đóng vai trò quan trọng trong việc ẩn đi logic của các đối tượng, giúp chúng “loose coupling” (một nguyên tắc quan trọng trong việc xây dựng kiến trúc ứng dụng). Từ dó chúng ta có thể dễ dàng bảo trì (maintain), mở rộng (extend) cũng như tái sử dụng.
Tôi không biết bạn là ai !! Nhưng tôi biết bạn có khả năng làm được cái tôi cần.
Để giúp mọi người hiểu nguyên lý trên mình sẽ lấy ví dụ mối quan hệ kinh điển là Chủ Nợ và Con Nợ.
1. Hướng đối tượng cơ bản: biết tuốt về nhau, cứ có đối tượng là moi hết ra xài
// Chủ Nợ vs Con Nợ// Chủ Nợ: có thể đòi nợ con nợ// Con Nợ: có thể mượn tiền chủ nợ// Theo cách viết thông thường ta có:// Class person là 1 super class cho 2 class Borrower (con nợ) và Lender (chủ nợ), mục dích của class này để minh họa cho việc tại sao nên dùng Protocol thay vì cứ đặt hết method vào superclass rồi extend ra.class Person {var name:Stringvar age:Intvar money:Int = 0init(name:String, age:Int) {self.name = nameself.age = age}}// Chủ nợclass Lender:Person {var borrower:Borrower? // giả sử chủ nợ sẽ có 1 con nợfunc requestPayment() {if let borrower = self.borrower {if borrower.money >= borrower.debt {borrower.money -= borrower.debtself.money += borrower.debtborrower.debt = 0}}}}// Con nợclass Borrower:Person {weak var lender:Lender?// giả sữ con nợ thì có 1 chủ nợ thôi//weak ở đây để chống retain cycle vì 2 class tham chiếu vào nhauvar debt:Int = 0func borrowMoney(lender:Lender,money: Int) {if lender.money >= money {lender.money -= moneyself.money += moneydebt = moneyself.lender = lenderlender.borrower = self}}}// logic vay tiền và đòi tiền đâu đó trong project như sau// Khởi tạo các đối tượng chủ nợ và con nợlet lenderObj = Lender(name: "Teo", age: 22)lenderObj.money = 2000let borrowerObj = Borrower(name: "Ti", age: 18)// Mượn chủ nợ 1000borrowerObj.borrowMoney(lenderObj, money: 1000)// Một thời gian sau chủ nợ tới đòi tiềnlenderObj.requestPayment()
Đoạn code trên đại khái là ta sẽ viết cho 2 đối tượng có thể gọi đến nhau thông qua các method của chúng để vay và trả tiền. Tới đây chương trình không có lỗi và ta tự tin rằng mình đã có kiến thức tốt về hướng đối tượng.
2. Logic của class nào ra class nấy: tôi biết anh là ai và tôi biết anh có thể làm cái tôi muốn
Nếu chúng ta để ý sẽ thấy rằng hình như có gì đó không ổn .... Người mượn tiền thì biết rõ số tiền của người cho mượn và người cho mượn cũng thế. Giống kiểu bạn giật bóp tiền người ta rồi tự lấy tiền và khi nợ tới hạn người ta cũng đòi tiền theo cách mà bạn mượn tiền họ. Tới đây ta nói 2 đối tượng chủ nợ và con nợ biết quá nhiều về nhau hay nói 1 cách khác là dính chặt vào nhau (tight). Mà đó là điều mà ta nên né tránh trong việc xây dựng ứng dụng có kiến trúc tốt.
Thêm vào đó, cách viết trên ta giả sử nếu mà con nợ (Borrower) có thẻ tín dụng, tiền trong ngân hàng thì sao. Lúc đó ta phải update cả 2 object trên gần như toàn bộ logic cho vay tiền ra sao và trả tiền thế nào !!! Và điều này trong thực tế ta gặp rất nhìu, vd như mối quan hệ giữa đơn hàng (Order) và giỏ hàng (Cart) và Sản Phẩm hoặc Kho ... Theo cách trên là cứ lấy hết ruột gan nhau ra mà xài (dù chúng có được encapsulation hay không).
Ta có thể thay đổi logic cho 2 đối tượng trên như sau:
// Chủ nợclass Lender:Person {var borrower:Borrower? // giả sử chủ nợ sẽ có 1 con nợ// Cho con nợ vay tiềnfunc lendMoney(borrower:Borrower, money:Int) -> Bool {// Nếu tiền không đủ để cho mượn thì return falseguard self.money >= money else { return false }self.borrower = borrowerself.money -= money// Đưa tiền cho người mượnborrower.receiveMoney(self, money: money)return true}// Đòi nợfunc requestPayment() {if let borrower = self.borrower {if let returnMoney = borrower.payMoneyBack() {// Con nợ đã trả hết tiền nợ, xong !self.money += returnMoneyself.borrower = nil} else {// Trường hợp returnMoney = nil, con nợ vẫn chưa trả đc tiền \}}}}// Con nợclass Borrower:Person {weak var lender:Lender?var debt:Int = 0// Con nợ nhận tiền từ chủ nợfunc receiveMoney(lender:Lender, money:Int) {self.lender = lenderdebt = moneyself.money += money}// Trả nợfunc payMoneyBack() -> Int? {var returnMoney:Int?// Nếu đủ tiền thì trả hết, hết nợif money >= debt {money -= debtreturnMoney = debtdebt = 0self.lender = nil} else {// Chưa đủ tiển trả nên hẹn lần saureturn nil}return returnMoney}// Hỏi mượn tiền từ chủ nợfunc askForMoney(lender:Lender, money:Int) {if lender.lendMoney(self, money: money) {print("Yeah !!")} else {// Ặc phải tìm người khác để mượn rồi}}}// logic vay tiền và đòi tiền đâu đó trong project như saulet lenderObj = Lender(name: "Teo", age: 22)lenderObj.money = 2000let borrowerObj = Borrower(name: "Ti", age: 18)// Mượn tiềnborrowerObj.askForMoney(lenderObj, money: 1000)// Chủ nợ đòi tiềnlenderObj.requestPayment()
Đoạn code sau phức tạp hơn khá nhiều nhưng đại khái lúc này ta nói rằng việc cho vay (của chủ nợ) và trả nợ (của con nợ) sẽ rõ ràng hơn. Đặc biệt ta không còn bị tình trạng mượn tiền kiểu côn đồ như cách đoạn code trước đó :). Logic cho việc lấy tiền ở đâu là việc riêng của cả 2 mà không muốn cho đối phương biết. Cách viết này tốt hơn cách đầu tiên nhưng vẫn chưa thể giải quyết vấn đề Lender - Borrower dính chặt vào nhau.
3. Tôi không biết bạn là ai nhưng tôi biết bạn có thể làm được cái tôi cần
Tới đây ta có thể giải quyết vấn đề trên bằng Protocol
protocol LenderBehavior:class {func lendMoney(borrower:BorrowerBehavior, money:Int) -> Boolfunc requestPayment()}protocol BorrowerBehavior {func askForMoney(lender:LenderBehavior, money:Int)func receiveMoney(lender:LenderBehavior, money:Int)func payMoneyBack() -> Int?}// Chủ nợclass Lender:Person, LenderBehavior {var borrower:BorrowerBehavior? // thay vì là Borrower, thay lại BorrowerBehaviorfunc lendMoney(borrower:BorrowerBehavior, money:Int) -> Bool {// Code như cũ}func requestPayment() {// Code như cũ}}// Con nợclass Borrower:Person, BorrowerBehavior {weak var lender:LenderBehavior?var debt:Int = 0func receiveMoney(lender:LenderBehavior, money:Int) {// code như cũ}func payMoneyBack() -> Int? {// code như cũ}func askForMoney(lender:LenderBehavior, money:Int) {// Code như cũ}}// logic vay tiền và đòi tiền đâu đó trong project như saulet lenderObj = Lender(name: "Teo", age: 22)lenderObj.money = 2000let borrowerObj = Borrower(name: "Ti", age: 18)// Mượn tiềnborrowerObj.askForMoney(lenderObj, money: 1000)// Chủ nợ đòi tiềnlenderObj.requestPayment()
Điều gì xảy ra nếu ra có class Worker nào đó mà muốn vay tiền ?? Nếu Worker là subclass của Person thì mọi thứ khá đơn giản, nhưng nếu nó đang là subclass của 1 class khác thì sao ?? Ta biết trong hướng đối tượng, 1 class không thể kế thừa từ 2 class. Vì có Protocol rồi thì mọi thứ sẽ đơn giản hơn nhiều:
class Worker:SomeClass, BorrowerBehavior {// mọi người thử viết cho class này nhé}// Hay thậm chí là 1 Company vừa phải vay tiền và lại vừa có thể đầu tư (cho vay)class Company:OtherClass, BorrowerBehavior, LenderBehavior {// ...}
Ta thấy rằng dù class Worker có nhìu logic của nó đến chừng nào thì việc mượn tiền chỉ cần adopt protocol BorrowerBehavior và viết chi tiết các method bên trong. Với Company cũng thế :).
Lúc này 2 đối tượng Lender - Borrower không còn kết dính nữa, chúng cũng chẳng biết ai là ai, con nợ và chủ nợ lúc này chỉ còn là “người có khả năng cho vay” và “người có thể sẽ phải vay tiền”. Từ đó ta có thể linh hoạt sử dụng các đối tượng hơn, không cứ phải là Lender hay Borrower nữa.
4. Tầm quan trọng của Protocol
Protocol đóng vai trò như một chất phân giải các đối tượng, giúp chúng độc lập với nhau. Vì vậy nó được xem như là nền tảng của các kiến trúc ứng dụng đương đại như: VIPER, Clean Architect, ...
Protocol hầu như xuất hiện mọi nơi trong ứng dụng iOS, điển hình như: UITableView và UICollectionView chỉ làm nhiệm vụ layout các cell, scroll để thấy các cell tiếp theo. Nhưng chúng sẽ không tự giải quyết được có bao nhiêu item, section trong chúng, cũng như cell cho một vị trí cụ thế (indexPath) sẽ ra sao.... Những cái đó sẽ được “ủy thác" cho datasource. Tới đây mọi người cũng hiểu luôn cái delegate (cũng là dùng Protocol) của 2 view kinh điển này rồi nhé :)
Nguồn: IDE Academy
Post a Comment