- MVC、MVP、MVVM、Angular.js、Knockout.js、Backbone.js、React.js、Ember.js、Avalon.js、Vue.js 概念摘錄
- 認清Android框架 MVC,MVP和MVVM
三層架構
將整個業務應用划分為:界面層(User Interface layer, UIL)、業務邏輯層(Business Logic Layer, BLL)、數據訪問層(Data access layer, DAL)。
1:界面層:
主要是指與用戶交互的界面。用於接收用戶輸入的數據和顯示處理后用戶需要的數據。如果邏輯層相當強大和完善,無論表現層如何定義和更改,邏輯層都能完善地提供服務。
2:業務邏輯層:
UI層和DAL層之間的橋梁。實現業務邏輯。業務邏輯具體包含:驗證、計算、業務規則等等。
3:數據訪問層:與數據庫打交道。主要實現對數據的增、刪、改、查。將存儲在數據庫中的數據提交給業務層,同時將業務層處理的數據保存到數據庫。(當然這些操作都是基於UI層的。用戶的需求反映給界面(UI),UI反映給BLL,BLL反映給DAL,DAL進行數據的操作,操作后再一一返回,直到將用戶所需數據反饋給用戶)。
規則
- 最關鍵的,UI層只能作為一個外殼,不能包含任何業務邏輯(BizLogic)的處理過程。只有少量(或者沒有)SQL語句或者存儲過程的調用,並且這些語句保證不會修改數據。
- 設計時應該從BLL出發,而不是UI出發。BLL層在API上應該實現所有BizLogic(以面向對象的方式)。如果把UILayer拿掉,項目還能在Interface/API的層次上提供所有功能)。
- 不管數據層是一個簡單的SqlHelper也好,還是帶有Mapping過的Classes也好,應該在一定的抽象程度上做到系統無關(DAL可以移植到其他類似環境的項目)。
- 不管使用COM+(Enterprise Service),還是Remoting,還是WebService之類的遠程對象技術,不管部署的時候是不是真的分別部署到不同的服務器上,最起碼在設計的時候要做這樣的考慮,更遠的,還得考慮多台服務器通過負載均衡作集群(三個模塊,可以分別運行於不同的服務器)。
優勢
1,結構清晰、耦合度低,
2,可維護性高,可擴展性高;
3,利於開發任務同步進行;容易適應需求變化
劣勢
1、有時會導致級聯的修改。這種修改尤其體現在自上而下的方向。如果在表示層中需要增加一個功能,為保證其設計符合分層式結構,可能需要在相應的業務邏輯層和數據訪問層中都增加相應的代碼
2、增加了代碼量,增加了工作量,增加了復雜度。
MVC

