二十8、帶給咱們一種新的編碼思路——EFW框架CS系統開發中的MVC模式探討

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

 EFW框架源代碼下載V1.3:http://pan.baidu.com/s/1c0dADO0算法

 EFW框架實例源代碼下載:http://pan.baidu.com/s/1eQCc69Gsql

 

      前言:記得最初寫出Winform版MVC的代碼是在公司的一個產品中,產品有幾個界面功能比較多,一個界面窗體的代碼盡然有1萬多行代碼,讓咱們在維護這幾個界面的時候很是的痛苦,你可能想能夠把這個大的界面拆分紅幾個小的界面在集成在一塊兒不就行了,但實際上這樣行不一樣,首先界面上的控件之間依賴性太強很差拆分,更主要的是大量代碼是針對網格控制的操做;後來我和另外一個同事以爲重構這幾個界面,同事也是一個對技術比較癡迷的那種,他利用委託來實現邏輯代碼與界面之間的分離,針對界面中的控件操做定義一系列委託,再另外建一個對象編寫業務邏輯並將數據經過委託在界面上顯示;這種方式也達到了分離界面代碼的目的,但寫代碼總感受比較彆扭,委託太多了根本搞不清楚,代碼寫起來也複雜,要弄清楚之間的調用關係不容易;而我參考了一下網上MVC的設計模式,建了一個控制器的對象用來封裝全部業務邏輯代碼,再把界面的全部數據操做封裝成一個接口,控制器經過調用接口的方式對界面取數據和返回數據;對比起上面的委託方式,確實代碼更簡單,並且思路清晰,起碼接口比委託封裝性要好,全部的數據操做均可以封裝在一個接口裏;這樣以來Winform控制器這種模式就初步成形了;經過使用此設計,讓原來1萬多行的界面代碼縮減到只有幾千行,就算加上控制器的代碼也比原來少了一半不止;這就是Winform控制器的神奇之處,當初寫完連本身都不相信;數據庫

       後來在項目實踐這種開發模式的過程當中,不斷的完善總結,也造成了一套內部約定吧,好比對界面接口該如何定義,複雜的業務邏輯中控制器對象又怎麼劃分等等,這些不太容易成文的東西達成了一種共識或理解;以爲一種設計方式不是說一下就能寫出來的,也不是說從書本上看到某個設計就能拿過來用的;這都只是帶給你靈感,促進你思考,而真要領悟它必須得在長期的實踐中積累,必定得多寫代碼,反覆的重構,這樣它纔會成爲屬於本身的開發模式,才能更好的傳播給他人;設計模式

本文要點:數據結構

1.Winform版MVC介紹架構

2.Winform版MVC使用實例框架

3.針對「程序=結構+算法」中的「結構」分析dom

4.控制器與界面之間的關係以及一些設計原則ide

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     }
View Code

 

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     }
View Code

 

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     }
View Code

 

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模式帶來的好處。

相關文章
相關標籤/搜索