UML软件工程组织 |
N-tier架构的 进阶设计模式(Design Pattern) ── Stateless与Stateful对象携手合作 |
林圣汉、林子扬 |
在「N-tier架构的基本模式」文章里,已经介绍5个基本的模式了。该文也强调:学习这些基本模式时,也必须学习如何修正它们,以便迅速落实于您的特殊新环境中,才算学以致用! 于是,本文就基于该5个模式而继续推演出更多模式,使其更合乎多层式(n-tier)的component-based架构之需要。请记得,您必须一边欣赏这些新模式,一边思考如何修正之,增强活用这些模式的能力,才是本文的目标。 本文的价值不在于介绍几个进阶的模式,而在于教您如何有样学样,无样自己想,举一反三,创造出无限的新模式。换句话说,本文的用意不在于钓几条鱼(模式)给您吃,而在于教您应变能力,能在不同湖泊或大海中皆能自己改进钓鱼技巧(创造新模式的能力),才会永远有鱼吃。 前言
在本期的「N-tier架构的基本模式」文章里,介绍了5个基本模式。本文将基于这些基本模式,而继续延伸出更多的实用模式。
兹回忆基本模式¹的基本结构为,如图1所示。其中的对象角色为: s有个stateful的对象含有企业营运规则(business
rules),可称为business对象。 s
有个stateless对象含有SQL指令负责存取数据库,可称为persist对象。
图1、基本模式¹的结构
在「N-tier架构的基本模式」一文里,也提到:学会模式并非终极目的,而必须能将模式修正成为您的特殊环境,创造出最佳的、最适用您自己的模式,才算完美。所以除了学习模式之外,也必须学习创造新模式。
于此,就跟您一起来进行一趟模式的创作之旅,将模式¹继续延伸出而创造出更多的模式,并实际应用于n-tier系统的开发工作上。
模式的修正弹性很大,连对象的名称皆是可视您的喜好而改变,只要您确定这样的改变是有利的,就行了。例如:
图2、基本模式¹的初步修正
将原来的Account改名为AccPersist会更明显表示出其负责存取数据库的角色。将原来的AccounX改名为Account更明确对应到企业领域(domain)里的「帐户」概念(concept)。
这样的名称改变,表示观点(view)有所改变。在原来的模式¹里,将persist对象取名为Account,就意味着以persist对象为重心。企业对象(business
object) ----- AccountX与DB表格只是担任辅佐persist对象,替persist对象保管其状态值(state)罢了。
现在将企业对象改名为Account,就意味着以企业对象为重心,persist对象只是把企业对象的状态存入DB表格里而已。因此persist对象是反过来辅佐企业对象的。 进阶模式» ●同时使用多个stateful对象
在上图2里﹐只有一个企业对象-----Account对象,此时您当然可以举一反三,而认知到:可以有很多个企业对象,而且都是stateful的对象。
例如,要存取数据库里的另一个表格──Customer表格﹐则可以定义一个CustPersist类别协助存取Customer表格,并定义一个Customer类别来含纳企业规则。
图3、模式»
----存取两个DB表格
如果您很欣赏模式¹的话,应该会觉得此模式»的是很自然而美好的。反之,如果您很习惯于使用MTS的SPM(shared
property manager)的话,您可能会觉得多定义了Account、Customer等类别,实在吃饱没事干的人才会如此做! 此外,如果您很熟悉传统2-tier架构的话,您会觉得Account、Customer等类别是多余的,这些类别里的企业规则(business rule)可以写在Form里就行了。
因此,这个模式»的最大弱点是:大多数的人会觉得Account、Customer等类别是多余的,而且增加程序设计者的限制,实在碍手碍脚的。然而有些人就能轻易看出Account、Customer等类别的美妙角色: s 对企业规则加以分门别类(classify)。让企业规则不再流离失所,难以管理。例如,将企业规则全部塞到Form里,则分散在各client端,就不易管理了。 s 也对不同DB表格的资料分门别类(classify)。让资料不再统统往SPM丢,让SPM成为垃圾堆。 下图4为模式»之例子。Form程序要求AccPersist对象读取Account表格的记录资料,然后不直接将资料送往Form,也不存到SPM里,而是送往Account对象里,暂存起来。
图4、
模式»应用之例
采取模式»,不但将企业规则分类,也对资料分类,并且分别而有系统的管理,企业规则与其所需要的资料紧密结合在一起,就像Stateful的Account或Customer类别了。
假如您的系统小小的,则使用模式»是有点杀鸡用牛刀的味道。然而您的系统为3-tier架构且未来会成长到需用到数十个组件(component)的话,模式»的分类能力可确保系统在成长的过程中,一直保持企业规则的井然有序,易于管理,而不会随着系统的变大而复杂,变成无法驾驽的怪兽。因此,模式»能确保系统的稳定成长,永保青春。
上图4里的client
程序从Account对象取CustID值,然后依据CustID值要求CustPersist对象读取Customer表格的记录资料,不直接将资料送往Form,也不存到SPM里,而是送往Customer对象里,暂存起来。client程序再从Account对象取CustName值,
现再看看上图4的AccPersist及CustPersist类别之定义如下: 'Ax1c-1 'AccPersist class 'Stateless '各App共享类别 'COM+
/ MTS version 'Requires
Transaction Implements
ObjectControl Private
accId As String Private
custId As String Private
b As Double Private
objCtx As ObjectContext Private
adoConn As ADODB.Connection Private
adoRS As ADODB.Recordset Private
Const strConn = "Provider=SQLOLEDB; Data Source=Bill_Lin; Initial
Catalog=BankDB; User Id=sa; Password=" Private
strSQL As String Private
Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled
= True End
Function Private
Sub ObjectControl_Activate() Set objCtx = GetObjectContext() Set adoConn = CreateObject("ADODB.Connection") adoConn.Open (strConn) End
Sub Private
Sub ObjectControl_Deactivate() Set objCtx = Nothing Set adoConn = Nothing End
Sub Public
Function Query(aid As
String, accx As Account) As Boolean 'On Error GoTo ErrorHandler '从DB读取目前余额 strSQL = "SELECT * FROM AccountTable
Where id = '" + aid + "'" Set adoRS = adoConn.Execute(strSQL) accId = adoRS.Fields("id").Value custId = adoRS.Fields("cid").Value b = adoRS.Fields("Balance").Value Call accx.SetData(accId, custId,
b) Query = True objCtx.SetComplete Set adoRS = Nothing Exit Function ErrorHandler: Query = False objCtx.SetAbort Set adoRS = Nothing End
Function 'End
AccPersist class '------------------------------------ 'CustPersist class 'Stateless '各App共享类别 'COM+
/ MTS version 'Requires
a transaction Implements
ObjectControl Private
custId As String Private
custNa As String Private
objCtx As ObjectContext Private
adoConn As ADODB.Connection Private
adoRS As ADODB.Recordset Private
Const strConn = "Provider=SQLOLEDB; Data Source=Bill_Lin; Initial
Catalog=BankDB; User Id=sa; Password=" Private
strSQL As String Private
Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled
= True End
Function Private
Sub ObjectControl_Activate() Set objCtx = GetObjectContext() Set adoConn = CreateObject("ADODB.Connection") adoConn.Open (strConn) End
Sub Private
Sub ObjectControl_Deactivate() Set objCtx = Nothing Set adoConn = Nothing End
Sub Public
Function Query(cid As
String, custObj As Customer) As Boolean On Error GoTo ErrorHandler '从DB读取目前余额 strSQL = "SELECT * FROM CustomerTable
Where id ='" + cid + "'" Set adoRS = adoConn.Execute(strSQL) custId = adoRS.Fields("id").Value custNa = adoRS.Fields("name").Value Call custObj.SetData(custId,
custNa) Query = True objCtx.SetComplete Set adoRS = Nothing Exit Function ErrorHandler: Query = False objCtx.SetAbort Set adoRS = Nothing End
Function 'End
CustPersist class
这两个类别皆是stateless对象,专司DB的存取工作。接着,兹定义stateful的看Account及Customer类别如下: ‘Ax1c-2 'Account class '各App共享类别 'Stateful Private
accId As String Private
custId As String Private
b As Double Public
Sub SetData(aid As String, cid As String, m As Double) accId = aid custId = cid b = m End
Sub Public
Function GetBalance() As Double GetBalance = b End
Function Public
Function GetCustID() As String GetCustID = custId End
Function Public
Function changeToUSD() As Double changeToUSD = b / 31.6 End
Function 'End
Account class '------------------------------------- 'Customer class '各App共享类别 'Stateful Private
custId As String Private
custName As String Public
Sub SetData(id As String, na As String) custId = id custName = na End
Sub Public
Function GetName() as String GetName = custName End
Function ‘End
Customer class Account对象与AccPersist对象一对(pair),它们分工且合作。而Customer对象与CustPersist对象也是一对,也分工且合作。虽然乍看起来类别个数增多了,但却是井然有序的。兹写个client程序如下: 'Ax1f 'Form 'Set
Reference to Ax1c-1 & Ax1c-2 Private
acc As Account Private
cust As Customer Private
accp As AccPersist Private
custp As CustPersist Private
Sub Form_Load() Set acc = CreateObject("aATMex1a.Account") Set cust = CreateObject("aATMex1a.Customer") End
Sub Private
Sub Form_Unload(Cancel As Integer) Set acc = Nothing Set cust = Nothing End
Sub Private
Sub Query_Click() Dim cid As String '读取AccountTable Set accp = CreateObject("aATMex1a.AccPersist") Call accp.Query("aid008",
acc) cid = acc.GetCustID() AccIdBtn.Text = cid BalanceBtn.Text = acc.GetBalance() USDBtn.Text = acc.changeToUSD() Set accp = Nothing '读取CustomerTable Set custp = CreateObject("aATMex1a.CustPersist") Call custp.Query(cid,
cust) CustNameBtn.Text = cust.GetName() Set custp = Nothing End
Sub
虽然上述类别定义的Account类别跟AccPersist类别是配对的,但是根据stateless对象的特性:类别可以分合自如。您可以将AccPersist类别和CustPersist类别合并起来。至于stateful类别就不宜随意合并了。如下图5所示。
图5、
stateless对象的合并
从上图4和图5可看出,stateful对象的最大用途就是对复杂的企业规则进行分门别类,让其井然有序,以达到分而治之的目标。
也许您会有疑虑:如果有许多clients同时上线执行此应用程序,是否会诞生许多的stateful对象呢?是否会占掉许多内存空间呢?而且stateful对象的生命期是由client程序所控制,其所占内存的时间是否会很长呢?这都是使用此模式时必须要考量的副作用。 模式»的特性──
优点:企业规则已经妥善地分而治之了。
缺点:企业对象之间还没有建立关系;企业对象可能用掉许多内存资源。 现在,请继续看新的模式¼,它支持企业对象之间建立直接沟通之关系。让各企业对象具有更多的智能,成为智能型的软件组件。 进阶模式¼ ●
建立Account与
Customer之关系 N-tier系统开发时,通常会采用面向对象分析与设计(OOAD)方法,此方法所得到的企业对象,皆是stateful对象。而且这些对象之间有其稳定的关系(relationship)。 OOAD所分析出来的类别图(class
diagram)的内容可落实到模式»的stateful对象,此模式¼的重点就在于:如何落实类别图里的「关系」。
当然,OOAD的类别图(class
diagram)内容也可落实到stateless对象,但需经过一些转换(translation)过程,会比较麻烦一些,所以模式¼是将类别图落实到stateful对象。于此就来说明其中的理由,例如有个简单的类别图:
图6、OOAD的类别图之例
因为在OOAD系统分析师(SA)心中,类别图里的企业对象皆是stateful,所以落实到stateful对象是很直截了当的,极为顺畅。如下的程序: ‘AA class ----stateful Private bObj As BB Private Sub SetLinkToBB(obj As BB) ‘从建立与BB对象之link关系 Set bObj = obj End Sub Private Sub Add100ToBB() bObj.Add( 100 ) End Sub Public Function queryBB() As Integer queryBB = bObj.query() End Function ‘------------------------------------------------------- ‘BB class ----stateful Private x As Integer Private Sub Class_Initialize() x = 1800 End Sub Private Sub Add( y As Integer ) x = x + y End Sub Private Function query() As Integer query = x End Function ‘-------------------------------------------------------- ‘Form1 Private a As AA Private b As BB Private Sub Form_Load() Set a = CreateObject(“AA”) Set b = CreateObject(“BB”) a.SetLinkToCust( b ) a.Add100ToBB bbBtn.Text = a.queryBB() End Sub
图6类别图里的AA类别有Add100ToBB()及queryBB()两个函数,在程序里的AA类别也1对1地有Add100ToBB()及queryBB()两个函数。OOAD的系统分析师心中认为在AA对象和BB对象诞生时已经存在了它们之间的关系。所以在程序里,只需提供一个SetLinkToBB()给Form在诞生AA及BB时可藉之建立关系。恰满足系统分析师的基本想法:就是在呼叫Add100ToBB()及queryBB()时,这些link关系已经存在了。执行Add100ToBB()函数时,会藉由该link关系而呼叫到BB类别的Add()函数。这是系统分析师的基本思维,程序员在撰写Add100ToBB()函数的指令时,也运用相同的思维,心心相印,所以顺畅而直截了当。
如果以stateless类别来表达图
6里的AA类别,就有所思维上的落差了。因为AA改变成为stateless了,每次呼叫AA对象的函数之后,该对象就可能会换新的个体(instance)。SetLinkToBB()函数的指令
----- Set
bObj = obj 设定了bObj之后,bObj的值就被丢弃了,徒劳无功!所以SetLinkToBB()函数是无意义的。
当Form呼叫Add100ToBB()时,可能会是一个新的对象个体,bObj里并没有值,表示AA对象与BB对象之间的尚无link关系,必须重新建立。这与OOAD分析师的基本想法不合。
请看看stateless程序的写法: ‘AA class ----stateless object Implements ObjectControl Private bObj As BB Private objCtx As ObjectContext Private Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled = True End Function Private Sub ObjectControl_Activate() Set objCtx = GetObjectContext() End Sub Private Sub ObjectControl_Deactivate() Set objCtx = Nothing End Sub Private Sub Add100ToBB( obj As BB ) Set bObj = obj bObj.Add( 100 ) objCtx.SetComplete End Sub Public Function queryBB(obj As BB) As Integer Set bObj = obj queryBB = bObj.query() objCtx.SetComplete End Function ‘------------------------------------------------------- ‘BB class ----stateful Private x As Integer Private Sub Class_Initialize() x = 1800 End Sub Private Sub Add( y As Integer ) x = x + y End Sub Private Function query() As Integer query = x End Function ‘-------------------------------------------------------- ‘Form1 Private a As AA Private b As BB Private Sub Form_Load() Set a = CreateObject(“AA”) Set b = CreateObject(“BB”) a.Add100ToBB(b) bbBtn.Text = a.queryBB(b) End Sub
此程序在心理层面改变多,但程序指令的改变不多。如果把BB类别也改为stateless的话,这程序指令的改变就会相当大了。这会大大阻碍系统从系统需求到程序的侦察追踪(trace)的效率,使得系统的维护成本大幅升高!于是,您可知到: l OOAD分析师所分析出来的企业对象(business
objects)之间常有稳定的关系。 l
如果将这些企业对象落实到stateless对象上,stateless对象之间非常难以落实OOAD所得到的企业对象关系。如果一定得落实到stateless对象上,则只能藉助于DB里的表格(table)之间的关系了。 在此模式¼里,建议您尽量将企业对象落实到stateful对象上,而且关系也落实到stateful对象之link关系上。如下图7所示。
图7、stateful企业对象之关系
这采取图5的做法:只有一个persist对象,将queryAcc()及
queryCust()两个函数定义在此对象的类别里。
上图7的类别定义如下: 'Ax2c-1 'ACPersist class 'Stateless '各App共享类别 'COM+
/ MTS version 'Requires
Transaction Implements
ObjectControl Private
objCtx As ObjectContext Private
adoConn As ADODB.Connection Private
adoRS As ADODB.Recordset Private
Const strConn = "Provider=SQLOLEDB; Data Source=Bill_Lin; Initial
Catalog=BankDB; User Id=sa; Password=" Private
strSQL As String Private
Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled
= True End
Function Private
Sub ObjectControl_Activate() Set objCtx = GetObjectContext() Set adoConn = CreateObject("ADODB.Connection") adoConn.Open (strConn) End
Sub Private
Sub ObjectControl_Deactivate() Set objCtx = Nothing Set adoConn = Nothing End
Sub Public
Function queryAcc(aid
As String, acc As Account) As Boolean On Error GoTo ErrorHandler Dim accId As String Dim custId As String Dim b As Double '从DB读取目前余额 strSQL = "SELECT * FROM AccountTable
Where id = '" + aid + "'" Set adoRS = adoConn.Execute(strSQL) accId = adoRS.Fields("id").Value custId = adoRS.Fields("cid").Value b = adoRS.Fields("Balance").Value Call acc.SetData(accId, custId,
b) Query = True objCtx.SetComplete Set adoRS = Nothing Exit Function ErrorHandler: Query = False objCtx.SetAbort Set adoRS = Nothing End
Function Public
Function queryCust(cid
As String, custObj As Customer) As Boolean Dim custId As String Dim custNa As String On Error GoTo ErrorHandler '从DB读取目前余额 strSQL = "SELECT * FROM CustomerTable
Where id = '" + cid + "'" Set adoRS = adoConn.Execute(strSQL) custId = adoRS.Fields("id").Value custNa = adoRS.Fields("name").Value Call custObj.SetData(custId,
custNa) Query = True objCtx.SetComplete Set adoRS = Nothing Exit Function ErrorHandler: Query = False objCtx.SetAbort Set adoRS = Nothing End
Function 'End
ACPersist class queryAcc()读取Account表格里的资料。queryCust()读取Customer表格里的资料。由于此类别为stateless,负责执行queryAcc()的对象个体可能跟负责执行queryCust()的对象个体是不相同的。
接着定义Account和Customer类别如下: 'Ax2c-2 'Account class '各App共享类别 'Stateful Private
accId As String Private
custId As String Private
b As Double Private
custObj As Customer Public
Sub SetCustLink(obj As Customer) Set custObj = obj End
Sub Public
Function GetCustName() As String GetCustName = custObj.GetName() End
Function Public
Sub SetData(aid As String, cid As String, m As Double) accId = aid custId = cid b = m End
Sub Public
Function GetBalance() As Double GetBalance = b End
Function Public
Function changeToUSD() As Double changeToUSD = b / 31.6 End
Function Public
Function GetCustID() As String GetCustID = custId End
Function 'End
Account class '------------------------------------ 'Customer class '各App共享类别 'Stateful Private
custId As String Private
custName As String Private
accObj As Account Public
Sub SetAccLink(obj As Account) Set accObj = obj End
Sub Public
Function GetAccBalance() As Double GetAccBalance = accObj.GetBalance() End
Function Public
Sub SetData(id As String, na As String) custId = id custName = na End
Sub Public
Function GetName() As String GetName = custName End
Function 'End
Customer class Account的对象里有个custObj指针(reference)可指向Customer的对象。Customer的对象里有个accObj指针可指向Account的对象。Account对象的GetCustName()会呼叫custObj所指向的Customer对象的GetName()函数。同样地,
Customer对象的GetAccBalance()会呼叫accObj所指向的Account对象的GetBalance()函数。
这意味着:Account对象和Customer对象就像好朋友一般,互相有电话联络,当Form程序向Account对象问到:您的主人是谁?这
Account对象自己会打电话向Customer对象询问其名字。此刻Customer对象就成为Account对象的秘书,运用其智能来替Account对象服务,让Account对象更有智能,并能做更多的服务。
同理,当Form程序向Customer对象问到:您的帐户余额多少?这
Customer对象会打电话向Account对象询问其金额。此刻Account对象反过来成为Customer对象的秘书,运用其智能来替Customer对象服务,让Customer对象更有智能,并能做更多的服务。
兹写个client程序如下: 'Ax2f 'Form 'Set
Reference to Ax2c-1 & Ax2c-2 Private
acc As Account Private
cust As Customer Private
acp As ACPersist Private
Sub Form_Load() Set acc = CreateObject("aATMex2a.Account") Set cust = CreateObject("aATMex2a.Customer") Call acc.SetCustLink(cust) Call cust.SetAccLink(acc) End
Sub Private
Sub Form_Unload(Cancel As Integer) Set acc = Nothing Set cust = Nothing End
Sub Private
Sub Query_Click() Dim cid As String Set acp = CreateObject("aATMex2a.ACPersist") '读取AccountTable Call acp.queryAcc("aid008",
acc) cid = acc.GetCustID() '读取CustomerTable Call acp.queryCust(cid, cust) Set acp = Nothing '读取Set
GUI objects AccIdBtn.Text = cid BalanceBtn.Text = cust.GetAccBalance() USDBtn.Text = acc.changeToUSD() CustNameBtn.Text = acc.GetCustName() End
Sub
从这个例子中,相信您已经了解到stateful对象在此模式里的角色和用途了。前面已经说明过,各人可能有不同的个角度来看待这stateful对象。 s
有些人认为:其角色接近MTS的SPM
(shared property manager),能管理一些暂存的资料,让有些资料能保留在中间层,留待client程序送来下一个讯息时可以使用,一方面节省数据传输的时间,一方面增强与client程序的互动性。如果系统简单不复杂的话,直接使用SPM就足够了。 s
有些人认为:其角色相当于Form的秘书对象,让Form里的一些表达式能管理得较有条理。如果系统简单不复杂的话,直接把表达式写在Form对象里即行了。 s
有些人认为:OOAD的结果很重要,类别图里的类别及其关系将会随着不断成长,即使目前系统简单,也应该以stateful对象表达,未来系统才能稳定成长。 以上是见仁见智之事,希望您能在适当的场合采取适当的看法。 模式¼的特性──
优点:能完整落实UML类别图里的对象和关系。
缺点:Account及Customer等企业对象皆由Form诞生并建立两者之间的关系,Form含有复杂的控制逻辑。
如果您很在意上述的缺点的话,可考虑使用下面的新模式½。 进阶模式½ ●支持OnDemand观念
在上一个模式¼里,Account对象和Customer对象皆是由client所诞生的,诞生完毕才将两者连结(link)起来。并且由client诞生并呼叫persist对象去DB读取资料。之后client才呼叫Account对象的GetCustName()函数。 整个诞生的任务皆归于client,则client会含有大量的控制逻辑(control
logic)。然而在N-tier架构,以及thin-client的潮流里,client程序应该简化,将一些控制逻辑授权给能胜任的企业对象,会更合乎N-tier架构的需求。
于此将从模式¼修正而衍生出另一种新模式½。在此新模式里,由Account对象诞生有关的Customer对象,并且诞生又呼叫persist对象读取DB里的Customer资料。让client程序可轻快纳凉一些!如下图8所示。
图8、OnDemand模式
当client呼叫OdmCustomer()时,client的意思是:请您自己找出有关的Customer对象,然后建立link关系。Account对象就显得很有智能,它知道先诞生Customer对象,也诞生CustPersist对象,然后将CustID传给CustPersist对象请它到DB读取顾客资料,回传到Customer对象里。这整个过程都不用client来操心,而全部委托Account对象去做了。
如果您是在规画N-tier系统,把Acoount和Customer对象摆在中间层Server里,这个模式很合乎N-tier架构的精神,让企业逻辑(business
logic)摆在中间层,统一管理,集中维护。而且您可以想象:如果Account类别是向外购买来的,而您买到的是采用模式½的Account类别,会觉得比采用模式¼的Account类别要划算多了,因为聪明多了。所以蛮合乎组件式(component-based)软件的精神。
如果您重视网络企业(net-business)管理,则Account对象就像企业系统里的知识工作者(knowledge
worker),它很有智能,知道去寻找别的知识工作者(如Customer对象)的帮忙,分享别人的智能。所以合乎网络经济社会的精神。
现在就看看图8例子里的persist类别之定义: 'Ax3c-1 'AccPersist class 'Stateless 'COM+
/ MTS version 'Requires
Transaction Implements
ObjectControl Private
accId As String Private
custId As String Private
b As Double Private
objCtx As ObjectContext Private
adoConn As ADODB.Connection Private
adoRS As ADODB.Recordset Private
Const strConn = "Provider=SQLOLEDB; Data Source=Bill_Lin; Initial
Catalog=BankDB; User Id=sa; Password=" Private
strSQL As String Private
Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled
= True End
Function Private
Sub ObjectControl_Activate() Set objCtx = GetObjectContext() Set adoConn = CreateObject("ADODB.Connection") adoConn.Open (strConn) End
Sub Private
Sub ObjectControl_Deactivate() Set objCtx = Nothing Set adoConn = Nothing End
Sub Public
Function Query(aid As
String, accx As Account) As Boolean On Error GoTo ErrorHandler '从DB读取目前余额 strSQL = "SELECT * FROM AccountTable
Where id = '" + aid + "'" Set adoRS = adoConn.Execute(strSQL) accId = adoRS.Fields("id").Value custId = adoRS.Fields("cid").Value b = adoRS.Fields("Balance").Value Call accx.SetData(accId, custId,
b) Query = True objCtx.SetComplete Set adoRS = Nothing Exit Function ErrorHandler: Query = False objCtx.SetAbort Set adoRS = Nothing End
Function 'End
AccPersist class '--------------------------------------- 'CustPersist class 'Stateless 'COM+
/ MTS version 'Requires
Transaction Implements
ObjectControl Private
custId As String Private
custNa As String Private
objCtx As ObjectContext Private
adoConn As ADODB.Connection Private
adoRS As ADODB.Recordset Private
Const strConn = "Provider=SQLOLEDB; Data Source=Bill_Lin; Initial
Catalog=BankDB; User Id=sa; Password=" Private
strSQL As String Private
Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled
= True End
Function Private
Sub ObjectControl_Activate() Set objCtx = GetObjectContext() Set adoConn = CreateObject("ADODB.Connection") adoConn.Open (strConn) End
Sub Private
Sub ObjectControl_Deactivate() Set objCtx = Nothing Set adoConn = Nothing End
Sub Public
Function Query(cid As
String, custObj As Customer) As Boolean On Error GoTo ErrorHandler '从DB读取目前余额 strSQL = "SELECT * FROM CustomerTable
where id = '" + cid + "'" Set adoRS = adoConn.Execute(strSQL) custId = adoRS.Fields("id").Value custNa = adoRS.Fields("name").Value Call custObj.SetData(custId,
custNa) Query = True objCtx.SetComplete Set adoRS = Nothing Exit Function ErrorHandler: Query = False objCtx.SetAbort Set adoRS = Nothing End
Function 'End
CustPersist class
这两个persist类别跟前面模式¼是一样的,主要的差异是在于下面的stateful类别,如下: 'Ex3c-2 'Account class '各App共享类别 'Stateful Private
accId As String Private
custId As String Private
b As Double Private
custObj As Customer Public
Sub SetCustLink(obj As Customer) Set custObj = obj End
Sub Public
Function OdmCustmer() As Customer Set custObj = CreateObject("aATMex3a.Customer") Dim custp As CustPersist '读取CustomerTable 'Initialize custObj Set custp = CreateObject("aATMex3a.CustPersist") Call custp.Query(custId, custObj) Set custp = Nothing Set OdmCustmer = custObj End
Function Public
Function GetCustName() As String GetCustName = custObj.GetName() End
Function Public
Sub SetData(aid As String, cid As String, m As Double) accId = aid custId = cid b = m End
Sub Public
Function GetBalance() As Double GetBalance = b End
Function Public
Function changeToUSD() As Double changeToUSD = b / 31.6 End
Function Public
Function GetCustID() As String GetCustID = custId End
Function 'End
Account class '---------------------------------------- 'Customer class '各App共享类别 'Stateful Private
custId As String Private
custName As String Private
accObj As Account Public
Sub SetAccLink(obj As Account) Set accObj = obj End
Sub Public
Function GetAccBalance() As Double GetAccBalance = accObj.GetBalance() End
Function Public
Sub SetData(id As String, na As String) custId = id custName = na End
Sub Public
Function GetName() As String GetName = custName End
Function 'End
Customer class Account类别里含有CustId值,将此值传给CustPersist对象,这persist对象就以此值为key到Customer表格里读取顾客资料。然后存入新诞生的Customer对象里。于是Customer对象就有其内容了,也跟Account对象建立出关系,两个对象就可以互相沟通合作了。因为能合作,使得两者皆显得更有智能。
兹写个client程序: 'Ex3f 'Form 'Set
Reference to Ax3c-1 & Ax3c-2 Private
acc As Account Private
accp As AccPersist Private
cust As Customer Private
Sub Form_Load() Set acc = CreateObject("aATMex3a.Account") End
Sub Private
Sub Form_Unload(Cancel As Integer) Set acc = Nothing End
Sub Private
Sub Query_Click() Dim cid As String '读取AccountTable Set accp = CreateObject("aATMex3a.AccPersist") Call accp.Query("aid008",
acc) Set accp = Nothing '取得Customer对象 Set cust = acc.OdmCustmer() '读取Set
GUI objects AccIdBtn.Text = acc.GetCustID CustNameBtn.Text = acc.GetCustName() BalanceBtn.Text = acc.GetBalance() USDBtn.Text = acc.changeToUSD() Set cust = Nothing End
Sub
首先由Form诞生Account对象及AccPersist对象,并送query()讯息给AccPersist对象,
请它读取DB里的资料,送到Account对象里。
图9、OdmCustomer()的前置动作 Form就删除掉AccPersist对象,然后送OdmCustomer()讯息给Account对象。Account对象就诞生Customer对象及CustPersist对象,并送query()讯息给CustPersist对象,
请它读取DB里的资料,送到Customer对象里。
图10、OdmCustomer()的内涵 CustPersist对象的任务完成了,Form就删除掉CustPersist对象,但是不删除掉Customer对象,然后把
Customer对象的指针传回给Form,如下图11。
图11、OdmCustomer()的结尾 Form拥有Account及Customer对象的指针,Form就可以送GetCustName()讯息给Account对象。此时Account对象会送GetName()讯息给Customer对象请求它的帮忙。如下图12所示。
图12、Account请求Customer的帮忙 Form拥有Customer对象的指针,Form也能直接送讯息给Customer对象。
图13、Form也能跟Customer沟通
这模式½的基本构想是:
在Account对象需要Customer对象帮忙的时刻
Customer才出现,帮忙完毕又可以离开。就像及时雨(on
demand)一般。 模式½的特性──
优点:可以由企业对象自己诞生与其有关系的对象并自动建立关系,并取的它们的合作。
缺点:由于企业对象多了一些控制逻辑,使得企业对象变得比较复杂。 以上的模式»~½皆是从基本模式¹衍生出来的,其中并没有使用到交易(transaction)对象。如何藉由交易对象来执行two-phase
commit,以确保不同表格或数据库的一致性和完整性,以上的模式»~½皆尚未考虑到。这就是所谓的分布式交易的问题。以下的新模式¾就提供您一些有用的建议。 进阶模式¾ ●支持分布式交易(distributed
transaction)
在本期的「N-tier架构的基本模式」文章里,介绍了5个基本模式。其中的模式º就含有交易对象,藉由交易对象来指挥persist对象读取DB里的资料。这个交易对象也能同时指挥数个persist对象,以存取数个表格或数个DB里的资料。于是就扩充模式º而衍生出新模式¾。
兹回忆基本模式º的结构如下图14:
图14、基本模式º的结构 此模式包含3种对象: s有个stateful的对象含有企业营运规则(business
rules),可称为business对象。 s
有个stateless对象含有SQL指令负责存取数据库,可称为persist对象。 s
另外有个stateless对象担任交易的主要发动者,可称为transaction对象。
于此,就跟您一起来进行一趟模式的创作之旅,将模式º继续延伸出而创造出更实用的模式。交易对象担任persist对象的指挥官,可指挥数个persist对象,将企业对象的状态值储存到数个数据库里。并且由交易对象激活交易,而persist对象则支持交易,负责与资源管理者(resource
manager) ---- 即DBMS
---- 沟通。如下图15所示。
如果数据库是分散于不同的server上,则persist对象也会跟随数据库而分散到不同的server上。如果不同server上的DBMS并不相同,就成为分散的异质性数据库,此时各persist对象负责与不同的DBMS沟通,激活DBMS的交易机制。
图15、分布式的交易
其中,交易对象激活交易,交易常分为数个子交易(sub-transaction),这交易对象呼叫各Server上的persist对象去执行子交易,persist对象则激活当地(local)的DBMS的交易,执行资料的存取工作。
而COM+
/ MTS就会根据交易对象及各地的persist对象的执行报告,进行TPC
(two-phase commit)协调工作,然后指挥各地的DBMS完整而一致地完成各数据库的存取工作。 AccPrsist对象负责存取数据库里的Account表格﹐CustPersist负责存取Customer表格。而UcTrans类别可以指挥AccPersist及CustPersist等对象存取分散的数据库。
尽量由UcTrans对象来统一激活分布式交易,将有助于降低此软件系统未来的维护工作。现在请您看看下图16的例子吧!
图16、分布式的交易之例
如果您的系统拥有分布式数据库,TPC观念常会扮演很吃重的角色。它是确保分布式(也可能是异质性)数据库稳定可靠的不二法宝。一个企业交易(例如银行转帐)常跨越不同地点的server上的许多不同的对象(如persist对象),当然也涉及不同的DBMS。在这幺复杂的整合环境里,程序员所最能依赖的就是交易(transaction)机制了。
交易既像「圣旨」又像「指挥棒」,是指挥众多server、对象、DBMS、表格等组件的最强有力的工具。server、对象、DBMS、表格等就像交响乐团的团员乐器,交易就像乐团指挥棒,使用得漂亮的话,能确保乐团的和谐美妙。
这个模式¾建议设定一个「指挥者」,实际拿着指挥棒取指挥乐团的演奏。它是一个stateless的对象,如下述程序里的UcTrans类别。
现在写个程序来落实上图16的例子,首先定义persist类别如下: 'Ax4c-1 'AccPersist class 'Stateless 'COM+
/ MTS version 'Requires
Transaction Implements
ObjectControl Private
AccID As String Private
CustId As String Private
b As Double Private
objCtx As ObjectContext Private
adoConn As ADODB.Connection Private
adoRS As ADODB.Recordset Private
Const strConn = "Provider=SQLOLEDB; Data Source=Bill_Lin; Initial
Catalog=BankDB; User Id=sa; Password=" Private
strSQL As String Private
Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled
= True End
Function Private
Sub ObjectControl_Activate() Set objCtx = GetObjectContext() Set adoConn = CreateObject("ADODB.Connection") adoConn.Open (strConn) End
Sub Private
Sub ObjectControl_Deactivate() Set objCtx = Nothing Set adoConn = Nothing End
Sub Public
Function insertDB(acc
As Account) As Boolean On Error GoTo ErrorHandler ' 新增AccountTable记录 strSQL = "INSERT AccountTable
(id, cid, Balance) Values ('" strSQL = strSQL + acc.AccID
+ "','" + acc.CustId + "','" + CStr(acc.Balance) + "')" Call adoConn.Execute(strSQL) objCtx.SetComplete insertDB = True Exit Function ErrorHandler: objCtx.SetAbort insertDB = False End
Function 'End AccPersist
class '------------------------------------- 'CustPersist class 'Stateless 'COM+
/ MTS version 'Requires
Transaction Implements
ObjectControl Private
CustId As String Private
custNa As String Private
objCtx As ObjectContext Private
adoConn As ADODB.Connection Private
adoRS As ADODB.Recordset Private
Const strConn = "Provider=SQLOLEDB; Data Source=Bill_Lin; Initial
Catalog=BankDB; User Id=sa; Password=" Private
strSQL As String Private
Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled
= True End
Function Private
Sub ObjectControl_Activate() Set objCtx = GetObjectContext() Set adoConn = CreateObject("ADODB.Connection") adoConn.Open (strConn) End
Sub Private
Sub ObjectControl_Deactivate() Set objCtx = Nothing Set adoConn = Nothing End
Sub Public
Function insertDB(cust
As Customer) As Boolean On Error GoTo ErrorHandler ' 新增CustomerTable记录 strSQL = "INSERT CustomerTable
(id, name ) Values ('" + cust.ID + "','" + cust.Name + "')" Call adoConn.Execute(strSQL) objCtx.SetComplete insertDB = True Exit Function ErrorHandler: objCtx.SetAbort insertDB = False End
Function 'End
CustPersist class AccPersist对象负责将资料存入Account表格里,CustPersist对象负责将资料存入Customer表格里。
而这些资料是来自stateful的Account对象及Customer对象里。如下述的企业对象类别之定义如下: 'Ex4c-2 'Account class '各App共享类别 'Stateful Dim
mAccID As String Dim
mCustId As String Dim
b As Double Public
Sub SetData(aid As String, cid As String, m As Double) mAccID = aid mCustId = cid b = m End
Sub Public
Property Get AccID() As String AccID = mAccID End
Property Public
Property Get Balance() As Double Balance = b End
Property Public
Function changeToUSD() As Double changeToUSD = b / 31.6 End
Function Public
Property Get CustId() As String CustId = mCustId End
Property 'End
Account class '------------------------------------ 'Customer class '各App共享类别 'Stateful Private
CustId As String Private
custName As String Public
Sub SetData(ID As String, na As String) CustId = ID custName = na End
Sub Public
Property Get ID() As String ID = CustId End
Property Public
Property Get Name() As String Name = custName End
Property 'End
Customer class
在定义好persist对象及企业对象之后,就来定义交易指挥中心
---- UcTrans对象之类别定义,如下: 'Ex4c-3 'UcTrans class 'Stateless 'COM+
/ MTS version 'Requires
new transaction Implements
ObjectControl Private
objCtx As ObjectContext Private
Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled
= True End
Function Private
Sub ObjectControl_Activate() Set objCtx = GetObjectContext() End
Sub Private
Sub ObjectControl_Deactivate() Set objCtx = Nothing End
Sub Public
Sub insertDBTrans(acc
As Account,
cust As Customer) '激活一个新交易 Dim accp As AccPersist Set accp = CreateObject("aATMex4a.AccPersist") If accp.insertDB(acc) = False
Then objCtx.SetAbort Exit
Sub End If Dim custp As CustPersist Set custp = CreateObject("aATMex4a.CustPersist") If custp.insertDB(cust) =
False Then objCtx.SetAbort Exit
Sub End If objCtx.SetComplete End
Sub 'End
UcTrans class
当UcTrans对象执行insertDBTrans()时,由UcTrans对象激活一个交易。此交易分为两个小交易,分别由AccPersist对象及CustPersist对象负责执行。当两个小交易皆圆满成功时,整个交易才完成。假如有任何一个小交易没有成功,则必须整个交易皆回复(roll-back)到原来的状态。这种数据库完整性的维持是透过COM+
/ MTS的TPC机制而达成的。
兹写个client程序如下: 'Ex4f 'Form 'Set
Reference to Ax4c-1,Ax4c-2 & Ax4c-3 Private
acc As Account Private
cust As Customer Private
Sub Form_Load() Set acc = CreateObject("aATMex4a.Account") Set cust = CreateObject("aATMex4a.Customer") End
Sub Private
Sub InertDB_Click() Call acc.SetData(AccIdBtn.Text,
CustIdBtn.Text, BalanceBtn.Text) Call cust.SetData(CustIdBtn.Text,
CustNameBtn.Text) Dim uct As UcTrans Set uct = CreateObject("aATMex4a.UcTrans") '进行TPC Call uct.insertDBTrans(acc, cust) End
Sub Private
Sub Form_Unload(Cancel As Integer) Set acc = Nothing Set cust = Nothing End
Sub 这个模式¾建议:交易起头(root
of transaction)尽量摆在UcTrans对象里,系统的维护工作会减轻许多。当然这只是个建议与指引而已,并非硬性规定。希望程序员在安排交易的起头时,能优先考虑摆在UcTrans里,将会是N-tier架构里的一个及美好的风范(style)。对于系统的美感及未来的维护性皆有极大的贡献。 模式¾的特性──
优点:交易对象配合COM+
/ MTS的TPC机制,进行分布式交易之处理。
缺点:由Form诞生企业对象及交易对象,使得Form程序变得复杂了。
目前,Form程序的简化问题尚未有效解决。下述的新模式¿提供个可行的方案。 进阶模式¿ ●
建立企业流程(business
process)对象
这个模式是从基本模式¹而衍生而来的。回忆模式¹的基本结构,也回忆本文前面的图2,兹重复之如下图17:
图17、基本模式¹的结构
如果您规画的是N-tier结构,而且将Account等stateful对象摆在中间层的Server上,则Form跟Account对象或AccPersist对象之间的沟通经常是跨计算机的,甚至是跨越WAN
/ Internet网络的。
此时,为了降低网络沟通的频率,client端的Form应该设有一位「大使」派驻在中间层的Server上,就近跟Account及AccPersist等对象保持原来的频繁沟通。至于Form跟这位大使之间的沟通将视网络特性、使用者对Form的需求、企业流程控制的需要、网络安全考量等等因素的变化而弹性调整。于是得出新模式的结构如下:
图18、新模式¿之结构
关于Uco对象的用途,也请您参阅本期杂志的「Web-based系统之架构、开发与管理」一文,有很详细的说明。此模式¿把Form里的GUI互动部份留在client端的Form里,而把跟企业对象及persist对象的互动部份独立出来成为Uco对象,而且安置于遥远的server上。
因而Uco对象有蛮高的重复使用性(reuasblity),能供不同的Form使用。例如,有个Uco对象能负责调度Account及AccPersist对象进行「提款交易」,则此Uco能够提供服务给不同的client
端,如ATM提款机画面、分行柜台teller使用的画面、顾客家中上网的计算机画面等等,如下图19所示。
图19、可重复使用(reuse)的Uco对象
有时候,一个Form也会使用到多个Uco对象。例如,Uco1类别的对象担任「提款」工作,Uco2类别的对象担任「存款」工作。而这两个Uco类别皆共享Account及AccPersist类别,但是不共享Account及AccPersist的对象个体,如下图20所示:
图20、一个Form用到多个Uco对象
如何看待Uco对象呢?这是随着各人从不同的观点(view)而有所不同。 s
有人把它视为企业对象的首领,领导与指挥一群企业对象,还包括指挥transaction对象,然后transaction对象指挥persist对象。 s
有人把它看成Form的代理者(agent),就像驻在外国的大使一般。 s
如果您采取OOAD方法分析您的系统,则您会将之对应到use
case上,每个use
case会有一个Uco对象代表之。 s
如果您很熟悉Gamma的Design
Patterns的话,则您可以将之对应到Facade
模式
(pattern)上,Uco对象成为中间层对象与Form沟通的接口对象(facade)。 您可以选择您认为最合理而实用的观点。接下来,请看个例子,如下图21所示:
图21、一个Form用到多个Uco对象
从上图21里,可看到两个
Uco对象时来自于不同的类别:一个是Uco1类别的对象,另一个是Uco2类别的对象。但是也看到了两个Account对象是来自同一个类别,而两个AccPersist对象也来自同一个类别。
为什幺Uco1和Uco2不合并起来成为一个类别呢?为什幺Account类别不分开为Account1及Account2两个类别呢?为什幺AccPersist类别不分开成为两个类别呢?
这是个常见、有趣而严肃的问题!如果分得好、分得漂亮,系统将会结构清晰,有弹性而易维护。反之,如果分得不妥善,系统将可能日益杂乱,不意增修维护和成长。这也是OOAD的崇高价值所在。
如果您依循OOAD过程,所得出的UML的
Use Case图,将会告诉您必须定义多少个Uco类别。同时,UML的Class图会告诉您应该定义多少个stateful的企业对象。而从数据库的ER表格模式(ER
model),可提示您应该定义多少persist对象。
换句话说,如果您很讲究上述这些的话,表示您蛮讲究系统架构,则OOAD及UML会是您的极佳利器。
请看看如何写程序来落实图21的例子: 'Ex5c-1 'AccPersist class 'Stateless 'COM+
/ MTS version 'Requires
Transaction Implements
ObjectControl Private
AccID As String Private
CustID As String Private
b As Double Private
objCtx As ObjectContext Private
adoConn As ADODB.Connection Private
adoRS As ADODB.Recordset Private
Const strConn = "Provider=SQLOLEDB; Data Source=Bill_Lin; Initial
Catalog=BankDB; User Id=sa; Password=" Private
strSQL As String Private
Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled
= True End
Function Private
Sub ObjectControl_Activate() Set objCtx = GetObjectContext() Set adoConn = CreateObject("ADODB.Connection") adoConn.Open (strConn) End
Sub Private
Sub ObjectControl_Deactivate() Set objCtx = Nothing Set adoConn = Nothing End
Sub Public
Function insertDB(acc As Account) As Boolean On Error GoTo ErrorHandler ' 新增AccountTable记录 strSQL = "INSERT AccountTable
(id, cid, Balance) Values ('" strSQL = strSQL + acc.AccID
+ "','" + acc.CustID + "','" + CStr(acc.Balance) + "')" Call adoConn.Execute(strSQL) objCtx.SetComplete insertDB = True Exit Function ErrorHandler: objCtx.SetAbort insertDB = False End
Function Public
Function query(aid As String, acc As Account) As Boolean On Error GoTo ErrorHandler '从DB读取目前余额 strSQL = "SELECT * FROM AccountTable
Where id = '" + aid + "'" Set adoRS = adoConn.Execute(strSQL) AccID = adoRS.Fields("id").Value CustID = adoRS.Fields("cid").Value b = adoRS.Fields("Balance").Value Call acc.SetData(AccID, CustID,
b) query = True objCtx.SetComplete Set adoRS = Nothing Exit Function ErrorHandler: query = False objCtx.SetAbort Set adoRS = Nothing End
Function 'End
AccPersist class
这个AccPersist类别定义了insertDB()及query()两个函数,担任存取DB资料的任务。接下来,请您看看企业对象之类别定义如下: 'Ex5c-2 'Account class '各App共享类别 'Stateful Private
mAccID As String Private
mCustID As String Private
b As Double Public
Sub SetData(aid As String, cid As String, m As Double) mAccID = aid mCustID = cid b = m End
Sub Public
Property Get AccID() As String AccID = mAccID End
Property Public
Property Get Balance() As Double Balance = b End
Property Public
Property Get CustID() As String CustID = mCustID End
Property 'End
Account class 这里的Account类别只是诞生对象来暂存资料之用。现再看看Uco1类别及Uco2类别之定义吧! 'Ex5c-3 'Uco1 class '各App「不」共享之类别 'Stateful Private
acc As Account Private
Sub Class_Initialize() Set acc = CreateObject("aATMex5a.Account") End
Sub Public
Function queryBalance(aid As String) As Double Dim accp As AccPersist Set accp = CreateObject("aATMex5a.AccPersist") Call accp.query(aid, acc) Set accp = Nothing queryBalance = acc.Balance End
Function Private
Sub Class_Terminate() Set acc = Nothing End
Sub 'End
Uco1 class
还记得吗?queryBalance()里的指令原来是摆在Form里面,此时被移到Uco1对象里。Form只要呼叫这queryBalance()即行了,一方面让Form较简化了,另一方面则降低client端跟中间层server的数据沟通频率。至于Uco2对象的用途也是相同的,如下之类别定义: 'Ex5c-4 'Uco2 class
'各App「不」共享之类别 'Stateful Private
acc As Account Private
Sub Class_Initialize() Set acc = CreateObject("aATMex5a.Account") End
Sub Public
Function AddAcc(aid As String, cid As String, m As Double) Call acc.SetData(aid, cid,
m) Dim accp As AccPersist Set accp = CreateObject("aATMex5a.AccPersist") Call accp.insertDB(acc) Set accp = Nothing End
Function Private
Sub Class_Terminate() Set acc = Nothing End
Sub
兹写个Form程序如下: 'Ex5f-1 'Form 'Set
Reference to Ex5c-1, Ex5c-2,
Ax5c-3 & Ax5c-4 Private
u1 As Uco1 Private
u2 As Uco2 Private
Sub Command2_Click() Form2.Show 1 End
Sub Private
Sub Form_Load() Set u1 = CreateObject("aATMex5a.Uco1") Set u2 = CreateObject("aATMex5a.Uco2") End
Sub Private
Sub Start_Click() Dim b As Double b = u1.queryBalance("aid008") Call u2.AddAcc("aid777", "cid1000",
b) End
Sub Private
Sub Form_Unload(Cancel As Integer) Set u1 = Nothing Set u2 = Nothing End
Sub Form诞生时,就诞生Uco1对象及Uco2对象。当Uco1对象接到queryBalance()讯息时,就诞生一个Account对象,也诞生一个persist对象,读取DB资料暂存于Account对象里。此时就删除了AccPersist对象。但是保留着Account对象。再来Uco1的queryBalance()呼叫Account对象的Get
Balance()取得帐户余额的值,传回到Form。
接着Uco2对象接到AddAcc()讯息时,就诞生一个Account对象,并将新的帐户资料存入Account对象里,也诞生一个persist对象,将Account对象里的资料存入DB的表格里。 此时就删除了AccPersist对象。但是保留着Account对象。目前在中间层server的内存里,共有两的Account的对象,有一个Uco1对象,有一个Uco2对象。
最后Form删除掉Uco1对象,此时Uco1先删除掉Account对象。Form也接着删除掉Uco2及其幕后的Account对象。
如果您希望节省内存空间的话,可先删除掉Uco1对象及其幕后的Account对象。如下述的Form程序: 'Ex5f-2 'Form 'Set
Reference to Ex5c-1, Ex5c-2,
Ax5c-3 & Ax5c-4 Private
u1 As Uco1 Private
u2 As Uco2 Private
Sub Start_Click() Dim b As Double Set u1 = CreateObject("aATMex5a.Uco1") b = u1.queryBalance("aid008") Set u1 = Nothing Set u2 = CreateObject("aATMex5a.Uco2") Call u2.AddAcc("aid777", "cid1000",
b) Set u2 = Nothing End
Sub
因为Uco1及Account皆为stateful对象,如果不立即删除,COM+
/ MTS不会像对待stateless对象般自动将之删除的。所以此程序用完了Uco1就删除之,然后才诞生Uco2对象,用完了Uco2就删除之,让Uco1、Uco2及Account对象占用最少的内存空间。提升了中间层Server资源的运用效率。 模式¿的特性──
优点:简化的client程序,与thin-client的潮流相结合。
缺点:又多出一种对象----
Uco,对于不熟悉组件式系统架构者而言,又徒增一些不习惯的事情。 俗语说:习惯成自然。希望您能逐渐熟悉N-tier、组件式、分散交易等系统之思维及技术,一日复一日,一旦旧习惯已经新陈代谢,换成习惯之后,就会驾轻就熟了。自然而然就养成新习惯了。 结语
本文从模式¹及º衍生出进阶模式»~¿,如图22所示。
图22、从基本模式¹及º衍生出进阶模式
本文只是举例说明如何从基本模式「衍生」出较适合N-tier架构,也较能落实OOAD分析结果的应用模式。别忘了,本文的目的并不在于这些模式本身,而在于举例说明「衍生」的思考过程,培养您自己的衍生兴趣和能力。希望您能熟悉「衍生」的思维和技巧,能继续基于模式¶~¿而衍生出更多花样、更实用的新模式,然后与别人共享之。俗语说:独乐乐,不如众乐乐。希望您也能乐于与别人分享。
例如,国内有数家软件公司,就将上述模式¿里的Uco对象由原来的stateful对象,更改为stateless对象,其主要理由和条件是:clint端Web
page之间的共享资料并不需要存放在Uco对象内,Uco对象就不需要储存跨function的资料,所以Uco可定义为stateless对象。您觉得这个修正之后的新模式好不好呢?其实模式没有绝对好或不好,端看上述的背景条件(context)而定。如上述,依据您的特殊背景条件而修正模式,这个做法是正确的,然而您必须再仔细分析: ©
这个背景条件的稳定性如何? ©
是不是公司里的其它Web
Application都满足这个背景条件? ©
过了数个月,或数年之后,此背景调件是否仍然存在呢? ©
假如背景条件不再成立了,Uco对象必须改为stateful时,对其后面的Account等对象会影响多大?需要修改多少对象?容易修改吗? ©
背景条件不成立的机会如果很小,是否能够视为稀有的例外状况,而采用另一种模式特别处理之呢?新模式对系统的整体效率影响如何? 只要您的开发团队仔细考虑了这些问题之后,并取得共识就可以了。
再如,有许多团队将上述的模式¾和模式¿结合搭配,如下图23所示。
图23、新模式之结构(模式¾+¿) 这是个理想的模式,将对象分为四种角色:Uco控制流程;企业对象管理企业规则:交易对象指挥交易;persist对象负责管理分散的企业资源料(资料)。
其中,Uco对象及企业对象可视为支持client程序所需的信息,储存了跟client互动所需的资料。而交易对象及persist对象可视为负责管理企业资源(enterprise
resource),让分散的企业资源能做最有效率的运用。
在著名的书籍
----- Herzum & Sims 所着[Her2000]
的: “Business
Component Factory: A
Comprehensive Overview of Component-Based Development for the Enterprise” 就将组件式软件架构分为4个tier,如下图19所示。
图24、4-tier分层方式 如果进一步将上图23的新模式里的对象,依据
Herzum & Sims的分层方式加以区分,其建构出来的组件式软件结构就如下图25所示。
本文的重点在于介绍N-tier架构模式的基础观念与活用,也说明这些模式与Herzum
& Sims等组件式架构之间的基本关系。至于Herzum
& Sims的4-tier架构的细节,就不在本文的范围之内。谢谢您了。n 参考资料
[Misoo98] Misoo对象教室,
「如何实作N-tier
系统的企业对象」,物件导向杂志No.11,
1998. [Gamma95] Erich Gamma, Design Patterns:Elements of Reusable Object-Oriented
Software, Addition-Wesley, 1995. [Hillier99] Scot Hillier, MTS Programming with Visual Basic, SAMS,
1999. [Lilly00] Susan Lilly, “How to Avoid Use-Case Pitfalls”, Software Development, Jan 2000. [Geler98] David Gelernter, Machine Beauty:Elegance
and the Heart of Technology,
Basic Books, 1998. [Her2000] Peter Herzum and Oliver Sims,
Business Compnent Factory, Wiley, 2000. [Con2000] Jim Conallen, Building Web Applications with UML, Addition
Wesley, 2000.
图25、依据Herzum的4-tier结构将模式与对象加以分层
|
版权所有:UML软件工程组织 |