继续界面与其它模块独立性的讨论。说出自己的见解就给分。

halfdream 2001-07-15 11:26:29
同时贴于文档中心


进一步将界面代码和功能代码分离(基于DELPHI/VCL)之一
-----如何将界面代码和功能代码分离(基于Delphi/VCL)一文之补充建议

读过Nicrosoft(奈软) 朋友的‘如何将界面代码和功能代码分离’一个,
很是佩服, 仔细读过之后, 我想补充我对在DELPHI上实现界面与功能代码,这方面的体会。

还是拿Nicrosoft的个人通讯录管理作为例子还说吧,
可参见:http://www.csdn.net/develop/article/8/8839.shtm

Nicrosoft朋友定义了一个TAddrBook的类来封装了一个地址簿存取操作的逻辑,
对外提供了
FindRecord, AddRecord, DelRecord。。。
等等必需的公共接口,
然后在一个TFORM作为地址簿的显示界面,仅使用这些公共接口去访问TAddrBook实例。
不涉及任何存取逻辑,每个模块的代码简单,易懂,便于维护。

Nicrosoft的TAddrBook类是没有指定父类, 也就是说继承自TObject,因为我们
谈的DELPHI, 为了充分利用它的可视化环境(当然,不仅仅因为这点),
我建议将其继承于TDataModule, 简单地说,创建一个数据模块,将其命名为TAddrBook.

仔细想想, 可能会发现仅仅这样,还有些地方不尽如人意,
在这个地址簿系统运行时,产生了一个TForm1类实例(暂称其Form1)
和一个TAddrBook类实例(暂称其AddrBook),它们之间通讯
是单向的,也就是说Form1知道AddrBook,而AddrBook不知道Form1,
Form1必需知道AddrBook是显而易见的,而AddrBook需要知道Form1吗?
这个嘛。。。。有点不大好说。。。至少有一个地方可能会需要的吧,
就是AddrBook记录改变的时候,得叫Form一声“喂,FORM,我变了”,
不过仅仅这样看来,它还是似乎可有可无,Form1可以主动去查询AddrBook的状态。

再多想想, 如果另建了一个实例TForm2类呢? 或者是一个TForm1的多个类实例呢?
这样有两个以上的FORM都要去显示AddrBook,那情况会是怎么样?
说得实际些,比如说FORM1是个LISTVIEW显示,
而FORM2是个DETAIL显示与编辑, 在FORM1中选了另一个人,FORM2显然必须得相应的
变化,直接由FORM1通知FORM2吗? 这不是个好主意。
如果这样,独立自在的FORM,本来只需要知道AddrBook,还得知道其它的FORM,FORM的数量越多,
越有扯不清的关系。增加删除一个FORM还得看其它的FORM的脸色行事,NO,这样不爽!
那好吧, 就决定让AddrBook知道有哪些FORM在用它吧,然后在必要的时候通知大家自己状态变了,
再来读自己一次。
具体实现的方法很多, 我是比较喜欢用TList,
首先,TAddrBook的私有成员中加一个
private
FViewerList:TList;//用来保存使用TAddrBookr的FORM
...
然后,加上这几个方法。。
public
...
procedure AddViewer(form:TForm); //用AddrBook的FORM登个记
procedure RemoveViewer(form:TForm); //注销这个FORM的登记
procedure TellAllViewer(); //通知所有登记过的FORM

...

procedure TAddrBook.AddViewer(form: TForm);
begin
FViewerList.Add(form);
end;
procedure Tfrm_Navigate.RemoveViewer(form: TForm);
begin
FViewerList.Remove(form);
end;
下面是通知所有使用TAddrBook的FORM, 有很多种方法的,
我这儿采用的是发送消息的方法,
已经自定义了一个消息
。。。
MY_ADDRBOOK_MSG =WM_USER+100;
。。。

procedure TAddrBook.TellAllViewer;//告诉所有的FORM‘喂,都该起来做事了’
var
i:integer;
begin
for i := 0 to FViewerList.Count-1 do
begin
postMessage(
TForm( FViewerList.Items[i] ).Handle,
MY_ADDRBOOK_MSG,
0 , //这儿两个参数可以用来区分你发出通知的种类,
0 //不主张用它们来直接传数据回FORM,
//而是在通知让FORM来取数据
);

