zoukankan      html  css  js  c++  java
  • MVC还是MVVM?或许VMVC更适合WinForm客户端

    最近开始重构一个稍嫌古老的C/S项目,原先采用的技术栈是『WinForm』+『WCF』+『EF』。相对于现在铺天盖地的B/S架构来说,看上去似乎和Win95一样古老,很多新入行的,可能就没有见过经典的C/S架构的系统。事实上,作为企业信息管理系统,包括ERP/CRM/SCM等,桌面客户端还是很OK的。

    这次重构原定的目标有两个:

    1、客户端还是WinForm不变,但使用MVC模式重写;

    2、WCF改成WebAPI。

    经过2周时间的尝试和探索,重构计划变更为:

    1、使用VMVC模式来重构WinForm客户端;

    2、用WCF实现伪WebAPI,其本质还是个WCF服务,但实现了RESTful风格的WebAPI。

    这次和大家分享我对客户端架构的一些探索,就不展开服务端相关的话题了。那么,什么是VMVC呢?呵呵,这个是我发明的新名称,和MVC的区别在于用ViewModel替换了Model。ViewModel和View之间实现双向数据绑定,View上面的交互产生的操作指令,还是由Controller接收,然后通过对ViewModel的操作,更新View的数据。

    简单地说,就是ViewModel负责数据流,View负责显示和接受用户指令,而Controller则居中调度。示意图如下:

    由于实现了数据双向绑定,所以在一定程度上简化了数据的存储。只需要执行ViewModel上的Save()方法,就可以将新的数据通过WebAPI存储到数据库了。

    ViewModel的职责非常明确,就是一个数据流引擎!所以基本上都是Load()、Save()、Show()、Refresh()、Close()这些无脑方法,一丁点的业务逻辑都木有。非常适合有一定编程经验,但不了解业务逻辑的程序员编写。

    而View就更简单了,完全由VS的窗体设计器生成。UI设计师从此不需要PS了,根据产品原型直接拖控件就OK。

    最后,所有的业务逻辑都写在Controller里面,这样就为自动化测试提供了可能。测试工程师只需要编写一段测试代码替代Controller,同时对View的数据进行注入就可以跑单元测试。

    下面是我用于尝试这种模式的示例,希望能够起到抛砖引玉的作用。

    代码结构:

    Controller(部分代码),通过订阅View上面的确定按钮点击事件实现用户操作的委托:

     1         /// <summary>
     2         /// 修改服务器配置
     3         /// </summary>
     4         private void ConfigServer()
     5         {
     6             _SetModel = new SetModel();
     7 
     8             // 订阅确定按钮点击事件
     9             _SetModel.View.ConfirmButton.Click += SetConfirm_Click;
    10             _SetModel.ShowDialog();
    11         }
    12 
    13         /// <summary>
    14         /// 点击确定按钮
    15         /// </summary>
    16         /// <param name="sender"></param>
    17         /// <param name="e"></param>
    18         private void SetConfirm_Click(object sender, EventArgs e)
    19         {
    20             if (!_SetModel.Test()) return;
    21 
    22             _SetModel.Save();
    23             _SetModel.Close();
    24         }

    ViewModel:

      1 using System;
      2 using System.Windows.Forms;
      3 using Insight.Utils.Client;
      4 using Insight.Utils.Common;
      5 using Insight.WS.Client.Common.Utils;
      6 using Insight.WS.Client.MainApp.Views;
      7 
      8 namespace Insight.WS.Client.MainApp.Models
      9 {
     10     public class SetModel
     11     {
     12         public LoginSet View = new LoginSet();
     13 
     14         private string _Address = Config.BaseAddress();
     15         private string _Port = Config.Port();
     16         private bool _SaveUser = Config.IsSaveUserInfo();
     17 
     18         /// <summary>
     19         /// 构造方法,初始化控件初始值
     20         /// 通过订阅事件实现双向数据绑定
     21         /// </summary>
     22         public SetModel()
     23         {
     24             View.AddressInput.EditValueChanged += AddressChanged;
     25             View.AddressInput.Text = _Address;
     26 
     27             View.PortInput.EditValueChanged += PortChanged;
     28             View.PortInput.Text = _Port;
     29 
     30             View.SaveUserCheckBox.CheckStateChanged += SaveUserChanged;
     31             View.SaveUserCheckBox.Checked = _SaveUser;
     32         }
     33 
     34         /// <summary>
     35         /// 显示对话框
     36         /// </summary>
     37         public void ShowDialog()
     38         {
     39             View.ShowDialog();
     40         }
     41 
     42         /// <summary>
     43         /// 关闭对话框
     44         /// </summary>
     45         public void Close()
     46         {
     47             View.DialogResult = DialogResult.OK;
     48             View.Close();
     49         }
     50 
     51         /// <summary>
     52         /// 测试服务器连通性
     53         /// </summary>
     54         /// <returns>bool 是否通过连通性测试</returns>
     55         public bool Test()
     56         {
     57             var url = $"http://{_Address}:{_Port}/commonapi/v1.0/test";
     58             var result = new HttpClient(url).Request(Params.Token);
     59             if (result.Code != "400") return true;
     60 
     61             Messages.ShowError("请配置正确的服务器地址和端口号!");
     62             return false;
     63         }
     64 
     65         /// <summary>
     66         /// 保存设置
     67         /// </summary>
     68         public void Save()
     69         {
     70             if (!_SaveUser) Config.SaveUserName(string.Empty);
     71 
     72             Config.SaveIsSaveUserInfo(_SaveUser);
     73             Config.SaveAddress(_Address, _Port);
     74 
     75             Params.InsightServer = $"http://{_Address}:{_Port}";
     76         }
     77 
     78         /// <summary>
     79         /// 服务器地址发生变化
     80         /// </summary>
     81         /// <param name="sender"></param>
     82         /// <param name="e"></param>
     83         private void AddressChanged(object sender, EventArgs e)
     84         {
     85             _Address = View.AddressInput.Text;
     86         }
     87 
     88         /// <summary>
     89         /// 服务端口发生变化
     90         /// </summary>
     91         /// <param name="sender"></param>
     92         /// <param name="e"></param>
     93         private void PortChanged(object sender, EventArgs e)
     94         {
     95             _Port = View.PortInput.Text;
     96         }
     97 
     98         /// <summary>
     99         /// 保存用户账号选项发生变化
    100         /// </summary>
    101         /// <param name="sender"></param>
    102         /// <param name="e"></param>
    103         private void SaveUserChanged(object sender, EventArgs e)
    104         {
    105             _SaveUser = View.SaveUserCheckBox.Checked;
    106         }
    107     }
    108 }
    View Code
  • 相关阅读:
    ORACLE备份脚本(4-RMAN1级增量备份)
    ORACLE备份脚本(3-RMAN0级全备)
    解决oracle数据库最大数据文件数超了
    ORACLE 数据库备份脚本(数据泵2-指定用户)
    oracle 数据库备份脚本(数据泵1-全库)
    Python操作hdfs
    ant实例
    Java网络编程客户端和服务器通信
    WordCount程序
    Python脚本开发练习
  • 原文地址:https://www.cnblogs.com/xuanbg/p/6148469.html
Copyright © 2011-2022 走看看