myamanda

博客园 首页 新随笔 联系 订阅 管理
Wrapper设计模式能够把一个类的接口修改成另一个类所需要的接口,然后让本来因为接口不兼容的类能够在一起配合工作。

在Delphi中,为了让两个类能够支持同样的接口,它们必须有同样的祖先类这样才能在其他类调用的时候实现多态性。但是有的时候我们希望两个没有 关系的类能够在一起工作,Wrapper设计模式就能够让一个类wrap部分另一个类的部分接口(当然也可以是全部,看具体的需要而定),从而能够模拟出 一个类似于“uses”的类的多继承关系。

  让我们来看一个例子,有一个类TSimple从TObject继承下来,但是我们希望 能把TSimple这个类放到Delphi的控件面板(component palette)上,然而我们知道,如果一个类能够放置到控件面板上,这个类必须从TComponent派生下来。现在我们不想在改变TSimple的祖 先类TObject的情况下(比如我们并没有TSimple的源代码)把TSimple放到控件面板上,我们可以构建一个继承自TComponent的 TSimpleWapper类,在TSimpleWapper类中Wrap TSimple的接口。下面是这个例子的范例代码:

type
 TSample = class (TObject)
private
 FSomeValue: Integer;
public
 function SomeAction(const Data: string): Boolean;
  property SomeValue: Integer read FSomeValue write FSomeValue;
 end;
 TSampleWrapper = class(TComponent)
private
 FSample: TSample;
public
 property Sample: TSample read FSample;
end;


  然后我们就可以在TSample类中实现TSample的接口了,代码如下:

TSampleWrapper = class (TComponent)
private
FSample: TSample;
protected
function GetSomeValue: Integer;
procedure SetSomeValue(Value: Integer);
public
function SomeAction(const Data: string): Boolean;
property Sample: TSample read FSample;
property SomeValue: Integer read GetSomeValue write SetSomeValue;


  接口实现代码如下:

function TSampleWrapper.GetSomeValue: Integer;
begin
Result := Sample.SomeValue;
end;
procedure TSampleWrapper.SetSomeValue(Value: Integer);
begin
Sample.SomeValue := Value;
end;
function TSampleWrapper.SomeAction(const Data: string): Boolean;
begin
Result := Sample.SomeAction(Data);
end;

Factory:

工厂模式中又分为简单工厂模式、工厂方法模式和抽象工厂模式 。这里给大家介绍的简单工厂模式是其中最简单的一种。如果大家支持的话我会继续贴出工厂方法模式和抽象工厂模式等后续篇,要看大家的反应程度哦!

        学习设计模式要对面向对象的程序设计有一定的理解,特别是多态性 ,如果能看懂下面的例子就没问题了,呵呵!

//水果类,它是一个抽象产品
TFruit = Class(TObject)
...
end;

//苹果类,水果类的具体化
TApple = class(TFruit)
...
end;

function Factory(): TFruit;
var
    f:TFruit;
begin
  //精髓就是这条语句了,明明创建了TApple对象,
  //却将他赋值给TFruit类型的变量
  //其实这样做好处大大的,后面就体会到了
  f:=TApple.Create();
  result:=f;
end
 
在例程中我用到了接口 ,不明白得可以把它当成一个比抽象类还抽象的抽象类,说白了把它当成一个类就没错。 下面开始吧。。。。。。。

这是说明:
//我们用一个小果园来说明什么是简单工厂

//这个果园里有葡萄、苹果和草莓三种水果

//所有的水果都有生长、耕作和收获三个步骤

//果园的任务就是让我们得到葡萄、苹果和草莓这三种水果对象

//我们利用得到的对象可以完成水果生长、耕作和收获三个步骤

//果园就是我们所说的简单工厂(Factory)

//而葡萄、苹果和草莓这三种水果就是工厂里的产品 (Pruduct)

//完成产品的过程称之为外部使用者(Produce)

//使用简单工厂的好处是:

//1、充分利用了多态性
//不管你种什么,果园返回的对象并不是具体的葡萄、苹果或者草莓
//而是返回一个他们的抽象对象 -- 水果(IFruit)

//2、充分利用了封装性
//内部产品发生变化时外部使用者不会受到影响

//他的缺点是:
//如果增加了新的产品,就必须得修改工厂(Factory)
 

这是定义简单工厂的单元文件源代码:
//SimpleFactory.pas 定义简单工厂的单元文件

//代码如下==========

unit SimpleFactory;

interface

uses
  SysUtils;

type

  //水果类,它是一个抽象产品
  //仅仅声明了所有对象共有的接口,并不实现他们
  IFruit = interface(IInterface)
    function Grow: string; //生长
    function Harvest: string; //收获
    function Plant: string;//耕作
  end;

  //葡萄类,水果类的具体化
  TGrape = class(TInterfacedObject, IFruit)
    function Grow: string;
    function Harvest: string;
    function Plant: string;
  end;

  //苹果类,水果类的具体化
  TApple = class(TInterfacedObject, IFruit)
    function Grow: string;
    function Harvest: string;
    function Plant: string;
  end;

  //草莓类,水果类的具体化
  TStrawberry = class(TInterfacedObject, IFruit)
    function Grow: string;
    function Harvest: string;
    function Plant: string;
  end;
 
  //果园类,它就是工厂类,负责给出三种水果的实例
  TFruitGardener = class(TObject)
  public
    //1、注意 class 关键字,它定义工厂方法 Factory 是一个静态函数,可以直接使用
    //2、注意返回值,他返回的是最抽象的产品 IFruit 水果类
    //3、注意他有一个参数,来告诉工厂创建哪一种水果
    class function Factory(whichFruit:string): IFruit;
  end;
 
  //声明一个异常,这不是重点
  NoThisFruitException = class(Exception)
  end;
 

implementation

{ ********** TGrape ********** }

function TGrape.Grow: string;
begin
  result:='葡萄正在生长......';
end;

function TGrape.Harvest: string;
begin
  result:='葡萄可以收获了......';
end;

function TGrape.Plant: string;
begin
  result:='葡萄已经种好了......';
end;

{ ********** TApple ********** }

function TApple.Grow: string;
begin
  result:='苹果正在生长......';
end;

function TApple.Harvest: string;
begin
  result:='苹果可以收获了......';
end;

function TApple.Plant: string;
begin
  result:='苹果已经种好了......';
end;

{ ********** TStrawberry ********** }

function TStrawberry.Grow: string;
begin
  result:='草莓正在生长......';
end;

function TStrawberry.Harvest: string;
begin
  result:='草莓可以收获了......';
end;

function TStrawberry.Plant: string;
begin
  result:='草莓已经种好了......';
end;

{ ********** TFruitGardener ********** }

class function TFruitGardener.Factory(whichFruit:string): IFruit;
begin
  //精髓就是这条语句了 result:= TApple.Create()
  //不明白赶紧去复习复习什么是多态性
  if(LowerCase(whichFruit)='apple')then result:=TApple.Create()
  else if(LowerCase(whichFruit)='grape')then result:=TGrape.Create()
  else if(LowerCase(whichFruit)='strawberry')then result:=TStrawberry.Create()
  else Raise NoThisFruitException.Create('这种水果还没有被种植!');
end;

end.
 

窗体界面:
//MainForm.pas 窗体文件,这里说明怎样使用简单工厂

unit MainForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs,SimpleFactory, StdCtrls;

type
  TForm1 = class(TForm)
    RadioButton1: TRadioButton;
    RadioButton2: TRadioButton;
    RadioButton3: TRadioButton;
    RadioButton4: TRadioButton;
    procedure RadioButton1Click(Sender: TObject);
    procedure RadioButton2Click(Sender: TObject);
    procedure RadioButton3Click(Sender: TObject);
    procedure RadioButton4Click(Sender: TObject);
  public
    procedure Produce(fruitName:string);
  end;
 
var
  Form1: TForm1;

implementation

{ ********** TForm1 ********** }

//这就是生产过程
//IFruit 类型的临时变量 f 自己知道种的是哪种水果,有趣吧
//想要什么尽管来种,果园大丰收啦!
procedure TForm1.Produce(fruitName:string);
var
  f: IFruit;
begin
  try
    f:=TFruitGardener.Factory(fruitName);
    ShowMessage(f.Plant());
    ShowMessage(f.Grow());
    ShowMessage(f.Harvest());
  except
    on e:NoThisFruitException do  Messagedlg(e.Message,mtInformation,[mbOK],0);
  end;
end;

{$R *.dfm}


procedure TForm1.RadioButton1Click(Sender: TObject);
begin
  Produce('apple');
end;

procedure TForm1.RadioButton2Click(Sender: TObject);
begin
   Produce('grape');
end;

procedure TForm1.RadioButton3Click(Sender: TObject);
begin
  Produce('strawberry');
end;

procedure TForm1.RadioButton4Click(Sender: TObject);
begin
  Produce('other');
end;

end.
 

        工厂模式的目的就是,把创建对象的责任和使用对象的责任分开,工厂负责统一创建具体产品(苹果、葡萄和草莓),然后再把这些产品转化为他们的抽象产品(水 果)返回给外部使用者,作为使用者关心的仅仅是抽象产品预留的接口,而不关心他们是怎么创建的。这样,即使因为某些原因导致创建产品的过程发生变化,也不 会影响到外部使用者,在一定程度上保证了程序的可维护性。

        如果把具体产品类(TApple、TFrabe、TStrawberry)暴露到外部,如果内部的代码发生了变动,外部也会受到影响,工厂就失去了他的意义。


“开闭原则”:一个模块应该易于扩展,免于修改

        问题:请考虑上一章的例子中,如果添加一个新的具体水果类“西瓜”需要做哪些工作。

        本章完成以下内容:
        1、代码用支持中文的 Delphi 2005 编译并通过,并去除了其中一些无关紧要的地方,如异常处理等 ;
        2、重新设计一个情景,分别用“简单工厂模式”和“工厂方法模式”两种方法实现,请体会其中的差别 ;
        3、在情景中添加一个子类后,请体会“简单工厂模式”和“工厂方法模式”两种方法不同的处理方式;
        4、如果不理解什么是接口、多态、静态函数等概念,这里不作解释,请看第一章或找相关资料;
        5、本章的情景和上一章差不多,只是把工厂从“果园”变成了“水果小贩”;同样的三种水果:苹果、葡萄、草莓;每种水果都封装了两个逻辑,在和外部“交易”时会用到这两个逻辑。 最后,请重新回顾“开闭原则”

        下面开始吧。。。。。。。

        这里是简单工厂模式的实现方法,在这种模式中:
        1、一个小贩要负责所有三种水果的交易,这对他来说是很大的挑战噢,不信您看!
        2、顾客必须对水果的名字有一个准确地描述,这样水果才会卖给你,这很影响生意呀!

//简单工厂类和水果类单元文件

unit SimpleFactory;

interface

type
  接口_水果 = interface(IInterface)
    function 提示():string;
    function 被评价():string;
  end;

  类_苹果 = class(TInterfacedObject, 接口_水果)
    function 提示():string;
    function 被评价():string;
  end;

  类_葡萄 = class(TInterfacedObject, 接口_水果)
    function 提示():string;
    function 被评价():string;
  end;

  类_草莓 = class(TInterfacedObject, 接口_水果)
    function 提示():string;
    function 被评价():string;
  end;
 
  工厂类_小贩 = class(TObject)
  public
    class function 工厂(水果名:string): 接口_水果;
  end;

implementation

{***** 类_苹果 *****}

function 类_苹果.提示():string;
begin
  result:='削了皮再吃!';
end;

function 类_苹果.被评价():string;
begin
  result:='恩,还不错,挺甜!';
end;

{*****类_葡萄 *****}

function 类_葡萄.提示():string;
begin
  result:='别把核咽下去了!';
end;

function 类_葡萄.被评价():string;
begin
  result:='没有核呀???';
end;

{***** 类_草莓 *****}

function 类_草莓.提示():string;
begin
  result:='别怪我没告诉你,很酸!';
end;

function 类_草莓.被评价():string;
begin
  result:='试试?哇,牙快酸掉了!';
end;

{***** 工厂类_小贩 *****}

