一区二区久久-一区二区三区www-一区二区三区久久-一区二区三区久久精品-麻豆国产一区二区在线观看-麻豆国产视频

.NET平臺(tái)上的Model-View-Presenter模式實(shí)踐

  為什么要寫(xiě)這篇文章

      筆者當(dāng)前正在負(fù)責(zé)研究所中一個(gè)項(xiàng)目,這個(gè)項(xiàng)目基于.NET平臺(tái),初步擬采用C/S部署體系,所以選擇了Windows Forms作為其UI。經(jīng)過(guò)幾此迭代,我們發(fā)現(xiàn)了一個(gè)問(wèn)題:雖然業(yè)務(wù)邏輯已經(jīng)封裝到Services層中,但諸多的UI邏輯仍然彌漫在各個(gè)事件Listener中,使得UI顯得臃腫不堪,并且存在諸多重復(fù)性代碼。另外,需求提供方說(shuō),根據(jù)實(shí)際需要,不排除將部署結(jié)構(gòu)改為B/S的可能性,甚至可能會(huì)要求此系統(tǒng)同時(shí)支持C/S和B/S兩種部署方式。那么,如果保持目前將UI邏輯編碼到Windows Forms中的方式,到時(shí)這些UI邏輯將無(wú)法復(fù)用,修改部署方式的代價(jià)很大。

      為了解決以上兩個(gè)問(wèn)題,筆者和相關(guān)人員商量后,決定引入既有成熟模式,重新設(shè)計(jì)表示層的架構(gòu)方式,并重構(gòu)既有代碼。

      提到表示層(Presentation Layer)的模式,我想大家腦海中第一個(gè)閃過(guò)的很可能是經(jīng)典的MVC(Model-View-Controller)。我最初也準(zhǔn)備使用MVC,但經(jīng)過(guò)分析和實(shí)驗(yàn)后,我發(fā)現(xiàn)MVC并不適合目前的情況,因?yàn)镸VC的結(jié)構(gòu)相對(duì)復(fù)雜,Model和View之間要實(shí)現(xiàn)一個(gè)Observer模式,并實(shí)現(xiàn)雙向通信。這樣重構(gòu)起來(lái)Services層也必須修改。我并不想修改Services層,而且我想將View和Model徹底隔離,因?yàn)槲覀€(gè)人并不喜歡View和Model直接通信的架構(gòu)方式。最終,我選擇了MVP(Model-View-Presenter)模式。

      經(jīng)過(guò)兩天的重構(gòu)和驗(yàn)證,目前已經(jīng)將MVP正式引入項(xiàng)目的表示層,并且解決了上文提到的兩個(gè)問(wèn)題。在這期間,積累了少許關(guān)于在.NET平臺(tái)上實(shí)踐MVP的經(jīng)驗(yàn),在這里匯集成此文,和朋友們共享。

  UI與P Logic

      首先,我想先明確一下UI和P Logic的概念。

      表示層可以拆分為兩個(gè)部分:User Interface(簡(jiǎn)稱UI)和Presentation Logic(簡(jiǎn)稱P Logic)。

      UI是系統(tǒng)與用戶交互的界面性概念,它的職責(zé)有兩個(gè)——接受用戶的輸入和向用戶展示輸出。UI應(yīng)該是一個(gè)純靜態(tài)的概念,本身不應(yīng)包含任何邏輯,而單純是一個(gè)接受輸入和展示輸出的“外殼”。例如,一個(gè)不包含邏輯的Windows Form,一張不包含邏輯的頁(yè)面,一個(gè)不包含邏輯的Flex界面,都屬于UI。

      P Logic是表示層應(yīng)有的邏輯性內(nèi)容。例如,某個(gè)文本內(nèi)容不能為空,當(dāng)某個(gè)事件發(fā)生時(shí)獲取界面上哪些內(nèi)容,這都屬于P Logic。應(yīng)該指出,P Logic應(yīng)該是抽象于具體UI的,它的本質(zhì)是邏輯,可以復(fù)用到任何與此邏輯相符的UI。

      UI與P Logic之間的聯(lián)系是事件,UI可以根據(jù)用戶的動(dòng)作觸發(fā)各種事件,P Logic響應(yīng)事件并執(zhí)行相應(yīng)的邏輯。P Logic對(duì)UI存在約束作用,P Logic規(guī)定一套UI契約,UI要根據(jù)契約實(shí)現(xiàn),才能被相應(yīng)的P Logic調(diào)用。

      下圖展示了UI與P Logic的結(jié)構(gòu)及交互原理。

