二十八、带给我们一种新的编码思路——EFW框架CS系统开发中的MVC模式探讨

回《【开源】EFW框架系列文章索引》       

EFW框架源代码下载V1.3:http://pan.baidu.com/s/1c0dADO0

EFW框架实例源代码下载:http://pan.baidu.com/s/1eQCc69G

      前言:记得最初写出Winform版MVC的代码是在公司的一个产品中,产品有几个界面功能比较多,一个界面窗体的代码尽然有1万多行代码,让我们在维护这几个界面的时候非常的痛苦,你可能想可以把这个大的界面拆分成几个小的界面在集成在一起不就好了,但实际上这样行不同,首先界面上的控件之间依赖性太强不好拆分,更主要的是大量代码是针对网格控制的操作;后来我和另一个同事觉得重构这几个界面,同事也是一个对技术比较痴迷的那种,他利用委托来实现逻辑代码与界面之间的分离,针对界面中的控件操作定义一系列委托,再另外建一个对象编写业务逻辑并将数据通过委托在界面上显示;这种方式也达到了分离界面代码的目的,但写代码总感觉比较别扭,委托太多了根本搞不清楚,代码写起来也复杂,要弄清楚之间的调用关系不容易;而我参考了一下网上MVC的设计模式,建了一个控制器的对象用来封装所有业务逻辑代码,再把界面的所有数据操作封装成一个接口,控制器通过调用接口的方式对界面取数据和返回数据;对比起上面的委托方式,确实代码更简单,而且思路清晰,起码接口比委托封装性要好,所有的数据操作都可以封装在一个接口里;这样以来Winform控制器这种模式就初步成形了;通过使用此设计,让原来1万多行的界面代码缩减到只有几千行,就算加上控制器的代码也比原来少了一半不止;这就是Winform控制器的神奇之处,当初写完连自己都不相信;

后来在项目实践这种开发模式的过程中,不断的完善总结,也形成了一套内部约定吧,比如对界面接口该如何定义,复杂的业务逻辑中控制器对象又怎么划分等等,这些不太容易成文的东西达成了一种共识或理解;觉得一种设计方式不是说一下就能写出来的,也不是说从书本上看到某个设计就能拿过来用的;这都只是带给你灵感,促进你思考,而真要领悟它必须得在长期的实践中积累,一定得多写代码,反复的重构,这样它才会成为属于自己的开发模式,才能更好的传播给他人;

本文要点:

1.Winform版MVC介绍

2.Winform版MVC使用实例

3.针对“程序=结构+算法”中的“结构”分析

4.控制器与界面之间的关系以及一些设计原则

5.带给我们一种新的编码思路

1.Winform版MVC介绍

Winform版MVC跟Web版类似,目的都是分离界面和后台逻辑代码,是一种开发模式,

Model:就是ObjectModel、Dao和Entity

View:就是WinForm

Controller:就是WinController

但是与Web版也有不同的地方,Winform版的界面与控制器关系更紧密、也更加灵活,比如界面上数据联动,Web版的话必须利用Ajax发送多次请求,而Winform版不管有多少次数据联动界面上不用处理,控制器可以自由控制界面上数据展示;这也是Winform版MVC与Web版MVC根本上的区别;另外,Winform版多了一个界面接口封装了界面数据,而界面接口的设计好坏充分体现了对MVC模式的理解深度;本章主要内容也是讲解界面层与控制器直接的关系。

2.Winform版MVC使用实例

实例还是用书籍管理来说明,一个界面维护书籍目录,实现书籍的添加、修改、删除和查询;

界面效果