end;
end;

而在TFORM1中,加入消息处理方法。

TForm1 = class(TForm)
...
public
procedure AddrBookNotify(var Message: TMessage); message MY_ADDRBOOK_MSG;
...

procedure TForm1.AddrBookNotify(var Message: TMessage);
begin

//在这里面重新读出AddrBook的信息,
//当然, 视你消息的分类,还可以处理得更细一些

end;

AddrBook中的方法进行改变数据状态的行为后,需要FORM相应改变显示或其它类似的操作时,
就调用TellAllViewer方法。


罗里罗嗦,终于说得差不多了, 真累,
如果知道设计模式的朋友, 一眼就可以知道这是观察者模式, 也就是VC应用程序框架中
所采用的Doc /View 模式, 就一两句话可以搞定。

之所以在标题上加了一个‘之一’, 是因为还有很多值得进一步思考的东西,
如果有必要, 我,或者其它有兴趣的朋友, 可以继续探讨,加上之二,之三。

halfdream(哈欠)halfdream@sina.com
2000。7。15

希望朋友们作进一步的讨论, 从抽象到具体,或从具体到抽象,
各方面都可以进一步探讨的。



...全文
148 16 打赏 收藏 转发到动态 举报
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
liuchcn 2001-07-19
  • 打赏
  • 举报
回复
我很赞成 属于界面的状态,就应该有界面来管理。

这样别人的系统也可以访问你所做的服务,比如TAddrBook,并且可以用他们自己的方式来显示。

当然,对于一些比较关键的功能,比如更新了某条记录,就可以发送一个消息,而客户端可以处理这个消息,也可以不处理,这样就很灵活了,不过前提是不处理这个消息不会导致错误。

chechy 2001-07-19
  • 打赏
  • 举报
回复
我赞成该文的观点。界面和业务逻辑应该分离,事实上,我在编码过程中,常常遵循着如下观点:
即Form可以调用DataModule(简称DM)一侧的方法、对象,而DM则决不能调用FORM一侧得分方法、对象。
原因在于DM中常常写的都是各种逻辑规则,功能,而form一侧通常则是与用户打交道的界面。将二者分开,有利于对逻辑规则的维护和修改,有利于DM地重用,另外还有利于软件的模块化思想。
在具体实现方法上,halfdream处理的有些生硬。毕竟Delphi是事件驱动的,如果弄Message出来,会有人觉得不爽的。此外在一些细节上还需要改进,但我赞成上述思想,将界面与逻辑分离。
持反对意见者,恐怕最大的理由是,这样做将导致代码变得复杂,有损Delphi的RAD形象。
我个人觉得这样的复杂是必要的,面向对象本身就比结构化要复杂。此外,将界面与逻辑分离后,可以基于此理论开发一套底层模块,而此模块的介入,必将使程序开发进一步简单化。
myxfang 2001-07-19
  • 打赏
  • 举报
回复
up
halfdream 2001-07-19
  • 打赏
  • 举报
回复
to liuchcn(michael)
嗯,是的,就是这样,尽管增加了消息, 但也是可以用也可以不用,不用的话代码不会增加一行, 另外可以把处理消息的FORM做成基类, 做一个事件, 让子类FORM愿意处理就处理。
torble 2001-07-18
  • 打赏
  • 举报
回复
我听
halfdream 2001-07-18
  • 打赏
  • 举报
回复
下一步还可考虑TAddrBook 其中同数据库访问的部分分离。
只有等到周末才再来好好地讨论了。
copy_paste 2001-07-16
  • 打赏
  • 举报
