回《【開源】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.帶給我們一種新的編碼思路
Winform版MVC跟Web版類似,目的都是分離界面和背景邏輯代碼,是一種開發模式,
Model:就是ObjectModel、Dao和Entity
View:就是WinForm
Controller:就是WinController
但是與Web版也有不同的地方,Winform版的界面與控制器關系更緊密、也更加靈活,比如界面上資料關聯,Web版的話必須利用Ajax發送多次請求,而Winform版不管有多少次資料關聯界面上不用處理,控制器可以自由控制界面上資料展示;這也是Winform版MVC與Web版MVC根本上的差別;另外,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 }
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 }
“程式=結構+算法”,其中“算法”同等于邏輯代碼,而“結構”分為三個方面,資料庫表結構、業務對象與實體、界面控件綁定資料源結構。而這三方面在程式中互相轉換,利用架構中ORM可以把資料庫表資料轉換為實體集合,把實體集合通過資料源綁定在DataGridView控件上顯示;界面控件通過指派轉換為實體對象,實體對象通過資料庫操作對象儲存到資料庫表中;是以代碼對于“結構”的封裝與轉換非常頻繁,結構處理得越好,那麼系統也就越清晰。實體與資料庫直接的轉換我們可以通過架構中的ORM來解決,而界面控件與業務實體直接轉換一般都很随意,以至于指派與取值代碼到處都是,經常跟邏輯層代碼混在一起,使我們後面對代碼的了解與維護都帶來了很多麻煩,是以需要一種好的開發架構來解決這個問題,而MVC模式就是不錯的選擇,使用界面接口把界面控件與業務對象直接的轉換都封裝起來,控制器都用接口的方式來操作界面;
以執行個體進行說明,先看書籍的“儲存”操作,傳統的方式肯定是這樣的,在儲存事件中先執行個體化Book對象,再把界面上的控件的值指派給Book對象,再把Book對象通過參數傳到背景進行儲存到資料中。再看界面上控件顯示書籍内容,傳統方式也是背景取出Book對象到界面,界面再一個個屬性指派在控件上。我們再看看使用MVC模式如何實作,先在界面接口IfrmBook中定義一個currBook的屬性,界面frmBookManager繼承IfrmBook接口實作currBook屬性,在get中實作界面控制指派給Book對象的代碼,在set中實作Book對象指派給界面控件的代碼;這樣我們就把取值與指派都封裝在一個屬性中,是不是很清晰,而且重用度很高;實作”儲存“操作,界面隻需向控制器發送一個消息,控制器自己通過接口擷取實體,再儲存到資料庫;
另外,MVC模式不止解決了“結構”上的問題,對比傳統的開發方式帶給了我們一種新的開發方式,讓我們實作功能的思路更清晰,代碼更精簡;
Winform版的MVC與Web版的控制器與界面關系雖然都是一對多的關系,一個控制器對應多個界面,Web版中雖然支援一個界面可以分别調用多個控制器,但這種方式不太建議,這會帶來程式上的複雜度,看起來比較亂;雖然兩者關系很相似,但卻有本質上的差別,Web版一個操作要擷取兩個資料,必須利用Ajax發送兩次請求分别擷取,等于資料與資料之間的邏輯是獨立的,完全沒有互動;而Winform版的就不一樣,兩個資料界面可以單獨向控制器請求,也可以一個請求控制器傳回多個資料在界面上。控制器利用界面接口可以随意的操作界面上的資料。
既然控制器操作界面這麼靈活,那麼為了編碼過程中不易失控,總結了一些界面與控制器的設計原則:
1.一個控制器對應多個界面接口,一個界面接口對應一個界面
2.先執行控制器代碼再執行界面代碼,由控制器操作界面而不是界面操作控制器
3.操作界面響應事件後,不在事件代碼中實作此功能,隻是發送一個消息到控制器,由控制器中調用業務邏輯實作此功能再通過界面接口傳回到界面
4.界面代碼除了事件代碼與實作接口代碼,盡量不要有其他代碼
5.同一控制器中的界面之間的資料傳遞不能通過構造函數或全部變量,隻能通過控制器傳遞
6.界面接口一般封裝的都是界面資料,界面資料又分為顯示資料和取值資料
7.控制器擷取界面值,除了通過接口方式,簡單的取值可以使用界面發送消息給控制器時一起發送過來
8.控制器可以通過接口調用界面,但界面不能直接調用控制器,界面隻能發送消息給控制器
9.全局變量一般都定義在控制器中
10.一個界面操作同控制器的其他界面是很容易的,同一控制器下的所有界面資料都是透明的
11.如果一個界面上的控件顯示有幾個特定狀态,比如:開始和結束兩個狀态下按鈕顯示,這時可以把這個狀态封裝在界面接口中
12.像錄入資料界面有多個控件,那麼對這些控件的取值和指派不需要全部封裝成接口,可以使用實體或其他結構封裝成一個接口屬性就行了
13.界面與控制器代碼分為兩個項目的話,接口檔案放在控制器項目中,界面項目引用控制器項目
在講新的編碼思路之前,先看一下傳統的編碼方法,以前一般都是先把界面畫好,再把界面上的功能一個個實作,從前台到背景,就比如“儲存”功能,先在儲存事件中編寫代碼,把界面控件上的值指派給Book對象,再編寫背景一個方法,界面調用背景方法把Book對象通過參數傳遞到背景,背景方法中編寫SQL語句把Book對象儲存到資料庫,再提示儲存成功。實作完儲存功能,可能接下來就實作查詢功能,删除功能等。從中得出傳統實作方式就像“點”到“面”,“點”就是界面上的功能,“面”就是一個個界面;這樣做起來是很順手,但是做完後我們再看代碼就能發現一些問題,因為界面上的功能并不是完全獨立,之間肯定存在或多或少的關聯,如果剛開始不從“面”上考慮,點與點之間的代碼必然會出現重複,這樣由少集多整個代碼就會變得複雜,這樣必定為以後得維護帶來很多麻煩。也許你可以事後對這些代碼進行重構來解決這些問題,但有沒有一種好的方法事前就解決掉這個問題了?這就是我說的新的編碼思路。
新的編碼思路簡單的說就是從“面”到“點”來編寫代碼,“面”不隻是指界面,也是指控制器,“點”就是實作功能。先看一下這種方式的實作過程:
MVC模式代碼編寫過程:
1.設計好界面
2.建立控制器對象及界面接口,以及控制器與界面的關聯
3.根據界面控件抽象出界面接口方法(綁定資料到界面控件)
4.根據業務操作抽象出控制器方法(界面操作事件)
5.界面繼承接口并實作接口與界面操作事件發送消息給控制器代碼
6.到此整個代碼架子已經完成,接下來隻要對控制器中的業務方法填空就行了
通過上面方式“面”中兩點把握好好後,基本後面“點”的實作隻要就簡單了,兩點分别是,封裝界面接口全面考慮資料結構轉換,提取控制器方法全面考慮業務功能;
6.總結
一般剛學習這種MVC模式的時候總是對界面接口這個檔案很不了解,因為以前的方式都是界面直接調用背景方法,搞個界面接口夾在中間非常多餘,這是因為剛開始對這種新的編碼思路還沒有了解,隻有了解了這種新的方式與以前的差別,再在開發中考慮上面所說的設計原則,那麼就能體驗到MVC模式帶來的好處。