frmBookManager界面文件

 1 public partial class frmBookManager : BaseForm, IfrmBook
 2     {
 3         public frmBookManager()
 4         {
 5             InitializeComponent();
 6
 7             frmForm.AddItem(txtbookname, "BookName","必须输入书籍名称!");
 8             frmForm.AddItem(txtprice, "BuyPrice");
 9             frmForm.AddItem(txtdate, "BuyDate");
10             frmForm.AddItem(ckflag, "Flag");
11
12             txtdate.Value = DateTime.Now;
13         }
14
15
16         #region IfrmBook 成员
17
18         public void loadbooks(DataTable dt)
19         {
20             gridBook.DataSource = dt;
21         }
22
23         private Book _book;
24         public Books.Entity.Book currBook
25         {
26             get
27             {
28                 frmForm.GetValue<Book>(_book);
29                 return _book;
30             }
31             set
32             {
33                 _book = value;
34                 frmForm.Load<Book>(_book);
35             }
36         }
37
38         public void DrawPie(DataTable dt, string title)
39         {
40             DataTable tbData = dt;
41             TableColumn[] columns = new TableColumn[1];
42             columns[0].ColumnName = "时间";
43             columns[0].ColumnField = "num";
44             GraphControl gc;
45             DataTableStruct datatablestruct = DataTableStruct.Rows;
46             Color[] colors = new Color[tbData.Rows.Count];
47             Random random = new Random();
48             for (int index = 0; index < tbData.Rows.Count; index++)
49             {
50                 int red = random.Next(255);
51                 int blue = random.Next(255);
52                 int green = random.Next(255);
53                 colors[index] = Color.FromArgb(red, green, blue);
54             }
55             //饼图
56             gc = new CakyGraphControl(this.panelPie, datatablestruct, columns, colors, tbData, "BuyDate", 0);
57             gc.GraphTitle = title;
58             gc.DrawGraph();
59         }
60
61         #endregion
62         //选择书籍
63         private void gridBook_Click(object sender, EventArgs e)
64         {
65             if (gridBook.CurrentCell != null)
66             {
67                 int rowindex = gridBook.CurrentCell.RowIndex;
68                 DataTable dt = (DataTable)gridBook.DataSource;
69                 //
70                 int Id = Convert.ToInt32(dt.Rows[rowindex]["Id"]);
71                 _book = new Book();
72                 _book.Id = Id;
73                 //取出网格数据赋值给控件
74                 frmForm.Load(dt.Rows[rowindex]);
75             }
76         }
77         //新增
78         private void btnadd_Click(object sender, EventArgs e)
79         {
80             //清空右边面板控件数据
81             _book = new Book();
82
83         }
84         //保存
85         private void btnsave_Click(object sender, EventArgs e)
86         {
87             if (frmForm.Validate())
88             {
89                 InvokeController("bookSave");
90             }
91         }
92         //导出Excel
93         private void btnExport_Click(object sender, EventArgs e)
94         {
95             InvokeController("ExportExcel");
96         }
97
98
99     }

IfrmBook界面接口文件

1  public interface IfrmBook : IBaseView
2     {
3         //给网格加载数据
4         void loadbooks(DataTable dt);
5         //当前维护的书籍
6         Book currBook { get; set; }
7         //画饼图
8         void DrawPie(DataTable dt, string title);
9     }

bookwinController控制器文件

 1 [EFWCoreLib.WinformFrame.Controller.Menu(DefaultName = "bookmenu", DefaultViewName = "frmBookManager")]//与系统菜单对应
 2     [View(Name = "frmBookManager", DllName = "Books.Winform.dll", ViewTypeName = "Books.Winform.Viewform.frmBookManager")]
 3     public class bookwinController : BaseController
 4     {
 5         IfrmBook frmBook;
 6         public override void Init()
 7         {
 8             frmBook = (IfrmBook)DefaultView;
 9             //初始化加载书籍目录
10             GetBooks();
11             GetPie();
12         }
13
14         //获取书籍目录
15         public void GetBooks()
16         {
17             IBookDao bdao = NewDao<IBookDao>();
18             DataTable dt = bdao.GetBooks("", 0);
19             frmBook.loadbooks(dt);
20         }
21         //保存
22         public void bookSave()
23         {
24             frmBook.currBook.BindDb(oleDb, _container);
25             //从界面获取数据保存
26             frmBook.currBook.save();
27             //从数据库获取数据显示在界面上
28             GetBooks();
29         }
30
31         //导出Excel
32         public void ExportExcel()
33         {
34             IBookDao bdao = NewDao<IBookDao>();
35             DataTable dt = bdao.GetBooks("", 0);
36             Dictionary<string,string> dicCol=new Dictionary<string,string>();
37             dicCol.Add("BookName", "书籍名称");
38             dicCol.Add("BuyPrice", "价格");
39             dicCol.Add("BuyDate", "购买时间");
40             ExcelHelper.Export(dt,"书籍目录",dicCol,"c:\\books.xls");
41         }
42
43         //查询数据画饼图
44         public void GetPie()
45         {
46             string strsql=@"SELECT CONVERT(varchar(100), BuyDate, 23) BuyDate,COUNT(*) num FROM dbo.Books GROUP BY CONVERT(varchar(100), BuyDate, 23) ";
47             DataTable dt=oleDb.GetDataTable(strsql);
48             frmBook.DrawPie(dt, "按时间书籍数量");
49         }
50     }