回复
我的想法很简单,Form只是显示和刷新用,但里面的东西可以用Record包装起来,把显示和刷新的功能用属性封装起来就完了,至于不同Form中的显示和刷新,是写在Form中,用消息我觉得的是。。。
比如我们Refresh一个DBGrid,和ListView,都会调用对应的代码,而不用关心它们是怎么去实现调用,用尽可能少,简单的代码完成功能就行了,我觉得这是必不可少的。
当然这也和数据的复杂有关系,这就可以出现用类的封装了。
一家之言,呵呵
halfdream 2001-07-16
  • 打赏
  • 举报
回复
我倒还有些好些对对象降耦合和增加扩充能力的构思,
但用在TAddrBook这样的小应用来说, 似乎有点变态
它是为了举例, 什么希奇古怪的想法都可以加在上面:)
halfdream 2001-07-16
  • 打赏
  • 举报
回复
是的, 我在实际写程序的时候也多半要用数据感知的控件的,
因为它们本来就构成了一个双向的联系。

其实OO理论的最终目的还是为了省事,保持系统的弹性。。
copy_paste 2001-07-16
  • 打赏
  • 举报
回复
捞分呵呵
Nicrosoft 2001-07-16
  • 打赏
  • 举报
回复
我的意思是,TAddrBook作为服务提供者的话,客户如何使用服务它是不比操心的,至于有多少个form使用它,它也不必知道。因为还是那句话:界面的状态由界面控制比较好。(比如:当前显示的是第几个记录,或者选中了哪个记录)

如果如你的文章所说,“比如说FORM1是个LISTVIEW显示,而FORM2是个DETAIL显示与编辑, 在FORM1中选了另一个人,FORM2显然必须得相应的变化,直接由FORM1通知FORM2吗? ”

我倒是情愿让form1和form2之间建立关联,也不想让服务端与客户端建立联系。

而如果多个form之间建立了联系,其实说明这个界面设计(即与用户交互的设计)有问题。

你说得对,具体问题具体分析。
DreamTiger 2001-07-16
  • 打赏
  • 举报
回复
我倒是觉得没必要由TAddrBook 来通知TForm,设想一下,如果这样,
每个Form都要有相应消息的功能,太麻烦了。另外,如果我不用Form,
用Frame 怎么办,是否也要保存到FViewerList中?

其实,halfdream(哈欠)说的就是现在的数据控件中一样存在,比如
说,我用query1显示数据,现在,我用query2修改了数据,你说query1
能自动显示修改结果么?不能,必须query1刷新一下才行。这个刷新
过程是由显示部分来控制的。

就像master/detail一样,master的数据控件位置变了,detail的数据
控件肯定是接收了master数据控件的改变消息,才会去重新取数据。但
是这个消息,我想不会是由tdatabase传给他的,应该是master数据控
件传给他的。

另外,我觉得,如果把这个东东考虑成最简单的C/S的话,那么,我们就
不应该保存其他状态在S端,我甚至觉得,都不应该有TAddrBook.GetCurIndex
这个函数,而应该是TAddrBook.GetAddrCount。
dana 2001-07-16
  • 打赏
  • 举报
回复
listen
Nicrosoft 2001-07-16
  • 打赏
  • 举报
回复
同意DreamTiger,我也不赞成有 TAddrBook.GetCurIndex(虽然我的原文中就有这个函数),一开始放到例子中,只是想简化一下客户端代码,不过现在看来可能也有欠考虑了。

是的,属于界面的状态,就应该有界面来管理。TAddrBook可以作为纯粹的服务提供者,如何使用服务是客户的事情。
Nicrosoft 2001-07-16
  • 打赏
  • 举报
回复
OO理论的最终目的是:提供更符合真实世界的抽象方式,更好的“重用”!(重用的包括代码和接口,可能接口重用更有价值)
halfdream 2001-07-16
  • 打赏
  • 举报
回复
copy_paste(木石三) 说的是从DELPHI的 RAD快速开发的思路来的:)
DELPHI中提供了非常多现成的东西,也给出了特定的框架,
现在从另一个角度来看看, 应该可以更清楚地知道为什么要这样或为什么不这样,
以及还有什么别的思路呢??

604

社区成员

发帖
与我相关
我的任务
社区描述
PowerBuilder 控件与界面
社区管理员
  • 控件与界面社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