class function 工厂类_小贩.工厂(水果名:string): 接口_水果;
begin
  if(水果名='苹果')then result:=类_苹果.Create()
  else if(水果名='葡萄')then result:=类_葡萄.Create()
  else if(水果名='草莓')then result:=类_草莓.Create();
end;
end.
 

//窗体单元文件

unit MainForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,SimpleFactory;

type
  TForm1 = class(TForm)
    RadioButton1: TRadioButton;
    RadioButton2: TRadioButton;
    RadioButton3: TRadioButton;
    procedure RadioButton3Click(Sender: TObject);
    procedure RadioButton2Click(Sender: TObject);
    procedure RadioButton1Click(Sender: TObject);
  private
    procedure 交易(水果名:string);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


procedure TForm1.交易(水果名:string);
var
  我买的水果: 接口_水果;
begin
    我买的水果:=工厂类_小贩.工厂(水果名);
    ShowMessage(我买的水果.提示);
    ShowMessage(我买的水果.被评价);
end;

procedure TForm1.RadioButton1Click(Sender: TObject);
begin
  交易('苹果');
end;

procedure TForm1.RadioButton2Click(Sender: TObject);
begin
  交易('葡萄');
end;

procedure TForm1.RadioButton3Click(Sender: TObject);
begin
  交易('草莓');
end;
end.  

        这里是工厂方法模式的实现方法,在这种模式中
        1、每一种水果都对应有一个小贩负责,这样他们做起生意来就轻松多了,呵呵!
        2、顾客直接和小贩打交道,他知道您要什么,这样就不会因为说不清那讨厌的水果名字而吃不上说水果了。



//工厂类和水果类单元文件

unit FactoryMethod;

interface

type
  接口_水果 = interface(IInterface)
    function 提示():string;
    function 被评价():string;
  end;

  类_苹果 = class(TInterfacedObject, 接口_水果)
    function 提示():string;
    function 被评价():string;
  end;

  类_葡萄 = class(TInterfacedObject, 接口_水果)
    function 提示():string;
    function 被评价():string;
  end;

  类_草莓 = class(TInterfacedObject, 接口_水果)
    function 提示():string;
    function 被评价():string;
  end;

  接口_小贩 = interface(IInterface)
    function 工厂(): 接口_水果;
  end;

  类_苹果小贩 = class(TInterfacedObject, 接口_小贩)
   function 工厂(): 接口_水果;
  end;

  类_葡萄小贩 = class(TInterfacedObject, 接口_小贩)
    function 工厂(): 接口_水果;
  end;

  类_草莓小贩 = class(TInterfacedObject, 接口_小贩)
    function 工厂(): 接口_水果;
  end;

implementation

{****** 类_苹果 ******}

function 类_苹果.提示():string;
begin
  result:='削了皮再吃!';
end;

function 类_苹果.被评价():string;
begin
  result:='恩,还不错,挺甜!';
end;

{****** 类_葡萄 ******}

function 类_葡萄.提示():string;
begin
  result:='别把核咽下去了!';
end;

function 类_葡萄.被评价():string;
begin
  result:='没有核呀???';
end;

{****** 类_草莓 ******}

function 类_草莓.提示():string;
begin
  result:='别怪我没告诉你,很酸!';
end;

function 类_草莓.被评价():string;
begin
  result:='试试?哇,牙快酸掉了!';
end;

{***** 类_苹果小贩 *****}

function 类_苹果小贩.工厂(): 接口_水果;
begin
  result:=类_苹果.Create()
end;

{***** 类_葡萄小贩 *****}

function 类_葡萄小贩.工厂(): 接口_水果;
begin
  result:=类_葡萄.Create()
end;

{***** 类_草莓小贩 *****}

function 类_草莓小贩.工厂(): 接口_水果;
begin
  result:=类_草莓.Create()
end;
end.
 

//窗体单元文件

unit MainForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,FactoryMethod;

type
  TForm1 = class(TForm)
    RadioButton1: TRadioButton;
    RadioButton2: TRadioButton;
    RadioButton3: TRadioButton;
    procedure RadioButton3Click(Sender: TObject);
    procedure RadioButton2Click(Sender: TObject);
    procedure RadioButton1Click(Sender: TObject);
  private
    procedure 交易(小贩:接口_小贩);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


