[Architecture Pattern] MVVM模式

转自http://www.dotblogs.com.tw/clark/archive/2011/10/02/38567.aspx

 

动机:
开发应用程式的时候,针对使用者介面开发。
业界有许多前辈提出了多种的设计模式,其中最为人所知的就是MVC模式。

MVC模式在实作上有许多种的方法,
不同的开发人员去理解它,都会有不同的理解。
不同的情景需求去套用它,也会有不同的实作。
但不论怎么理解跟实作,它最基本的观念依然都是:
「将系统职责拆解至Model、View、XXX三种类别,并且定义它们之间的相依关系及沟通方式。」

在微软.NET技术架构下,目前最为众人讨论的MVC延伸模式,
应该是适用WPF、Silverlight、Windows phone平台的MVVM模式(Model-View-ViewModel)。
可以说近年微软.NET架构下新推出的介面框架,多是主打套用这个设计模式。

本篇文章使用领域驱动设计的方式去分析设计,并且实作使用Domain Object的MVVM模式。
希望能透过这样的方式,让开发人员能对模式概念及如何实作有进一步的了解。
*这边要强调,本文的设计模式都是概念式模式。 每个人都有不同的理解跟实作,没有谁是绝对正确的跟错误的。

相关资料可以参考:

定义:

在开始设计模式的实作之前,还需要为后续的实作加上一些定义。

*执行状态首先来讨论「执行状态」这个定义。
以HTML为基础的Web网页,属于无状态的应用程式模型。
而相对于它的WinForm应用程式,就属于有状态的应用程式模型。
投射到物件上,也是有相同的概念。
可以依照物件在系统执行生命周期里,它的执行状态是否留存在系统内,
来区分为有状态的物件模型及无状态物件模型。

「执行状态」这个定义,会影响到实作设计模式的难易度。
当我们在一个无状态的应用程式模型上,选择实作某个有状态的物件模型。
在这种情景下,执行状态的维持就需要开发人员,在系统内作额外的设计。

*物件生成再来讨论「物件生成」这个定义。
当一个模式里有多个物件在交互运作的时候,哪个物件从哪边取得,是一件很重要的职责。
这里所谓的取得,不单单是指所谓的建立(Creation),也包含了注入(Inversion)等动作。

「物件生成」这个定义,会影响到物件相依性、建立物件的顺序及来源。
大多的设计模式都隐含了这个定义,但大多也都没有特别描述这个定义。
因为这有太多的实作方式,各种不同的组合会带来不同的效益。
但仔细参考设计模式文件的范例程式,可以去理解到各个设计模式隐含的物件生成职责。

范例:

本篇文章物件模型拆解的比较琐碎,建议开发人员下载范例程式后。
开启专案做对照,能比较容易理解文字描述的内容。

范例原始码 : 点此下载

实作- Domain :

本文实作一个「新增使用者」的功能,来当作设计模式的范例。
这个功能情景很简单,
1. 使用者输入使用者资料。
2. 使用者资料存入SQL资料库。
3. 清空使用者资料等待输入。
而使用者资料的栏位,单纯的只有编号跟姓名两个栏位。

依照这个功能描述,使用领域驱动设计的方式去分析设计。
我们可以先得到一个领域物件User。
以及一个将User资料进出系统的边界介面IUserRepository。
还有一个实际将User资料存入SQL资料库的资料存取物件SqlUserRepository。
后面的实作章节,将会使用这些物件,来完成「新增使用者」的功能。

