MVVM là một hỗ trợ băng tần cho các lớp liên kết dữ liệu được thiết kế kém. Đặc biệt, nó đã được sử dụng rất nhiều trong thế giới WPF / silverlight / WP7 vì những hạn chế trong liên kết dữ liệu trong WPF / XAML.
Từ giờ trở đi, tôi sẽ cho rằng chúng ta đang nói về WPF / XAML vì điều này sẽ làm cho mọi thứ rõ ràng hơn. Hãy xem xét một số thiếu sót mà MVVM đặt ra để giải quyết trong WPF / XAML.
Hình dạng dữ liệu so với hình dạng UI
'VM' trong MVVM tạo ra một tập hợp các đối tượng được xác định trong C # ánh xạ vào một tập hợp các đối tượng trình bày được xác định trong XAML. Các đối tượng C # này thường được kết nối với XAML thông qua các thuộc tính DataContext trên các đối tượng trình bày.
Do đó, biểu đồ đối tượng viewmodel cần ánh xạ lên biểu đồ đối tượng trình bày của ứng dụng của bạn. Điều đó không có nghĩa là ánh xạ cần phải là một đối một, nhưng nếu điều khiển danh sách được chứa bởi điều khiển cửa sổ, thì phải có cách lấy từ đối tượng DataContext của cửa sổ đến một đối tượng mô tả dữ liệu của danh sách đó.
Biểu đồ đối tượng viewmodel tách riêng biểu đồ đối tượng mô hình khỏi biểu đồ đối tượng ui thành công, nhưng với chi phí của một lớp viewmodel bổ sung phải được xây dựng và duy trì.
Nếu tôi muốn chuyển một số dữ liệu từ màn hình A sang màn hình B, tôi cần phải loay hoay với chế độ xem. Trong suy nghĩ của một anh chàng kinh doanh, đây là một sự thay đổi UI. Nó sẽ diễn ra hoàn toàn trong thế giới của XAML. Đáng buồn thay, nó hiếm khi có thể. Tồi tệ hơn, tùy thuộc vào cách cấu trúc các khung nhìn và cách dữ liệu thay đổi tích cực, khá nhiều định tuyến lại dữ liệu có thể được yêu cầu để thực hiện thay đổi này.
Làm việc xung quanh ràng buộc dữ liệu không ấn tượng
Các ràng buộc WPF / XAML không đủ biểu cảm. Về cơ bản, bạn có thể cung cấp một cách để đến một đối tượng, một đường dẫn thuộc tính để đi qua và các trình biến đổi ràng buộc để điều chỉnh giá trị của thuộc tính dữ liệu với những gì đối tượng trình bày yêu cầu.
Nếu bạn cần liên kết một thuộc tính trong C # với bất kỳ thứ gì phức tạp hơn thế, thì về cơ bản bạn đã hết may mắn. Tôi chưa bao giờ thấy một ứng dụng WPF mà không có trình chuyển đổi ràng buộc biến thành đúng / sai thành Hiển thị / Thu gọn. Nhiều ứng dụng WPF cũng có xu hướng có một cái gì đó gọi là NegatingVisibilityConverter hoặc tương tự làm đảo lộn sự phân cực. Điều này nên được đặt ra chuông báo động.
MVVM cung cấp cho bạn các hướng dẫn để cấu trúc mã C # của bạn có thể được sử dụng để vượt qua giới hạn này. Bạn có thể hiển thị một thuộc tính trên chế độ xem của mình được gọi là someButtonVisibility và chỉ cần liên kết nó với khả năng hiển thị của nút đó. XAML của bạn bây giờ rất đẹp và đẹp ... nhưng bạn đã biến mình thành một nhân viên bán hàng - bây giờ bạn phải phơi bày + cập nhật các ràng buộc ở hai nơi (UI và mã trong C #) khi UI của bạn phát triển. Nếu bạn cần cùng một nút trên một màn hình khác, bạn đã phải phơi bày một thuộc tính tương tự trên chế độ xem mà màn hình đó có thể truy cập. Tệ hơn nữa, tôi không thể chỉ nhìn vào XAML và xem khi nào nút này sẽ hiển thị nữa. Ngay khi các ràng buộc trở nên hơi không cần thiết, tôi phải làm công việc thám tử trong mã C #.
Truy cập dữ liệu được tích cực trong phạm vi
Vì dữ liệu thường xâm nhập vào giao diện người dùng thông qua các thuộc tính DataContext, thật khó để thể hiện dữ liệu toàn cầu hoặc phiên trong toàn bộ ứng dụng của bạn.
Ý tưởng về "người dùng hiện đang đăng nhập" là một ví dụ tuyệt vời - đây thường là một điều thực sự toàn cầu trong một phiên bản của ứng dụng của bạn. Trong WPF / XAML, rất khó để đảm bảo quyền truy cập toàn cầu vào người dùng hiện tại một cách nhất quán.
Những gì tôi muốn làm là sử dụng từ "CurrentUser" trong các ràng buộc dữ liệu một cách tự do để chỉ người dùng hiện đang đăng nhập. Thay vào đó, tôi phải đảm bảo rằng mọi DataContext cung cấp cho tôi một cách để đến đối tượng người dùng hiện tại. MVVM có thể chứa được điều này, nhưng các khung nhìn sẽ trở nên lộn xộn vì tất cả chúng đều phải cung cấp quyền truy cập vào dữ liệu toàn cầu này.
Một ví dụ khi MVVM rơi xuống
Nói rằng chúng tôi có một danh sách người dùng. Bên cạnh mỗi người dùng, chúng tôi muốn hiển thị nút "xóa người dùng", nhưng chỉ khi người dùng hiện đang đăng nhập là quản trị viên. Ngoài ra, người dùng không được phép tự xóa.
Các đối tượng mô hình của bạn không nên biết về người dùng hiện đang đăng nhập - họ sẽ chỉ đại diện cho hồ sơ người dùng trong cơ sở dữ liệu của bạn, nhưng bằng cách nào đó, người dùng hiện đang đăng nhập cần phải tiếp xúc với các ràng buộc dữ liệu trong các hàng danh sách của bạn. MVVM ra lệnh rằng chúng ta nên tạo một đối tượng viewmodel cho mỗi hàng danh sách bao gồm người dùng hiện đang đăng nhập với người dùng được đại diện bởi hàng danh sách đó, sau đó hiển thị một thuộc tính có tên là "DeleteButtonVisibility" hoặc "CanDelete" trên đối tượng viewmodel đó (tùy thuộc vào cảm xúc của bạn về bộ chuyển đổi ràng buộc).
Đối tượng này sẽ trông rất giống một đối tượng Người dùng theo hầu hết các cách khác - nó có thể cần phải phản ánh tất cả các thuộc tính của đối tượng mô hình người dùng và chuyển tiếp cập nhật cho dữ liệu đó khi nó thay đổi. Cảm giác này thực sự tuyệt vời - một lần nữa, MVVM khiến bạn trở thành một nhân viên bán hàng bằng cách buộc bạn phải duy trì đối tượng giống người dùng này.
Xem xét - có lẽ bạn cũng phải thể hiện các thuộc tính của người dùng trong cơ sở dữ liệu, mô hình và chế độ xem. Nếu bạn có API giữa bạn và cơ sở dữ liệu của bạn, thì điều đó còn tệ hơn - chúng được thể hiện trong cơ sở dữ liệu, máy chủ API, ứng dụng khách API, mô hình và chế độ xem. Tôi thực sự do dự khi áp dụng một mẫu thiết kế đã thêm một lớp khác cần được chạm vào mỗi khi một thuộc tính được thêm hoặc thay đổi.
Thậm chí tệ hơn, lớp này chia tỷ lệ với độ phức tạp của giao diện người dùng của bạn, chứ không phải với độ phức tạp của mô hình dữ liệu của bạn. Thường thì cùng một dữ liệu được thể hiện ở nhiều nơi và trong giao diện người dùng của bạn - điều này không chỉ thêm một lớp, nó thêm lớp với nhiều diện tích bề mặt thêm!
Làm thế nào mọi thứ có thể đã được
Trong trường hợp được mô tả ở trên, tôi muốn nói:
<Button Visibility="{CurrentUser.IsAdmin && CurrentUser.Id != Id}" ... />
Hiện tại Người dùng sẽ được tiếp xúc trên toàn cầu với tất cả XAML trong ứng dụng của tôi. Id sẽ đề cập đến một thuộc tính trên DataContext cho hàng danh sách của tôi. Tầm nhìn sẽ tự động chuyển đổi từ boolean. Mọi cập nhật cho Id, CurrentUser.IsAdmin, CurrentUser hoặc CurrentUser.Id sẽ kích hoạt cập nhật cho khả năng hiển thị của nút này. Dễ như ăn bánh.
Thay vào đó, WPF / XAML buộc người dùng của mình tạo ra một mớ hỗn độn. Theo như tôi có thể nói, một số blogger sáng tạo đã tát một tên vào mớ hỗn độn đó và tên đó là MVVM. Đừng để bị lừa - nó không cùng lớp với các mẫu thiết kế của GoF. Đây là một hack xấu xí để làm việc xung quanh một hệ thống ràng buộc dữ liệu xấu xí.
(Cách tiếp cận này đôi khi được gọi là "Lập trình phản ứng chức năng" trong trường hợp bạn đang tìm đọc thêm).
Túm cái vạy lại là
Nếu bạn phải làm việc trong WPF / XAML, tôi vẫn không đề xuất MVVM.
Bạn muốn mã của mình được cấu trúc như ví dụ "cách mọi thứ có thể xảy ra" ở trên sẽ có nó - mô hình được hiển thị trực tiếp để xem, với các biểu thức liên kết dữ liệu phức tạp + các giá trị linh hoạt. Đó là cách tốt hơn - dễ đọc hơn, dễ ghi hơn và dễ bảo trì hơn.
MVVM cho bạn biết cấu trúc mã của bạn theo cách dài dòng hơn, ít bảo trì hơn.
Thay vì MVVM, hãy xây dựng một số nội dung để giúp bạn ước tính trải nghiệm tốt: Phát triển một quy ước để hiển thị trạng thái toàn cầu cho UI của bạn một cách nhất quán. Xây dựng cho mình một số công cụ từ các bộ chuyển đổi liên kết, MultiBinding, vv cho phép bạn thể hiện các biểu thức ràng buộc phức tạp hơn. Xây dựng cho mình một thư viện các bộ chuyển đổi ràng buộc để giúp các trường hợp cưỡng chế thông thường bớt đau đớn.
Thậm chí tốt hơn - thay thế XAML bằng một cái gì đó biểu cảm hơn. XAML là một định dạng XML rất đơn giản để khởi tạo các đối tượng C # - sẽ không khó để đưa ra một biến thể biểu cảm hơn.
Đề nghị khác của tôi: không sử dụng bộ công cụ buộc các loại thỏa hiệp này. Chúng sẽ làm tổn hại đến chất lượng sản phẩm cuối cùng của bạn bằng cách đẩy bạn về phía crap như MVVM thay vì tập trung vào miền vấn đề của bạn.