procedure TForm1.交易(小贩:接口_小贩);
var
  我买的水果:接口_水果;
begin
    我买的水果:=小贩.工厂();
    ShowMessage(我买的水果.提示);
    ShowMessage(我买的水果.被评价);
end;

procedure TForm1.RadioButton1Click(Sender: TObject);
begin
  交易(类_苹果小贩.Create);
end;

procedure TForm1.RadioButton2Click(Sender: TObject);
begin
  交易(类_葡萄小贩.Create);
end;

procedure TForm1.RadioButton3Click(Sender: TObject);
begin
  交易(类_草莓小贩.Create);
end;
end.
  
        夏天来了,西瓜上市了;
        在简单工厂模式中,由于只有一个小贩,为了引进西瓜他只好对自己的工厂进行了修改;
        在工厂方法模式中,由于每个小贩负责一种水果,只需要再引进一个卖西瓜的小贩就行了,对其他小贩的销售不会造成影响 。


下面先看看在简单工厂模式中是怎么做的:

1、在工厂类和水果类单元文件中,引入一个新的西瓜类(这里是扩展,不会影响到已有的代码)

//=============================================================================
类_西瓜 = class(TInterfacedObject, 接口_水果)
  function 提示():string;
  function 被评价():string;
end;

{****** 类_西瓜 ******}

function 类_西瓜.提示():string;
begin
  result:='刚上市的沙瓤大西瓜,2元钱一斤!';
end;

function 类_西瓜.被评价():string;
begin
  result:='靠,被骗了,根本没熟!';
end;
//=============================================================================  


2、在工厂类和水果类单元文件中,修改小贩的工厂方法(这里是修改,已经违反了“开闭原则”)

//=============================================================================
class function 工厂类_小贩.工厂(水果名:string): 接口_水果;
begin
  if(水果名='苹果')then result:=类_苹果.Create()
  else if(水果名='葡萄')then result:=类_葡萄.Create()
  else if(水果名='草莓')then result:=类_草莓.Create()
  //请注意,下面这条语句是新加上去的,工厂被修改了!!!!
  else if(水果名='西瓜')then result:=类_西瓜.Create();
end;
//=============================================================================  



3、在窗体单元文件中,添加一个新的事件处理过程(这里是扩展,不会影响到已有的代码)

//=============================================================================
RadioButton4: TRadioButton;
procedure RadioButton4Click(Sender: TObject);

procedure TForm1.RadioButton4Click(Sender: TObject);
begin
   交易('西瓜');
end;
//=============================================================================  




下面再看看在工厂方法模式中是怎么做的:

1、这一步和在简单工厂模式中做的一样,在工厂类和水果类单元文件中,引入一个新的西瓜类(这里是扩展,不会影响到已有的代码)

//=============================================================================
类_西瓜 = class(TInterfacedObject, 接口_水果)
  function 提示():string;
  function 被评价():string;
end;

{****** 类_西瓜 ******}

function 类_西瓜.提示():string;
begin
  result:='刚上市的沙瓤大西瓜,2元钱一斤!';
end;

function 类_西瓜.被评价():string;
begin
  result:='靠,被骗了,根本没熟!';
end;
//=============================================================================  


2、区别就在这里了,在工厂类和水果类单元文件中,引入一个新的西瓜小贩类(这里是扩展,不会影响到已有的代码)
//=============================================================================
类_西瓜小贩 = class(TInterfacedObject, 接口_小贩)
  function 工厂(): 接口_水果;
end;

{***** 类_西瓜小贩 *****}

function 类_西瓜小贩.工厂(): 接口_水果;
begin
  result:=类_西瓜.Create()
end;
//=============================================================================  


3、在窗体单元文件中,添加一个新的事件处理过程(这里是扩展,不会影响到已有的代码)

//=============================================================================
RadioButton4: TRadioButton;
procedure RadioButton4Click(Sender: TObject);

procedure TForm1.RadioButton4Click(Sender: TObject);
begin
  交易(类_西瓜小贩.Create);
end;
//
posted on 2009-08-25 10:58  myamanda  阅读(493)  评论(0)    收藏  举报