如果說ORM(Object Relation Mapping)等框架是數據訪問層的框架模式,解除了業務邏輯和數據之間的耦合,業務邏輯不再關心底層數據如何存儲和讀取。所有數據呈現給業務邏輯層的就是一個個的對象。
MVC則是表現層的框架模式。用於解除業務邏輯和視圖之間的耦合。從而易於擴展,便於測試。
MVC誕生於1979年,體現了“關注點分離”這一設計方針,它將一個人機交互應用涉及的功能分為三部分。
Model
Model對應應用狀態和業務功能的封裝,可以將它理解為同時包含數據和行為的領域模型(Domain Model)。Model接受Controller的請求並完成相應的業務處理,在應用狀態改變的時候可以向View發出相應的通知。
View
View實現可視化界面的呈現並捕捉最終用戶的交互操作。View可以直接調用Model查詢其狀態信息,而Model也可以在自己的狀態發生改變時,主動通知View。
Controller
Contoller是M和V之間的連接器,用於控制應用程序的流程。
View捕獲用戶交互操作后直接轉發給Contoller,后者完成相應的UI邏輯。如果需要涉及業務功能的調用,Contoller會直接調用Model及修改Model狀態。Contoller也可以主動控制原View或創建新的View對用戶交互操作予以響應。
MVC使用的誤區
把Model理解成實體類(Entity)
在MVC中Model應該包含2部分功能,一部分是處理業務邏輯,一部分是提供View顯示的數據。
它應該是業務邏輯真正的實現層。所以Model的實際上是Business Model(業務模型)。而
Controller僅僅起一個“橋梁”作用,它負責把View的請求轉發給Model,再負責把Model處理結束的消息通知View。Controller的存在是為了使UI界面、UI邏輯、業務邏輯之間分離。
大量業務邏輯代碼堆積在Controller端
MVC中的控制器,內里封裝了通訊,容易變成大而全 的高度耦合的集中器。
- 控制器中寫業務代碼
- UI邏輯可以寫在Controller中,而業務邏輯應該在Model里,Controller只是去調用它們。
-
控制器變得依賴信息數據中心或數據庫,對象將間接地通過控制器的action耦合在一起
- 可以通過引入IOC容器來解決
總結
MVC最早的定義畢竟是79年提出的,到現在GUI編程環境,業務復雜程度都有了很大改變。 當采用MVC模式設計UI應用時,一般會根據開發框架的特點來對Model,View和Contoller設置一個明確的界限,同時為它們之間的交互制定一個更加嚴格的規則。
MVC變體
在軟件發展歷程中出現了一些MVC變體,它們遵循定義在MVC中的基本原則,但對三元素直接的交互制定了更為嚴格的規范。
MVP
- MVP適用於基於事件驅動的應用框架中,如Asp.net Web Forms 和Windows Forms應用。
- MVP中嚴格禁止View和Model間的直接交互,必需通過Presenter來完成。Model的獨立性得到了真正的體現,它不僅僅與可視化元素的呈現(View)無關,與UI處理邏輯也無關。使用MVP的應用是用戶驅動而不是Model驅動,所以Model不需要主動通知view。
- MVP模式中的V代表的是一個接口,一個將UI界面提煉而抽象出來的接口。接口意味着任何實現了該接口的界面,都能夠復用已有的Presenter和Model代碼。是真正意義上的隔離View的細節和復雜性的模式,降低了Presenter對View的依賴。帶來的好處就是定義在Presenter中的UI處理邏輯變得易於測試。
MVP中Presenter與Model的交互很清晰,僅體現為Presenter對Model的單向調用。而View與Presenter直接的交互方式就成了MVP的核心。按照View與Presenter的交互方式依據View本身的職責范圍,Martin Folwer將MVP分為PV(Passive View)和SC(Supervising Contoller)兩種模式。
PV
view是難以測試的,通過讓它盡可能不涉及UI處理邏輯來使它不需要測試,這就是PV模式的目的。
這意味着需要將View中的UI元素通過屬性的形式暴露出來。將所有UI處理邏輯全部定義到Presenter中。定義在IView里的只有屬性。
SC
PV需要將View中可供操作的UI元素定義在對應的接口中,對於一些復雜的富客戶端應用的View來說,接口成員的數量可能會非常多,這無疑提升了代碼量和Presenter的復雜度。
SC則將一些單純,獨立的UI邏輯交由View自身來完成。當然它處理的數據是Presenter實時推送給它的。View盡可能不維護數據狀態。定義在IView里的接口盡量只包含方法,且是無返回值的方法(單向傳遞消息)。
public class Employee
{
public string Id { get; private set; }
public string Name { get; private set; }
public string Gender { get; private set; }
public DateTime BirthDate { get; private set; }
public string Department { get; private set; }
public Employee(string id,string name,string gender,DateTime birthDate,string department)
{
Id = id;
Name = name;
Gender = gender;
BirthDate = birthDate;
Department = department;
}
}
public class EmployeeRespository
{
private static IList<Employee> employees;
static EmployeeRespository()
{
employees = new List<Employee>()
{
new Employee("001","張三","男",new DateTime(1981,8,24),"銷售部"),
new Employee("002","李四","男",new DateTime(1981,8,24),"人事部"),
new Employee("003","王五","女",new DateTime(1981,8,24),"人事部")
};
}
public IEnumerable<Employee> GetEmployees(string department = "")
{
if (string.IsNullOrEmpty(department))
{
return employees;
}
return employees.Where(e => e.Department == department).ToArray();
}
}
public class EmployeePresenter
{
public IEmployeeView View { get; private set; }
public EmployeeRespository Respository { get; private set; }
public EmployeePresenter(IEmployeeView view)
{
this.View = view;
this.Respository = new EmployeeRespository();
this.View.DepartmentSelected += OnDepartmentSelected;
}
public void Initialize()
{
IEnumerable<Employee> employees = this.Respository.GetEmployees();
this.View.BindEmployees(employees);
string[] departments
= new string[]{
"","銷售部","采購部","人事部"
};
this.View.BindDepartments(departments);
}
protected void OnDepartmentSelected(object sender, DepartmentSelectedEventArgs args)
{
string department = args.Department;
var employees = this.Respository.GetEmployees(department);
this.View.BindEmployees(employees);
}
}
public interface IEmployeeView
{
void BindEmployees(IEnumerable<Employee> employees);
void BindDepartments(IEnumerable<string> departments);
event EventHandler<DepartmentSelectedEventArgs> DepartmentSelected;
}
public class DepartmentSelectedEventArgs : EventArgs
{
public string Department { get; private set; }
public DepartmentSelectedEventArgs(string department)
{
this.Department = department;
}
}
public partial class Form1 : Form, IEmployeeView
{
private EmployeePresenter Presenter{ get; private set; }
public Form1()
{
InitializeComponent();
Presenter = new EmployeePresenter(this);
Presenter.Initialize();
}
public event EventHandler<DepartmentSelectedEventArgs> DepartmentSelected;
public void BindDepartments(IEnumerable<string> departments)
{
this.comboBox1.DataSource = departments;
}
public void BindEmployees(IEnumerable<Employee> employees)
{
this.listBox1.DataSource = employees;
this.listBox1.DisplayMember = "Name";
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
string department = (string)this.comboBox1.SelectedItem;
DepartmentSelectedEventArgs eventArgs = new DepartmentSelectedEventArgs(department);
DepartmentSelected?.Invoke(sender, eventArgs);
}
}
WebForm實現的UI界面
public partial class Default : Page, IEmployeeView
{
public EmployeePresenter Presenter { get; private set; }
public event EventHandler<DepartmentSelectedEventArgs> DepartmentSelected;
public Default()
{
this.Presenter = new EmployeePresenter(this);
}
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
this.Presenter.Initialize();
}
}
protected void ButtonSearch_Click(object sender, EventArgs e)
{
string department = this.DropDownListDepartments.SelectedValue;
DepartmentSelectedEventArgs eventArgs = new DepartmentSelectedEventArgs(department);
if (null != DepartmentSelected)
{
DepartmentSelected(this, eventArgs);
}
}
public void BindEmployees(IEnumerable<Employee> employees)
{
this.GridViewEmployees.DataSource = employees;
this.GridViewEmployees.DataBind();
}
public void BindDepartments(IEnumerable<string> departments)
{
this.DropDownListDepartments.DataSource = departments;
this.DropDownListDepartments.DataBind();
}
}
在MVP里,可以根據User Story來首先設計和開發Presenter。在這個過程中,View是很簡單的,能夠把信息顯示清楚就可以了。在后面,根據需要再隨便更改View, 而對Presenter沒有任何的影響了。
如果要實現的UI比較復雜,而且相關的顯示邏輯還跟Model有關系,可以在View和 Presenter之間放置一個Adapter。由這個 Adapter來訪問Model和View,避免兩者之間的關聯。而同時,因為Adapter實現了View的接口,從而可以保證與Presenter之 間接口的不變。這樣就可以保證View和Presenter之間接口的簡潔,又不失去UI的靈活性。
MVVM
MVVM模式中,一個ViewModel和一個View匹配,它沒有MVP中的IView接口,而是完全的和View綁定,所有View中的修改變化,都會自動更新到ViewModel中,同時ViewModel的任何變化也會自動同步到View上顯示。
這種自動同步之所以能夠的原因是ViewModel中的屬性都實現了observable這樣的接口,也就是說當使用屬性的set的方法,都會同時觸發屬性修改的事件,使綁定的UI自動刷新。(在WPF中,這個observable接口是 INotifyPropertyChanged; 在knockoutjs中,是通過函數ko.observable() 和ko.observrableCollection()來實現的)
在MVP中,V是接口IView, 解決對於界面UI的耦合; 而MVVM則干脆直接使用ViewModel和UI無縫結合, ViewModel直接就能代表UI。但是MVVM做到這點是要依賴具體的平台和技術實現。 這也就是為什么ViewModel不需要實現接口的原因,因為對於具體平台和技術的依賴,本質上使用MVVM模式就是不能替換UI的使用平台的。
MVVM的提出源於WPF,主要是用於分離應用界面層和業務邏輯層。WPF/Siverlight是
基於數據驅動開發的。
在MVC和MVP模式中, 開發View層的是程序員, 雖然UI/UE團隊會做很多工作, 但這個層的"實現者"仍然是程序員。
而在WPF開發中將View層的代碼邏輯抽取出來,並使View層很純粹以便完全讓美工去打造它。相應地, 需要將View層的相應邏輯抽取到一個代碼層上,以便讓程序員專注在這里。
Asp.net Mvc 應用中的請求處理
- 每個HTTP請求的目標是Controller中的一個Action,具體體現為定義在Controller類型中的一個public方法。所以對請求的處理最終體現為對目標Controller對象的激活和對目標Action方法的執行。
- Controller的類型和Action方法的名稱及作為Action方法的部分參數可以直接通過請求的Url解析出來。
- 通過一個攔截器(Interceptor)對抵達Web服務器的HTTP請求進行攔截。這個攔截器根據當前請求解析出目標Controller的類型和對應的Action方法的名稱,隨后目標Controller被激活,相應的Action方法被執行。
- Action方法執行過程中,可以調用Model獲取相應的數據及改變其狀態。在Action執行的最后階段一般會創建一個View,后者最終被轉換為HTML以HTTP響應的形式返回到客戶端。
- 綁定在View上的數據稱為ViewModel,來源於Model或者基於顯示要求進行的簡單邏輯計算。它與MVVM中的ViewModel是完全不同的概念,后者不僅包括呈現在View中的數據,也包括數據操作行為。