01    using System;
02    using System.Data;
03    using System.Data.SqlClient;
04     
05    namespace MvcSamples.Domain
06    {
07        public class User
08        {
09            // Properties
10            public string Id { get; set; }
11     
12            public string Name { get; set; }
13        }
14     
15        public interface IUserRepository
16        {
17            // Methods
18            void Add(User item);
19        }
20    }
21     
22    namespace MvcSamples.Domain.Concretion
23    {
24        public class SqlUserRepository : IUserRepository
25        {
26            // Fields
27            private readonly string _connectionString = @"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\Concretion\SqlMvcSamplesDatabase.mdf;Integrated Security=True;User Instance=True";
28     
29     
30            // Methods
31            private SqlConnection CreateConnection()
32            {
33                return new SqlConnection(_connectionString);
34            }
35     
36            public void Add(User item)
37            {
38                #region Require
39     
40                if (item == null) throw new ArgumentNullException();
41     
42                #endregion
43                SqlCommand command;
44                using (SqlConnection connection = this.CreateConnection())
45                {
46                    // Connection
47                    connection.Open();
48     
49                    // Insert User
50                    using (command = connection.CreateCommand())
51                    {
52                        command.CommandType = CommandType.Text;
53                        command.CommandText = "INSERT INTO [User](Id, Name) VALUES (@Id, @Name)";
54                        command.Parameters.AddWithValue("@Id", item.Id);
55                        command.Parameters.AddWithValue("@Name", item.Name);
56                        command.ExecuteNonQuery();
57                    }
58                }
59            }
60        }
61    }

实作- MVVM模式:

*模式结构下图是MVVM模式的结构图,很简单的就是将系统拆解成三个类别(Model、View、ViewModel)。
各个类别的主要职责为:Model负责企业资料逻辑、View负责画面资料逻辑、ViewModel负责执行状态维持、画面流程逻辑及企业流程逻辑。

其中ViewModel-Model之间,是ViewModel直接使用Model开放的成员,属于ViewModel到Model的单向沟通连接。
而View-ViewModel之间,是透过Binding技术及Command的设计模式,将两者作双向的沟通连接。

*模式特征做为MVC延伸模式的MVVM模式,其最大的特征就是,
在View-ViewModel之间,是透过Binding技术及Command的设计模式,将两者作双向的沟通连接。
并且在模型结构设计上,将ViewModel定义为有状态的物件模型,由ViewModel负责维持执行状态。

这样设计最大的好处,是可以将View与ViewModel之间的相依关系,设计为单向相依。
ViewModel做是独立的个体不相依View,让View的职责回归到单纯的完成输入及显示的工作。
并且方便特定的设计工具设计View的外观,可以将View的设计交由完全不懂程式设计的人员作处理。

*实作分析
1. MVVM模式本身在模型结构设计上,是将ViewModel设计为有状态的物件模型。
实作范例的内容,将ViewModel架构在有状态的应用程式模型上,不做额外的设计。
2. 而MVVM模式物件之间的生成模式,实作上设计成以View当作主要物件,生成ViewModel及Model,并且将Model注入至ViewModel。
3. 以DDD的观念去分析Model,可以将Model视为Domain Layer,是整个模式重用的焦点。
这个Domain Layer里面,包含了整个Presentation会使用到的资料物件、边界物件、逻辑物件...等等。
4. 以DDD的观念去分析ViewModel,可以将ViewModel视为Application Layer。
这个Application Layer封装View所需要的资料、操作及状态维持,用来提供给View使用。

经过这些分析与设计的种种考量,可以设计出如下图的物件图。

*实作程式有了物件图,剩下的就只是建立物件的实作程式码。
这边选择能简易套用MVVM的WPF当做范例的介面框架,示范如何实作MVVM模式。

首先先建立一个ActionCommand物件,让我们后续方便把函式包装成Binding所支援的ICommand。

 

01    using System;
02    using System.Windows.Input;
03     
04    namespace MvcSamples.Mvvm.Infrastructure
05    {
06        public class ActionCommand : ICommand
07        {
08            // Fields
09            private readonly Action _action = null;
10     
11            private bool _canExecute = true;
12     
13     
14            // Constructor
15            public ActionCommand(Action action)
16                : this(action, true)
17            {
18     
19            }
20     
21            public ActionCommand(Action action, bool canExecute)
22            {
23                #region Require
24     
25                if (action == null) throw new ArgumentNullException();
26     
27                #endregion
28                _action = action;
29                _canExecute = canExecute;
30            }
31     
32     
33            // Methods
34            public void SetCanExecute(bool canExecute)
35            {
36                _canExecute = canExecute;
37                this.OnCanExecuteChanged(this, EventArgs.Empty);
38            }  
39     
40            public bool CanExecute(object parameter)
41            {
42                return _canExecute;
43            }       
44     
45            public void Execute(object parameter)
46            {
47                if (this.CanExecute(parameter) == false)
48                {
49                    throw new InvalidOperationException();
50                }
51                else
52                {
53                    _action();
54                }
55            }
56     
57     
58            // Events
59            public event EventHandler CanExecuteChanged;
60            private void OnCanExecuteChanged(object sender, EventArgs e)
61            {
62                #region Require
63     
64                if (sender == null) throw new ArgumentNullException();
65                if (e==null) throw new ArgumentNullException();
66     
67                #endregion
68                EventHandler eventHandler = this.CanExecuteChanged;
69                if (eventHandler != null)
70                {
71                    eventHandler(sender, e);
72                }
73            }
74        }
75    }

再来建立UserViewModel物件,封装提供给View使用的资料与操作。
并且加上UserViewModelRepository物件、IUserViewModelRepositoryProvider介面,做为UserViewModel进出边界的介面。

01    using System;
02    using System.Collections.Generic;
03    using System.Linq;
04    using System.Text;
05     
06    namespace MvcSamples.Mvvm.ViewModel
07    {
08        public interface IUserViewModelRepositoryProvider
09        {
10            // Methods
11            void Add(UserViewModel item);
12        }
13    }

01    using System;
02    using System.Collections.Generic;
03    using System.Linq;
04    using System.Text;
05     
06    namespace MvcSamples.Mvvm.ViewModel
07    {  
08        public class UserViewModelRepository
09        {
10            // Fields
11            private readonly IUserViewModelRepositoryProvider _provider = null;
12     
13     
14            // Constructor
15            public UserViewModelRepository(IUserViewModelRepositoryProvider provider)
16            {
17                #region Require
18     
19                if (provider == null) throw new ArgumentNullException();
20     
21                #endregion
22                _provider = provider;
23            }
24     
25     
26            // Methods
27            public void Add(UserViewModel item)
28            {
29                #region Require
30     
31                if (item == null) throw new ArgumentNullException();
32     
33                #endregion
34                _provider.Add(item);
35            }
36        }
37    }

01    using System;
02    using System.Collections.Generic;
03    using System.Linq;
04    using System.Text;
05    using System.ComponentModel;
06     
07    namespace MvcSamples.Mvvm.ViewModel
08    {   
09        public class UserViewModel : INotifyPropertyChanged
10        {
11            // Fields
12            private string _id = null;
13     
14            private string _name = null;
15     
16     
17            // Constructor
18            public UserViewModel()
19            {
20                _id = string.Empty;
21                _name = string.Empty;
22            }
23             
24     
25            // Properties
26            public string Id
27            {
28                get
29                {
30                    return _id;
31                }
32                set
33                {
34                    _id = value;
35                    this.OnPropertyChanged("Id");
36                }
37            }
38     
39            public string Name
40            {
41                get
42                {
43                    return _name;
44                }
45                set
46                {
47                    _name = value;
48                    this.OnPropertyChanged("Name");
49                }
50            }
51                   
52     
53            // Events
54            public event PropertyChangedEventHandler PropertyChanged;
55            private void OnPropertyChanged(string propertyName)
56            {
57                #region Require
58     
59                if (string.IsNullOrEmpty(propertyName) == true) throw new ArgumentNullException();
60     
61                #endregion
62                PropertyChangedEventHandler propertyChangedEventHandler = this.PropertyChanged;
63                if (propertyChangedEventHandler != null)
64                {
65                    propertyChangedEventHandler(this, new PropertyChangedEventArgs(propertyName));
66                }
67            }
68        }
69    }

 

接着就是建立AddUserViewModel物件,封装提供给View使用的资料与操作。

