zoukankan      html  css  js  c++  java
  • [Architecture Pattern] MVVM

    动机 :

    开发应用程序的时候,针对用户接口开发。
    业界有许多前辈提出了多种的设计模式,其中最为人所知的就是 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。
    后面的实作章节,将会使用这些对象,来完成「新增用户」的功能。

    using System;
    using System.Data;
    using System.Data.SqlClient;
    
    namespace MvcSamples.Domain
    {
        public class User
        {
            // Properties
            public string Id { get; set; }
    
            public string Name { get; set; }
        }
    
        public interface IUserRepository
        {
            // Methods
            void Add(User item);
        }
    }
    
    namespace MvcSamples.Domain.Concretion
    {
        public class SqlUserRepository : IUserRepository
        {
            // Fields
            private readonly string _connectionString = @"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\Concretion\SqlMvcSamplesDatabase.mdf;Integrated Security=True;User Instance=True";
    
    
            // Methods
            private SqlConnection CreateConnection()
            {
                return new SqlConnection(_connectionString);
            }
    
            public void Add(User item)
            {
                #region Require
    
                if (item == null) throw new ArgumentNullException();
    
                #endregion
                SqlCommand command;
                using (SqlConnection connection = this.CreateConnection())
                {
                    // Connection
                    connection.Open();
    
                    // Insert User
                    using (command = connection.CreateCommand())
                    {
                        command.CommandType = CommandType.Text;
                        command.CommandText = "INSERT INTO [User](Id, Name) VALUES (@Id, @Name)";
                        command.Parameters.AddWithValue("@Id", item.Id);
                        command.Parameters.AddWithValue("@Name", item.Name);
                        command.ExecuteNonQuery();
                    }
                }
            }
        }
    }

    实作 - 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里面,包含了整个系统会使用到的数据对象、边界对象、逻辑对象...等等。
    4. 以DDD的观念去分析ViewModel,可以将ViewModel视为Application Layer。
      这个Application Layer封装View所需要的数据、操作及状态维持,用来提供给View使用。

    经过这些分析与设计的种种考虑,可以设计出如下图的对象图。

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

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

    using System;
    using System.Windows.Input;
    
    namespace MvcSamples.Mvvm.Infrastructure
    {
        public class ActionCommand : ICommand
        {
            // Fields
            private readonly Action _action = null;
    
            private bool _canExecute = true;
    
    
            // Constructor
            public ActionCommand(Action action)
                : this(action, true)
            {
    
            }
    
            public ActionCommand(Action action, bool canExecute)
            {
                #region Require
    
                if (action == null) throw new ArgumentNullException();
    
                #endregion
                _action = action;
                _canExecute = canExecute;
            }
    
    
            // Methods
            public void SetCanExecute(bool canExecute)
            {
                _canExecute = canExecute;
                this.OnCanExecuteChanged(this, EventArgs.Empty);
            }   
    
            public bool CanExecute(object parameter)
            {
                return _canExecute;
            }        
    
            public void Execute(object parameter)
            {
                if (this.CanExecute(parameter) == false)
                {
                    throw new InvalidOperationException();
                }
                else
                {
                    _action(); 
                }
            }
    
    
            // Events
            public event EventHandler CanExecuteChanged;
            private void OnCanExecuteChanged(object sender, EventArgs e)
            {
                #region Require
    
                if (sender == null) throw new ArgumentNullException();
                if (e==null) throw new ArgumentNullException();
    
                #endregion
                EventHandler eventHandler = this.CanExecuteChanged;
                if (eventHandler != null)
                {
                    eventHandler(sender, e);
                }
            }
        }
    }
    

    再来建立UserViewModel对象,封装提供给View使用的数据与操作。
    并且加上UserViewModelRepository对象、IUserViewModelRepositoryProvider接口,做为UserViewModel进出边界的接口。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace MvcSamples.Mvvm.ViewModel
    {
        public interface IUserViewModelRepositoryProvider
        {
            // Methods
            void Add(UserViewModel item);
        }
    }
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace MvcSamples.Mvvm.ViewModel
    {   
        public class UserViewModelRepository 
        {
            // Fields
            private readonly IUserViewModelRepositoryProvider _provider = null;
    
    
            // Constructor
            public UserViewModelRepository(IUserViewModelRepositoryProvider provider)
            {
                #region Require
    
                if (provider == null) throw new ArgumentNullException();
    
                #endregion
                _provider = provider;
            }
    
    
            // Methods
            public void Add(UserViewModel item)
            {
                #region Require
    
                if (item == null) throw new ArgumentNullException();
    
                #endregion
                _provider.Add(item);
            }
        }
    }
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel;
    
    namespace MvcSamples.Mvvm.ViewModel
    {    
        public class UserViewModel : INotifyPropertyChanged
        {
            // Fields
            private string _id = null;
    
            private string _name = null;
    
    
            // Constructor
            public UserViewModel()
            {
                _id = string.Empty;
                _name = string.Empty;
            }
            
    
            // Properties
            public string Id 
            {
                get 
                {
                    return _id;
                }
                set
                {
                    _id = value;
                    this.OnPropertyChanged("Id");
                } 
            }
    
            public string Name
            {
                get
                {
                    return _name;
                }
                set
                {
                    _name = value;
                    this.OnPropertyChanged("Name");
                }
            }
                  
    
            // Events
            public event PropertyChangedEventHandler PropertyChanged;
            private void OnPropertyChanged(string propertyName)
            {
                #region Require
    
                if (string.IsNullOrEmpty(propertyName) == true) throw new ArgumentNullException();
    
                #endregion
                PropertyChangedEventHandler propertyChangedEventHandler = this.PropertyChanged;
                if (propertyChangedEventHandler != null)
                {
                    propertyChangedEventHandler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }
    }
    

    接着就是建立AddUserViewModel对象,封装提供给View使用的数据与操作。

    using System;
    using System.ComponentModel;
    using System.Windows.Input;
    using MvcSamples.Mvvm.Infrastructure;
    using MvcSamples.Mvvm.ViewModel;
    
    namespace MvcSamples.Mvvm.ViewModel
    {    
        public class AddUserViewModel : INotifyPropertyChanged
        {
            // Fields
            private readonly UserViewModelRepository _userViewModelRepository = null;
    
            private readonly ICommand _addUserCommand = null;
    
            private UserViewModel _userViewModel = null;      
    
    
            // Constructor
            public AddUserViewModel(UserViewModelRepository userViewModelRepository)
            {
                #region Require
    
                if (userViewModelRepository == null) throw new ArgumentNullException();
    
                #endregion
                _userViewModelRepository = userViewModelRepository;
                _addUserCommand = new ActionCommand(this.AddUser);  
                _userViewModel = new UserViewModel();                     
            }
    
    
            // Properties
            public UserViewModel User
            {
                get
                {
                    return _userViewModel;
                }
                private set
                {
                    _userViewModel = value;
                    this.OnPropertyChanged("User");
                }
            }
            
            public ICommand AddUserCommand
            {
                get
                {
                    return _addUserCommand;
                }
            }
    
    
            // Methods
            private void AddUser()
            {
                _userViewModelRepository.Add(this.User);
                this.User = new UserViewModel();
            }
    
    
            // Events
            public event PropertyChangedEventHandler PropertyChanged;
            private void OnPropertyChanged(string propertyName)
            {
                #region Require
    
                if (string.IsNullOrEmpty(propertyName) == true) throw new ArgumentNullException();
    
                #endregion
                PropertyChangedEventHandler propertyChangedEventHandler = this.PropertyChanged;
                if (propertyChangedEventHandler != null)
                {
                    propertyChangedEventHandler(this, new PropertyChangedEventArgs(propertyName));
                }
            }        
        }
    }
    

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

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using MvcSamples.Mvvm.ViewModel;
    
    namespace MvcSamples.Mvvm.ViewModel.Concretion
    {
        public class UserViewModelRepositoryProvider : IUserViewModelRepositoryProvider
        {
            // Fields
            private readonly MvcSamples.Domain.IUserRepository _userRepository = null;
    
    
            // Constructor
            public UserViewModelRepositoryProvider(MvcSamples.Domain.IUserRepository userRepository)
            {
                #region Require
    
                if (userRepository == null) throw new ArgumentNullException();
    
                #endregion
                _userRepository = userRepository;
            }
    
    
            // Methods
            private MvcSamples.Domain.User CreateUser(UserViewModel item)
            {
                #region Require
    
                if (item == null) throw new ArgumentNullException();
    
                #endregion
                MvcSamples.Domain.User user = new MvcSamples.Domain.User();
                user.Id = item.Id;
                user.Name = item.Name;
                return user;
            }
    
    
            public void Add(UserViewModel item)
            {
                #region Require
    
                if (item == null) throw new ArgumentNullException();
    
                #endregion
                _userRepository.Add(this.CreateUser(item));
            }
        }
    }
    
    

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

    using MvcSamples.Domain;
    using MvcSamples.Domain.Concretion;
    using MvcSamples.Mvvm.ViewModel;
    using MvcSamples.Mvvm.ViewModel.Concretion;
    using MvcSamples.Mvvm.ViewModel;
    
    namespace MvcSamples.Mvvm.Runtime
    {
        public class AddUserViewModelHost
        {
            // Fields
            private AddUserViewModel _viewModel = null;
    
    
            // Properties
            public AddUserViewModel ViewModel
            {
                get
                {
                    if (_viewModel == null)
                    {
                        _viewModel = this.Create();
                    }
                    return _viewModel;
                }
            }
    
    
            // Methods
            private AddUserViewModel Create()
            {
                IUserRepository userRepository = new SqlUserRepository();
    
                IUserViewModelRepositoryProvider userViewModelRepositoryProvider = new UserViewModelRepositoryProvider(userRepository);
    
                UserViewModelRepository userViewModelRepository = new UserViewModelRepository(userViewModelRepositoryProvider);
    
                return new AddUserViewModel(userViewModelRepository);
            }
        }
    }
    

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

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

    结果 :

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



  • 相关阅读:
    Memcache相关面试题
    Memcache使用场景
    php链接memcache操作
    永久数据被踢现象
    linux 压缩/解压命令大全
    Linux系统信息查看命令大全
    bundle update: env: ruby_executable_hooks: No such file or directory
    Pod::Executable pull
    Android签名机制:生成keystore、签名、查看签名信息
    Difference between 2>&-, 2>/dev/null, |&, &>/dev/null and >/dev/null 2>&1
  • 原文地址:https://www.cnblogs.com/clark159/p/2205164.html
Copyright © 2011-2022 走看看