Android Architecture Patterns (phần 1): Model-View-Controller
Cách đây 1 năm, phần lớn team Android hiện tại của tôi đã bắt tay làm ứng dụng upday – 1 ứng dụng không mạnh mẽ, cũng không ổn định như mong đợi. Chúng tôi đã cố gắng tìm hiểu lý do tại sao kết quả các dòng code của mình lại ra như thế và đã tìm ra được 2 nguyên nhân chính: do UI thay đổi liên tục và thiếu 1 architecture hỗ trợ linh hoạt.
Ứng dụng hiện đã được redesign lần thứ 4 trong 6 tháng. Design pattern được chọn có vẻ như là Model-View-Controller nhưng lại xảy ra “đột biến” khiến mọi thứ không như kì vọng.
Bài viết dưới đây sẽ giải thích rõ Model-View-Controller pattern là gì; cách chúng được áp dụng vào Android trong những năm qua; làm thế nào để tối ưu khả năng test và 1 số ưu, nhược điểm của MVC.
Pattern Model-View-Controller
Trong 1 thế giới mà logic giao diện người dùng có xu hướng thay đổi nhiều hơn business logic thì các lập trình viên desktop, web cần phải tìm cách tách biệt functionality trong giao diện người dùng. Pattern MVC chính là giải pháp đó.
- Model – layer dữ liệu, chịu trách nhiệm quản lý business logic và xử lý network hoặc database API
- View – UI Layer, visualisation của dữ liệu từ Model
- Controller – logic layer nhận thông tin từ hành vi của người dùng và cập nhật Model khi cần thiết
Như vậy, cả Controller và View đều phụ thuộc vào Model: Controller để cập nhật dữ liệu, View để cập nhật dữ liệu. Nhưng, quan trọng nhất với các dev desktop và web vào thời điểm trước đây chính là: Model được tách biệt và có thể được test độc lập với UI. Rất nhiều variants của MVC đã xuất hiện. Những variants tốt nhất đều liên quan tới liệu Model chủ động hay bị động thông báo nó đã thay đổi. Chi tiết như sau:
Passive Model
Trong phiên bản Passive Model, Controller là class duy nhất vận dụng Model. Dựa trên actions của user, Controller phải điều chỉnh Model. Sau khi Model được update, Controller sẽ thông báo View cần phải update. Lúc này, View sẽ request data từ Model.
Active Model
Trong trường hợp Controller không phải là class duy nhất modify Model, Model cần phải có 1 cách để thông báo đến View và các classes khác về các updates. Pattern Observer sẽ hỗ trợ điều này. Model gồm tập hợp các observers quan tâm đến các updates. View implement giao diện observer và đăng kí như 1 observer của Model.
Mỗi khi Model cập nhật, Mdel sẽ iterate qua bộ các observers và gọi method update
. Quá trình implement của method này trong View sau đó sẽ kích hoạt request dữ liệu mới nhất từ Model.
Model-View-Controller trong Android
Năm 2011, khi Android bắt đầu trở nên nổi tiếng hơn, các câu hỏi architecture cũng tự nhiên xuất hiện. Vì MVC là 1 trong những patterns UI nổi tiếng nhất thời gian đó nên các lập trình viên cũng đã cố gắng áp dụng vào Android.
Nếu bạn tìm trên StackOverflow những câu hỏi như: “Làm cách nào để áp dụng MVC vào Android”, 1 trong những câu trả lời phổ biến nhất là trong Android, 1 Activity gồm cả View và Controller. Câu trả lời nghe có vẻ điên rồ nhưng thời điểm đó, trọng tâm trính là tạo Model tes được và lựa chọn implementation với View và Controller lại phụ thuộc vào platform.
MVC được áp dụng như thế nào trong Android?
Ngày nay, câu hỏi làm thế nào để áp dụng pattern MVC đã có 1 câu trả lời dễ dàng hơn nhiều. Các Activities, Fragments và Views nên là Views trong thế giới MVC. Controller nên là các classes riêng biết không extend hoặc sử dụng bất kì class Android nào, tương tự với Models.
Một vấn đề nổi lên khi kết nối Controller với View, vì Controller cần phải thông báo View để update. Trong architecture Model MVC passive, Controllers cần phải giữ 1 tham chiếu đến View. Tuy tập trung vào testing, cách dễ nhất để thực hiện được điều này là phải có giao diện BaseView mà Activity/Fragment/View sẽ extend. Vì vậy, Controller sẽ có 1 tham chiếu đến BaseView.
Ưu điểm
Pattern Model-View-Controller sẽ phân tán bớt các vấn đề. Ưu điểm này không chỉ tăng khả năng test của code mà còn dễ extend hơn, cho phép implementation các tính năng mới khá dễ dàng.
Các classes Model không có bất kì tham chiếu nào đến các classes Android, mà tiến thẳng đến unit test. Controller không extend hay implement bất kì Android classes vào và nên có 1 tham chiếu đến interface class của View. Bằng cách này, unit testing của Controller là hoàn toàn có thể.
Nếu Views tuân theo nguyên tắc trách nhiệm duy nhất (single responsibility principle), sau đó vai trò của chúng chỉ để update Controller cho mỗi user event và chỉ hiển thị dữ liệu từ Model mà không implement bất kì business logic nào. Trong trường hợp này, nên có đủ UI tests để đáp ứng được hết các functionalities của View.
Khuyết điểm
View phụ thuộc vào Controller và Model
Việc phụ thuộc của View vào Model ban đầu là 1 mặt trái trong Views phức tạp. Để tối thiểu logic trong View, Model nên cung cấp các methods test được cho mỗi yếu tố cần phải hiển thị. Trong 1 implementation Model active, việc này sẽ tăng số lượng classes và methods theo cấp số nhân cho dù các Observers cho mỗi loại dữ liệu là bắt buộc.
Dù View phụ thuộc vào cả Controller và Model, những thay đổi trong UI logic đòi hỏi những cập nhật trong nhiều classes, làm giảm tính linh hoạt của pattern.
Ai sẽ xử lý UI logic?
Theo pattern MVC, Controller cập nhật Model và View lấy dữ liệu để được hiển thị từ Model. Nhưng ai sẽ là người quyết định cách hiển thị dữ liệu? Là Model hay View? Hãy cân nhắc ví dụ sau: chúng ta có 1 User
, với tên và họ. Trong View, chúng ta cần hiển thị user name như “Lastname, Firstname” (Ví dụ: “Doe, John”).
Nếu vai trò của Model chỉ là cung cấp dữ liệu “thô”, đồng nghĩa là code trong View sẽ như thế này:
1
2
3
4
|
String firstName < strong class = "markup--strong markup--pre-strong" >= < / strong > userModel < strong class = "markup--strong markup--pre-strong" > . < / strong > getFirstName < strong class = "markup--strong markup--pre-strong" > ( ) ; < / strong >
String lastName < strong class = "markup--strong markup--pre-strong" >= < / strong > userModel < strong class = "markup--strong markup--pre-strong" > . < / strong > getLastName < strong class = "markup--strong markup--pre-strong" > ( ) ; < / strong >
|
1
2
3
|
nameTextView < strong class = "markup--strong markup--pre-strong" > . < / strong > setText < strong class = "markup--strong markup--pre-strong" > ( < / strong > lastName < strong class = "markup--strong markup--pre-strong" > + < / strong > ", " < strong class = "markup--strong markup--pre-strong" > + < / strong > firstName < strong class = "markup--strong markup--pre-strong" > ) < / strong >
|
Vậy View sẽ chịu trách nhiệm xử lý UI logic nhưng lúc này UI logic sẽ không thể unit test.
Cách tiếp cận khác là để Model chỉ được tiếp cận với dữ liệu cần phải hiển thị, giấu business logic bất kì khỏi View. Nhưng sau đó, hệ quả là Models sẽ xử lý cả business logic và UI logic. Nó sẽ unit test được nhưng sau khi Model xong sẽ ngầm hiểu là phụ thuộc vào View.
1
2
3
|
String name < strong class = "markup--strong markup--pre-strong" >= < / strong > userModel < strong class = "markup--strong markup--pre-strong" > . < / strong > getDisplayName < strong class = "markup--strong markup--pre-strong" > ( ) ; < / strong > nameTextView < strong class = "markup--strong markup--pre-strong" > . < / strong > setText < strong class = "markup--strong markup--pre-strong" > ( < / strong > name < strong class = "markup--strong markup--pre-strong" > ) ; < / strong >
|
Kết luận
Trong thời gian đầu của Andorid, pattern Model-View-Controller có vẻ khá rối rắm với nhiều lập trình viên, dẫn đến tình trạng code khó khăn hoặc không thể thực hiện unit test.
Sự phụ thuộc của View với Model và logic trong View dẫn code-base đến 1 trạng thái không thể phục hồi mà không refactor toàn bộ ứng dụng. Đâu là cách tiếp cận mới trong architecture? Đọc thêm blog này.
- Khôi phục cài đặt iPhone bằng phím cứng
- Ứng dụng Telemedicine là gì?
- Hồ sơ, thủ tục xác nhận hàng hóa sử dụng cho ươm tạo công nghệ
- Xuất khẩu phần mềm được luật quy định như thế nào?
- Python tiến bước trong bảng xếp hạng các ngôn ngữ lập trình phổ biến
- Các Hệ Mã Hóa
- 6 Tip để trở thành lập trình viên giỏi
- E-Logistics: Công nghệ cao, thân thiện
- Sách hay về khởi nghiệp và cuộc sống
- Alexa Alexa Rank là gì? cách tăng Alexa Rank hiệu quả nhất
- Mã sạch là gì?
- Template Magento
DVMS chuyên:
- Tư vấn, xây dựng, chuyển giao công nghệ Blockchain, mạng xã hội,...
- Tư vấn ứng dụng cho smartphone và máy tính bảng, tư vấn ứng dụng vận tải thông minh, thực tế ảo, game mobile,...
- Tư vấn các hệ thống theo mô hình kinh tế chia sẻ như Uber, Grab, ứng dụng giúp việc,...
- Xây dựng các giải pháp quản lý vận tải, quản lý xe công vụ, quản lý xe doanh nghiệp, phần mềm và ứng dụng logistics, kho vận, vé xe điện tử,...
- Tư vấn và xây dựng mạng xã hội, tư vấn giải pháp CNTT cho doanh nghiệp, startup,...
Vì sao chọn DVMS?
- DVMS nắm vững nhiều công nghệ phần mềm, mạng và viễn thông. Như Payment gateway, SMS gateway, GIS, VOIP, iOS, Android, Blackberry, Windows Phone, cloud computing,…
- DVMS có kinh nghiệm triển khai các hệ thống trên các nền tảng điện toán đám mây nổi tiếng như Google, Amazon, Microsoft,…
- DVMS có kinh nghiệm thực tế tư vấn, xây dựng, triển khai, chuyển giao, gia công các giải pháp phần mềm cho khách hàng Việt Nam, USA, Singapore, Germany, France, các tập đoàn của nước ngoài tại Việt Nam,…
Quý khách xem Hồ sơ năng lực của DVMS tại đây >>
Quý khách gửi yêu cầu tư vấn và báo giá tại đây >>