delphi下的MVC架构-eMVC
Easy MVC開發人員指南
?
- 翻譯:丁士鋒
- 東莞虎門鎮居岐
- ?
- 1.Introduction
- ?
- 1.1 What's Easy MVC
- ?
- Easy MVC or eMVC是一個輕量級MVC框架,為Delphi程序員設計來開發Windows應用程序。
- ?
- 1.2?為什么我們需要eMVC?
- ?
- Model-View-Controller(MVC)成為一個通用性和強有力的架構很多年了,Internet上有百計的MVC框架(免費的或商業的)可以使用,但他們大多數都很龐大,難于學習難以理解,特別是在軟件設計方面知識和經驗都有限的初學者。
- ?
- 另一個問題是,近來所有的MVC框架都是用JAVA,PHP而不是DELPHI寫的,這是我們什么寫eMVC的原因。
- ?
- 1.3?基于eMVC的應用程序看起來像什么?
- ?
- eMVC實現了?Model-View-Controller設計模式,比之其它MVC框架,eMVC引入了一個新概念:mset(mset),mset是一個能完成實際功能的模塊,在程序中作為一個最小元素。
- ?
- 一個標準的mset包含一個控制器,一個模型和一個或多個視圖,模型包含應用程序商業邏輯,視圖作為接受輸入或顯示信息的用戶界面。
- ?
- 框架提供了單一入口點-ControlCenter,所有的控制器必須注冊到ControlCenter;ControlCenter存放所有注冊的Controller到一個隊列。
- ?
- 下圖顯示了一個高級別的框架概要圖。
- ?
This image has been resized to fit in the page. Click to enlarge.
?
?
在圖1.1中可以見到,基于eMVC框架的應用程序有一個ControlCenter,ControlCenter維護著一個控制器隊列,你可以添加一個或多個mSet到應用程序,我們將簡短說明下mset中的每個組件的細節。
?
2,The Beneifts(好處)
?
設計模式(不光是MVC模式)現在是一個工業標準,關于這個主題有很多優秀的書和資源,以幫助開發團隊加速學習過程。學習使用像eMVC這樣的框架是需要一些努力的,大多數努力是值得的,無論如何作為一個認真的delphi程序員,通過使用如MVC一這些的設計模式所帶來的好處,來回報你的這種努力。(大概就是這樣):
?
1,加強模塊化和應用程序分層。
?
2,弱代碼藕合。
?
3,增強開發/設計角色分配,并行工作成為可能。
?
4,增加代碼可管理性。
?
5,增加代碼可擴展性。(有變更采納能力)
?
更多有用的功能將在新版本中加入,未來將有更多好處,不要忘了最重要的事情
?
eMVC開源授權書讓你完全的訪問源代碼....
?
3,Essentials?本質
?
好,在使用eMVC前,最好是有一些使用2種簡單和常用的設計模式Observer(觀察者)和職責鏈的知識與動機。著名的?Model-View controller模式也是。實際上,MVC并不屬于26種設計模式。
?
3.1?觀察者模式。
?
3.1.1?知識
?
考慮(圖3.1)的案例,你有三個Windows(Observer 1,2,3),每個Window包含一個SpreadSheet,一個Bar Chart和一個Pie Graphic.都描述同一相應用程序數據對象的信息。SpreadSheet(表格),bar Chart(條形圖)和Pie Graphic(餅圖)之間并不相互了解。因而你能在你需要時重用他們中的任何一個。當用戶在電子表格中改變了信息,條形圖和餅圖立即反映出這種改變。其他的也可以以此類推。
?
?
觀察者模式描述如何建立這些關系,我們知道,在這個模式中的關建對象是Observable Subject和Observers.
?
這個模式有一些規則:
a)一個obServable subject或許有多個依賴的observers.
b)所有的observers必須將自己注冊到observable subject。
c)只要Observable subject被變化所有的Observers都被通知。
d),在響應方面,每個Observer將查詢observable subject來同步他的狀態和Subject的狀態。
?
3.1.2?eMVC中的Observer
?
?
圖3.2是eMVC框架的類模型,這里有兩個接口用于觀察者模式。IObservable?和IObserver.
IObservable?被設計用于要觀察的對象(Observable object),IObserver為被觀察對象。
TObservable是IObservable的默認實現.源自TInterfaceObject且實現IObservable接口。
這兩個接口的原代碼如下:
//Observer interface
?
IObserver = interface
['{3E91264F-BBC0-44DF-8272-BD8EA9B5846C}']
? Procedure UpdateView(o: TObject);
end;
?
//Observable interface
IObservable = interface
['{A7C4D942-011B-4141-97A7-5D36C443355F}']
? procedure RegObserver(observer: IObserver);
? procedure Notify(o: TObject);
end
?
?
我們見到,這兩個接口相當簡單。
?
A:IObservable
IObservable只有兩個過程:RegObserver()和Notify().RegObserver()被用來注冊Observers.當Observable Subject改變調用?notify()以告訴所有被觀察的對象(Observers)。
?
B:IObServer
IObServer只有一個過程UpdateView(),當Notify被調用時將被自動觸發。見TObservalbe中notify()的實現。
?
C:TObservable
TObservable是IObservable接口的默認實現,我強烈建議你從TObservable派生你的新類以取代使用IObservable接口。
在TObservable類中有一個Private域稱為IObservers.以存放所有以注冊的Observers.
在?Notify()過程里,一個接一個地為在IObservers列表中的每個Observer對象調用UpdateView().意味著,一旦notify方法被調用,所以注冊的視圖的UpdateView方法將自動被觸發。
?
TObservable源代碼如下所示:
TObservable = class(TInterfacedObject, IObservable)
private
? iObservers: TClassList;
? icurrentObject: TObject;
?
public
? constructor Create;
? destructor Destroy; override;
? procedure setCurrentObject(o: TObject);
? procedure RegObserver(observer: IObserver);
? procedure Notify(o: TObject);
? property CurrentObject: TObject read icurrentObject write icurrentObject;
end;
?
..
?
procedure TObservable.RegObserver(observer: IObserver);
begin
? if iObservers = nil then
??? iObservers := TClassList.Create;
?
? iObservers.Add(TClass(observer));
end;
?
procedure TObservable.Notify(o: TObject);
var
? i: integer;
? observer: IObserver;
?
begin
? if iObservers = nil then exit;
?
? if o = nil then exit;
?
? self.setCurrentObject(o);
?
? for i := 0 to iObservers.Count - 1 do
? begin
??? observer := IObserver(iObservers.Items[i]);
??? observer.UpdateView(o);//trigger the UpdateView function of IObserver
? end;
end;
?
?
3.2 MVC
?
3.2.1?What's MVC?
?
OK,我們現在己經知道觀察者模式,現在,是時候學習些關于MVC的東西了。
?
讓我們先簡要回顧下觀察者模式,你知道,觀
察者模式由兩個主要部分組成,Observers和Observable對象,所有的Observers必須先被注冊到Observable對象,因此假如在Observable對象中有任何數據變更,所有的Observers將被通知。這里的問題是誰將負責注冊呢?
?
我想或許你也認識到依照觀察者模式的規則,Observers通夠查詢Observable對象的狀態并且依賴這些狀態來更新自身,顯然,這不夠好,在真實的案例中,我們需要更多的交互,比如在ObServer中單擊一個按紐,讓Observable做一些事情和單擊其他菜單或按紐做別的事情。讓我們稍稍改造下觀察者模式,在這里引入第三個東西,且賦給他在Observers和Observable對象間控制注冊和通信的責任。因為他做所有的控制工作,故我們命名為控制器(Controller)。為了區別觀察者模式,我們給被觀察者(Observer)一個新名字-視圖。然后給Observable(觀察者)一個新名字-Model,OK,我們現在看到什么?Model,View和Controller,那就是MVC,不是嗎?
?
那么,回答是:MVC是相當簡單的,只是觀察者模式加上一個控制器。
?
3.2.2?MVC in eMVC framework
?
?
圖3.3是eMVC框加中的類定義模型,你或許認識到我沒將TObservable改名到TModel,TObserver到IView,以便于一個設計能被兩種模式所用,但是你知道Observer是視圖,Observable是模式。就是那樣。
3.2.2?MVC Set
?
MVC框架引入一個新概念?MVC Set(mset).
?
1)它是什么?
?
一個mset必須只能有一個Controller,它或許包含一個(建議)或多個模型。每個模型有一個或多個注冊的視圖。一個mset完成實際的功能,在基于eMVC的應用程序里它被作為最小可重用的單元。
?
?
2)在一個基于eMVC的應用程序中可以有多少個msets?
?
到少一個,具體數量依賴于你的應用程序有多復雜和你如何組織他們。
?
3)這些mset如何被組織在一起?
?
基于eMVC的應用程序中所有的mset用職責鏈模式組織在一起。
?
3.3Chain of Responsibility(職責鏈)
?
3.3.1?CoR是什么?
?
GOF《設計模式》一書中典型的職責鏈模式定義為:
?
"Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it."
?
經由給多于一個對象一個處理數據的機會,以避免請求發送者到他的接收者之間的藕合。鏈接接收到象并沿著鏈路傳遞請求直到一個對象處理它。
?
?
典型的對象結構或許看起來象下圖。
?
?
從上面的例示,我們可以概要如下:
1)多個處理器(Handler)可能會處理一個請求,但是僅有一個處理器實際的處理請求。
2)所有的處理器形成一個隊列,一個處理器只有一個引用到下一個處理器。
3)請求者即不知道有多少個處理器可以處理它的請求也不知道哪一個處理器處理他的請求。
4)請求者對處理器沒有任何控制能力。
5)處理器能被動態指定。
6)更改處理器列表將不會影響請求者代碼。
?
3.3.2?Cor in eMVC
?
在Cor模式里,處理器負責處理請求,在MVC模式里,一個控制器(Controller)也負責處理一些事情,我們能統一他們嗎?當然我們能。
?
在eMVC里,Controller實現了一個叫做SendCommand()的過程,這是標準CoR處理器的定義的名為HandleRequest()的另外一個名稱.意味著所有的控制器(Controller)也能作為一個處理器,這使組織所有mset到COR模式中成為可能。
?
3.3.3?ControlCenter
?
1)ControlCenter是什么?
?
在告訴你ControlCenter是什么前,我們必須明白一個標準delphi應用程序中應用程序變量,在Delphi幫助文件中我找到是這樣:
?
‘Each GUI application automatically declares an Application variable as the instance of the application. If the?Delphi?application is not a Web server application, control panel applet, or NT service application, this variable is of type TApplication.’
?
每個GUI應用程序自動定義了一個Application變量作為應用程序實例,如果Delphi應用程序不是一個Web Server應用程序,控制面板,或NT服務應用程序,這個變量是TApplication類型。
?
這里有一個標準的Proejct File.
program Project1;
?
uses
? Forms,
? Unit1 in 'Unit1.pas' {Form1};
?
{$R *.res}
?
begin
? Application.Initialize;
? Application.CreateForm(TForm1, Form1);
? Application.Run;
end.
?
?
Application是一個應用程序范圍的變量以用于控制一個delphi程序,有些繞口不是嗎?(英文有點)在eMVC,一個新的TControlCenter被定義以接管TApplication,命名為ControlCenter的TControlCenter實例被定義在每個基于eMVC的應用程序中。
?
這里有一個基于eMVC的Delphi項目文件,比較標準Delphi項目文件,你能在begin end對之間看不同之處。
program NewMVCApp;
uses
? Forms,
? patterns,//include eMVC define unit
? MainCtrl in 'MainCtrl.pas',
? MainMdl in 'MainMdl.pas',
? MainView in 'MainView.pas' {ViewMain};
?
{$R *.res}
?
begin
? ControlCenter.Run;
end.
?
?
ControlCenter只是隱藏了Application變量,并沒有移除它,你仍然能在基于eMVC的應用程序的任何地方,任何時間使用Application變量,就像你在標準Delphi應用程序中編碼一樣。
?
2)如何注冊一個新定義的Controller到ControlCenter
?
這非常容易,在每個Controller的初始塊調用?ControlCenter's的RegController()函數
initialization
? ControlCenter.RegController(TControllerMain.Create); //register to ControlCenter
?
?
3)誰負責釋放己注冊的控制器
?
不用擔心,所有注冊的控制器在應用程序終止時被ControlCenter自動釋放
?
3.4?還有什么?
?
我還需要知道什么?好,包括上面3個設計模式,理解和使用eMVC在你的開發工作中或許更有用,如果你花了一點時間在其他三個模式上的話:Tempate,Command?和SingleTon
?
4,核心組件
?
eMVC框架由幾個base Classes組成,但是我們不必知道太多他們如何工作的細節。為了使用框架,下面的圖例顯示了我們需要知道的核心件。以便于我們開始使用框架。
?
This image has been resized to fit in the page. Click to enlarge.
?
?
The core components of the eMVC library:
? TController.
? IObservable interface and TObservable class:
? IObserver interface:
? TControlCenter class
? TCommand class.
?
4.1 TController
?
TController只是一個模板類,不要嘗試實例化,應該定義一個派生自TController的你自己的控制器類。
?
因而一個典型的Controller類象下面這樣。
?
type
TControllerTypical = class (TController)
protected
? Procedure DoCommand(Command: string; const args: string=''); override;
?
public
? Constructor Create;
? Destructor Destroy; override;
end;
?
你己經知道,TController擔當兩個角色,第一是MVC模式的控制器,其次是CoR模式的處理器。
?
4.1.1?作為一個Controller(控制器)
?
控制器有兩個主要任務,一個是注冊所有的的Views到Model,這十分容易,另一個是在視圖(Views)和Model(模型)之間控制交互與請求。或甚至是與其他控制器通信。
?
A TASK 1:注冊視圖到模型(向模型注冊視圖)
?
這個工作必須手工的在每個控制器的Create方法中完成。
Constructor TControllerMain.Create;
begin
? inherited;
? model := TModelMain.Create;
? Application.CreateForm(TViewMain, view);
? model.RegObserver(view); //注冊視圖到模型
end;
?
?
你是否有多于一個視圖,只要如以上代碼調用模型的?RegObserver即可。
?
B TASK 2:邏輯控制
?
控制器支持從視圖接收交互式請求。(鼠標或健盤輸入).然后,依賴于這些請求,控制器能要求模型處理或發送請求到其他控制器。
?
這是易說不易實現的,在JAVA中,有很多預定義的Listener接口,比如如果你想一個類類能接受從一個標準 樹狀組件的選擇(Selection)事件,只要實現TreeSelectionListener接口,且添加
?
你的類到TreeView的Listener列表。你的類就能接收和處理所有的選擇事件。
?
不幸的是,delphi沒有這種機制,eMVC也不能立即提供,因此,我們必須手工實現。
?
這兒,我展示了如何監控視圖的所有按紐單擊事件,讓我們假定視圖中有兩個按鈕,設置button1的Caption為'&Close'和button2的Caption為"&About'.
?
Setp 1:
?
在你的視圖類中添加一個public函數命名為?SetClickEvent ,或其它你喜歡的名字
type
? TViewMain = class(TFORM, IObserver)
??? Button1: TButton;
??? Button2: TButton;
? private
? { Private declarations }
??? procedure UpdateView(o: TObject); //from IObserver?來自IObserver接口的方法
? public
? { Public declarations }
??? procedure setClickEvent(AEventHandler: TNotifyEvent);
? end;
?
?
在實現區,定義函數實現如下:
procedure TViewMain.setClickEvent(AEventHandler: TNotifyEvent);
begin
? button1.OnClick := AEventHandler;
? button2.OnClick := AEventHandler;
end;
?
?
Step 2:
?
在Controller類中,添加一個私有(Private)方法名?OnClick
type
TControllerMain = class(TController)
? model: TModelMain;
? view: TViewMain;
?
Private
? ..
? Procedure OnClick(Sender: TObject); //
? ..
end;
?
?
實現如下:
Procedure TControllerMain.OnClick(Sender: TObject);
Begin
? If Sender is TButton then
? Begin
??? If TButton(Sender).caption = ‘&Close’ then
??? ? View.close
??? Else If TButton(Sender).caption = ‘&About’ then
??? ? Application.messageBox(‘About box’);
? End;
end;
?
?
Step 3:
?
修改Controller的構造函數, 添加?'View.SetClickEvent(OnClick);'?。
Constructor TControllerMain.Create;
begin
? inherited;
?
? model := TModelMain.Create;
? Application.CreateForm(TViewMain, view);
? model.RegObserver(view);
? view.setClickEvent(OnClick);
end;
?
?
現在每個按紐的單擊事件將被Controller(控制器)捕捉。
?
C?一個簡單的控制器類代碼
unit MainCtrl;
?
interface
?
uses SysUtils, forms, buttons, classes, controls, patterns, MainMdl, MainView;
?
type
? TControllerMain = class (TController)
??? model: TModelMain;
?? ?view: TViewMain;
?
? Private
??? Procedure OnClick(Sender: TObject); //
?
? protected
??? Procedure DoCommand(Command: string; const args: string=”); override;
?
? public
??? Constructor Create;
??? Destructor Destroy; override;
? end;
?
implementation
?
Constructor TControllerMain.Create;
begin
? inherited;
?
? model := TModelMain.Create;
? Application.CreateForm(TViewMain, view);
? model.RegObserver(view);
? view.setClickEvent(OnClick);
end;
?
Destructor TControllerMain.destroy;
begin
? freeAndNil(model);
? inherited;
end;
?
Procedure TControllerMain.DoCommand(Command: string; const args: string=”);
begin
?
end;
?
Procedure TControllerMain.OnClick(Sender: TObject);
Begin
? If Sender is TButton then
? Begin
??? If TButton(Sender).caption = ‘&Close’ then
??? ? View.close
??? Else If TButton(Sender).caption = ‘&About’ then
??? ? Application.messageBox(‘About box’);
? End;
end;
?
initialization
?
? ControlCenter.RegController(TControllerMain.Create); //register to ControlCenter
?
end.
?
?
4.1.2?作為一個處理器
?
Be a handler
?
為了成為職擇鏈模式的處理器,首先,控制器必須存入到鏈中,調用ControlCenter.RegController方法添加一個新定義的控制器到職責鏈。
?
其次,一個控制器必須有能力處理來自其他控制器的處理請求,不用說,也能發送請求,讓我們看看用這種方法控制器是如何工作的。
?
A發送請求。
?
在?eMVC里,我們叫做Request command.兩種Command能被使用。String Command和Object Command.細節參見4.5節。
?
發送一個Command很簡單
?
Step 1,
?
在public單元定義一個唯一的具名(用戶友好的,代表其意義的)常量字符串,不論你將使用哪種Command.
?
Step 2,
?
使用SendCommand?方法發送Command.在TController類中定義了SendCommand的5種重載方法。依據Command類型和參數可任選一種。
procedure SendCommand(ACommand: ICommand; const args: TObject = nil); overload;
procedure SendCommand(ACommand: string); overload;
procedure SendCommand(ACommand: string; args: string); overload;
procedure SendCommand(ACommand: string; args: TObject); overload;
procedure SendCommand(ACommand: string; args: pointer); overload;
?
?
B.處理請求
?
派生自TController的你的類中覆蓋DoCommand方法
?
如果你想接受Object Command,覆蓋這個:
Procedure DoCommand(ACommand:ICommand;Const args:TObject=nil);
?
?
如果你想接受無參數的String Command?覆蓋這個:
Procedure DoCommand(ACommand:String);
?
?
如果你想接受有參數的String Command ,你有三種選擇
Procedure DoCommand(ACommand:String;const args:String='');
Procedure DoCommand(ACommand:String;const args:TObject=nil);
procedure DoCommand(ACommand:String;const args:Pointer=nil);
?
?
?
4.2 IObservable and TObservable
?
Tobservable?默認實現IObservable接口,擔當MVC模式中的MODEL(模型).
?
通常,我們建義一個新的模型類派生自IObservable.這兒有一個新定義的模型的樣例:
unit MainMdl;
?
interface
?
uses Classes, forms, SysUtils, patterns;
?
type
? TModelMain = class(TObservable)
? public
??? constructor Create;
??? destructor Destroy; override;
? end;
?
implementation
?
constructor TModelMain.Create;
begin
?
end;
?
?
4.2.1模型的任務是什么?
?
TASK 1:?為控制器服務
?
當控制器捕獲視圖的事件時,它將'要求'模型處理。比如做一些計算或者從數據庫讀取數據。意味著,模型必
?
須為控制器提供一系列的函數和過程。
?
TASK 2:為視圖預備和提供'彈藥';
?
顯示在視圖上的任何東西(數據)都來自于模型。
?
4.2.2?如何通知?
?
TObservable?有一個方法叫做?Notify(O:TObject).如果你想更新所有己注冊的視圖,調用這個方法。
?
4.2.3?通知什么?
?
Tobservable上的Notify(O:TObject)有一個TObject類型的參數,包含著數據。他可以是派生自TObject的任何類。
?
4.2.4?在觸發Notify方法后發生了什么?
?
See Chapter 4.5 IObserver.
?
4.3 IObserver Interface
?
定義視圖可能是開發基于eMVC應用程序最簡單的工作了,只需實現IObServer接口
?
視圖的祖先類可以是TForm,TFrame,TPanel,TTReeView或任何其他派生自TWinControl的類
unit MainView;
?
interface
?
uses
? Windows, Messages, SysUtils, Classes, Graphics, Controls, StdCtrls, ComCtrls,
? ExtCtrls, Forms, patterns;
?
type
? TViewMain = class(TFORM, IObserver)
? private
? { Private declarations }
??? procedure UpdateView(o: TObject);//from IObserver
?
? public
? { Public declarations }
? end;
?
implementation
?
{$R *.dfm}
?
procedure TViewMain.UpdateView(o: TObject);
begin
? {write your code here}
end;
?
end.
?
?
你能見到在如上的源代碼中,唯一需要注意的事情是UpdateView方法,意味著每個視圖必須實現它自己的UpdateView方法。
?
4.4 TControlCenter
?
通常,不需要定義TControlCenter的實例,eMVC為每個應用程序自動創建了一個實例
?
更多細節信息參見本文檔3.3.3
?
4.5 TCommand
?
eMVC是一個命令驅動的框架,如我們己知的,有兩種類型的Commands被eMVC使用,String Command和Object Command.
?
4.5.1?Object Command是什么?
?
首先,一個對象命令(Object Command)必須派生自TObject.
?
TCommand默認實現Object Command.實際上,String Command是一個Wrapper Class(包裝類),但是它提供了更強大和靈活的String Command.
?
4.5.2?如何創建一個Object Command.
Constructor Create (ACommand: string = ''; const AParam: Pointer = nil;AParamObject: TObject = nil; AParamStr: string = '';Owner: TController = nil; ReleaseParam: Boolean = true);
?
?
當你需要一個Object Command時,我強烈建議你使用TCommand.像這樣:
Cmd:=TCommand.Create(CMD_DO_SOMETHING);
?
?
CMD_DO_SOMETHING是一個預定義的字符串常量。但是有時,僅發送一個字符串遠遠不夠,當controller處理這個Command時需要額外的信息或數據時。
?
我們見到上面的Tcommand的構造函數。Create方法有好幾個參數。
?
ACommand:當你創建Tcommand的實例時,必須給定一個唯一的字符串命令,它不能為空。這是Create方法唯一一個必須的參數
?
AParam:Command過程所需要的有意義的信息的記錄指針。
AParamObject:與Aparam相同,但它是一個對象實例。
AParamStr:是的,一個字符串參數。
Owner:發送Command的Controller.
ReleaseParam:在Delphi中,所有的對象在不需要時必須釋放。Object Command也是,如果ReleaseParam設置為True,隊列中的最后一個Controller將釋放這個Object Command.否則,它需要手工釋放或產生內存泄漏。
?
因為是下載的doc文件,轉載地址就沒有辦法加了。 項目地址: eMVC | Free Development software downloads at SourceForge.net http://sourceforge.net/projects/emvc/ 附上我重新修改過的XE5下可用的控件包。 eMVC_1.0a3_XE5可用.rar轉載于:https://www.cnblogs.com/azhe127/p/3436917.html
總結
以上是生活随笔為你收集整理的delphi下的MVC架构-eMVC的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移植u-boot1.1.6到友善mini
- 下一篇: 快速幂取模