圖1、UI與P Logic

  Model-View-Presenter模式

      MVP模式最早由Taligent的Mike Potel在《MVP: Model-View-Presenter The Taligent Programming Model for C++ and Java》(點(diǎn)擊這里下載)一文中提出。MVP的提出主要是為了解決MVC模式中結(jié)構(gòu)過(guò)于復(fù)雜和模型-視圖耦合性過(guò)高的問(wèn)題。MVP的核心思想是將UI分離成View,將P Logic分離成Presenter,而業(yè)務(wù)邏輯和領(lǐng)域相關(guān)邏輯都分離到Model中。View和Model完全解除耦合,不再像MVC中實(shí)現(xiàn)一個(gè)Observer模式,兩者的通信則依靠Presenter進(jìn)行。Presenter響應(yīng)View接獲的用戶動(dòng)作,并調(diào)用Model中的業(yè)務(wù)邏輯,最后將用戶需要的信息返回給View。

      下圖直觀表示了MVP模式:

圖2、MVP模式

      圖2清楚地展示了MVP模式的幾個(gè)特點(diǎn):

      1、View和Model完全解耦,兩者不發(fā)生直接關(guān)聯(lián),通過(guò)Presenter進(jìn)行通信。

      2、Presenter并不是與具體的View耦合,而是和一個(gè)抽象的View Interface耦合,View Interface相當(dāng)于一個(gè)契約,抽象出了對(duì)應(yīng)View應(yīng)實(shí)現(xiàn)的方法。只要實(shí)現(xiàn)了這個(gè)接口,任何View都可以與指定Presenter兼容,從而實(shí)現(xiàn)了P Logic的復(fù)用性和視圖的無(wú)縫替換。

      3、View在MVP里應(yīng)該是一個(gè)“極瘦”的概念,最多也只能包含維護(hù)自身狀態(tài)的邏輯,而其它邏輯都應(yīng)實(shí)現(xiàn)在Presenter中。

      總的來(lái)說(shuō),使用MVP模式可以得到以下兩個(gè)收益:

      1、將UI和P Logic兩個(gè)關(guān)注點(diǎn)分離,得到更干凈和單一的代碼結(jié)構(gòu)。

      2、實(shí)現(xiàn)了P Logic的復(fù)用以及View的無(wú)縫替換。

  在.NET平臺(tái)上實(shí)現(xiàn)MVP模式

      這一節(jié)通過(guò)一個(gè)示例程序展示在.NET平臺(tái)上實(shí)現(xiàn)MVP的一種實(shí)踐方法。本來(lái)想通過(guò)我目前負(fù)責(zé)的實(shí)際項(xiàng)目中的代碼片段作為Demo,但這樣做存在兩個(gè)問(wèn)題:一是這樣做可能會(huì)違反學(xué)校的保密守則,二是這個(gè)項(xiàng)目應(yīng)用了許多其他框架和模式,如通過(guò)Unity實(shí)現(xiàn)依賴注入,通過(guò)PostSharp實(shí)現(xiàn)AOP來(lái)負(fù)責(zé)異常處理和事務(wù)管理等,通過(guò)NHibernate實(shí)現(xiàn)的ORM等等,這樣如果讀者不了解系統(tǒng)整體架構(gòu)就很難完全讀懂代碼片段,MVP模式不夠突出。因此,我專(zhuān)門(mén)為這篇文章實(shí)現(xiàn)了一個(gè)Demo,其中的MVP實(shí)踐方式與實(shí)際項(xiàng)目中是一致的,而且Demo規(guī)模小,排除了其他干擾,使得讀者更容易理解其中的MVP實(shí)現(xiàn)方式。

      這個(gè)簡(jiǎn)單的Demo運(yùn)行效果如下:

