hdwgxz

一、理论

1 MVC:模型-视图-控制器

模型:

指应用程序中,业务逻辑入口点对象。模型中包括:应用程序状态视图展示的数据响应用户请求的操作执行控制器请求的操作

控制器:

由视图触发执行某个操作,对模型进行修改。

使用MVC意味着要创建视图,控制器和业务层

2 MVP:

目前一般不会直接用MVP,而使用它的两个变体:SC(Supervising Controller)

PV(Passive View)。

1SC

Presenter

处理输入响应,操纵视图以完成更复杂的视图逻辑,同步视图和模型。

 

UI变化时,会发出抛出一个事件,致使Controller中相应的方法被调用,这个方法会处理请求并更新模型。视图会观察模型的变化并更新。

SC模式把一部分UI处理逻辑放到视图层,例如显示样式等。

2)PV:

Presenter

响应用户事件,更新视图,负责UI处理逻辑,包括UI的呈现样式等。

UI变化时,控制器更新模型和视图。

 

3. PM

模型:

PM中的模型不是业务层,而是包含多个属性的类,专门服务于视图层,含有展示视图所需的所有数据。

视图:

视图是UI元素的集合,UI元素绑定到模型属性上。用户触发的事件都将发送给展示器。

模型更新后,展示器控制视图更新。

视图持有对展示器的引用,模型通过展示器暴露给视图,视图不会暴露出任何接口。

 

展示器:

接收视图请求,调用表现层或业务逻辑层。

展示器持有模型对象的引用,并且暴露公开的方法和属性为视图提供数据。

二、代码示例

视图界面

每种方法的UI呈现都是相同的,不同的是接口,展示器等

1MVP-PV

视图接口

 

public interface IView
{
        string Tips { set; }//对应TextBox控件
        string Detail { set; get; }//对应RichTextBox控件
        string SelectedItem { set; get; }//对应ComboBox控件被选择元素
        List<string> Items { set; }//对应ComboBox控件
}

 

视图接口的实现

 

public partial class Form_MVP_PV : Form,IView
    {
        Presenter prt;
        public Form_MVP_PV()
        {
            InitializeComponent();
        }

        public string Tips
        {
            set 
            {
                this.Invoke(new Action(() => { this.tbxPV.Text = value; }));
            }
        }


        string IView.Detail
        {
            get
            {
                return this.rtbxPV.Text;
            }
            set 
            {
                this.Invoke(new Action(() => { this.rtbxPV.Text += value; }));
            }
        }

        private void btnExe_Click(object sender, EventArgs e)
        {
            btnExe.Enabled = false;
            btnExe.Text = "正在执行";
            this.cbxPv.Enabled = false;
            Task.Factory.StartNew(() => 
            {
                prt.Colculate();
                btnExe.Enabled = true;
                btnExe.Text = "开始";
                this.cbxPv.Enabled = true;
            });
        }

        private void Form_MVP_PV_Load(object sender, EventArgs e)
        {
            prt = new Presenter(this);
            prt.Initialize();
        }

        public string SelectedItem
        {
            get
            {
                Control.CheckForIllegalCrossThreadCalls = false;

                return this.cbxPv.SelectedItem.ToString();
            }
            set
            {
                this.cbxPv.SelectedItem = value; 
            }
        }

        public List<string> Items
        {
            set
            {
                this.Invoke(new Action(() => { this.cbxPv.Items.AddRange(value.ToArray()); }));
            }
        }

 

展示器Presenter

public class Presenter
    {
        IView iView;
        public Presenter(IView view)
        {
            this.iView = view;
        }

        public void Initialize()
        {
            iView.Items = new List<string> { "first","second","thrid" };
            iView.SelectedItem = "first"; 
        }
        
        public void Colculate()
        {
            for (int i = 1; i < 11; i++)
            {
                iView.Tips = string.Format("第{0}组,共{1}个-执行完{2}-正在计算第{3}个", iView.SelectedItem, 10, i - 1, i);

                Thread.Sleep(1000);//具体工作,此处以挂起进程代替
                
                string msg = string.Format("计算到第{0}"+Environment.NewLine,i);
                iView.Detail = msg;
            }
            iView.Tips = "全部完成";
        }
}

说明:

1)PresenterModel的调用没有体现,一般来讲Model是业务层,这里为了体现PV的设计宗旨,即将视图和展示器分离,所以省略了Presenter对业务层调用。

2)你会发现在属性SelectedItemget方法中加了Control.CheckForIllegalCrossThreadCalls = false;这行代码,目的是从不是创建cbxPv这个控件的线程访问它,那么哪些线程会访问它呢?一个自然就是创建此空间的线程,另一个就是private void btnExe_Click(object sender, EventArgs e)方法中所创建的一个线程。在此方法中创建线程是为了能够异步执行长时间计算任务,同时将任务生成的阶段性结果异步地展示到UI上。

