Mô hình kiến trúc iOS (phần 2)
MVVM
Mới nhất và tuyệt vời nhất trong mô hình MV(X)
MVVM là mới nhất trong kiểu MV(X), hãy hy vọng nó sẽ giải quyết được các vấn đề mà MV(X) đối mặt trước đây.
Về lý thuyết Model-View-ViewModel rất tốt. View và Model đã quen thuộc với chúng ta, nhưng còn phần trung gian, là ViewModel.
Nó khá giống với MVP:
- MVVM xử lý ViewController như là View.
- Không có liên kết chặt chẽ giữa View và Model.
Ngoài ra, nó binding giống như phiên bản Supervising của MVP. Tuy nhiên, không phải giữa View và Model mà là giữa View và ViewModel.
Các ViewModel trong iOS thực tế là gì? Đó là UIKit đại diện cho View và các state của nó. ViewModel sẽ gọi ra những thay đổi trong Model và cập nhật chính nó với bản Model mới cập nhật và chúng ta có 1 binding giữa View và ViewModel.
Bindings
Tôi đã giới thiệu vắn tắt trong phần MVP, nhưng chúng ta hãy thảo luận một chút ở đây. Bindings được lấy ra từ quá trình phát triển phần mềm của OSX, nhưng chúng ta không có nó trong công cụ của iOS. Tất nhiên là chúng ta có KVO và notification, nhưng nó không thuận tiện như binding.
Vì vậy, tôi không muốn nói về chúng nữa, chúng ta có 2 lựa chọn:
- Một kiểu KVO dựa trên thư viện binding như RZDataBinding hoặc SwiftBond.
- functional reactive programming như ReactiveCocoa, RxSwift or PromiseKit
Trong thực tế, khi bạn nghe thấy “MVVM” - bạn nghĩ ngay đến ReactiveCocoa và ngược lại. Mặc dù nó có thể được xây dựng dựa trên MVVM với các binding đơn giản, ReactiveCocoa (hoặc anh chị em của nó) sẽ cho phép bạn sử dụng hầu hết các phần của MVVM.
Có một sự thật cay đắng về reactive framework là khả năng lớn đi kèm với trách nhiệm lớn. Nó thực sự dễ lộn xộn khi bạn reactive. Nói cách khác, khi bạn làm điều gì đó sai, bạn có thể phải dành nhiều thời gian cho việc gỡ lỗi cho ứng dụng, vì vậy cần có 1 cái nhìn tổng quan về call stack.
Trong ví dụ dưới, FRF framework hoặc KVO là quá mức cần thiết, thay vì chúng ta yêu cầu rõ ràng View Model cập nhật sử dụng method showGreeting và sử dụng property cho callback của greetingDidChange.
1 |
|
Và cùng đánh giá các tính năng của MVVM:
- Sự phân chia - nó không rõ ràng trong ví dụ ở trên, nhưng thực tế View của MVVM có nhiều trách nhiệm hơn View của MVP. Bởi vì View của MVVM cập nhật từ ViewModel bằng cách cài đặt binding, View của MVP chỉ chuyển tiếp các sự kiện đến Presenter và không cập nhật chính nó.
- Khả năng kiểm thử - ViewModel không biết gì về View, điều này cho phép chúng ta dễ dàng kiểm thử. View cũng có thể được kiểm thử, nhưng nó phụ thuộc vào UIKit làm bạn có thể muốn bỏ qua nó.
- Dễ sử dụng - nó có số lượng dòng code tương tự như MVP trong ví dụ trên, nhưng trong thực tế, bạn muốn chuyển tiếp tất cả các sự kiện từ View đến Presenter và cập nhật View thủ công, MVVM có thể ít code hơn nếu bạn sử dụng binding.
VIPER
Áp dụng kinh nghiệm chơi LEGO vào thiết kế iOS app
VIPER là ứng viên cuối của chúng ta, nó đặc biệt thú vị vì nó không thuộc loại MV(X).
Từ bây giờ bạn phải đồng ý rằng mức độ chi tiết trong trách nhiệm là rất tốt. VIPER lặp lại ý tưởng phân chia trách nhiệm, và lần này chúng ta có 5 layer.
- Interactor - chứa các business logic liên quan đến data (Entities) hoặc networking, giống như tạo các instance mới của entity hoặc fetching chúng từ server. Để áp dụng bạn sẽ sử dụng một vài Services và Managers mà không được xem như là phần của mô-đun VIPER mà chỉ là 1 phần mở rộng bên ngoài.
- Presenter - có chứa các business logic có liên quan đến UI(nhưng độc lập với UIKit), gọi các method trong Interactor.
- Entities - là những đối tượng dữ liệu (data objects) của bạn, không phải là lớp truy cập dữ liệu (data access layer) bởi vì đó là trách nhiệm của Interactor.
- Router - chịu trách nhiệm cho việc định tuyến (segues) giữa các mô-đun VIPER.
Về cơ bản, module VIPER có thể là một màn hình hoặc toàn bộ các user story ứng dụng của bạn - nghĩ về xác thực, đó có thể là một màn hình hoặc một vài màn hình liên quan.
Nếu chúng ta so sánh nó với kiểu MV(X), chúng ta sẽ thấy một vài điểm khác về sự phân chia trách nhiệm:
- Model (tương tác dữ liệu) logic chuyển vào Interactor với các entities như những cấu-trúc dữ-liệu (data structure) ngớ ngẩn.
- Nhiệm vụ biểu diễn UI của Controller/Presenter/ViewModel được di chuyển vào Presenter, nhưng không có khả năng thay đổi dữ liệu.
- VIPER là mô hình đầu tiên giải quyết một cách rõ ràng trách nhiệm điều hướng, và được thực hiện bởi Router.
Ví dụ dưới không bao gồm định tuyến (routing) hay tương tác giữa các module, giống như những ví dụ của các mô hình thuộc MV(X).
1 |
|
Một lần nữa, cùng nhìn lại với các tính năng:
- Sự phân chia - không nghi ngờ gì nữa, VIPER là nhà vô địch trong phân chia trách nhiệm.
- Khả năng kiểm thử - không có gì ngạc nhiên, phân chia tốt hơn kéo theo khả năng kiểm thử tốt hơn.
- Dễ sử dụng - Cuối cùng, hai điều ở trên kéo theo chi phí bảo trì như thế nào bạn cũng đoán được. Nhưng bạn phải viết số lượng rất lớn interface cho các class với những trách nhiệm rất nhỏ.
Những gì về LEGO?
Trong khi sử dụng VIPER, bạn có cảm giác giống như xây dựng toà nhà Empire State từ những khối trò chơi LEGO, và đó là dấu hiệu cho thấy bạn đang có vấn đề. Có lẽ, còn quá sớm để áp dụng VIPER cho ứng dụng của bạn và bạn nên cân nhắc cái gì đó đơn giản hơn. Một số người bỏ qua điều này và tiếp tục bắn pháo vào một con chim sẻ [0]. Tôi đoán họ tin rằng các ứng dụng của họ sẽ được hưởng lợi từ VIPER ít nhất là trong tương lai, ngay cả khi hiện nay chi phí bảo trì cao bất hợp lý. Nếu bạn cũng tin như vậy, tôi muốn khuyên bạn nên thử dùng Generamba - một công cụ tạo bộ khung cho VIPER. Đối với tôi nó giống như việc sử dụng hệ thống nhắm tự động cho khẩu pháo thay vì đơn giản là dùng ná cao su.
Cùng so sánh về mặt tính năng giữa các mô hình kiến trúc ta đã nói ở trên qua slide của tác giả
Kết luận
Chúng ta đã tìm hiểu qua một số mô hình kiến trúc, và tôi hy vọng bạn đã tìm thấy câu trả lời cho những gì bạn cần, nhưng bạn cần biết rằng không có viên đạn bạc [1] cho việc lựa chọn mô hình kiến trúc, đây là vấn đề của sự cân bằng trong tình huống cụ thể của bạn.
Vì vậy, cũng là tự nhiên khi kết hợp nhiều mô hình kiến trúc trong cùng một ứng dụng. Ví dụ: bạn đã bắt đầu với MVC, sau đó bạn nhận ra rằng một màn hình cụ thể đã trở nên quá khó khăn để bảo trì hiệu quả với MVC và chuyển sang MVVM, nhưng chỉ áp dụng riêng màn hình đặc biệt này. Không cần tái cấu trúc lại các màn hình khác mà MVC thực sự không làm việc tốt, bởi vì cả hai kiến trúc có thể dễ dàng tương thích với nhau.
[0]. Chú thích của người dịch: tác giả ám chỉ đến việc sử dụng “dao mổ trâu để giết gà” ↩
[1]. silver bullet, xuất phát từ những câu chuyện về người sói. Người sói được cho là dễ bị thương hoặc có thể chết vì viên đạn bạc.↩