C#_MVP Pattern_探究(1)再次溫故MVC














在很久之前有寫了兩篇是使用JAVA來分享關於MVC的基礎觀念和練習示範
有興趣的開發朋友可以點下面回顧


JAVA MVC_以MVC架構來進行Java GUI程式開發(一)
介面層邏輯和業務層邏輯的拆分
https://coolmandiary.blogspot.com/2019/01/java-mvcmvcjava-gui.html

JAVA MVC_以MVC架構來進行Java GUI程式開發(二)
建構簡單的四則運算計算器_model中負責計算
http://coolmandiary.blogspot.com/2019/01/java-mvcmvcjava-guimodel.html


那近期剛好又認識到了另一個架構設計的模式跟它有點相似叫做「MVP」
Model-View-Presenter
是基於MVC又再延伸發展出來的一種使用者介面設計模式

這裡我們做一個MVC和 MVP的比對

MVC主要由如下三元素組成,各自職責分別如下:
Model(模型):資料保存
View(視圖):使用者介面
Controller(控制器):業務邏輯

主要思維:
1.由View負責傳送指令到Controller
2.Controller完成業務邏輯流程之後要求Model改變狀態
3.Model在去將資料發送到View上,使用者獲得回饋

運作方式:
在使用者操作時候,MVC又可分為兩種方式
一種通過View來接受指令再傳送給Controller
另一種則是直接去透過Controller接收指令(上一次Java設計的範例就是採用這樣子設計思維)

MVC的優缺點:
優點:
(1)把業務邏輯全部分離到Controller中,可模組化程度較高。
當業務邏輯變更的時候,不需要變更View和Model,只需將Controller換成另外一個Controller就行了。(Swappable  Controller)

(2)通常透過Observer Pattern來進行多個View的同步更新。

缺點:
(1)在沒有UI環境下對Controller做Unit Test時,比較不容易測試。
因為View的同步操作是由View自己執行的。(View只能在有UI的環境下運行。)

(2)View無法組件化。View是強依賴特定的Model的,
如果需要把這個View抽出來作為一個另外一個應用程序可複用的組件就困難了。
因為不同程式的Domain Model是不一樣的。



MVP Pattern(Model-View-Presenter)

MVP模式則是將Controller改名字為Presenter,同時改變View、Model之間溝通的方向

主要思維:
1.各部分之間的溝通傳遞皆為雙向的
2.View和Model不會發生任何關聯(不會有任何耦合),皆透過Presenter當中介
3.View很薄不會包含任何業務邏輯,因此被稱為被動視圖(Passive View),
Presenter則超級厚(注意是厚到爆炸那種!!!,因為所有邏輯都部屬在這層)

基本運作方式:
1.由View 接收用戶交互請求
2.View 將請求轉交給 Presenter
3.Presenter 操作Model進行數據更新
4.Model 通知Presenter數據發生變化
5.Presenter 更新View數據


MVP的優缺點:
優點:
(1)由於Presenter對View是通過Interface進行,View會
藉由IoC來告知Presenter要對UI做哪些事情,因此在對Presenter進行不依賴UI環境的單元測試的時候就可以更專注更好分割測試完整的Presenter業務邏輯正確性,也比較不會被UI綁死死的。

(2)View可以進行組件化。在MVP當中,View不依賴Model (分離了View和Model)。
這樣就可以讓View從特定的業務場景中脫離出來,可以說View可以做到
對業務邏輯完全無知。它只需要提供一系列接口(介面)提供給上層操作。這樣就可以做就高度可複用的View組件。

(3)使用情境時常用在事件驅動類型的應用程式種類
比方Windows Form 、 Web Form或者Android (網路上查MVP也常看到它)等等

以WebForm來舉例
CodeBehind的.cs程式中通常開發習慣就是
對一些事件作註冊並把邏輯跟UI屬性更改直接參雜一起寫在各個Event Code Block中
而當我們有需要做一些業務規則區分則一併都要在裡面做判斷



全部參雜在一起有時一些前面幾行更動可能就會導致後面程式運行不正確,就像疊疊樂一樣整個崩塌,程式碼的可讀性也會較差,也比較難導入單元測試,因為無明確職責區分。




缺點:
(1)View 會變成被動,因為是藉由Presenter來做主導控制,然而Presenter則
可能會受限於View介面的動作,而無法做更進一步對 View 的控制。



藉由Model-View-Presenter
清除釐清各自職責
Model ==> 要呈現的資料準備
Presenter==>業務邏輯運作
View ==>負責資料呈現


這裡用webform來簡單示範如何透過MVP設計模式
改善code-behind 違反SOLID單一職責的原則


TDD with Web Forms





假使要設計一需求要做一個閱書清單呈現


以往寫法


BookRead.cs 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MVP_WebTest.Models
{
    public class BookRead
    {
        public int ReadBookId { get; set; }
        public string Name { get; set; }
        public string Author { get; set; }
        public string ISBN { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public int Rating { get; set; }
        public string PurchaseLink { get; set; }
    }
}

Index.aspx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MVP_WebTest.Index" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <asp:Repeater ID="rptBooksRead" runat="server">
        <ItemTemplate>
            <div class="readBook">
                <div class="bookInfo">
                    <h2><%# ((MVP_WebTest.Models.BookRead)Container.DataItem).Name %></h2> 
                    <p><%# ((MVP_WebTest.Models.BookRead)Container.DataItem).Author %></p>
                    <p>ISBN: <%# ((MVP_WebTest.Models.BookRead)Container.DataItem).ISBN %></p>
                    <p>Date Finished: <%# ((MVP_WebTest.Models.BookRead)Container.DataItem).EndDate.ToString("yyyy/MM/dd")%></p>
                    <a href='<%# ((MVP_WebTest.Models.BookRead)Container.DataItem).PurchaseLink %>' target="_blank">Purchase</a>
                </div>
                <div class="ratingContainer">
                    <span class="rating"><%# ((MVP_WebTest.Models.BookRead)Container.DataItem).Rating %></span>
                </div>
            </div>
        </ItemTemplate>
    </asp:Repeater>
</asp:Content>


Index.aspx.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using MVP_WebTest.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MVP_WebTest
{
    public partial class Index : System.Web.UI.Page
    {

        public List<BookRead> Data { get; set; }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                BindData();
            }
        }


        public void BindData()
        {
            Data = new List<BookRead> {
                    new BookRead {ReadBookId = 1, Name = "愈成熟,愈天真:與自己的內在小孩重逢", Author = "吳若權", ISBN = "9789865101374", StartDate = new DateTime(2021, 3, 5), EndDate = new DateTime(2023, 3, 30), Rating = 10, PurchaseLink = "https://www.books.com.tw/products/0010884982?loc=P_0003_052" },
                    new BookRead {ReadBookId = 2, Name = "謙卑的力量:放下,才是真正的抵達", Author = "吳若權", ISBN = "9789865100506", StartDate = new DateTime(2019, 12, 30), EndDate = new DateTime(2025, 2, 12), Rating = 9, PurchaseLink = "https://www.books.com.tw/products/0010844852" },
                    new BookRead {ReadBookId = 3, Name = "激痛點按摩全書:圖解7大疼痛部位╳激痛點按摩9大原則,終結疼痛、還原身體活動力", Author = "克萊爾‧戴維斯, 柏‧戴維斯", ISBN = "9789865072582", StartDate = new DateTime(2025, 2, 4), EndDate = new DateTime(2010, 3, 12), Rating = 3, PurchaseLink = "https://www.books.com.tw/products/0010882856" }
                };


            rptBooksRead.DataSource = Data;
            rptBooksRead.DataBind();
        }


    }
}



通常設計思維是
Step1.可以針對View Interface優先做一些設計?
(一個View要做到呈現閱書清單的話應該要做哪些事情呢?、會需要存捨麼資料類型)
可能有綁定資料、資料請求查詢更新、可能要存取書單
(備註:記得public修飾會在其他地方跨程式存取)

IDisplayBooksReadView.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
using MVP_WebTest.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MVP_WebTest
{
    public interface IDisplayBooksReadView
    {
        List<BookRead> Data { get; set; }
        void BindData();
        event EventHandler DataRequested;
    }
}

返回是原先某頁的 aspx.cs (這裡是Index.aspx.cs)
讓其實作相應方法並引入相應屬性



Step2.設計Presenter
有了View開出的規格就可以產生一些Fake的View(利於測試的特性)傳入給
Presenter來做測試只要你是有
實作過IDisplayBooksReadView (你是有拿過IDisplayBooksReadView認證的)我都能接收喔

DisplayBooksReadPresenter.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using MVP_WebTest.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MVP_WebTest
{
    public class DisplayBooksReadPresenter
    {
        public IDisplayBooksReadView View { get; set; }

        public DisplayBooksReadPresenter(IDisplayBooksReadView view)
        {
            View = view;
            View.DataRequested += GetData;
        }

        private void GetData(object sender, EventArgs e)
        {
            View.Data = new List<BookRead> {
                        new BookRead {ReadBookId = 1, Name = "愈成熟,愈天真:與自己的內在小孩重逢", Author = "吳若權", ISBN = "9789865101374", StartDate = new DateTime(2021, 3, 5), EndDate = new DateTime(2023, 3, 30), Rating = 10, PurchaseLink = "https://www.books.com.tw/products/0010884982?loc=P_0003_052" },
                        new BookRead {ReadBookId = 2, Name = "謙卑的力量:放下,才是真正的抵達", Author = "吳若權", ISBN = "9789865100506", StartDate = new DateTime(2019, 12, 30), EndDate = new DateTime(2025, 2, 12), Rating = 9, PurchaseLink = "https://www.books.com.tw/products/0010844852" },
                        new BookRead {ReadBookId = 3, Name = "激痛點按摩全書:圖解7大疼痛部位╳激痛點按摩9大原則,終結疼痛、還原身體活動力", Author = "克萊爾‧戴維斯, 柏‧戴維斯", ISBN = "9789865072582", StartDate = new DateTime(2025, 2, 4), EndDate = new DateTime(2010, 3, 12), Rating = 3, PurchaseLink = "https://www.books.com.tw/products/0010882856" }
                       };

            View.BindData();
        }
    }
}



index.aspx.cs最終程式
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using MVP_WebTest.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MVP_WebTest
{
    public partial class Index : System.Web.UI.Page, IDisplayBooksReadView
    {
        private DisplayBooksReadPresenter presenter;

        public List<BookRead> Data { get; set; }

        public event EventHandler DataRequested;

        public void BindData()
        {
            rptBooksRead.DataSource = Data;
            rptBooksRead.DataBind();
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                presenter = new DisplayBooksReadPresenter(this);
                DataRequested(sender, e);
                //BindData();
            }
        }
    }
}















Reference:

UI的設計模式MVP模式 - Passive View
https://skychang.github.io/2011/09/28/UI%E7%9A%84%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8FMVP%E6%A8%A1%E5%BC%8F-Passive-View/

UI的設計模式MVP模式–Supervising Controller
https://skychang.github.io/2011/10/13/UI%E7%9A%84%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8FMVP%E6%A8%A1%E5%BC%8F%E2%80%93Supervising-Controller/

MVP (Model View Presenter) – Supervising Controller.
https://www.c-sharpcorner.com/UploadFile/rmcochran/mvp-model-view-presenter-%E2%80%93-supervising-controller/

Android MVP 基本了解與實作
http://andy08691.blogspot.com/2016/07/android-mvp.html






留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header