3)你会发现private void btnExe_Click(object sender, EventArgs e)方法中包含了UI控件的部分显示逻辑,这似乎违背了PV设计的宗旨,但是这样的实现方式简便、直观、易于控制。下面为了将这段UI控件显示逻辑从视图挪走,放到Presenter中,代码修改如下:

首先,在IView中添加如下代码

bool BtnEnable { set; }
string BtnText { set; }
bool CheckBoxEnable { set; }

变为:

public interface IView
{
        string Tips { set; }//对应TextBox控件
        string Detail { set; get; }//对应RichTextBox控件
        string SelectedItem { set; get; }//对应ComboBox控件被选择元素
        List<string> Items { set; }//对应ComboBox控件

        bool BtnEnable { set; }
        string BtnText { set; }
        bool CheckBoxEnable { set; }

}

在接口实现(Form_MVP_PV)中实现新添加的属性:

public bool BtnEnable
        {
            set 
            {
                Control.CheckForIllegalCrossThreadCalls = false;
                this.btnExe.Enabled = value; 
            }
        }

        public string BtnText
        {
            set 
            {
                Control.CheckForIllegalCrossThreadCalls = false;
                this.btnExe.Text = value;
            }
        }

        public bool CheckBoxEnable
        {
            set 
            {
                Control.CheckForIllegalCrossThreadCalls = false;
                this.cbxPv.Enabled = value;
            }
        }

注掉btnExe_Click方法中关于UI显示逻辑的带码,变为:

private void btnExe_Click(object sender, EventArgs e)
        {
            //btnExe.Enabled = false;
            //btnExe.Text = "正在执行";
            //this.cbxPv.Enabled = false;
            Task.Factory.StartNew(() => 
            {
                prt.Colculate();
                //btnExe.Enabled = true;
                //btnExe.Text = "开始";
                //this.cbxPv.Enabled = true;
            });
        }

至此完成修改Presenter中的Colculate()方法,变为:

public void Colculate()
        {
            iView.BtnEnable = false;
            iView.CheckBoxEnable = false;
            iView.BtnText = "正在执行...";
            for (int i = 1; i < 11; i++)
            {
                iView.Tips = string.Format("第{0}组,共{1}个-执行完{2}-正在计算第{3}个", iView.SelectedItem, 100, i - 1, i);

                Thread.Sleep(1000);//具体工作,此处以挂起进程代替
                
                string msg = string.Format("计算到第{0}"+Environment.NewLine,i);
                iView.Detail = msg;
            }
            iView.Tips = "全部完成";
            iView.BtnEnable = true;
  iView.BtnText = "执行";
            iView.CheckBoxEnable = true;
        }

可以看到,为了将上面注掉的UI显示逻辑代码从视图层挪走,添加的代码量是注掉的代码的几倍。

2 MVP-SC

视图接口

 

public interface IView
{
        void UpdateUI(Model model);//更新执行过程信息
        string GetSelecteditem();//获得选择的元素
        void AddItems(IEnumerable<string> set);//初始化添加元素
        void Begin(Model model);//开始执行计算的时候,更新UI显示
        void Complete(Model model);//结束执行计算的时候,更新UI显示
}

 

视图接口实现

 

public partial class Form_MVP_SC : Form,IView
    {
        Presenter prt;
        public Form_MVP_SC()
        {
            InitializeComponent();
        }

        
        private void btnExeSC_Click(object sender, EventArgs e)
        {
            Task.Factory.StartNew(() => 
            {
                prt.Colculate(); 
            });
        }

        private void Form_MVP_SC_Load(object sender, EventArgs e)
        {
            prt = new Presenter(this);
            prt.Initialize();
        }

        public void UpdateUI(Model model)
        {
            this.Invoke(new Action(() => 
            {
                this.tbxSC.Text = string.Format("第{0}组,共{1}个-执行完{2}-正在计算第{3}个", this.cbxSC.SelectedItem.ToString(),
                    model.AllCount, model.DoingIndex - 1, model.DoingIndex);
                this.rtbxSC.Text += string.Format("计算到第{0}" + Environment.NewLine, model.DoingIndex);
            }));
        }

        public string GetSelecteditem()
        {
            Control.CheckForIllegalCrossThreadCalls = false;
            return this.cbxSC.SelectedItem.ToString();
        }


        public void AddItems(IEnumerable<string> set)
        {
            this.cbxSC.Items.AddRange(set.ToArray());
            this.cbxSC.SelectedIndex = 0;
        }


        public void Begin(Model model)
        {
            if (!model.Complete)
            {
                Control.CheckForIllegalCrossThreadCalls = false;
                this.cbxSC.Enabled = false;
                this.btnExeSC.Enabled = false;
                this.btnExeSC.Text = "正在执行...";
            }
        }

        public void Complete(Model model)
        {
            if (model.Complete)
            {
                Control.CheckForIllegalCrossThreadCalls = false;
                this.cbxSC.Enabled = true;
                this.btnExeSC.Enabled = true;
                this.btnExeSC.Text = "执行";
            }
        }
}

 

