MVVM - це смугова допомога для погано розроблених шарів зв’язування даних. Зокрема, він отримав велику користь у світі WPF / silverlight / WP7 через обмеження у прив'язці даних у WPF / XAML.
Відтепер я припускаю, що ми говоримо про WPF / XAML, оскільки це зробить все більш зрозумілим. Розглянемо деякі недоліки, які MVVM має вирішити в WPF / XAML.
Форма даних та форма інтерфейсу користувача
'VM' у MVVM створює набір об'єктів, визначених у C #, які відображають набір об’єктів презентації, визначених у XAML. Ці об'єкти C # зазвичай підключаються до XAML через властивості DataContext на об'єктах презентації.
Як результат, графік об'єкта viewmodel повинен відображати на графіку об'єкта презентації вашої програми. Це не означає, що відображення повинно бути однозначним, але якщо контроль списку міститься у вікні управління, то повинен бути спосіб дістатися з об’єкта DataContext вікна до об'єкта, який описує дані цього списку.
Об'єктний графік viewmodel успішно відокремлює модельний графік об'єкта від графічного об'єкта ui, але за рахунок додаткового шару viewmodel, який необхідно будувати та підтримувати.
Якщо я хочу перенести деякі дані з екрана А на екран В, мені потрібно зіпсуватись із переглядами моделей. У розумі ділового хлопця, це зміна інтерфейсу користувача. Це має відбуватися виключно у світі XAML. На жаль, це рідко може. Гірше, залежно від того, як структуровані моделі перегляду та наскільки активно змінюються дані, для досягнення цієї зміни може знадобитися небагато перенаправлення даних.
Обізнання неекспресивних прив’язок даних
Прив'язки WPF / XAML недостатньо виразні. В основному ви маєте запропонувати спосіб дістатися до об'єкта, шлях властивості пройти, а також прив'язувати перетворювачі, щоб адаптувати значення властивості даних до того, що потрібно для об’єкта презентації.
Якщо вам потрібно прив’язати властивість у C # до чогось більш складного, ніж це, вам, по суті, не пощастить. Я ніколи не бачив додаток WPF без прив'язуючого перетворювача, який перетворив true / false у Visible / Collapsed. У багатьох додатках WPF також є щось, що називається NegatingVisibilityConverter або подібне, що перевершує полярність. Це має бути вимкненням дзвінків тривоги.
MVVM дає вам вказівки щодо структурування коду C #, який можна використовувати для згладження цього обмеження. Ви можете розкрити властивість у своїй моделі перегляду під назвою SomeButtonVisibility і просто прив'язати її до видимості цієї кнопки. Ваш XAML приємний і симпатичний зараз ... але ви перетворилися на службовця - тепер вам доведеться виставити + оновлення прив’язок у двох місцях (інтерфейс користувача та код у C #), коли ваш інтерфейс розвивається. Якщо вам потрібна така ж кнопка, щоб знаходитись на іншому екрані, вам доведеться відкрити аналогічну властивість у моделі перегляду, до якого може отримати доступ цей екран. Гірше, я не можу просто подивитися на XAML і побачити, коли кнопка вже буде видно. Як тільки прив'язки стають трохи нетривіальними, мені доводиться виконувати детективні роботи в коді C #.
Доступ до даних охоплюється агресивно
Оскільки дані зазвичай надходять у користувальницький інтерфейс через властивості DataContext, важко представляти глобальні дані або дані сеансу у вашому додатку.
Ідея "поточного користувача" - прекрасний приклад - це часто справді глобальна річ в екземплярі вашого додатка. У WPF / XAML дуже важко послідовно забезпечити глобальний доступ до поточного користувача.
Що я хотів би зробити, це використовувати слово "CurrentUser" у прив'язці даних, щоб вільно посилатися на користувача, який зараз увійшов. Натомість я повинен переконатися, що кожен DataContext дає мені спосіб дістатись до об'єкта поточного користувача. MVVM може пристосувати це, але в моделях перегляду буде безлад, оскільки всі вони повинні забезпечити доступ до цих глобальних даних.
Приклад, коли MVVM перепадає
Скажімо, у нас є список користувачів. Поруч із кожним користувачем ми хочемо відобразити кнопку "видалити користувача", але лише у тому випадку, якщо користувач, який зараз увійшов у систему, є адміністратором. Також користувачі не можуть видаляти себе.
Об'єкти вашої моделі не повинні знати про поточно зареєстрованого користувача - вони просто представлятимуть записи користувачів у вашій базі даних, але якимось чином зареєстровані в даний час користувачі повинні піддаватися прив'язці даних у рядах списку. MVVM продиктує, що ми повинні створити об’єкт viewmodel для кожного списку рядків, який складається з поточного входу користувача з користувачем, представленим цим рядком списку, а потім відкрити властивість під назвою "DeleteButtonVisibility" або "CanDelete" на цьому об'єкті viewmodel (залежно від ваших почуттів) про зв'язуючі перетворювачі).
Цей об'єкт буде виглядати дуже багато як об’єкт User у більшості інших способів - можливо, він повинен буде відображати всі властивості об'єкта моделі користувача та пересилати оновлення до цих даних у міру зміни. Це відчуває насправді неприємно - знову ж таки, MVVM робить вас діловодом, змушуючи підтримувати цей об’єкт, що працює на користувача.
Подумайте - ви, ймовірно, також повинні представляти властивості свого користувача в базі даних, моделі та представлення. Якщо у вас є API між вами та вашою базою даних, то це ще гірше - вони представлені в базі даних, сервері API, клієнтові API, моделі та представленні даних. Я б дуже вагався прийняти шаблон дизайну, який додав би ще один шар, який потрібно торкатися кожного разу, коли властивість додається чи змінюється.
Ще гірше, що цей шар масштабується зі складністю вашого інтерфейсу, а не зі складністю вашої моделі даних. Часто одні і ті ж дані представлені в багатьох місцях і у вашому інтерфейсі - це не лише додає шар, але додає шар з великою кількістю зайвої поверхні!
Як все могло бути
У випадку, описаному вище, я хочу сказати:
<Button Visibility="{CurrentUser.IsAdmin && CurrentUser.Id != Id}" ... />
CurrentUser буде виставлений на весь світ для всіх XAML в моєму додатку. Id буде посилатися на властивість у DataContext для мого рядка списку. Видимість автоматично перетвориться з булева. Будь-які оновлення Id, CurrentUser.IsAdmin, CurrentUser або CurrentUser.Id спричинить оновлення видимості цієї кнопки. Простенька.
Натомість WPF / XAML змушує своїх користувачів створювати повний безлад. Наскільки я можу сказати, деякі креативні блогери ляснули ім’я в цьому безладі, і це ім'я було MVVM. Не обманюйте - це не той самий клас, як шаблони дизайну GoF. Це некрасивий хакер для вирішення проблеми з потворною системою прив'язки даних.
(Цей підхід іноді називають "функціональним реактивним програмуванням" у випадку, якщо ви шукаєте для подальшого читання).
У висновку
Якщо вам потрібно працювати в WPF / XAML, я все одно не рекомендую MVVM.
Ви хочете, щоб ваш код був структурований так, як у прикладі "як все могло бути", це було б - модель піддається прямому перегляду зі складними виразами зв'язування даних + гнучкими коерционами значення. Це набагато краще - легше читати, читати і підтримувати.
MVVM повідомляє вам структурувати свій код більш докладно, менш рентабельним способом.
Замість MVVM побудуйте деякі матеріали, які допоможуть вам наблизити хороший досвід: Розробляйте конвенцію про послідовне відкриття глобального стану для вашого інтерфейсу. Створіть собі деякий інструментарій із перетворювачів зв'язування, багатозв'язності тощо, що дозволяє виражати більш складні вирази зв’язування. Створіть собі бібліотеку обов'язкових перетворювачів, щоб зробити менш поширеними випадки примусу.
Ще краще - замініть XAML чимось більш виразним. XAML - це дуже простий формат XML для інстанціювання об'єктів C # - не важко буде придумати більш виразний варіант.
Моя інша рекомендація: не використовуйте набори інструментів, які змушують подібні компроміси. Вони зашкодять якості вашого кінцевого продукту, підштовхуючи вас до лайна, як MVVM, замість того, щоб зосередитись на вашому проблемному домені.