圖3、Demo界面

      這個(gè)Demo的功能如下:這是一個(gè)簡(jiǎn)單的點(diǎn)餐軟件。系統(tǒng)中存有餐廳所有菜品的信息,客戶只需在界面右側(cè)輸入菜品名稱和數(shù)量,單擊“添加”按鈕,菜品就會(huì)被添加到左側(cè)點(diǎn)餐列表,并顯示此菜品詳細(xì)信息。如果所點(diǎn)菜品不存在則軟件會(huì)給出提示。另外,在左側(cè)已點(diǎn)餐品列表中右鍵單擊某個(gè)條目,在彈出菜單中點(diǎn)擊“刪除”,則可將此菜品從列表刪除。

      下面分步驟介紹應(yīng)用了MVP模式的實(shí)現(xiàn)方式。

      第一步,解決方法及工程結(jié)構(gòu)

      這個(gè)Demo共有三個(gè)工程,MVPSimple.Model為Mock方式實(shí)現(xiàn)的Services,作為Model;MVPSimple.Presenters為Presenter工程,其中包括Presenter和View Interface;MVPSimple.WinUI為View的Windows Forms實(shí)現(xiàn)。

      第二步,構(gòu)建Mock方式的Services

      因?yàn)橹攸c(diǎn)在于表示層,所以這里的Services使用了Mock方式,并沒(méi)有包含真正的業(yè)務(wù)領(lǐng)域邏輯。其中MVPSimple.Model工程里兩個(gè)文件的代碼如下:

      FoodDto.cs:

using System;namespace MVPSimple.Model{    /// <summary>    /// 表示菜品類(lèi)別的枚舉類(lèi)型    /// </summary>    public enum FoodType    {        主菜 = 1,        湯 = 2,        甜品 = 3,    }    /// <summary>    /// 菜品的Data Transfer Object    /// </summary>    public class FoodDto    {        /// <summary>        /// ID,標(biāo)識(shí)字段        /// </summary>        public Int32 ID { get; set; }        /// <summary>        /// 菜品名稱        /// </summary>        public String Name { get; set; }        /// <summary>        /// 菜品類(lèi)型        /// </summary>        public FoodType Type { get; set; }        /// <summary>        /// 菜品價(jià)格        /// </summary>        public Double Price { get; set; }        /// <summary>        /// 點(diǎn)菜數(shù)量        /// </summary>        public Int32 Amount { get; set; }    }}

      FoodServices.cs:

using System;using System.Collections.Generic;namespace MVPSimple.Model{    /// <summary>    /// 菜品Services的Mock實(shí)現(xiàn)    /// </summary>    public class FoodServices    {        private IList<FoodDto> foodList = new List<FoodDto>();        /// <summary>        /// 默認(rèn)構(gòu)造函數(shù),初始化各個(gè)菜品        /// </summary>        public FoodServices()        {            this.foodList.Add(                new FoodDto()                {                    ID = 1,                    Name = "牛排",                    Price = 60.00,                    Type = FoodType.主菜,                }            );            this.foodList.Add(                new FoodDto()                {                    ID = 2,                    Name = "法式蝸牛",                    Price = 120.00,                    Type = FoodType.主菜,                }            );            this.foodList.Add(                new FoodDto()                {                    ID = 3,                    Name = "水果沙拉",                    Price = 58.00,                    Type = FoodType.甜品,                }            );            this.foodList.Add(                new FoodDto()                {                    ID = 4,                    Name = "奶油紅菜湯",                    Price = 15.00,                    Type = FoodType.湯,                }            );            this.foodList.Add(                new FoodDto()                {                    ID = 5,                    Name = "雜拌湯",                    Price = 20.00,                    Type = FoodType.湯,                }            );        }        /// <summary>        /// 按照菜品名稱獲取菜品詳細(xì)信息        /// </summary>        /// <param name="foodName">菜品名稱</param>        /// <returns>含有指定菜品信息的DTO</returns>        public FoodDto GetFoodDetailByName(String foodName)        {            foreach (FoodDto f in this.foodList)            {                if (f.Name.Equals(foodName))                {                    return f;                }            }            return new FoodDto() { ID = 0 };        }    }}

      第三步,通過(guò)View Interface規(guī)定View契約

      如果想實(shí)現(xiàn)Presenter和View的交互和無(wú)縫替換,必須在它們之間規(guī)定一個(gè)契約。一般來(lái)說(shuō),每一張界面(注意是界面不是視圖)都應(yīng)該對(duì)應(yīng)一個(gè)View接口,不過(guò)由于Demo只有一個(gè)頁(yè)面,所以也只有一個(gè)View接口。

      這里需要特別強(qiáng)調(diào),View接口必須抽象于任何具體視圖而服務(wù)于Presenter,所以,View接口中絕不能出現(xiàn)任何與具體視圖相關(guān)的元素。例如,我們的Demo中是使用Windows Forms作為視圖實(shí)現(xiàn),但View接口中絕不可出現(xiàn)與Windows Forms相耦合的元素,如返回一個(gè)Winform的TextBox。因?yàn)槿绻@樣做的話,使用其他技術(shù)實(shí)現(xiàn)的View就無(wú)法實(shí)現(xiàn)這個(gè)接口了,如使用Web Forms實(shí)現(xiàn),而Web Forms是不可能返回一個(gè)Winform的TextBox的。

      下面給出視圖接口的代碼。

      IMainView.cs:

using System;using System.Collections.Generic;using MVPSimple.Model;namespace MVPSimple.Presenters{    /// <summary>    /// MainView的接口,所有MainView必須實(shí)現(xiàn)此接口,此接口暴露給Presenter    /// </summary>    public interface IMainView    {        /// <summary>        /// View上的菜品名稱        /// </summary>        String foodName { get; set; }        /// <summary>        /// View上點(diǎn)菜數(shù)量        /// </summary>        Int32 Amount { get; set; }        /// <summary>        /// 判斷某一菜品是否已經(jīng)存在于點(diǎn)菜列表中        /// </summary>        /// <param name="foodName">菜品名稱</param>        /// <returns>結(jié)果</returns>        bool IsExistInList(String foodName);        /// <summary>        /// 將某一菜品加入點(diǎn)菜列表        /// </summary>        /// <param name="food">菜品DTO</param>        void AddFoodToList(FoodDto food);        /// <summary>        /// 將某一已點(diǎn)菜品從列表中移除        /// </summary>        /// <param name="foodName">欲移除的菜品名稱</param>        void RemoveFoodFromList(String foodName);        /// <summary>        /// View顯示提示信息給用戶        /// </summary>        /// <param name="message">信息內(nèi)容</param>        void ShowMessage(String message);        /// <summary>        /// View顯示確認(rèn)信息并返回結(jié)果        /// </summary>        /// <param name="message">信息內(nèi)容</param>        /// <returns>用戶回答是確定還是取消。True - 確定,F(xiàn)alse - 取消</returns>        bool ShowConfirm(String message);    }}

      可以看到,IMainView抽象了如圖3所示的界面,但又不包含任何與Windows Forms相耦合的元素,因此如果需要,以后完全可以使用Web Forms、WPF或SL等技術(shù)實(shí)現(xiàn)這個(gè)接口。

      第四步,實(shí)現(xiàn)Presenter

      上文說(shuō)過(guò),一個(gè)界面應(yīng)該對(duì)應(yīng)一個(gè)Presenter,這個(gè)Demo里只有一個(gè)界面,所以只有一個(gè)Presenter。Presenter僅于視圖接口耦合,而并不和具體視圖耦合,最好證據(jù)就是Presenter工程根本沒(méi)有引用WinUI工程!代碼如下:

      MainPresenter.cs:

using System;using System.Collections.Generic;using MVPSimple.Model;namespace MVPSimple.Presenters{    /// <summary>    /// MainView的Presenter    /// </summary>    public class MainPresenter    {        /// <summary>        /// 當(dāng)前關(guān)聯(lián)View        /// </summary>        public IMainView View { get; set; }        /// <summary>        /// 默認(rèn)構(gòu)造函數(shù),初始化View        /// </summary>        /// <param name="view">MainView對(duì)象</param>        public MainPresenter(IMainView view)        {            View = view;        }        #region Acitons        /// <summary>        /// Action:將所點(diǎn)菜品增加到點(diǎn)菜列表        /// </summary>        public void AddFoodAction()        {            if (String.IsNullOrEmpty(View.foodName))            {                View.ShowMessage("請(qǐng)選輸入菜品名稱");                return;            }            if (View.Amount <= 0)            {                View.ShowMessage("點(diǎn)菜的份數(shù)至少要是一份");                return;            }            if (View.IsExistInList(View.foodName))            {                View.ShowMessage(String.Format("菜品【{0}】已經(jīng)在您的菜單中", View.foodName));                return;            }            FoodServices foodServ = new FoodServices();            FoodDto food = foodServ.GetFoodDetailByName(View.foodName);            if (food.ID == 0)            {                View.ShowMessage(String.Format("抱歉,本餐廳沒(méi)有菜品【{0}】",View.foodName));                return;            }            View.AddFoodToList(food);        }        /// <summary>        /// Action:從點(diǎn)菜列表移除某一菜品        /// </summary>        /// <param name="foodName">被移除菜品的名稱</param>        public void RemoveFoodAction(String foodName)        {            if (View.ShowConfirm("確定要?jiǎng)h除嗎?"))            {                View.RemoveFoodFromList(foodName);            }        }        #endregion    }}

      第五步,實(shí)現(xiàn)View

      這里我們使用Windows Forms實(shí)現(xiàn)View。如果朋友們有興趣,完全可以自己試著用Web或WPF實(shí)現(xiàn)以下視圖,同時(shí)可以驗(yàn)證P Logic的可復(fù)用性和視圖無(wú)縫替換,親身體驗(yàn)一下MVP模式的威力。Winform的View代碼如下。

      frmMain.cs:

using System;using System.Windows.Forms;using MVPSimple.Model;using MVPSimple.Presenters;namespace MVPSimple.WinUI{    /// <summary>    /// MainView的Windows Forms實(shí)現(xiàn)    /// </summary>    public partial class frmMain : Form, IMainView    {        /// <summary>        /// 相關(guān)聯(lián)的Presenter        /// </summary>        private MainPresenter presenter;        /// <summary>        /// 默認(rèn)構(gòu)造函數(shù),初始化Presenter        /// </summary>        public frmMain()        {            InitializeComponent();            this.presenter = new MainPresenter(this);        }        #region IMainView Members        /// <summary>        /// View上的菜品名稱        /// </summary>        public String foodName        {            get { return this.tbFoodName.Text; }            set { this.tbFoodName.Text = value; }        }        /// <summary>        /// View上點(diǎn)菜數(shù)量        /// </summary>        public Int32 Amount        {            get { return (Int32)this.tbAmount.Value; }            set { this.tbAmount.Value = (Decimal)value; }        }        /// <summary>        /// 判斷某一菜品是否已經(jīng)存在于點(diǎn)菜列表中        /// </summary>        /// <param name="foodName">菜品名稱</param>        /// <returns>結(jié)果</returns>        public bool IsExistInList(String foodName)        {            foreach (ListViewItem i in this.lvFoods.Items)            {                if (i.Text == foodName)                {                    return true;                }            }            return false;        }        /// <summary>        /// 將某一菜品加入點(diǎn)菜列表        /// </summary>        /// <param name="food">菜品DTO</param>        public void AddFoodToList(FoodDto food)        {            ListViewItem item = new ListViewItem();            Double price = food.Price * (Double)this.tbAmount.Value;            item.Text = food.Name;            item.SubItems.Add(food.Type.ToString());            item.SubItems.Add(this.tbAmount.Value.ToString());            item.SubItems.Add(price.ToString());            this.lvFoods.Items.Add(item);        }        /// <summary>        /// 將某一已點(diǎn)菜品從列表中移除        /// </summary>        /// <param name="foodName">欲移除的菜品名稱</param>        public void RemoveFoodFromList(String foodName)        {            foreach (ListViewItem i in this.lvFoods.Items)            {                if (i.Text == foodName)                {                    this.lvFoods.Items.Remove(i);                }            }        }        /// <summary>        /// View顯示提示信息給用戶        /// </summary>        /// <param name="message">信息內(nèi)容</param>        public void ShowMessage(String message)        {            MessageBox.Show(message, "信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);        }        /// <summary>        /// View顯示確認(rèn)信息并返回結(jié)果        /// </summary>        /// <param name="message">信息內(nèi)容</param>        /// <returns>用戶回答是確定還是取消。True - 確定,F(xiàn)alse - 取消</returns>        public bool ShowConfirm(String message)        {            DialogResult result = MessageBox.Show(message, "確認(rèn)", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);            return DialogResult.OK == result;        }        #endregion        #region Event Listeners        private void btnAdd_Click(object sender, EventArgs e)        {            this.presenter.AddFoodAction();        }        private void miDeleteFood_Click(object sender, EventArgs e)        {            if (this.lvFoods.SelectedItems.Count != 0)            {                String foodName = this.lvFoods.SelectedItems[0].Text;                this.presenter.RemoveFoodAction(foodName);            }        }        #endregion    }}

      可以看到,使用了MVP后,View的代碼變的非常干凈整潔,以前充斥著厚重表示邏輯的事件Listener方法變得“瘦”了許多。    

  總結(jié)

      這篇文章首先討論表示層的組成,說(shuō)明User Interface和Presentation Logic是表示層的兩個(gè)重要組成部分,并分別說(shuō)明了兩者的作用及交互方式。接著討論了MVP模式。最后,通過(guò)一個(gè)Demo展示了在.NET平臺(tái)上實(shí)現(xiàn)MVP的一種實(shí)踐方式。應(yīng)該說(shuō),MVP很類(lèi)似簡(jiǎn)化了MVC,MVP不但可以分離關(guān)注、使得代碼變得干凈整潔、并實(shí)現(xiàn)P Logic的復(fù)用,而且實(shí)現(xiàn)起來(lái)比MVC在結(jié)構(gòu)上要簡(jiǎn)單很多。MVP是一種模式,本身有諸多實(shí)現(xiàn)方式,本文只是介紹了筆者使用的一種實(shí)踐,朋友們也可以在此基礎(chǔ)上摸索自己的實(shí)踐。

NET技術(shù).NET平臺(tái)上的Model-View-Presenter模式實(shí)踐,轉(zhuǎn)載需保留來(lái)源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 国产成人亚综合91精品首页 | 一级做a爱片性色毛片武则天五则 | 狠狠做深爱婷婷久久一区 | 亚洲日本一区二区 | 六月激情| 国产欧美91 | 四虎永久免费网站免费观看 | 婷婷五色 | 亚洲人成77777在线观看网 | 五月四房婷婷 | 国产精品亚洲四区在线观看 | 91国高清视频 | 丁香六月在线 | 久久伊人婷婷 | 91精品国产免费青青碰在线观看 | 精品久久中文字幕 | 久久er国产精品免费观看8 | 国产麻豆成91| 亚洲91 | 伊人色网| 欧美卡一卡二卡新区aaa | 欧美人与物另类 | 伊人插 | 欧美日韩理论 | 久久久久国产一级毛片高清板 | 中文字幕第一页国产 | 激情五月视频 | 黄网站在线观看视频 | 中文字幕va一区二区三区 | 亚洲视频在线观看网站 | 午夜精品乱人伦小说区 | 日本三级韩国三级美三级91 | 九色国产在视频线精品视频 | 日产欧产va高清 | 在线五月婷婷 | 91免费国产精品 | 欧美另类xxx| 国产成人理在线观看视频 | 性色综合| 日本久久道一区二区三区 | 婷婷激情五月综合 |