3.针对“程序=结构+算法”中的“结构”分析

“程序=结构+算法”,其中“算法”同等于逻辑代码,而“结构”分为三个方面,数据库表结构、业务对象与实体、界面控件绑定数据源结构。而这三方面在程序中相互转换,利用框架中ORM可以把数据库表数据转换为实体集合,把实体集合通过数据源绑定在DataGridView控件上显示;界面控件通过赋值转换为实体对象,实体对象通过数据库操作对象保存到数据库表中;所以代码对于“结构”的封装与转换非常频繁,结构处理得越好,那么系统也就越清晰。实体与数据库直接的转换我们可以通过框架中的ORM来解决,而界面控件与业务实体直接转换一般都很随意,以至于赋值与取值代码到处都是,经常跟逻辑层代码混在一起,使我们后面对代码的理解与维护都带来了很多麻烦,所以需要一种好的开发架构来解决这个问题,而MVC模式就是不错的选择,使用界面接口把界面控件与业务对象直接的转换都封装起来,控制器都用接口的方式来操作界面;

以实例进行说明,先看书籍的“保存”操作,传统的方式肯定是这样的,在保存事件中先实例化Book对象,再把界面上的控件的值赋值给Book对象,再把Book对象通过参数传到后台进行保存到数据中。再看界面上控件显示书籍内容,传统方式也是后台取出Book对象到界面,界面再一个个属性赋值在控件上。我们再看看使用MVC模式如何实现,先在界面接口IfrmBook中定义一个currBook的属性,界面frmBookManager继承IfrmBook接口实现currBook属性,在get中实现界面控制赋值给Book对象的代码,在set中实现Book对象赋值给界面控件的代码;这样我们就把取值与赋值都封装在一个属性中,是不是很清晰,而且重用度很高;实现”保存“操作,界面只需向控制器发送一个消息,控制器自己通过接口获取实体,再保存到数据库;

另外,MVC模式不止解决了“结构”上的问题,对比传统的开发方式带给了我们一种新的开发方式,让我们实现功能的思路更清晰,代码更精简;

4.控制器与界面之间的关系以及一些设计原则

Winform版的MVC与Web版的控制器与界面关系虽然都是一对多的关系,一个控制器对应多个界面,Web版中虽然支持一个界面可以分别调用多个控制器,但这种方式不太建议,这会带来程序上的复杂度,看起来比较乱;虽然两者关系很相似,但却有本质上的区别,Web版一个操作要获取两个数据,必须利用Ajax发送两次请求分别获取,等于数据与数据之间的逻辑是独立的,完全没有交互;而Winform版的就不一样,两个数据界面可以单独向控制器请求,也可以一个请求控制器返回多个数据在界面上。控制器利用界面接口可以随意的操作界面上的数据。

既然控制器操作界面这么灵活,那么为了编码过程中不易失控,总结了一些界面与控制器的设计原则:

1.一个控制器对应多个界面接口,一个界面接口对应一个界面

2.先执行控制器代码再执行界面代码,由控制器操作界面而不是界面操作控制器

3.操作界面响应事件后,不在事件代码中实现此功能,只是发送一个消息到控制器,由控制器中调用业务逻辑实现此功能再通过界面接口返回到界面

4.界面代码除了事件代码与实现接口代码,尽量不要有其他代码

5.同一控制器中的界面之间的数据传递不能通过构造函数或全部变量,只能通过控制器传递

6.界面接口一般封装的都是界面数据,界面数据又分为显示数据和取值数据

7.控制器获取界面值,除了通过接口方式,简单的取值可以使用界面发送消息给控制器时一起发送过来