01    using System;
02    using System.ComponentModel;
03    using System.Windows.Input;
04    using MvcSamples.Mvvm.Infrastructure;
05    using MvcSamples.Mvvm.ViewModel;
06     
07    namespace MvcSamples.Mvvm.ViewModel
08    {   
09        public class AddUserViewModel : INotifyPropertyChanged
10        {
11            // Fields
12            private readonly UserViewModelRepository _userViewModelRepository = null;
13     
14            private readonly ICommand _addUserCommand = null;
15     
16            private UserViewModel _userViewModel = null;     
17     
18     
19            // Constructor
20            public AddUserViewModel(UserViewModelRepository userViewModelRepository)
21            {
22                #region Require
23     
24                if (userViewModelRepository == null) throw new ArgumentNullException();
25     
26                #endregion
27                _userViewModelRepository = userViewModelRepository;
28                _addUserCommand = new ActionCommand(this.AddUser); 
29                _userViewModel = new UserViewModel();                    
30            }
31     
32     
33            // Properties
34            public UserViewModel User
35            {
36                get
37                {
38                    return _userViewModel;
39                }
40                private set
41                {
42                    _userViewModel = value;
43                    this.OnPropertyChanged("User");
44                }
45            }
46             
47            public ICommand AddUserCommand
48            {
49                get
50                {
51                    return _addUserCommand;
52                }
53            }
54     
55     
56            // Methods
57            private void AddUser()
58            {
59                _userViewModelRepository.Add(this.User);
60                this.User = new UserViewModel();
61            }
62     
63     
64            // Events
65            public event PropertyChangedEventHandler PropertyChanged;
66            private void OnPropertyChanged(string propertyName)
67            {
68                #region Require
69     
70                if (string.IsNullOrEmpty(propertyName) == true) throw new ArgumentNullException();
71     
72                #endregion
73                PropertyChangedEventHandler propertyChangedEventHandler = this.PropertyChanged;
74                if (propertyChangedEventHandler != null)
75                {
76                    propertyChangedEventHandler(this, new PropertyChangedEventArgs(propertyName));
77                }
78            }       
79        }
80    }

继续建立UserViewModelRepositoryProvider,用来让整个模式跟Domain连接。

01    using System;
02    using System.Collections.Generic;
03    using System.Linq;
04    using System.Text;
05    using MvcSamples.Mvvm.ViewModel;
06     
07    namespace MvcSamples.Mvvm.ViewModel.Concretion
08    {
09        public class UserViewModelRepositoryProvider : IUserViewModelRepositoryProvider
10        {
11            // Fields
12            private readonly MvcSamples.Domain.IUserRepository _userRepository = null;
13     
14     
15            // Constructor
16            public UserViewModelRepositoryProvider(MvcSamples.Domain.IUserRepository userRepository)
17            {
18                #region Require
19     
20                if (userRepository == null) throw new ArgumentNullException();
21     
22                #endregion
23                _userRepository = userRepository;
24            }
25     
26     
27            // Methods
28            private MvcSamples.Domain.User CreateUser(UserViewModel item)
29            {
30                #region Require
31     
32                if (item == null) throw new ArgumentNullException();
33     
34                #endregion
35                MvcSamples.Domain.User user = new MvcSamples.Domain.User();
36                user.Id = item.Id;
37                user.Name = item.Name;
38                return user;
39            }
40     
41     
42            public void Add(UserViewModel item)
43            {
44                #region Require
45     
46                if (item == null) throw new ArgumentNullException();
47     
48                #endregion
49                _userRepository.Add(this.CreateUser(item));
50            }
51        }
52    }

建立完上述的程式码之后,额外再加一个AddUserViewModelHost。
用来提供无参数的建构物件,方便后续作Binding的操作。

