Delphi中MVC模式的实现

一、  参考资料
1、J2EE技术内幕(J2EE Unleashed)      机械工业出版社(SAMS)

 
二、  传统的应用开发模式
    传统的应用开发是围绕着事件驱动用户界面来组织应用结构。在该模式中,开发人员创建界面,然后在界面的事件中编写相应的动作。对于小型,业务清晰,而且不用重复进行功能、业务修改的应用系统来说,这种模式明显很合适,不但直观,而且方便,开发与运行效率也很高。但对于较大型系统或分布系统,则不一定合适。
  l、大多数复杂的应用需要以多种查看数据的方式来查看数据。直接在界面中编写代码导致在系统的多个模块中出现重复,业务的变更需要在多处进行修改。
  2、数据操作逻辑、格式化、显示代码同用户事件等代码混在一起时,应用的维护变得非常困难。如果经过一段时间后、或由外人修改代码时,情况可想而知。
  3、如果应用逻辑与界面混在一起时,用户界面或应用逻辑都不能被重用。
  4、增加、修改功能、界面都需要对代码进行多处的修改,而这样的修改被会非常困难。
  5、由于应用逻辑与界面依赖程度太高,造成在团体开发中,无法由多人来开发一个模块。
 
三、  MVC结构简述
    MVC结构即是:模型(Model)-视图(VIEW)-控制器(CONTROLOR)模式。模型代表了应用数据和操作这些数据的方法;视图向用户显示这些数据并接受用户的操作事件,然后转发给控制器;控制器负责解释用户的动作,并把操作发送给模型。然后模型更新视图,反映数据的变化。在模型中不应包含对输入数据格式的翻译工作,该翻译工作由控制器执行,而模型不应该负责判断如何显示结果。
    MVC结构中VC中很早就开始使用,在J2EE中得到进一步的推广与完善,现在已经是分布式系统中的一种必选模式了。可惜JinBo以前一直忙于项目,没有时间进行充电,在Delphi中分离应用逻辑与界面设计这方面走了不少弯路,最近才发现MVC结构就是我想要达到的目标,心里那个是恨啊。
 
四、  MVC结构的优缺点
优点:
  1、通过从数据显示和用户交互中分离出数据模型澄清的应用设计。
  2、允许使用多种方式观察相同数据。
  3、从表示部分分离出应用功能,增强重用性。
  4 、增加灵活性,因为数据模型、用户交互和数据显示可以是可拆装的。
  5、把应用系统的功能划分由模块变为对象,真正地进行面向对象的系统分析。
缺点:
  1、组件间不能实现代码共享,导致应用复杂程度上升,效率下降。(JinBo个人认为Java的运行效率低的其中一个原因就是强调面向对象。假如Java类库设计得跟MFC一样的话,肯定效率会上升很多)
  2、存在更多的通信量和其他潜在问题,开发人员必须小心处理。
  3、模型的设计要求更高,而控制器对模型的依赖很高,模型设计的不合理将导致控制器实现很困难。可以配合设计模式中的命令模式来改善这一点。
 
五、  个人的体会
    在Delphi这事件驱动的开发环境中,很自然地大部分开发人员直接以传统方式进行应用系统的开发。然后项目进行到一定的时间,需求变化了很多,回头一看,程序中有很多地方还是可重用,但是打算提取出来时却发现里面千丝万缕,界面、应用逻辑代码纠缠不清,把有用的地方切割出来还不如重新再写。于是项目的新版本只能重新开发了。这样的情况我相信做过一、两个项目的开发人员都会有比较大的体会。而使用MVC结构来将界面与应用逻辑分离将在一定程序上能解决这个问题。
    关于代码重用,个人的意见是在众多项目中做到代码重用还是非常困难的,但是在一个项目的多个版本中达到代码重用却是可行的,并且有相当大的实际意义,这也是我在Delphi中应用MVC结构的目的。
 
六、  在Delphi中的应用
    将Delphi系统中的一个功能或模块划分为视图(窗体)、控制器、模型等三个部分。视图直接面对用户,显示数据并接受用户的操作事件,视图中只包括视图本身的维护代码,如控件间的互动,状态的设定等。而视图中表示的数据定义为视图中的一个或多个属性。控制器负责对用户的事件进行响应,然后转发给模型,由模型进行实际的操作,即干拉皮条的活。模型是真正干活的主,包含全部的应用逻辑代码,直接操纵数据库如果需要的话。
    我所提到的应用方式中,实际上是将控制器作为一个入口,供其它类调用。而且作为视图与模型沟通的桥梁,视图与模型都先经过控制器的访问,它们使用的一些资源、类等都在控制器中定义。因此,控制器在这种方式中,可重用性是最低的。
    当然,如果功能太简单,比如AboutDailog等,就不必强求整个系统都以这种方式来构造,这时编码就足够了。对于不大不小的功能或模块,则可以根据实际的情况,将控制器与视图进行结合,而将模型分离来,这是因为应用逻辑相同与界面来说,需求变化的可能性更小,而应用逻辑需求变化后界面不变的可能性就更小了。这样模型同样还具备相当大的重用性。
    在具体应用中,将视图、控制器、模型做到完全相互独立,都具备用重用性,还是非常困难的。因此,建议将MVC三个组件都进行更高层次的抽象,把共性的部分提炼出来,然后将它们的父类作为用重用的类。如果业务没有那么复杂或提炼有困难,可以将不可重用的代码集中在视图或控制器,然后保证其他的组件具备一定的重用性。如果还是不行,那就算了吧,该怎么着就怎么着,不要勉强了,呵呵。
 
