@TOC
前言 做项目的时候,用到了设计模式,总结下来,供以后参考,与诸君共勉。
一、我为什么要用MVVM 1.假设有一天,在你还有三个小时下班时,公司来了个新需求,说是开发一个简单的加法计算器【两个输入框可以输入加数和被加数,另外一个输入框显示和,还有一个按钮用于保存求和结果】。作为一个始于Java,ing于C#的小学生,哼哧哼哧就开始了: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 <Window x:Class="AddNotUseMVVM.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:AddNotUseMVVM" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Button Content="Save" x:Name="saveButton" Click="saveButton_Click" /> <Grid Grid.Row="1" > <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <TextBox x:Name="tb1" Grid.Row="0" Background="LightBlue" FontSize="24" Margin="4" /> <TextBox x:Name="tb2" Grid.Row="1" Background="LightBlue" FontSize="24" Margin="4" /> <TextBox x:Name="tb3" Grid.Row="2" Background="LightBlue" FontSize="24" Margin="4" /> <Button x:Name="addButton" Grid.Row="3" Content="Add" Width="120" Height="80" Click="addButton_click" /> </Grid> </Grid> </Window>using Microsoft.Win32;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes;namespace AddNotUseMVVM { public partial class MainWindow : Window { public MainWindow () { InitializeComponent(); } private void addButton_click (object sender, RoutedEventArgs e ) { double addVal1 = double .Parse(tb1.Text); double addVal2 = double .Parse(tb2.Text); double res = addVal1 + addVal2; this .tb3.Text = res.ToString(); } private void saveButton_Click (object sender, RoutedEventArgs e ) { SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.ShowDialog(); } } }
2.你快要走时,客户说,你好,我们公司有一台设备,我要经常用一个手去维护,也就是说,我只能用一只手去操作你开发的计算器,我一只手输入数字很麻烦,能不能开发成为那种可拖动的杆,拖动到不同位置就代表不同数字,然后我再点相加按钮,这样我就可以单手操作了
你心里开始盘,我去,这我UI里面很多name、事件名等属性,都在后台的业务逻辑中用到了,相当于是强耦合,改了UI就要改动一大片UI中的控件及其属性,还有后台用到控件属性的代码…你开始emo了,但作为一个始于Java,ing于C#的小学生,哼哧哼哧就开始了…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 <Window x:Class="AddNotUseMVVM.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:AddNotUseMVVM" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <!-- <Button Content="Save" x:Name="saveButton" Click="saveButton_Click" /> --> <Menu> <MenuItem Header="_File" > <MenuItem Header="_Save" x:Name="SaveMenu" Click="save_click" /> </MenuItem> </Menu> <Grid Grid.Row="1" > <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Slider x:Name="slider1" Grid.Row="0" Background="LightBlue" Margin="4" /> <Slider x:Name="slider2" Grid.Row="1" Background="GreenYellow" Margin="4" /> <Slider x:Name="slider3" Grid.Row="2" Background="LightGray" Margin="4" /> <Button x:Name="addButton" Grid.Row="3" Content="Add" Width="120" Height="80" Click="addButton_click" /> </Grid> </Grid> </Window>using Microsoft.Win32;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes;namespace AddNotUseMVVM { public partial class MainWindow : Window { public MainWindow () { InitializeComponent(); } private void addButton_click (object sender, RoutedEventArgs e ) { this .slider3.Value = this .slider2.Value + this .slider1.Value; } private void save_click (object sender, RoutedEventArgs e ) { new SaveFileDialog().ShowDialog(); } } }
二、既然用到了MVVM模式,那么不得拉出来掰扯掰扯嘛 1.Microsoft prism【<—>Microsoft Blend SDK】
2.MVVM:Model -View-ViewModel
再怎么说,多多少少也得整点八股呗
使用设计模式的优点:【但是也不能乱使用呗,项目需求决定。大项目使用,不能小项目或者游戏这种都使用】
团队层面:统一团队的思维方式、实现方式。减少项目新手学习曲线
架构层面:稳定、解耦【解的是UI和业务逻辑】
代码层面:可读、可测、可替换
3.MVVM具体解释及代码结构对应【一般你去看别人代码或者你写代码,想用MVC/MVVM,要让别人一看你的代码结构就知道你用的是什么设计模式】
那怎么才能一眼就让别人看到,咱使用的是MVVM呢,肯定是代码的逻辑结构呗【说白了就是你项目代码文件夹的分布,你都有哪些文件夹呀】
View = UI,由组件、控件构成的用户操作界面
用户首先跟View(= User Interface)进行交互
ViewModel = Model for View 【ViewModel就是View的建模或者说模型】
ViewModel一般都是在View的名字后面加上ViewModel这个后缀,然后把这个放在ViewModel文件夹中
UI中的对象建模,比如界面的两个文本框,UI或者说View上需要显示什么内容、UI或者说View能做什么操作,你ViewModel基于编程三大范式就要进行建模 【ViewModel就是View的建模或者说模型】 ,实现和View的呼应,一般是View和ViewModel一一对应,但不是绝对的【View,也就是User Interface跟View Model,也就是Model for View通过下面两种方式,一种双向的,一种单向的沟通方式进行交互】
传递数据–双向【View<–>ViewModel】的数据属性
传递操作–单向的命令属性【View->ViewModel】
Model对应entity,现实世界中对象的抽象,面向对象建模【建立模型】
ViewModel再跟Model、Services交互,同时Model跟Services也有交互
Service再跟DB进行交互
4.难点拉出来再说说,上面说的ViewModel = Model for View 【ViewModel就是View的建模或者说模型】 , 既然ViewModel是View的建模或者说模型,那么咱们针对一个C#中的UI如何进行建模呢:
从两方面两角度进行分析:【以两数求和,点击求和按钮可以显示求和结果,并可以保存结果的例子代码为例】:
数据流转【属性】与命令属性:
数据流转:有两个输入值【加数和被加数】,可以让用户进行输入、和一个值可以向用户显示输出。也就是说一种有三个数据属性
命令:
一个操作【求和按钮的点击,可以进行这种计算】、一个操作【保存按钮,可以进行保存运算结果】,也就是说有两个命令属性
5.此时,哪怕UI有大概动【 没有发生本质变化,View和ViewModel之间的映射关系【数据属性中的输入和输出个数、命令属性中的输入和输出个数等】没有发生变化,只是控件的类型等发生了变化 】,也就是把加数、被加数、和,三个TextBox变为拖动的拖拽杆,下面的情况都不会发生:
1.不会出现编译失败,因为现在我后台的业务逻辑没有用到前端UI的name…等一些信息,不是强耦合的,所以首先不会编译失败
2.此时,需要改动的点,就是UI中的绑定部分:也就是Text=”{Binding Input1}”,改为Value=”{Binding Input1}”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 using AddNotUseMVVM.ViewModel.ViewModelImpl;using Microsoft.Win32;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes;namespace AddUseMVVM { public partial class MainWindow : Window { public MainWindow () { InitializeComponent(); this .DataContext = new MainWindowViewModel(); } } } <Window x:Class="AddUseMVVM.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:AddUseMVVM" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Button Content="Save" x:Name="saveButton" Command="{Binding SaveCommand}" /> <Menu> <MenuItem Header="_File" > <MenuItem Header="_Save" x:Name="SaveMenu" Click="save_click" /> </MenuItem> </Menu> <Grid Grid.Row="1" > <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <TextBox x:Name="tb1" Grid.Row="0" Background="LightBlue" Margin="4" Text="{Binding Input1}" /> <TextBox x:Name="tb2" Grid.Row="1" Background="GreenYellow" Margin="4" Text="{Binding Input2}" /> <TextBox x:Name="tb3" Grid.Row="2" Background="LightGray" Margin="4" Text="{Binding Result}" /> <Button x:Name="addButton" Grid.Row="3" Content="Add" Width="120" Height="80" Command="{Binding AddCommand}" /> </Grid> </Grid> </Window>using System;using System.Collections.Generic;using System.ComponentModel;using System.Linq;using System.Text;using System.Threading.Tasks;namespace AddUseMVVM.ViewModel { internal class NotificationObject : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; public void RaisePropertyChanged (string propertyName ) { if (this .PropertyChanged != null ) { this .PropertyChanged.Invoke(this , new PropertyChangedEventArgs(propertyName)); } } } }using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows.Input;namespace AddNotUseMVVM.Commands { internal class DelegateCommand : ICommand { public event EventHandler? CanExecuteChanged; public bool CanExecute (object ? parameter ) { if (this .CanExecuteFunc == null ) { return true ; } return this .CanExecuteFunc(parameter); } public void Execute (object ? parameter ) { if (this .ExecuteAction == null ) { return ; } this .ExecuteAction(parameter); } public Action<object > ExecuteAction { get ; set ; } public Func<object , bool > CanExecuteFunc { get ; set ; } } }using AddNotUseMVVM.Commands;using AddUseMVVM.ViewModel;using Microsoft.Win32;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace AddNotUseMVVM.ViewModel.ViewModelImpl { internal class MainWindowViewModel :NotificationObject { #region 数据属性 #region 第一个数据属性 private double input1; public double Input1 { get { return input1; } set { input1 = value ; this .RaisePropertyChanged("Input1" ); } } #endregion #region 第二个数据属性 private double input2; public double Input2 { get { return input2; } set { input2 = value ; this .RaisePropertyChanged("Input2" ); } } #endregion #region 第三个数据属性 private double result; public double Result { get { return result; } set { result = value ; this .RaisePropertyChanged("Result" ); } } #endregion #endregion #region 命令属性 #region 第一个命令属性 public DelegateCommand AddCommand { get ; set ; } private void Add (object parameter ) { this .Result = this .Input1 + this .Input2; } #endregion #region 第二个命令属性 public DelegateCommand SaveCommand { get ; set ; } private void Save (object parameter ) { new SaveFileDialog().ShowDialog(); } #endregion public MainWindowViewModel () { this .AddCommand = new DelegateCommand(); this .AddCommand.ExecuteAction = new Action<object >(this .Add); this .SaveCommand = new DelegateCommand(); this .SaveCommand.ExecuteAction = new Action<object >(this .Save); } #endregion } }
三、这个项目,毕竟还是太小了,成熟性、稳定性、安全性还都是有待验证,说了半天我工作时写代码参考不了,你在这说啥…这个呢嘛,第一篇嘛,有些八股和基础在里面,欲知后事如何,请看下篇硬货 ……未完待续 巨人的肩膀
Head First 设计模式
设计模式之禅
公众号啦、OSCHINA啦、上面的有关设计模式的文章