(Post 03/08/2010) 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 MVP.
Trong các nền tảng lập trình hiện đại như .NET, khi mà
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ì chúng ta thường 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 thành phần UI này
chứa nhiều xử lý logic và dữ liệu mà lẽ ra nên tách rời thành những thành
phần riêng. Việc tách rời xử lý và trình bày vì những lí do 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 thành phần UI giúp có thể thay đổi
các thành phần 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 các thành phần dữ liệu khỏi UI
còn cho phép trên cùng một thành phần dữ liệu này, ta có thể phát triển
nhiều thành phần UI khác nhau cho các cách trình bày khác nhau.
Các code xử lý đặt trong các thành phần UI còn gây khó
khăn cho việc testing (unit-test). Việc test các thành phần UI thường
hoặc phải chạy ứng dụng thủ công hoặc sử dụng các runner script để thực
hiện việc tương tác tự động lên các thành phần 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 các 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
là 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 thành phần 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.
Các vấn đề trên là các vấn đề cơ bản được đặt ra cho
các hệ thống tương tác (interactive system). Để 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.
Mẫu kiến trúc Model-View-Controller
cổ điển
Tổng quan
Mẫu kiến trúc Model-View-Controller là phương pháp chia
nhỏ các các thành phần dữ liệu (data), trình bày (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.
Lịch sử
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 hiện tại nó vẫn được sử dụng cực kì rộng rãi như là kiến trúc
cơ bản trong các môi trường lập trình web.
Cấu trúc
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 (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 trình bày từ 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, widget,…
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
sử dụng và ứng dụng.
Phối hợp các thành phần
Trong kiến trúc MVC, mỗi bộ ba Model-View-Controller
được thiết kế tương ứng cho các đối tượng mà người dùng có thể tương tác.
Model nắm giữ trạng thái, cấu trúc và các hành vi của
dữ liệu được thể hiện và tương tác bởi người dùng. Model không phục thuộc
và tương tác trực tiếp lên các thành phần khác. Thay vì vậy, khi có thay
đổi, nó thông báo cho những thành phần như View tương ứng thông qua cơ
chế là Observer pattern. Model còn cung cấp phương tiện để các thành phần
khác tương tác lên nó.
View lấy các thông tin từ Model và trình bày đến người
dùng. Trên cùng một model, có thể có nhiều View cùng đăng ký. Khi có một
thay đổi từ Model, tất cả các View đều được thông báo thông qua observer
mà nó đã đăng ký.
Mỗi View khi được tạo ra sẽ tạo ra một Controller đi
kèm với nó. Trong khi các View đảm nhận kết xuất dữ liệu thì các Controller
đảm nhận việc xử lý dữ liệu từ người dùng. Với mỗi sự kiện nhận được,
Controller có thể xử lý và tương tác trực tiếp lên thành phần View và
Model tương ứng để đáp trả. View và Controller là hai thành phần cấu thành
nên giao diện người dùng của ứng dụng. Chúng lưu giữ liên kết trực tiếp
đến Model. Trong khi Controller có thể thay đổi dữ liệu theo yêu cầu của
người dùng thì View tương tác để lấy dữ liệu cập nhật vào chính nó từ
Model.
Nhầm lẫn thường gặp
Một nhầm lẫn thường gặp trong quan hệ giữa các thành
phần MVC là khi xem mục đích của Controller như là thành phần trung gian
để tách rời View khỏi Model. Trong khi thực tế, kiến trúc MVC tách rời
dữ liệu và xử lý trung tâm khỏi phần trình bày thông qua cơ chế là Observer
Pattern chứ không phải Controller. Nhiệm vụ của Controller là cần nối
giữa người dùng là ứng dụng, không phải giữa View và Model.
Vấn đề của MVC
Trong khi mục đích chính của MVC là tách rời trình bày
và các xử lý bên trong. Việc phân rõ vai trò xử lý ouput (View) và input
(Controller) là một hệ quả nhằm hoàn thiện cơ chế cho ý tưởng trên. Hiện
nay trong nhiều môi trường lập trình hiện đại, nhiều control được cung
cấp và hoàn thiện hơn (nhiều xử lý sự kiện cơ bản đã được hỗ trợ sẵn)
so với trước đây nên việc khoáng tất cả các xử lý sự kiện cho một thành
phần độc lập như Controller không còn là vấn đề quan trọng nữa.
Trong khi đó, những cơ chế như Observer Pattern cũng
có vấn đề của riêng chúng. Trong khi đượ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, nó 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 thực hiện các testing cũng khó khăn hơn.
Hơn nữa, do Model chỉ liên kết gián tiếp đến View thông qua Observer Pattern,
khi sự thay đổi trạng thái của Model cần đến một vài thao tác xử lý phức
tạp để áp dụng lên giao diện thì với mô hình cổ điển sẽ gặp khó khăn.
Một vấn đề khác là chúng ta cần lưu trữ tình trạng hiện
tại của UI (UI state), ví dụ trong danh sách sinh viên thì chúng ta cần
biết sinh viên nào đang được chọn. Trong khi thành phần UI nắm giữ dữ
liệu trình bày đang được chọn thì dữ liệu sinh viên thuộc về Model, như
vậy dữ liệu về sinh viên được chọn sẽ được lưu trữ ở đâu khi cần truy
xuất đến?
Vì những lí do trên, MVC sau này đã có những thay đổi
và bổ sung nhất định (như khái niệm Application Model). 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 mục đích khắc phục các khuyết điểm đã có.
Mẫu kiến trúc Dolphin Smalltalk
Model-View-Presenter
Tổng quan
Mẫu kiến trúc Dolphin Smalltalk Model-View-Presenter
chia ứng dụng thành các phần dữ liệu (data), trình bày (presentation)
và các xử lý logic thuộc phần trình bày (presentation logic) thành những
thành phần riêng biệt.
Lịch sử
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.
Cấu trúc
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 (business logic).
View là thành phần đảm nhận trình bày từ 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, widget,…
Presenter là thành phần đảm nhận các xử lý về trình bày
mà nó cần đến sự tương tác trên dữ liệu.
Phối hợp các thành phần
Trong khi vai trò của Model không thay đổi so với MVC
cổ điển. Thành phần View của Dolphin MVP cũng thực hiện việc trình bày
từ nội dung của Model và gắn kết với Model thông qua Observer Pattern.
Tuy nhiên, nó thực hiện bắt các sự kiện xảy ra ngay bên trong nó. Một
vài trường hợp đơn giản như TextView, dữ liệu nhập được xử lý trực tiếp
và cập nhật thay đổi trực tiếp vào Model còn hầu hết các trường hợp khác,
việc xử lý được chuyển giao cho Presenter đảm trách khi cần thao tác đến
Model.
Trong khi View đảm nhận trình bày thì Presenter đảm trách
cách Model được thao tác và thay đổi như thế nào bởi giao diện người dùng.
Presenter là nơi chứa các xử lý đặc trưng của ứng dụng (application logic
so với business logic của Model). Một điểm đáng chú ý khác là Presenter
có khả năng thao tác trực tiếp lên View mà nó gắn kết, điều này khác biệt
với phiên bản Taligent MVP xuất hiện trước đó.
So sánh Dolphin MVP và MVC cổ điển
Điểm khác biệt cơ bản của Dolphin MVP và MVC cổ điển
là sự khác nhau về vai trò của Presenter và Controller, nó cũng dẫn đến
sự khác nhau về vai trò giữa hai thành phần View. Trong Dolphin MVP, sự
hiện diện của Controller bị loại bỏ, thay vào đó, việc xử lý các dữ liệu
input được View đảm nhận và được chuyển cho Presenter khi có yêu cầu tương
tác đến Model.
Mẫu kiến trúc MVP của Martin
Fowler
Tóm tắt
Martin Fowler là một trong những người đâu tiên đi sâu
và nghiên cứu về các phiên bản của MVP. Vào năm 2006, Fowler tuyên bố
sự kết thúc của các kiến trúc MVP cổ điển và tự chia MVP thành hai phiên
bản mới là Passive View và Supervising Controller.
Martin
Fowler dùng thuật ngữ Controller thay vì Presenter trong mẫu của
mình |
|
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 đảm nhận.
So với Supervising Controller chứa View đảm nhận xử lý
sự kiện đơn giản thì 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 Presenter xử lý. Điều này tạo thuận lợi hơn cho việc
testing vì khi đó các thành phần Model và Presenter 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
Lịch sử
Khởi đầu từ các framework Smart Client Software Factory
and Web Client Software Factory dựa trên kiến trúc MVP được các nhóm nghiên
cứu về pattern của Microsoft tạo ra từ năm 2006, Microsoft sau đó bắt
đầu đưa MVP vào các 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ấu trúc
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 (business logic).
View là thành phần đảm nhận trình bày từ 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ý về trình bày
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 nó bổ sung thêm ràng buộc là các Presenter chỉ có thể truy
cập đến View thông qua các 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 test Presenter
một cách độc lập. Điều này đạt được do sử dụng IView, chúng ta có thể
dùng kĩ thuật “Mock” để thay thế các xử lý cụ thể của View khi test 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ả của các liên kết là chúng ta có một mô hình
đa lớp (multi-layer) như cấu trúc ở trên theo ý nghĩa các thành phần ở
một layer chỉ phụ thuộc và sử dụng các thành phần ở layer ngay bên dưới
nó.
Theo mẫu Microsoft MVP, khi 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 đó, nó 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ý data nằm 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, thay
vì sử dụng Presenter để tương tác với Model, một cách là giao tất cả các
thao tác này cho Application Controller đảm trách.
Một ví dụ minh họa đơn giản
Để 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 trên danh sách sinh viên đó. Với mục tiêu là minh họa
cách tổ chức và liên kết giữa các thành phần là chủ yếu, 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à application
còn các project khác là library. 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ức các loại dữ liệu được dùng chung cho 3 project View, Preseneter 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
TDD (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.
Tài liệu tham khảo:
Võ Hồng Vinh |