Trong các nền tảng lập trình hiện đại như .NET, khi các công cụ và kỹ thuật hỗ trợ lập trình giao diện người dùng (UI) ngày càng trở nên mạnh mẽ và tiện dụng thì người ta có xu hướng đưa nhiều xử lý bên ngoài vào các lớp UI. Kết quả là các UI chứa nhiều xử lý logic và dữ liệu mà lẽ ra nên tách rời thành từng phần riêng. Lý do của việc tách rời như sau:
• Trong một hệ thống, UI là thành phần có nhiều khả năng thay đổi nhất nên việc tách rời các UI giúp có thể thay đổi các UI này một cách độc lập mà không ảnh hưởng đến các thành phần xử lý khác. Đặc biệt việc tách rời dữ liệu khỏi UI còn cho phép ta có thể phát triển nhiều UI khác nhau cho các cách thể hiện khác nhau.
• Mã lệnh (code) xử lý đặt trong UI gây khó khăn cho việc kiểm tra (test). Việc kiểm tra các UI thường hoặc phải chạy ứng dụng thủ công hoặc sử dụng kịch bản (script) để thực hiện việc tương tác tự động lên các UI, do đó sẽ tốn nhiều chi phí và thời gian hơn để kiểm tra các xử lý bên trong thành phần này.
• Việc đưa nhiều xử lý vào các lớp UI còn dẫn đến khả năng trùng lắp code xử lý. Những xử lý lẽ ra có thể dùng chung thì lại xuất hiện lặp lại ở nhiều UI có chức năng tương tự nhau. Việc tách các xử lý này ra ngoài ra còn tăng tính tái sử dụng code và dễ bảo trì hơn.
Đây là các vấn đề cơ bản được đặt ra cho các hệ thống tương tác. Để giải quyết chúng, một số giải pháp được đề ra, trong đó phổ biến nhất là mẫu kiến trúc MVC (Model-View-Controller). Trong bài viết này, chúng ta sẽ khảo sát mẫu kiến trúc MVC cổ điển và một mẫu kiến trúc thừa kế từ nó, hiện được ứng dụng rộng rãi trong môi trường .NET, đó là kiến trúc Model-View-Presenter (MVP). Ý nghĩa của các thuật ngữ trong hình minh họa vui lòng xem "Giải thích các project" cuối bài.
Model-View-Controller cổ điển
MVC được hình thành bởi các nghiên cứu của Trygve Reenskaug vào khoảng các năm 1978-1979. Sau đó nó được điều chỉnh và được cài đặt lần đầu tiên vào các lớp của thư viện Xerox PARC Smalltalk-80. MVC cổ điển hiện tại ít được sử dụng trong môi trường lập trình desktop như trước đây, nhưng vẫn được sử dụng rộng rãi trong môi trường lập trình web.
Mẫu kiến trúc Model-View-Controller chia nhỏ các thành phần dữ liệu, thể hiện (output) và dữ liệu nhập từ người dùng (input) thành những thành phần riêng biệt.
Model chứa dữ liệu và các tính toán xử lý logic để giải quyết vấn đề mà phần mềm hướng tới (business logic). Thành phần model thường được trình bày ở dạng Domain Model.
View là thành phần đảm nhận việc thể hiện những dữ liệu của Model. View bao gồm những gì thể hiện trên màn hình như các control, form... Trên cùng một Model, có thể có nhiều View.
Controller là thành phần đảm nhận việc xử lý đáp trả lại các dữ liệu được đưa vào từ người dùng như các sự kiện chuột, bàn phím, các tương tác lên các control... Controller là cầu nối giữa người dùng và ứng dụng.
Mục đích chính của MVC là tách rời phần thể hiện và các xử lý bên trong. Trong nhiều môi trường lập trình hiện đại, nhiều xử lý sự kiện cơ bản đã được hỗ trợ sẵn nên thành phần Controller không còn quan trọng nữa.
Trong khi đó, những cơ chế như Observer Pattern được dùng như phương tiện hiệu quả để loại bỏ sự phụ thuộc của Model vào các thành phần khác, có một vấn đề lớn là tại một thời điểm, chúng ta khó có thể xác định điều gì sẽ xảy ra bằng cách đọc code và việc kiểm tra cũng khó khăn hơn.
Ngoài ra còn những vấn đề khác như lưu trữ tình trạng hiện tại của UI... Vì vậy MVC sau này đã có những thay đổi và bổ sung nhất định. Kiến trúc MVP chúng ta sẽ bàn dưới đây cũng dựa trên tư tưởng cơ bản của MVC nhưng với cách tiếp cận khác nhằm khắc phục các hạn chế của MVC cổ điển.
Mẫu Dolphin Smalltalk Model-View-Presenter
Phiên bản Dolphin Smalltalk Model-View-Presenter (gọi tắt là Dolphin MVP) là phiên bản của MVP được xây dựng dựa trên phiên bản Taligent MVP xuất hiện trước đó. Dolphin MVP được xây dựng về cơ bản bên ngoài tương tự như MVC cổ điển nhưng khác nhau ở vai trò của Controller và Presenter.
Mẫu kiến trúc Dolphin Smalltalk Model-View-Presenter chia ứng dụng thành các phần dữ liệu, thể hiện (presentation) và xử lý logic thuộc phần thể hiện (presentation logic).
Trong mẫu Passive View, thành phần View được loại bỏ hoàn toàn các xử lý logic và tương tác đến Model. Thay vì vậy, nó chuyển giao các xử lý cho Controller đảm trách. Controller đảm nhận tương tác đến Model và cập nhật View khi có thay đổi từ Model. Controller là thành phần trung gian liên lạc giữa View và Model.
Trong mẫu Supervising Controller, View đầu tiên bắt lấy các sự kiện và sau đó chuyển giao cho Controller xử lý. Để cập nhật thay đổi từ Model, View dùng data-binding và Observer Pattern cho các xử lý đơn giản còn đối với các xử lý phức tạp sẽ nhờ đến Controller.
So với Supervising Controller có View đảm nhận xử lý sự kiện đơn giản, với Passive View thành phần View được tách rời hoàn toàn khỏi các xử lý, kể cả các xử lý cơ bản ở mức giao diện cũng được giao hoàn toàn cho Controller. Điều này tạo thuận lợi hơn cho việc kiểm tra vì khi đó các thành phần Model và Controller có thể được kiểm tra một cách độc lập mà không phụ thuộc vào giao diện. Phiên bản MVP của Microsoft đầu tiên được xây dựng dựa trên Passive View, sau đó mẫu Supervising Controller cũng được hỗ trợ với một số điều chỉnh.
Phiên bản MVP của Microsoft
Khởi đầu từ các framework Smart Client Software Factory và Web Client Software Factory dựa trên kiến trúc MVP được các nhóm nghiên cứu của Microsoft tạo ra từ năm 2006, Microsoft sau đó bắt đầu đưa MVP vào tài liệu hướng dẫn và ví dụ về lập trình giao diện trong .NET framework. Mô tả dưới đây dựa trên phiên bản MVP được mô tả trong tạp chí MSDN vào năm 2006 (tham khảo 2) và có thể xem là kiến trúc cơ bản cho các framework trên. Ngoài ra, còn có một số framework MVP khác cũng được phát triển trên nền tảng .NET như MVC# hay NMVP.
Xin lưu ý là phiên bản cũ của Web Client Software Factory ban đầu được tổ chức dựa trên Passive View (cấu trúc bên dưới) nhưng với phiên bản hiện tại thì cấu trúc dựa trên Supervising Controller cũng được hỗ trợ.
Các thành phần
Model chứa dữ liệu và các tính toán xử lý logic để giải quyết vấn đề mà phần mềm hướng tới.
View là thành phần đảm nhận việc thể hiện những dữ liệu của Model và là tổng hợp của các form, control được sử dụng.
Presenter là thành phần đảm nhận các xử lý thể hiện cũng như tương tác đến dữ liệu bên dưới và có thể tương tác để thay đổi View trong quá trình xử lý.
Phối hợp các thành phần
Mẫu kiến trúc MVP của Microsoft tương tự như Passive View nhưng có bổ sung ràng buộc là Presenter chỉ có thể truy cập đến View thông qua interface IView. Điều này giúp Presenter không phụ thuộc đến cài đặt cụ thể của View và có thể dễ dàng kiểm tra Presenter một cách độc lập. Chúng ta có thể dùng kỹ thuật "Mock" để thay thế các xử lý cụ thể của View khi kiểm tra Presenter. Ngoài ra, mối quan hệ giữa Presenter và Model là quan hệ một chiều (chiều còn lại là gián tiếp). Hệ quả là chúng ta có một mô hình đa lớp như cấu trúc ở trên theo ý nghĩa các thành phần ở một lớp chỉ phụ thuộc và sử dụng các thành phần ở lớp ngay bên dưới nó.
Theo mẫu Microsoft MVP, một View gắn liền với một interface IView. Khi View được tạo ra, nó tạo ra một đối tượng private Presenter và gắn nó vào đối tượng này thông qua IView. Khi một sự kiện xảy ra, View bắt lấy và sau đó kích hoạt một phương thức của Presenter mà sau đó có thể tương tác với Model. Một số sự kiện như bắt đầu hiển thị và đóng của View cũng được gửi tới để Presenter kích hoạt một số phương thức tương ứng, điều này giúp cho một số công việc như khởi tạo và giải phóng dữ liệu cho View được dễ dàng hơn.
Model theo Microsoft MVP cũng thường được tổ chức bổ sung một lớp Service nằm bên trên để tương tác với Presenter, qua đó giảm bớt sự phụ thuộc đến các xử lý dữ liệu sâu bên dưới. Ngoài ra, để điều khiển việc liên lạc giữa các View hay các Presenter, một thành phần thường được bổ sung gọi là Application Controller. Trong một ứng dụng có thể có nhiều Application Controller để quản lý các nhóm MVP. Ngoài ra, Application Controller có thể đảm trách việc tương tác với Model, thay vì sử dụng Presenter.
Ví dụ minh họa
Để minh họa mẫu Microsoft MVP, chúng ta sẽ cùng khảo sát một ví dụ đơn giản: tạo một form hiển thị danh sách sinh viên cùng chức năng thêm mới vào danh sách đó. Với mục tiêu minh họa cách tổ chức và liên kết giữa các thành phần, chúng ta không đi sâu vào cài đặt cụ thể (bạn có thể tham khảo source code đi kèm bài viết, source code được tổ chức ở dạng project của VS 2008).
Ứng dụng được tổ chức thành 5 project: View, Presenter, DataService, Model và DataTransferObject. Trong đó, View là ứng dụng còn các project khác là thư viện. Các project phụ thuộc lẫn nhau theo thứ tự project phía trước sử dụng trực tiếp project phía sau, ngoại trừ DataTransferObject chứa các loại dữ liệu được dùng chung cho 3 project View, Presenter và DataService. Model là project chứa các tương tác trực tiếp đến dữ liệu và không phụ thuộc vào bất cứ thành phần nào khác.
Giải thích các project:
- Model: Chứa dữ liệu giả lập (StudentData.xml), schema (StudentDataSet.xsd) và lớp tiện ích được xây dựng bên trên (DataAccess.cs).
- DataTransferObject: Chứa đối tượng dữ liệu được dùng chung cho các project ở mức cao hơn (Student.cs).
- DataService: được xây dựng bên trên Model, chuyển đổi dữ liệu từ Model sang DataTransferObject và xây dựng các tiện ích bên trên đối tượng transfer này (Service.cs).
- Presenter: Chứa interface của View dùng cho Presenter truy cập (IViewForm) và lớp Presenter (StudentPresenter).
- View: Chứa form view (ViewForm.cs) và Program.cs.
Trong bài viết khác, chúng ta sẽ tìm hiểu phương pháp TTD (Test-driven development) và ứng dụng nó để phát triển ứng dụng theo mẫu Microsoft MVP trong môi trường .NET.