01    using MvcSamples.Domain;
02    using MvcSamples.Domain.Concretion;
03    using MvcSamples.Mvvm.ViewModel;
04    using MvcSamples.Mvvm.ViewModel.Concretion;
05    using MvcSamples.Mvvm.ViewModel;
06     
07    namespace MvcSamples.Mvvm.Runtime
08    {
09        public class AddUserViewModelHost
10        {
11            // Fields
12            private AddUserViewModel _viewModel = null;
13     
14     
15            // Properties
16            public AddUserViewModel ViewModel
17            {
18                get
19                {
20                    if (_viewModel == null)
21                    {
22                        _viewModel = this.Create();
23                    }
24                    return _viewModel;
25                }
26            }
27     
28     
29            // Methods
30            private AddUserViewModel Create()
31            {
32                IUserRepository userRepository = new SqlUserRepository();
33     
34                IUserViewModelRepositoryProvider userViewModelRepositoryProvider = new UserViewModelRepositoryProvider(userRepository);
35     
36                UserViewModelRepository userViewModelRepository = new UserViewModelRepository(userViewModelRepositoryProvider);
37     
38                return new AddUserViewModel(userViewModelRepository);
39            }
40        }
41    }

最后就是建立显示用的XAML。

 

01    <Window x:Class="MvcSamples.Mvvm.WpfDemoApp.MainWindow"
02            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
03            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
04            xmlns:viewModel="clr-namespace:MvcSamples.Mvvm.ViewModel;assembly=MvcSamples.Mvvm"  
05            xmlns:runtime="clr-namespace:MvcSamples.Mvvm.Runtime;assembly=MvcSamples.Mvvm"  
06            Title="MainWindow" Height="350" Width="525">
07        <Window.Resources>
08            <runtime:AddUserViewModelHost x:Key="addUserViewModelHost" />       
09        </Window.Resources>
10        <Window.DataContext>
11            <Binding Source="{StaticResource addUserViewModelHost}" Path="ViewModel" Mode="OneTime" />
12        </Window.DataContext>
13        <Grid>
14            <TextBox Name="textBox1" Height="23" Margin="10,10,0,0"  VerticalAlignment="Top" HorizontalAlignment="Left" Width="120" DataContext="{Binding User}"  Text="{Binding Id}" />
15            <TextBox Name="textBox2" Height="23" Margin="10,39,0,0"  VerticalAlignment="Top" HorizontalAlignment="Left" Width="120" DataContext="{Binding User}"  Text="{Binding Name}" />
16            <Button  Name="button1"  Height="23" Margin="55,68,0,0"  VerticalAlignment="Top" HorizontalAlignment="Left" Width="75"  Content="Button" Command="{Binding AddUserCommand}" />
17        </Grid>
18    </Window>

结果:

编译后执行, 在画面上输入资料并按下按钮。 于程式的中断点做检查,可以发现程式有正常执行。





期许自己~
能以更简洁的文字与程式码,传达出程式设计背后的精神。
真正做到「以形写神」的境界。

 

时间: 2024-09-12 09:23:42

[Architecture Pattern] MVVM模式的相关文章

WPF中MVVM模式原理分析与实践[转]

1, 前提 可以说MVVM是专为WPF打造的模式, 也可以说MVVM仅仅是MVC的一个变种, 但无论如何, 就实践而言, 如果你或你的团队没有使用"Binding"的习惯, 那么研究MVVM就没有多大意义. 另外,个人觉得, 使用Command以及打造一种合理的简化的方式去使用Command也与使用Binding一样重要. 2, 诞生 为了解决现实世界中的问题,我们需要将现实世界中的事物加以抽象, 然后得到了Domain Object, 无论贫血的还是富血的, 我们都可以简单地把他们归

Silverlight 学习笔记——MVVM模式实现主从数据显示

转自http://www.cnblogs.com/xiaomi7732/archive/2010/01/21/1653482.html 写本篇纯属意外.原来想用主从数据显示的例子记录页面间切换的方法的,后来在园子里看到有一篇写页面切换的文章介绍得很详尽了,代码做了一半,真是鸡肋啊.于是想,干脆把代码改改,弄成个MVVM模式来展示主从数据吧. 为了突出重点,示例不考虑美工方面的问题--嘿嘿,美工实在太差了,各位见谅. 首先来看完成后的效果: 启动时候,显示一个空的页面,点击"Show Data&q