展示器-Presenter

public class Presenter
    {
        IView iView;
        public Presenter(IView view)
        {
            this.iView = view;
        }
        public void Initialize()
        {
            iView.AddItems(new List<string> { "first", "second", "third" });
        }
        public void Colculate()
        {
            Model vm = new Model();
            iView.Begin(vm);
            for (int i = 1; i < 11; i++)
            {
                vm.AllCount = 100;

                string selectedItem = iView.GetSelecteditem();

                //为了展示,从视图获取的数据,这里将DoingIndex修改为
                switch (selectedItem)
                {
                    case "first":
                        vm.DoingIndex = i + 0;
                        break;
                    case "second":
                        vm.DoingIndex = i + 1;
                        break;
                    case "third":
                        vm.DoingIndex = i + 2;
                        break;
                }
                Thread.Sleep(1000);//具体工作,此处以挂起进程代替

                iView.UpdateUI(vm);
            }
            vm.Complete = true;
            iView.Complete(vm);
        }
}

说明:

1)可以看到,Presenter中不包括UI展示细节,仅仅包含简单的UI处理逻辑,即:开始计算,计算过程中,计算任务完成以后调用了不同的方法来展示UI。

2)视图接口不包含任何属性,只有对UI进行控制的方法。展示器向接口传递Model数据,并且通过接口GetSelecteditem方法获得更新后的视图模型数据。

3 PM模式

 

在给出正式的PM模式之前,给出一个不标准的PM例子。

 

PM模式中强调UI控件绑定到模型属性上,但下面的例子,有点违背这一定义。

 

视图类:

 

 

public partial class Form_PM : Form
    {
        Presenter pt;

        public Form_PM()
        {
            InitializeComponent();
        }

        private void Form_PM_Load(object sender, EventArgs e)
        {
            pt = new Presenter();
            pt.UpdateUI += UpdateUI;

            this.cbxSC.Items.AddRange(pt.GetAllItem().ToArray());
            this.cbxSC.SelectedIndex = 0;
        }

        private void btnExePM_Click(object sender, EventArgs e)
        {
            cbxSC.Enabled = false;
            btnExePM.Enabled = false;
            btnExePM.Text = "正在执行...";
            Task.Factory.StartNew(() => { 
                pt.Colculate(); 
            });
        }

        private void UpdateUI()
        {
            this.Invoke(new Action(() => 
            {
                if (pt.vm.Complete)
                {
                    cbxSC.Enabled = true;
                    btnExePM.Enabled = true;
                    btnExePM.Text = "执行";

                    this.tbxPM.Text = "全部完成";
                }
                else
                {
                    this.tbxPM.Text = string.Format("{3}组,共{0}个-执行完{1}-正在计算第{2}个",
                                       pt.vm.AllCount, pt.vm.CompleteIndex, pt.vm.CompleteIndex + 1, this.cbxSC.SelectedItem.ToString());
                }
                this.rtbxPM.Text += string.Format("计算完第{0}" + Environment.NewLine, pt.vm.CompleteIndex);
                
            }));
        }

        private void cbxSC_SelectedIndexChanged(object sender, EventArgs e)
        {
            pt.Group = this.cbxSC.SelectedItem.ToString();
        }
}

 

展示器:

 

public class Presenter
    {
        public Model vm {set;get;}
        public string Group { set; get; }
        public Action UpdateUI;
        
        public Presenter()
        {
            vm = new Model();
        }
        public void Colculate()
        {
            vm.Complete = false;
            vm.AllCount = 10;
            for (int i = 1; i < 11; i++)
            {
                //为了展示,从视图获取的数据,这里将DoingIndex修改为
                switch (Group)
                {
                    case "first":
                        vm.CompleteIndex = i+ 0;
                        break;
                    case "second":
                        vm.CompleteIndex = i+ 1;
                        break;
                    case "third":
                        vm.CompleteIndex = i+ 2;
                        break;
                }
                Thread.Sleep(1000);//具体工作,此处以挂起进程代替
                if (i == vm.AllCount)
                {
                    vm.Complete = true;
                }
                UpdateUI();
            }
            
        }

        public List<string> GetAllItem()
        {
            return new List<string> { "first","second","thrid" };
        }
}

 

模型:

public class Model
{
        public int AllCount { set; get; }
        public int CompleteIndex { set; get; }
        public bool Complete { set; get; }
        public List<string> AllItems { set; get; }
}

说明:

1)展示器持有Model对象的引用并且Model对象作为展示器的公共属性暴露给视图,视图持有展示器的引用。

视图通过调用展示器的属性vm(Model类型) 和GetAllItem方法获得数据。

值得注意的是,展示器另一个公有字段UpdateUI的类型为Action,这里使用委托的目的是,当执行public void Colculate()方法时,每更新一次模型,展示器都能控制视图使用更新后的模型数据刷新视图UI

2)模型不含有方法,只有属性

3)视图层包含了一部分UI呈现逻辑,展示器没有将其完全包含,这样做的好处和MVP-SC模式是一样的。

    此外,视图会更新展示器的公共属性Group。Group实际对应着视图层的ComboBox控件。这里似乎有两个模型,一个是视图展示数据用的模型,一个是展示器更新业务层数据用的模型。两者可以合二为一。

下面我们将UI逻辑完全挪到展示器中去,要实现这一目标,视图、模型、展示器都有调整。

视图

public partial class Form_PM : Form
    {
        Presenters pt;

        public Form_PM()
        {
            InitializeComponent();
        }

        private void Form_PM_Load(object sender, EventArgs e)
        {
            pt = new Presenters();
            pt.UpdateUI += UpdateUI;
            pt.Begin += Begin;
            pt.Complete += Complete;

            this.cbxSC.Items.AddRange(pt.GetAllItem().ToArray());
            this.cbxSC.SelectedIndex = 0;
        }

        private void btnExePM_Click(object sender, EventArgs e)
        {
            Task.Factory.StartNew(() => { 
                pt.Colculate(); 
            });
        }
        private void Begin()
        {
            this.Invoke(new Action(() => 
            {
                this.cbxSC.Enabled = false;
                this.btnExePM.Enabled = false;
                this.btnExePM.Text = "正在执行...";
            }));
        }
        private void UpdateUI()
        {
            this.Invoke(new Action(() => 
            {
                this.tbxPM.Text = pt.vm.Tip;
                this.rtbxPM.Text += pt.vm.Detil;//string.Format("计算完第{0}" + Environment.NewLine, pt.vm.CompleteIndex);
                
            }));
        }

        private void Complete()
        {
            this.Invoke(new Action(() => 
                {
                    this.cbxSC.Enabled = true;
                    this.btnExePM.Enabled = true;
                    this.btnExePM.Text = "执行";

                    this.tbxPM.Text = "全部完成";
                }));
            
        }

        private void cbxSC_SelectedIndexChanged(object sender, EventArgs e)
        {
            pt.vm.SelectedItem = this.cbxSC.SelectedItem.ToString();
        }
}

模型:

public class Models
    {
        public string Tip { set; get; }
        public string Detil { set; get; }
        public string SelectedItem { set; get; }
        public List<string> AllItems { set; get; }
}

展示器:

public class Presenters
    {
        public Models vm {set;get;}
        public Action UpdateUI;
        public Action Begin;
        public Action Complete;
        public Presenters()
        {
            vm = new Models();
        }
        public void Colculate()
        {
            Begin();
            for (int i = 1; i < 11; i++)
            {
                //为了展示,从视图获取的数据,这里将DoingIndex修改为
                int vs = 0;
                switch (vm.SelectedItem)
                {
                    case "first":
                        vs = i + 0;
                        break;
                    case "second":
                        vs = i + 1;
                        break;
                    case "third":
                        vs = i + 2;
                        break;
                }

                vm.Tip = string.Format("{0}组,共{1}个-执行完{2}-正在计算第{3}个",
                                       vm.SelectedItem, 10, i, i+1);
                vm.Detil = string.Format("计算完第{0}" + Environment.NewLine, vs);

                Thread.Sleep(1000);//具体工作,此处以挂起进程代替
                UpdateUI();
            }
            Complete();
        }

        public List<string> GetAllItem()
        {
            return new List<string> { "first","second","thrid" };
        }
}

主要的变化有:

1)关于模型。模型中的属性绝大部分都可简单地绑定到视图层控件上。

2)关于展示器。展示器全部的UI显示逻辑都被挪到了展示器中,为完成这种设计,添加了三个类型都为Action的字段,分别代表了任务开始,执行过程中,任务完成。

3)关于视图。视图中的UI逻辑都被挪到了展示器中,只留下UI控件和模型的绑定实现

4)关于视图和展示器的关联。使用多播委托来控制UI的刷新。

 

 

 

 

 

 

 

 

 

 

 

 

2 MVP:

目前一般不会直接用MVP,而使用它的两个变体:SC(Supervising Controller)

PV(Passive View)

1SC

相关文章: