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.否则,它需要手工释放或产生内存泄漏。