界面之下:还原真实的 MVC、MVP、MVVM 模式

前言 做客户端开发.前端开发对MVC.MVP.MVVM这些名词不了解也应该大致听过,都是为了解决图形界面应用程序复杂性管理问题而产生的应用架构模式.网上很多文章关于这方面的讨论比较杂乱,各种MV*模式之间的区别分不清,甚至有些描述都是错误的.本文追根溯源,从最经典的Smalltalk-80 MVC模式开始逐步还原图形界面之下最真实的MV*模式. GUI程序所面临的问题 图形界面的应用程序提供给用户可视化的操作界面,这个界面提供给数据和信息.用户输入行为(键盘,鼠标等)会执行一些应用逻辑,应用逻辑

Windows 8 Store Apps学习(55) 绑定: MVVM 模式

介绍 重新想象 Windows 8 Store Apps 之 绑定 通过 MVVM 模式实现数据的添 加.删除.修改和查询 示例 1.Model 层 Binding/MVVM/Model/ProductDatabase.cs /* * Model 层的数据持久化操作(本地或远程) * * 本例只是一个演示 */ using System; using System.Collections.Generic; using System.Linq; namespace XamlDemo.Binding

MVVM模式中ViewModel和View、Model有什么区别?

  这篇文章主要介绍了MVVM模式中ViewModel和View.Model有什么区别?本文分别解释了它们的功能和作用,然后总结了它之间的区别,需要的朋友可以参考下 Model:很简单,就是业务逻辑相关的数据对象,通常从数据库映射而来,我们可以说是与数据库对应的model. View:也很简单,就是展现出来的用户界面. 基本上,绝大多数软件所做的工作无非就是从数据存储中读出数据,展现到用户界面上,然后从用户界面接收输入,写入到数据存储里面去.所以,对于数据存储(model)和界面(view)这两

MVVM 模式介绍

本文讲的是MVVM 模式介绍, 我考察了一段时间安卓的数据绑定类库,决定尝试下它的"Model-View-ViewModel"模式.因为我曾经和 @matto1990 合作开发过一款应用 HackerNews Reader,所以我决定利用这种模式重新实现它. 这篇文章通过一款简单的App来论证MVVM模式,我建议你先看看这个项目,让你大概了解下它. 什么是MVVM模式? Model-View-ViewModel 就是将其中的 View 的状态和行为抽象化,让我们可以将UI和业务逻辑分开

mvvm-WPF 采用MVVM模式,ViewModel中如何控制焦点?

问题描述 WPF 采用MVVM模式,ViewModel中如何控制焦点? 如题 WPF 采用MVVM模式,ViewModel中如何控制焦点? 解决方案 我是在ViewModel中引用UI对象来控制的.

“Win10 UAP 开发系列”之 在MVVM模式中控制ListView滚动位置

原文:"Win10 UAP 开发系列"之 在MVVM模式中控制ListView滚动位置 这个扩展属性从WP8.1就开始用了,主要是为了解决MVVM模式中无法直接控制ListView滚动位置的问题.比如在VM中刷新了数据,需要将View中的ListView滚动到顶部,ListView只有一个ScrollIntoView()方法可以控制滚动的位置,但最好在VM中不要出现直接控制View的代码,需要通过其他的方式. 使用一个扩展属性即可实现: /// <summary> ///

移动开发架构之MVVM模式

MVVM概念的提出和起源 MVVM是Model-View-ViewModel的简写,最早是由微软公司提出并运用,是MVP(Model-View-Presenter)模式与WPF结合的应用方式时发展演变过来的一种新型架构架构. MVVM概念解释和要点 一.基本概念 mvvm1.png Model:主要为应用程序提供数据. View:还是MVC和MVP中的那个表示层,同时实现UI元素和ViewModel属性的绑定. ViewModel:为View提供数据支持. 以胖瘦的观点来看,在MVVM中的Mod