主题:讲解三层代码讲解(ActiveList的Action的工作方式)--第五课(*****)
主讲:A1
时间:2004-06-03 15:00
2004-06-03 14:52:19 烟灰缸
叮。。。叮。。。叮。。。
上课了。。。。。。。。。。。
2004-06-03 14:53:26 枫长舞
前几天有事,没听,不知今天讲的能不能跟上。
2004-06-03 14:54:26 烟灰缸
斜阳呢?
2004-06-03 14:55:08 天之痕_若虹
他还没上线吧
2004-06-03 14:55:57 天之痕_若虹
烟灰缸 今天讲的主题是什么呢?
2004-06-03 14:56:05 烟灰缸
少了斜阳,就不太精彩了。
2004-06-03 14:56:15 烟灰缸
忘了,我找找看。
2004-06-03 14:57:59 烟灰缸
在我的主页上,你可是主角啊。
2004-06-03 14:57:34 斜阳
你才是主角啊,程序是你作的啊
2004-06-03 14:57:52 天之痕_若虹
斜阳 是讲师N
2004-06-03 14:58:21 枫长舞
差不多了,开了吧。。。
2004-06-03 14:58:49 斜阳
以后要是不想作程序了,还真想去当老师
2004-06-03 14:58:53 烟灰缸
好,开始了,我先找找上次最后的东西。
2004-06-03 14:59:09 天之痕_若虹
他潜水的都叫上来。。。
2004-06-03 15:00:42 烟灰缸
主题是:ActiveList的Action的工作方式。时间是星期四下午3点。
2004-06-03 15:01:11 斜阳
说实在的,三层结构已经至少两年没接触了,这次被 烟 大侠钩起了好多回忆
2004-06-03 15:01:38 烟灰缸
你都够利害,几年了还记得。
2004-06-03 15:02:39 天之痕_若虹
开始吧...
2004-06-03 15:02:44 烟灰缸
大家打开swServer2.prj和dmDefault.prj
2004-06-03 15:02:48 斜阳
我作三层的时候在北方三层结构的技术还不是很成熟,走了好多的弯路,所以记忆尤新啊
2004-06-03 15:03:23 烟灰缸
我也是,经历了几个版本才有今天的。
2004-06-03 15:03:36 斜阳
闲话修提了,开始吧
2004-06-03 15:03:53 烟灰缸
都打开了么?
2004-06-03 15:04:53 斜阳
简单复习一下吧
2004-06-03 15:04:57 烟灰缸
好,我先找打旧记录
2004-06-03 15:06:31 烟灰缸
在 Operation 方法中调用了一个虚方法ActionList
这个虚方法要干什么,子类说了算
2004-06-03 15:06:53 烟灰缸
就好比人,规定了当被问到问题的时候(Operation)必须回答(ActionList)
至于使用什么语言,怎么回答,那具体的人就具体分析了,有的人用英语,有的人用汉语
2004-06-03 15:07:03 烟灰缸
回答的方式,就交给子类作吧
就好像 人 是基类,中国人、英国人 是人的子类,在回答问题的方式上是不一样的
2004-06-03 15:07:41 斜阳
在客户端,向服务器请求(OpenData方法,在这个方法中,调用了服务器的DataModule方法),在服务器的DataModule中通过CreateModule激活了相应的业务逻辑, 然后通过Operation方法正式通知业务逻辑开始工作
2004-06-03 15:07:44 烟灰缸
上面这些都是上一次 斜阳 所讲的
2004-06-03 15:09:45 烟灰缸
斜阳还有继续的吗?
2004-06-03 15:09:56 斜阳
其中在 CreateModule 方法中调用了每个业务逻辑动态库导出的(每个业务动态库必须导出这个函数)CreateDataModule函数创建了业务模块,在这里就是创建了 TdmDefault
2004-06-03 15:11:28 烟灰缸
请继续。
2004-06-03 15:11:37 斜阳
在TdmDefault类的基类是TBaseDataModule,在TBaseDataModule的Create方法中,保存了中转服务器传过来的相关信息
2004-06-03 15:11:58 斜阳
我打字比较慢,用全拼,所以大家别着急啊
2004-06-03 15:13:23 斜阳
大家可以打开dfDefault工程的 dmBaseModuleImp 单元,在这里就定义了TBaseDataModule
2004-06-03 15:14:32 斜阳
在这个类的Create方法中,有如下的代码
Service := BaseService;
Sql := THMSqlEx.Create;
StoreProc := THMSqlStoreProcEx.Create;
MsgList := TStringList.Create;
Sql.BaseService := BaseService;
StoreProc.BaseService := BaseService;
2004-06-03 15:16:00 斜阳
Service := BaseService;
Sql.BaseService := BaseService;
StoreProc.BaseService := BaseService;
是分别将服务器传来的接口保存到相关的类中,通过这个接口,业务逻辑层就可以和中转服务器通讯,比如获取客户的请求等等
2004-06-03 15:17:04 斜阳
根据 烟 大侠讲 Sql(THMSqlEx) 是用来管理SQL语句的,StoreProc(THMSqlStoreProcEx)是用来管理存储过程的
2004-06-03 15:18:56 斜阳
激活业务逻辑层的过程就是这些,下一步就是中转服务器准备参数(FParams.LoadFromOle(Param)),然后调用FdmModule.Operation(Data, Msg)
让业务逻辑层的Operation具体去完成相关的业务
2004-06-03 15:19:50 斜阳
看看 dmBaseModuleImp 单元中TBaseDataModule类的Operation方法
2004-06-03 15:20:16 斜阳
其中有两句
CmdIndex := Service.Params.Action;
Result := ActionList(CmdIndex, Data, Msg);
2004-06-03 15:21:44 断翅
Service.Params.Action;
这语句有和作用?
2004-06-03 15:22:07 烟灰缸
这句存有客户端指定的Action
2004-06-03 15:22:17 斜阳
这就是定义了业务逻辑动态库(也就是业务逻辑层了)的主要响应规则
1、获得客户请求参数中的模块号
2、调用虚方法ActionList
2004-06-03 15:23:12 斜阳
CmdIndex := Service.Params.Action;
就是取出客户端向这个业务逻辑层请求了那个子逻辑或者说是子模块
2004-06-03 15:23:57 烟灰缸
对。
2004-06-03 15:24:01 斜阳
然后将这个子模块的索引号作为参数,调用
ActionList
2004-06-03 15:26:10 斜阳
因为ActionList是一个虚的过程,所有TBaseDataModule的子类可以通过重载这个过程定义不同的功能,这就是为什么调用的方式只有一种(即中转服务器只是调用一下Operation)但是实现却有很多种的原因了
2004-06-03 15:26:32 烟灰缸
对。
2004-06-03 15:27:07 斜阳
我们可以看看在TBaseDataModule 中的ActionList方法都干了什么
Msg := format('没有可执行的命令(%d)', [CmdIndex]);
Result := False;
基本上是什么也没作
2004-06-03 15:27:42 斜阳
为什么呢?因为ActionList要作什么,基类TBaseDataModule是不知道的,只有子类才知道
2004-06-03 15:27:44 烟灰缸
对,它在偷懒嘛N
2004-06-03 15:28:10 斜阳
我们再看看TBaseDataModule的一个子类TdmDefault
2004-06-03 15:28:22 烟灰缸
unit dmDefaultModule;
2004-06-03 15:28:24 斜阳
在dmDefaultModule单元
2004-06-03 15:30:00 斜阳
需要提醒大家的是,在中转服务器调用CreateModule激活业务逻辑的时候,实际上是创建了一个TdmDefault类
2004-06-03 15:30:30 烟灰缸
这一点在Project的开头里定义了。
2004-06-03 15:32:58 斜阳
大家回到dmDefaultModule单元,看TdmDefault类
(工作这么多年了,对付不懂技术的领导还是有一套的M,这不,乐呵呵地走了)
2004-06-03 15:34:58 斜阳
在TdmDefault的ActionList方法中,大家可以看到,是一个case语句组,根据刚刚解析出来的子模块索引号分别调用不同的功能
case CmdIndex of
acOpenView: Result := OpenView(Data, Msg);
acOpenStoreProc: Result := OpenStoreProc(Data, Msg);
acExecute: Result := Execute(Data, Msg);
acOpenSql: Result := OpenSql(Data, Msg);
acUpdateWithDelta: Result := UpdateWithDelta(Data, Msg);
2004-06-03 15:35:17 烟灰缸
对。
2004-06-03 15:36:10 斜阳
比如当 CmdIndex 等于 acOpenView 时,就调用OpenView函数
2004-06-03 15:36:45 斜阳
OpenView也就是我们说的子模块了
2004-06-03 15:36:50 烟灰缸
对,acOpenView在swModuleIndex.pas里定义,你也可以定义其它的。
2004-06-03 15:38:32 斜阳
程序到这里,再跟踪下去已经没有什么必要了,因为再往下(比如OpenView是怎么工作的)就涉及到相关业务了,但是需要继续讨论的是,业务逻辑层是如何返回数据给中间层的呢,唉,还要进一个具体业务实现去走走
2004-06-03 15:39:02 斜阳
就拿OpenView开刀吧
2004-06-03 15:39:47 斜阳
再往下就请 烟灰缸 老师继续吧
2004-06-03 15:40:22 烟灰缸
我?还是你比较精彩点,我都是口呆了。
2004-06-03 15:42:06 斜阳
说实在的,这几天一直在作一个统计分析的东西,所以具体的业务层没有详细地跟踪下去,还是你说吧,如果你说的和我猜想的没有什么出入而又需要我补充的,那我再说吧,好吗?
2004-06-03 15:42:20 烟灰缸
好。
2004-06-03 15:44:47 烟灰缸
就拿刚才的OpenView来讲,它要跟据客户的须求返回数据,所在,在OpenView这个子模块里,会读取客户端的参数。
2004-06-03 15:45:48 斜阳
具体什么类型的参数呢?是客户请求的SQl语句还是别的什么
2004-06-03 15:47:41 松鼠
LoadFromStore(Ole.ViewIndex, slSql);
老师,这个做什么?
2004-06-03 15:47:46 烟灰缸
他的工作流程如下:
1.读取SQL语句 LoadFromStore(Ole.ViewIndex, slSql);
2.如果SQL语句有参数的话,就读取对应的客户端参数;(Sql.Params.ParamValue[Sql.Params.Items[i - 1].Param] := Ole[Sql.Params.Items[i - 1].Param];
3.
2004-06-03 15:47:50 松鼠
LoadFromStore(Ole.ViewIndex, slSql);
老师,这个做什么?
2004-06-03 15:49:18 松鼠
Sql.Params.ParamValue[Sql.Params.Items[i - 1].Param] := Ole[Sql.Params.Items[i - 1].Param];
这个怎么看?
2004-06-03 15:49:36 烟灰缸
3.通知中转服务器,装载Sql,(但还没Open) Service.Query.LoadSql(Sql.SqlLanguage);
2004-06-03 15:50:20 烟灰缸
to 松鼠,可以把它看成是 SQL 的参数= 客户端的参数
2004-06-03 15:50:34 松鼠
好
2004-06-03 15:51:39 烟灰缸
4.通知服务层,SQL语句已经装载了,可以返回数据给客户端了,数据在Service.Query里。
2004-06-03 15:51:51 烟灰缸
Service.ReceiveDataWithDefault;
2004-06-03 15:52:23 烟灰缸
5。返回成功信息,流程到此就结束了。
2004-06-03 15:53:01 烟灰缸
找另一个更简单的。
2004-06-03 15:56:05 烟灰缸
OpenView大概流程如下:
读取SQL语句->设置SQL语句的参数->通知中转服务层载入SQL语句->通知服务层可以返回数据给客户->返回信息
2004-06-03 15:56:52 烟灰缸
大家看得明白吗?
2004-06-03 15:57:05 斜阳
明白
2004-06-03 15:57:18 llyygg
OK
2004-06-03 15:57:31 松鼠
明白
2004-06-03 15:58:07 烟灰缸
请大家打开dmDemo.prj
2004-06-03 15:58:30 烟灰缸
看看另一个模块的作业方式
2004-06-03 15:58:40 烟灰缸
unit dmDemoModule;
2004-06-03 15:59:58 烟灰缸
ActionList里没有定义什么acOpenView之类的东西,而是直接是1,2,3,所以说,你可以不用在swModuleIndex.pas里定义,直接写序号就可以了。
2004-06-03 16:00:47 烟灰缸
看看TdmDemo.Action1,这是一个更新客户付款价格的Action
2004-06-03 16:01:21 烟灰缸
因为关系到数据更新,所以要用到“事务”
2004-06-03 16:02:36 烟灰缸
Self.CheckTranstion;
检查中转服务层自身是否已经在事务中(不同的客户有自己的中转服务层,互不干扰)
2004-06-03 16:04:05 烟灰缸
数据更新前,要先通知中转服务层“开始事务”
Service.BeginTrans;
2004-06-03 16:04:27 松鼠
Service.BeginTrans;
这个好象要在try前面吧?
2004-06-03 16:04:55 烟灰缸
呵呵,有时我也忘了。
2004-06-03 16:05:49 斜阳
明白了
2004-06-03 16:06:05 松鼠
continue
2004-06-03 16:06:30 烟灰缸
这一段是装入SQL存取过程语句,并赋值存取语句的参数,最后通知中转服务层执行语句。
StoreProc.LoadFromStore(100006);
StoreProc['@mm_id'] := '1389';
StoreProc['@mm_pay'] := 1.15;
Service.Execute(StoreProc.SqlLanguage);
2004-06-03 16:07:36 烟灰缸
最后,通知中转服务层“提交事务”,并跟它说,我无须返回数据。
Service.CommitTrans;
Service.ReceiveDataWithNoData;
2004-06-03 16:07:47 烟灰缸
下面的就一样的了,就不讲了。
2004-06-03 16:07:55 烟灰缸
有不明白的吗?
2004-06-03 16:08:50 松鼠
StoreProc['@mm_id'] := '1389';
StoreProc['@mm_pay'] := 1.15;
Service.Execute(StoreProc.SqlLanguage);
StoreProc['@mm_id'] := '0877';
StoreProc['@mm_pay'] := 0.88;
Service.Execute(StoreProc.SqlLanguage);
怎么执行两次,是更新同一个记录吗?
2004-06-03 16:09:11 烟灰缸
到现在,我们返回的方式已经讲了两种,还有两种没讲
procedure ReceiveDataWithDefault; stdcall;
procedure ReceiveDataWithCustom; stdcall;
procedure ReceiveDataWithResult; stdcall;
procedure ReceiveDataWithNoData; stdcall;
2004-06-03 16:09:48 烟灰缸
to 松鼠,我这样写只是示例一下而已。
2004-06-03 16:10:18 松鼠
哦
2004-06-03 16:11:05 烟灰缸
关于这两个,可能要有示例才能讲明白。
procedure ReceiveDataWithCustom; stdcall;
procedure ReceiveDataWithResult; stdcall;
2004-06-03 16:11:56 烟灰缸
简单的说一下,ReceiveDataWithCustom是对应要返回多个表的,
2004-06-03 16:12:33 烟灰缸
ReceiveDataWithResult则是要返回自定义数据的,数据就是开头的var Data:OleVariant
2004-06-03 16:12:45 烟灰缸
有不明白的吗?
2004-06-03 16:14:14 松鼠
这两个函数在哪实现的?
2004-06-03 16:14:38 烟灰缸
unit dmBaseService;里
2004-06-03 16:14:51 松鼠
好
2004-06-03 16:16:10 烟灰缸
现在看回中转服务层,它是如果接收并返回数据给客户端的。
2004-06-03 16:16:32 烟灰缸
unit DataServer_form;
2004-06-03 16:17:14 烟灰缸
Result := FdmModule.Operation(Data, Msg);
if Result then
begin
case FBaseService.ReceiveDataType of
rdNoData:
begin
Data := Null
end;
rdDefault:
begin
Data := dspTest.Data;
end;
rdCustom:
begin
Ole.SaveToOle(Data);
Ole.Clear;
end;
2004-06-03 16:18:04 断翅
Result := FdmModule.Operation(Data, Msg);
客户请求的数据是不是已经在Data里了
2004-06-03 16:19:41 烟灰缸
我们刚才讲到OpenView ,它调用了Service.ReceiveDataWithDefault;
所以,上面的代码中中转服务层对应的就是rdDefault
2004-06-03 16:19:52 烟灰缸
TO断翅 不是。
2004-06-03 16:20:10 烟灰缸
Data := dspTest.Data;
2004-06-03 16:20:40 断翅
那不是跑了一大圈还是没有取得客户需要的数据?
2004-06-03 16:20:51 烟灰缸
因为dspTest.DataSet就是Query
2004-06-03 16:21:19 松鼠
明白
2004-06-03 16:22:06 烟灰缸
而Query在规则里已经通知中转服务层装SQL语句了,所以Data := dspTest.Data; 时,Data就是Query的数据。
2004-06-03 16:22:34 烟灰缸
而这个Data就是要返回给客户的。
2004-06-03 16:23:23 烟灰缸
下面的工作就是请除骨存,就不细讲了。
大家能看得明白吗?
2004-06-03 16:25:44 松鼠
上面你讲的那段代码什么时候触发的?
2004-06-03 16:25:58 烟灰缸
哪段?
2004-06-03 16:26:08 松鼠
Result := FdmModule.Operation(Data, Msg);
if Result then
begin
case FBaseService.ReceiveDataType of
rdNoData:
begin
Data := Null
end;
rdDefault:
begin
Data := dspTest.Data;
end;
2004-06-03 16:26:49 烟灰缸
从 if Result then 开始是“规则DLL”返回时发生的。
2004-06-03 16:29:45 松鼠
什么时候有用到TDataServer2.DataModule这个方法?
2004-06-03 16:30:27 烟灰缸
客户端请求数据里,请看看上一次的记录,OpenData那一段。
2004-06-03 16:31:06 松鼠
好,我看一下
2004-06-03 16:34:50 烟灰缸
我的基本上讲完了,斜阳看看能不能补充一下。
2004-06-03 16:39:16 烟灰缸
要不这样,大家问问和这相关的问题,我和斜阳来回答。
2004-06-03 16:41:49 断翅
Result := FdmModule.Operation(Data, Msg);
没有取到客户需求的数据,那它的主要作用是什么呢,是不是和dspTest的DataSet Query有关
2004-06-03 16:42:21 烟灰缸
是,一般情况上,数据都在dspTest.Data里。
2004-06-03 16:42:53 斜阳
真正取数据的不是这里,这里只是告诉业务逻辑层,"你要提相关供服务了"
2004-06-03 16:43:25 烟灰缸
只有在ReceiveDataWithResult;时,才在你说的Operation(Data, Msg);的Data里
2004-06-03 16:44:12 llyygg
烟灰缸:CheckTranstion发现有事务就产生异常,为什么不是利用已经有的事务呢?
2004-06-03 16:45:39 烟灰缸
CheckTranstion主要是想检查一下,上次的事务是不是没有“提交”,还是出错了。因为每个Action里必须“提交”事务。
2004-06-03 16:46:43 llyygg
哦,就是不会出现一个事务没有完成,又调用下一个需要事务的情况?
2004-06-03 16:47:02 烟灰缸
是啊。
2004-06-03 16:55:06 松鼠
听得晕晕的,真理掌握在少数人手中M
2004-06-03 16:55:55 斜阳
慢慢来吗,你试着从客户端开始,一直跟踪一下,你就会有点收获的
2004-06-03 16:56:15 松鼠
好的
2004-06-03 16:56:58 断翅
DataServer_form里的Connection是在什么地方激活的啊?
2004-06-03 17:03:47 松鼠
procedure TfrmDataServer.StartServer1Click(Sender: TObject);
begin
if ServiceState = srStop then StartService;
end;
这个启动有什么作用,客户端如何知道服务器有启动?
2004-06-03 17:05:13 烟灰缸
在TDataServer2.DataModule里有提示。
2004-06-03 17:07:02 松鼠
哦,明白了
2004-06-03 17:14:43 斜阳
to:无翅飞翔 Connection是在TDataServer2的Create方法中设置的,其中有个mdLibrary.Open;
就是间接打开了数据源
2004-06-03 17:21:59 松鼠
斜阳:
他写的都是用到类的,以前都没用过,看得很不习惯
2004-06-03 17:22:33 烟灰缸
类都头晕,那接口不更晕了。。。。。。。。。。
2004-06-03 17:22:59 斜阳
可能是你对接口不是很了解,所以会晕头
2004-06-03 17:23:18 松鼠
是啊M
2004-06-03 17:23:47 斜阳
还是找本接口的书恶补一下吧,你会发现越学接口越觉得你发挥的空间越大
2004-06-03 17:24:30 松鼠
好象李维那本《inside VCL》就有讲到接口的,不过我也没去看
2004-06-03 17:24:32 烟灰缸
对,接口的用途太大了。我的程序基本上用接口最多。
2004-06-03 17:25:26 斜阳
以前接触类的时候,觉得面向对象简直是神了,想干什么就干什么,后来发现还有比这个更牛的,那就是接口了,这时我学习的过程了
2004-06-03 17:25:37 松鼠
接口比一般类较复杂
2004-06-03 17:25:51 烟灰缸
想通了就不复杂了。
2004-06-03 17:26:20 斜阳
复杂不到哪去的,你觉得接口复杂是因为你被微软的COM给忽悠住了,真正麻烦的是COM
2004-06-03 17:27:10 银狼
具体点,COM是个什么概念啊
2004-06-03 17:27:33 松鼠
接口不好看,比较难懂,我前天看了一些关于接口的多态性,头都大了
2004-06-03 17:28:43 斜阳
真叫你问住了,天天用COM,突然忘了COM到底是什么了
2004-06-03 17:29:13 松鼠
好象COM=Component Object Model
2004-06-03 17:29:21 斜阳
好像是的