8.控制器可以通过接口调用界面,但界面不能直接调用控制器,界面只能发送消息给控制器

9.全局变量一般都定义在控制器中

10.一个界面操作同控制器的其他界面是很容易的,同一控制器下的所有界面数据都是透明的

11.如果一个界面上的控件显示有几个特定状态,比如:开始和结束两个状态下按钮显示,这时可以把这个状态封装在界面接口中

12.像录入数据界面有多个控件,那么对这些控件的取值和赋值不需要全部封装成接口,可以使用实体或其他结构封装成一个接口属性就行了

13.界面与控制器代码分为两个项目的话,接口文件放在控制器项目中,界面项目引用控制器项目

5.带给我们一种新的编码思路

在讲新的编码思路之前,先看一下传统的编码方法,以前一般都是先把界面画好,再把界面上的功能一个个实现,从前台到后台,就比如“保存”功能,先在保存事件中编写代码,把界面控件上的值赋值给Book对象,再编写后台一个方法,界面调用后台方法把Book对象通过参数传递到后台,后台方法中编写SQL语句把Book对象保存到数据库,再提示保存成功。实现完保存功能,可能接下来就实现查询功能,删除功能等。从中得出传统实现方式就像“点”到“面”,“点”就是界面上的功能,“面”就是一个个界面;这样做起来是很顺手,但是做完后我们再看代码就能发现一些问题,因为界面上的功能并不是完全独立,之间肯定存在或多或少的关联,如果刚开始不从“面”上考虑,点与点之间的代码必然会出现重复,这样由少集多整个代码就会变得复杂,这样必定为以后得维护带来很多麻烦。也许你可以事后对这些代码进行重构来解决这些问题,但有没有一种好的方法事前就解决掉这个问题了?这就是我说的新的编码思路。

新的编码思路简单的说就是从“面”到“点”来编写代码,“面”不只是指界面,也是指控制器,“点”就是实现功能。先看一下这种方式的实现过程:

MVC模式代码编写过程:

1.设计好界面

2.新建控制器对象及界面接口,以及控制器与界面的关联

3.根据界面控件抽象出界面接口方法(绑定数据到界面控件)

4.根据业务操作抽象出控制器方法(界面操作事件)

5.界面继承接口并实现接口与界面操作事件发送消息给控制器代码

6.到此整个代码架子已经完成,接下来只要对控制器中的业务方法填空就行了

通过上面方式“面”中两点把握好好后,基本后面“点”的实现只要就简单了,两点分别是,封装界面接口全面考虑数据结构转换,提取控制器方法全面考虑业务功能;

6.总结

一般刚学习这种MVC模式的时候总是对界面接口这个文件很不理解,因为以前的方式都是界面直接调用后台方法,搞个界面接口夹在中间非常多余,这是因为刚开始对这种新的编码思路还没有理解,只有理解了这种新的方式与以前的区别,再在开发中考虑上面所说的设计原则,那么就能体验到MVC模式带来的好处。

时间: 2024-12-22 23:03:01

二十八、带给我们一种新的编码思路——EFW框架CS系统开发中的MVC模式探讨的相关文章

二十七、EFW框架BS系统开发中的MVC模式探讨

回<[开源]EFW框架系列文章索引>        EFW框架源代码下载V1.3:http://pan.baidu.com/s/1c0dADO0 EFW框架实例源代码下载:http://pan.baidu.com/s/1eQCc69G 上一章<EFW框架破茧成蝶>通过讲叙自己的一些编程经验,有对系统架构的认识,也有EFW框架中MVC模式的由来.整个过程分为五个阶段: asp网站:做asp网站的时候根本没有什么系统的概念,就是几段代码复制粘贴拼装成外表不一样,功能就是那么几个的企业网

数据结构(二十八)图的五种存储结构

由于图的结构比较复杂,任意两个顶点之间都可能存在联系,因此无法以数据元素在内存中的物理位置来表示元素之间的关系,也就是说,图不可能用简单的顺序存储结构来表示. 多重链表的方式,即以一个数据域和多个指针域组成的结点表示图中的一个结点,尽管可以实现图结构,但是如果各个顶点的度数相差很大,按度数最大的顶点设计结点结构会造成很多存储单元的浪费,而若按每个顶点自己的度数设计不同的结点结构,又带来操作的不变. 图的类型主要有4种:无向图.有向图.无向网和有向网. 图的五种常见的存储结构:领接矩阵.领接表.十