七、  实现的细节问题
1、  一般点击事件。
这里指的是一些简单的操作性质事件,如保存、删除等,直接由控制器转发由模型执行业务处理类即可
procedure TfrmExampleView.btnSaveClick(Sender: TObject);
begin
  ExampleCtrl.ExampleModel.Save;
end;
 
2、  设定界面控件的状态或属性。
将控件的状态或属性都定义为窗体的属性或方法,然后控制器可以通过设定属性或调用方法来达到设定界面控件状态的目的.
 
例:需在运行时设定窗体中的若干个控件为只读或可读写状态。则在该窗体中定义只读属性,并编写相关代码,然后控制器简单地设定该属性为TRUE或FALSE来达到目的。
 
视图中定义EditReadOnly属性,该属性决定了leName等多个控件的可读定状态
TfrmExampleView = class(TForm)
  tvExample: TTreeView;
  leName: TLabeledEdit;
  …
private
  …
  function GetReadOnly: boolean;
  procedure SetReadOnly(const Value: boolean);
public
  …
  property EditReadOnly : boolean read GetReadOnly write SetReadOnly;
end;
 
procedure TfrmExampleView.SetReadOnly(const Value: boolean);
begin
  leName.ReadOnly = Value;
  …
end;
 
function TfrmExampleView.GetReadOnly: boolean;
begin
  result := leName.ReadOnly;
end;
    然后在控制器中设定视图控件的状态就很简单了。
    frmExampleView.EditReadOnly := True;
 
3、  对视图数据的读取、更新。
    对视图进行数据读取、更新时,分为两种情况,一种数据量比较小,如对某个Edit的赋值,应该使用刚才的方式,在视图中增加一个属性,然后进行属性的设置。
  TfrmExampleView = class(TForm)
  public
   property UserName : string read GetUserName write SetUserName;
  end;
 
  function TfrmExampleView.GetUserName: string;
  begin
   result := Trim(leName.Text);
  end;
 
  procedure TfrmExampleView.SetUserName(const Value: string);
  begin
   leName.Text := Value;
  end;
 

  对于比较大量的数据更新,如TStringGrid、多个TEdit的赋值、取值,可以利用J2EE设计模式中值对象(即有多个属性的对象),控制器将一个值对象传递给视图或模型,然后视图或模型自己对值对象进行操作。
 
  先定义一个值对象
  TUserValueObject = class
   private
   FUserName: string;
   FUserID: integer;
   FUserTel: string;
    …
   public
   property UserID : integer read FUserID write FUserID;
   property UserName : string read FUserName write FUserName;
   property UserTel : string read FUserTel write FUserTel;
    …
   end;
 
    在视图中定义一个值对象的属性
    TfrmExampleView = class(TForm)
     private
     function GetValues: TUserValueObject;
     procedure SetValues(const Value: TUserValueObject);
     public
     property Values : TUserValueObject read GetValues write SetValues;
     end;
 
    function TfrmExampleView.GetValues: TUserValueObject;
    begin
     result.UserName := Trim(leName.Text);
     result.UserTel := Trim(leTel.Text);
     …
    end;
 
    procedure TfrmExampleView.SetValues(const Value: TUserValueObject);
    begin
     leName.Text := Value.UserName
     leTel.Text := Value.UserTel;
     …
    end;
 
    在控制器中创建值对象
    TExampleCTRL = class
     private
     FExampleModel: TExampleModel;
     FfrmExampleView: TfrmExampleView;
     FUserValueObject: TUserValueObject;
     protected
     public
     constructor Create;
     destructor Destroy; override;
 
     property frmExampleView : TfrmExampleView read FfrmExampleView;
     property ExampleModel : TExampleModel read FExampleModel;
 
     property UserValueObject : TUserValueObject read FUserValueObject;
     end;
 
    constructor TExampleCTRL.Create;
    begin
     FfrmExampleView := TfrmExampleView.Create(nil);
     FExampleModel := TExampleModel.Create;
      //==创建值对象==
     FUserValueObject := TUserValueObject.Create;
    end;
 
    然后就可以进行访向了
    //==取值对象==
    UserValueObject := ExampleModel.Values;
    //==设定视图的值对象==
    frmExampleView.Values := UserValueObject;
 

4、  控件的事件。
  在应用系统的设计中,经常需要对用户的操作进行同步的数据更新或即时反应用户的操作。所以需要系统直接响应控件的事件,如TreeView中OnChange事件中对树节点变化的响应等。这种情况可以在控制器中建立对应的方法,响应事件并从模型中取出数据并更新到视图中,然后在初始化时将该事件指向处理方法。另一种方式就是定义一个更大的值对象或对象链表,然后扔过去给视图自己处理。
 
  constructor TExampleCTRL.Create;
  begin
   …
   frmExampleView.tvExample.OnChange := DoNodeChange;
  end;
 
  procedure TExampleCTRL.DoNodeChange(Sender: TObject; Node: TTreeNode);
  begin
   //==进行节点改变事件的处理==
  end;
 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值