[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 {  getset; }
11     
12             public  string Name {  getset; }
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 ==  nullthrow  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的设计模式,将两者作双向的沟通连接。

[Architecture Pattern] MVVM模式_第1张图片


*模式特征做为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使用。


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

[Architecture Pattern] MVVM模式_第2张图片


*实作程式有了物件图,剩下的就只是建立物件的实作程式码。
这边选择能简易套用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 ==  nullthrow  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 ==  nullthrow  new ArgumentNullException();
65                 if (e== nullthrow  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 ==  nullthrow  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 ==  nullthrow  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) ==  truethrow  new ArgumentNullException();
60     
61                 #endregion
62                PropertyChangedEventHandler propertyChangedEventHandler =  this.PropertyChanged;
63                 if (propertyChangedEventHandler !=  null)
64                {
65                    propertyChangedEventHandler( thisnew 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 ==  nullthrow  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) ==  truethrow  new ArgumentNullException();
71     
72                 #endregion
73                PropertyChangedEventHandler propertyChangedEventHandler =  this.PropertyChanged;
74                 if (propertyChangedEventHandler !=  null)
75                {
76                    propertyChangedEventHandler( thisnew 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 ==  nullthrow  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 ==  nullthrow  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 ==  nullthrow  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 >

结果:

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

[Architecture Pattern] MVVM模式_第3张图片
[Architecture Pattern] MVVM模式_第4张图片
[Architecture Pattern] MVVM模式_第5张图片


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

 

你可能感兴趣的:(Architecture)