QT开发(二十八)——QT常用类(二)

QT开发(二十八)--QT常用类(二) 一.QDir 1.QDir简介 QDir提供对目录结构及其内容的访问. QDir通过相对或绝对路径指向一个文件. 2.QDir成员函数 QDir主要成员函数如下: QDir::QDir ( const QDir & dir ) QDir::QDir ( const QString & path = QString() ) Dir::QDir ( const QString & path, const QString & nameFil

二十八个 HTML5 特性与技巧

1. New Doctype  你还在使用令人讨厌的难记的XHTML文档类型声明吗?<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd>如果还在用,为什么呢?如同Douglas Quaid所说,转到新的HTML5这种文档类型吧,它会使你看起来更年轻.实际上,你当真知道XHTML的文档类型声明

javaweb学习总结(二十八)——JSTL标签库之核心标签【转】

原文地址:javaweb学习总结(二十八)——JSTL标签库之核心标签 一.JSTL标签库介绍 JSTL标签库的使用是为弥补html标签的不足,规范自定义标签的使用而诞生的.使用JSLT标签的目的就是不希望在jsp页面中出现java逻辑代码 二.JSTL标签库的分类 核心标签(用得最多) 国际化标签(I18N格式化标签) 数据库标签(SQL标签,很少使用) XML标签(几乎不用) JSTL函数(EL函数) 三.核心标签库使用说明 JSTL的核心标签库标签共13个,使用这些标签能够完成JSP页面的

Cocos2dx 3.0 过渡篇(二十八)C++11强类型枚举

一朋友在微信朋友圈晒了张照片,随手点开大图,带着欣赏的眼光扫了下,恩,几个月不见,又漂亮了...咦?等等,她戴的这是什么?酷炫的造型!金属边框!微型摄像头!这不是传说中的谷歌眼镜么?土豪啊,还好我们已经是朋友了...我先给了她一个赞,然后直奔主题,霸气回复道:我过几天去找你,你戴的是谷歌眼镜吧,哼哼小样,不想死的话...就让我...摸一下下可以么,我不奢求戴,摸一下就满足了...(哎,丢人啊). ------------------- 在cocos2dx 3.0的文档里有这么一句话:以 k 开头

二十八条社会潜规则

今天登录简书逛了一会,无意中看到一篇有意思的文章,感觉有点道理,于是顺便摘抄了下来,与大家共勉. 二十八条社会潜规则:先学会不生气,再学会气死人 1.能在一定位置上的人,一定有他的过人之处,不管你多么讨厌他. 2.要想屏蔽某些人的朋友圈,最好把他同事微信分到一个组里,要屏蔽一起都屏蔽了. 3.不要总在旁人面前提你的朋友多牛逼,你要懂得,别人的成就与你无关. 4.朋友同事之间,帮忙是情分,不帮忙是本分,不要把别人对你的好,当作理所当然. 5.和同事拼单买东西叫外卖,一定把支付明细的截图发给每个人,

纯干货!二十八道BATJ大厂Java岗之"多线程与并发"面试题分享

年底了,又到了跳槽季啦,该刷题走起了.这里总结了一些被问到可能会懵逼的面试真题,有需要的可以看下- 一.进程与线程 进程是资源分配的最小单位,线程是cpu调度的最小单位.线程也被称为轻量级进程. 所有与进程相关的资源,都被记录在PCB中 进程是抢占处理及的调度单位:线程属于某个进程,共享其资源 一个 Java 程序的运行是 main 线程和多个其他线程同时运行. 二.Thread中的start和run方法的区别 调用start()方法会创建一个新的子线程并启动 run()方法只是Thread的一

angular学习笔记(二十八)-$http(6)-使用ngResource模块构建RESTful架构

ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入angular-resource.min.js文件 2.在模块中依赖ngResourece,在服务中注入$resource var HttpREST = angular.module('HttpREST',['ngResource']); HttpREST.factory('cardResource