Ansel's Blog

It would probably render the application more complex

博客园 首页 新随笔 联系 订阅 管理

利用ModelMaker实现Singleton模式 2004年11月13日03:34星期六  [专题文章]  研究ModelMaker也有一段日子了,最近对其中的模式颇感兴趣,特将一些使用体会记录下来,都是很简单的例子,以供初学者参考。
因为我最近写的一个类正在使用Singleton模式,所以自然先写的就是Singleton了,不过使用的例子则是最为简单的,因为我不希望读者的视线被太多不相关的细节所迷惑。
读此文字之前,希望读者最好能有一些ModelMaker的使用经验,例如如何生成类,如何产生代码。。。
首先打开ModelMaker,我使用的是7.20版本,配合Delphi7。
打开Classes面板,在TObject上单击右键,选择Add Class,在Class Name处填入类的名称,这里是TAppInfo,这样就会以TObject为父类生成一个子类,当然这个时候是个什么都没有的空类。在Classes面板的下方members面板上有几个图标,点击P+,也就是添加属性,这里我们添加一个Integer的Tag属性,读写全部选择为Field,这样就会自动生成一个Private级别的FTag域。
这个时候在保持Classes面板上我们的类处于选中状态,单击菜单View,选择Patterns项,这个时候会出现一个PageControl,里面分类放置了几个不同的模式,我们先找到Wizzard里面最后一项(也就是图标中有一个1的),这就是我们需要实现的Singleton模式。点击后会看到他自动生成了数个method,暂时不管,关掉模式窗口即可。
接下来要做的是将类关联到单元文件中,切换到Units面板(Classes右边),点击Add New Unit来引入一个新的单元文件,随后出现的窗口中,只要更改Relative File Name即可,也就是生成的.Pas文件的名称。确定后会在units面板中生成一个子项,在Classes not assigns to units中找到我们刚刚创建的TAppInfo类,拖放到刚刚创建的unit子项中,这样就完成了类和单元的关联。
之后就是生成delphi可以使用的.pas源代码了,确保菜单栏下面工具栏中的Unlock code Generation处于打开状态(就是那个锁头的图标),点击Generate图标,如果一切正常的话这个单元应该已经被正常的生成了。
打开delphi,创建一个默认的application,回到ModelMaker,点击工具栏的Locate in Delphi(就是Delphi图标的按钮,很好找的),这样delphi就会自动打开这个单元文件,出现在你面前的就是ModelMaker生成的pas源代码了。在工程中uses单元名称,compile一下是否能够通过编译,如果通过了,恭喜,我们可以进行下一步的测试了,反之则回头找一下错误。
接下来是测试,在form1中声明这样两个变量AppInfo1, AppInfo2 : TAppInfo;放三个按钮在form上。点击事件依次为:
 appinfo1 := TAppInfo.Instance;
 Appinfo1.Tag := Random(1000);

 appinfo2 := TAPPinfo.Instance;
 AppInfo2.Tag := Random(1000);
可以看出两个按钮是分别为两个TAppInfo对象创建实例,并且赋不同的值给TAG(为什么不用TAppInfo.Create后文会讲述)。第三个按钮则是显示两个对象的TAG数值
ShowMessage('AppInfo1.Tag = '+IntToStr(AppInfo1.Tag) + 'AppInfo2.Tag = '+ IntToStr(AppInfo2.Tag));
下面运行一下看看吧,是不是两个对象的TAG值都是一样的~?有兴趣的朋友可以用创建对象数组,循环一下看看是不是所有的对象都是一样的,这是因为他们都指向了同一个实例,这也就是Singleton模式。
接下来暂时忘掉ModelMaker,来探究一下Singleton模式的实现方法吧。因为Delphi没有C++/Java那样的Static静态方法,所以比较普遍的做法是使用全局变量,hubdog的《delphi深度探索》里面是覆盖了TObject.NewInstance,有兴趣的朋友可以自行参阅相关资料,下面主要介绍ModelMaker的实现方法。
首先仔细观察一下类的声明:
type
 TAlAppInfo = class(TObject)
 private
   FTag : Integer;
 protected
   constructor CreateInstance;
   class function AccessInstance(Request: Integer): TAlAppInfo;
 public
   constructor Create;
   destructor Destroy; override;
   class function Instance: TAlAppInfo;
   class procedure ReleaseInstance;
   property Tag : integer read FTag write FTag;
 end;
发现共有两个构造器,一个是protected级别的CreateInstance,另一个是Public级别的Create,因为我们无法在类单元以外调用protected级别的函数,所以先观察Create,其实现代码如下:
constructor TAppInfo.Create;
begin
 inherited Create;
 raise Exception.CreateFmt('Access class %s through Instance only', [ClassName]);
end;
代码很明显表达了这么一个观点:“任何时候都不要调用我,否则就给你好看!”无奈,只好放弃他转而观察其他的public方法。Destroy?怎么可能-.-,剩下的只有两个类方法了Instance和ReleaseInstance。而ReleaseInstance是个过程,很显然不能放在等号的右侧,唯一的可能性就只有类函数Instance了,他的返回值是类本身的实例。ok,let's go!
class function TAppInfo.Instance: TAppInfo;
begin
 Result := AccessInstance(1);
end;
easy,一眼就可以看出他调用了protected级别的类函数AccessInstance创建类的实例,接下来就是魔法的关键了:
class function TAppInfo.AccessInstance(Request: Integer): TAppInfo;
 { $J+ }
 const FInstance: TAppInfo = nil;
 { $J- }
begin
 case Request of
   0 : ;
   1 : if not Assigned(FInstance) then FInstance := CreateInstance;
   2 : FInstance := nil;
 else
   raise Exception.CreateFmt('Illegal request %d in AccessInstance', [Request]);
 end;
 Result := FInstance;
end;
这是一个设计的很精巧的函数,这里定义的FInstance才是这个类唯一的实例,通过const赋初值为nil,同时使用编译器开关{ J+ }使得此值可以修改。根据传入参数的不同,我们来看看都会产生什么样的结果吧!
0: 函数的返回值为FInstance
1: 如果FInstance尚未初始化,则调用上文提过的protected级别的构造器CreateInstance为FInstance创建实例,返回FInstance。
2: 将FInstance指向空指针,此时返回值同样为空指针。
除此以外的参数则会抛出一个异常。
追寻上面的痕迹,我们来看看TAppInfo.Instance的处理流程吧!他传入的参数是1,也就是第1个结果,很明显了,如上文所提,他永远返回FInstance,无论调用几次TAppInfo.Instance,其返回值指向的都是同一个实例。到此几乎一切都已经很明显了,不过重要的构造器还是要讲的~
constructor TAppInfo.CreateInstance;
begin
 inherited Create;
end;
很简单,他调用了父类的构造器,这里是TObject.Create,我们有理由认为,所有类的初始化动作都应该在这里面加入,放在Inherited create之后即可正确的完成类的初始化动作。实例创建后还需要销毁,这里有些迷惑,我们应该调用xxx.free还是xxx.ReleaseInstance呢?还是看代码吧。
class procedure TAppInfo.ReleaseInstance;
begin
 AccessInstance(0).Free;
end;
destructor TAppInfo.Destroy;
begin
 if AccessInstance(0) = Self then AccessInstance(2);
 inherited Destroy;
end;
先看类方法ReleaseInstance,他是传入0给AccessInstance,按照上面的分析,他返回FInstance的实例,同时调用了对象的destructor,即Free。因此这两个方法是等价的,随便调用哪个都可以,当然还是free好,别人看代码会更加清晰一些=.=,当需要手动销毁类包含的资源的时候,代码应该放到Destroy里面大家应该是没问题的吧?类方法是不能操作类成员对象的,呵呵。
后记:MM产生的代码虽然很精巧,但是有一个较大的弱点,不易扩展,他无法保证其的子类也是singleton模式,事实上,要想从这个类派生子类是有些困难的,甚至还不如重新利用MM的向导生成代码。网上有一篇文章,实现了一个可以保证子类也只有一个实例的TSingleObject类,有需要的不妨借用,而我个人认为这个类的存在并无太大意义,要知道Object Pascal是不支持多重继承的,这样使用环境的限制就太大了。呵呵
至此利用ModelMaker实现Singleton模式已经结束了,由于是第一篇关于ModelMaker的文章,所以对MM的操作细节讲述较多,而且好像也稍嫌罗嗦,有轻视读者水平之嫌疑:P,很抱歉,这是希望一些初学者也能通过这篇文章认识到delphi的OOP知识,以后可能会稍微略去这些细节。网上有一篇很好的ModelMaker6.20中文手册,建议感兴趣的朋友仔细研读。
 

 

posted on 2005-08-23 08:52  Ansel  阅读(597)  评论(0编辑  收藏  举报