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) ----- AccountXDB表格只是担任辅佐persist对象,替persist对象保管其状态值(state)罢了。

    现在将企业对象改名为Account,就意味着以企业对象为重心,persist对象只是把企业对象的状态存入DB表格里而已。因此persist对象是反过来辅佐企业对象的。

   

 

进阶模式»

●同时使用多个stateful对象

 

    在上图2里﹐只有一个企业对象-----Account对象,此时您当然可以举一反三,而认知到:可以有很多个企业对象,而且都是stateful的对象。

    例如,要存取数据库里的另一个表格──Customer表格﹐则可以定义一个CustPersist类别协助存取Customer表格,并定义一个Customer类别来含纳企业规则。

 

3、模式» ----存取两个DB表格

 

    如果您很欣赏模式¹的话,应该会觉得此模式»的是很自然而美好的。反之,如果您很习惯于使用MTSSPM(shared property manager)的话,您可能会觉得多定义了AccountCustomer等类别,实在吃饱没事干的人才会如此做!

    此外,如果您很熟悉传统2-tier架构的话,您会觉得AccountCustomer等类别是多余的,这些类别里的企业规则(business rule)可以写在Form里就行了。

    因此,这个模式»的最大弱点是:大多数的人会觉得AccountCustomer等类别是多余的,而且增加程序设计者的限制,实在碍手碍脚的。然而有些人就能轻易看出AccountCustomer等类别的美妙角色:

s 对企业规则加以分门别类(classify)。让企业规则不再流离失所,难以管理。例如,将企业规则全部塞到Form里,则分散在各client端,就不易管理了。

s 也对不同DB表格的资料分门别类(classify)。让资料不再统统往SPM丢,让SPM成为垃圾堆。

 

下图4为模式»之例子。Form程序要求AccPersist对象读取Account表格的记录资料,然后不直接将资料送往Form,也不存到SPM里,而是送往Account对象里,暂存起来。


 

   4 模式»应用之例

 


    采取模式»,不但将企业规则分类,也对资料分类,并且分别而有系统的管理,企业规则与其所需要的资料紧密结合在一起,就像StatefulAccountCustomer类别了。

    假如您的系统小小的,则使用模式»是有点杀鸡用牛刀的味道。然而您的系统为3-tier架构且未来会成长到需用到数十个组件(component)的话,模式»的分类能力可确保系统在成长的过程中,一直保持企业规则的井然有序,易于管理,而不会随着系统的变大而复杂,变成无法驾驽的怪兽。因此,模式»能确保系统的稳定成长,永保青春。

    上图4里的client 程序Account对象取CustID值,然后依据CustID值要求CustPersist对象读取Customer表格的记录资料,不直接将资料送往Form,也不存到SPM里,而是送往Customer对象里,暂存起来。client程序再从Account对象取CustName值,

   现再看看上图4AccPersistCustPersist类别之定义如下:

 

'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的看AccountCustomer类别如下:

 

‘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对象。于此就来说明其中的理由,例如有个简单的类别图:

    6OOAD的类别图之例

 

    因为在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类别也11地有Add100ToBB()queryBB()两个函数。OOAD的系统分析师心中认为在AA对象和BB对象诞生时已经存在了它们之间的关系。所以在程序里,只需提供一个SetLinkToBB()Form在诞生AABB时可藉之建立关系。恰满足系统分析师的基本想法:就是在呼叫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所示。


 

 

    7stateful企业对象之关系

 


    这采取图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()的对象个体是不相同的。

    接着定义AccountCustomer类别如下:

 

 

'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 有些人认为:其角色接近MTSSPM (shared property manager),能管理一些暂存的资料,让有些资料能保留在中间层,留待client程序送来下一个讯息时可以使用,一方面节省数据传输的时间,一方面增强与client程序的互动性。如果系统简单不复杂的话,直接使用SPM就足够了。

s 有些人认为:其角色相当于Form的秘书对象,让Form里的一些表达式能管理得较有条理。如果系统简单不复杂的话,直接把表达式写在Form对象里即行了。

s 有些人认为:OOAD的结果很重要,类别图里的类别及其关系将会随着不断成长,即使目前系统简单,也应该以stateful对象表达,未来系统才能稳定成长。

以上是见仁见智之事,希望您能在适当的场合采取适当的看法。

模式¼的特性──

    优点:能完整落实UML类别图里的对象和关系。

    缺点:AccountCustomer等企业对象皆由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所示。

  


 

  8OnDemand模式

 


    client呼叫OdmCustomer()时,client的意思是:请您自己找出有关的Customer对象,然后建立link关系。Account对象就显得很有智能,它知道先诞生Customer对象,也诞生CustPersist对象,然后将CustID传给CustPersist对象请它到DB读取顾客资料,回传到Customer对象里。这整个过程都不用client来操心,而全部委托Account对象去做了。

    如果您是在规画N-tier系统,把AcoountCustomer对象摆在中间层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对象就以此值为keyCustomer表格里读取顾客资料。然后存入新诞生的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对象里。

 

 9OdmCustomer()的前置动作

 

   Form就删除掉AccPersist对象,然后送OdmCustomer()讯息给Account对象。Account对象就诞生Customer对象及CustPersist对象,并送query()讯息给CustPersist对象, 请它读取DB里的资料,送到Customer对象里。

 

 10OdmCustomer()的内涵

   

    CustPersist对象的任务完成了,Form就删除掉CustPersist对象,但是不删除掉Customer对象,然后把 Customer对象的指针传回给Form,如下图11

  

 11OdmCustomer()的结尾

 

    Form拥有AccountCustomer对象的指针,Form就可以送GetCustName()讯息给Account对象。此时Account对象会送GetName()讯息给Customer对象请求它的帮忙。如下图12所示。

 

 12Account请求Customer的帮忙

 

    Form拥有Customer对象的指针,Form也能直接送讯息给Customer对象。

 

 13Form也能跟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类别可以指挥AccPersistCustPersist等对象存取分散的数据库。

    尽量由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表格里。 而这些资料是来自statefulAccount对象及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+ / MTSTPC机制而达成的。

    兹写个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+ / MTSTPC机制,进行分布式交易之处理。

    缺点:由Form诞生企业对象及交易对象,使得Form程序变得复杂了。

 

  目前,Form程序的简化问题尚未有效解决。下述的新模式¿提供个可行的方案。

 

 

进阶模式¿

建立企业流程(business process)对象

    这个模式是从基本模式¹而衍生而来的。回忆模式¹的基本结构,也回忆本文前面的图2,兹重复之如下图17

 

 

17、基本模式¹的结构

 

   如果您规画的是N-tier结构,而且将Accountstateful对象摆在中间层的Server上,则FormAccount对象或AccPersist对象之间的沟通经常是跨计算机的,甚至是跨越WAN / Internet网络的。

    此时,为了降低网络沟通的频率,client端的Form应该设有一位「大使」派驻在中间层的Server上,就近跟AccountAccPersist等对象保持原来的频繁沟通。至于Form跟这位大使之间的沟通将视网络特性、使用者对Form的需求、企业流程控制的需要、网络安全考量等等因素的变化而弹性调整。于是得出新模式的结构如下:

 

18、新模式¿之结构

 

    关于Uco对象的用途,也请您参阅本期杂志的「Web-based系统之架构、开发与管理」一文,有很详细的说明。此模式¿Form里的GUI互动部份留在client端的Form里,而把跟企业对象及persist对象的互动部份独立出来成为Uco对象,而且安置于遥远的server上。

    因而Uco对象有蛮高的重复使用性(reuasblity),能供不同的Form使用。例如,有个Uco对象能负责调度AccountAccPersist对象进行「提款交易」,则此Uco能够提供服务给不同的client 端,如ATM提款机画面、分行柜台teller使用的画面、顾客家中上网的计算机画面等等,如下图19所示。

 

19、可重复使用(reuse)Uco对象

 

   有时候,一个Form也会使用到多个Uco对象。例如,Uco1类别的对象担任「提款」工作,Uco2类别的对象担任「存款」工作。而这两个Uco类别皆共享AccountAccPersist类别,但是不共享AccountAccPersist的对象个体,如下图20所示:

 

20、一个Form用到多个Uco对象

 

    如何看待Uco对象呢?这是随着各人从不同的观点(view)而有所不同。

s  有人把它视为企业对象的首领,领导与指挥一群企业对象,还包括指挥transaction对象,然后transaction对象指挥persist对象。

s  有人把它看成Form的代理者(agent),就像驻在外国的大使一般。

s  如果您采取OOAD方法分析您的系统,则您会将之对应到use case上,每个use case会有一个Uco对象代表之。

s  如果您很熟悉GammaDesign Patterns的话,则您可以将之对应到Facade 模式 (pattern)上,Uco对象成为中间层对象与Form沟通的接口对象(facade)

 

您可以选择您认为最合理而实用的观点。接下来,请看个例子,如下图21所示:


 

21、一个Form用到多个Uco对象


 

    从上图21里,可看到两个 Uco对象时来自于不同的类别:一个是Uco1类别的对象,另一个是Uco2类别的对象。但是也看到了两个Account对象是来自同一个类别,而两个AccPersist对象也来自同一个类别。

    为什幺Uco1Uco2不合并起来成为一个类别呢?为什幺Account类别不分开为Account1Account2两个类别呢?为什幺AccPersist类别不分开成为两个类别呢?

    这是个常见、有趣而严肃的问题!如果分得好、分得漂亮,系统将会结构清晰,有弹性而易维护。反之,如果分得不妥善,系统将可能日益杂乱,不意增修维护和成长。这也是OOAD的崇高价值所在。

     如果您依循OOAD过程,所得出的UML Use Case图,将会告诉您必须定义多少个Uco类别。同时,UMLClass图会告诉您应该定义多少个stateful的企业对象。而从数据库的ER表格模式(ER model),可提示您应该定义多少persist对象。

    换句话说,如果您很讲究上述这些的话,表示您蛮讲究系统架构,则OOADUML会是您的极佳利器。

    请看看如何写程序来落实图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对象。再来Uco1queryBalance()呼叫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

 

    因为Uco1Account皆为stateful对象,如果不立即删除,COM+ / MTS不会像对待stateless对象般自动将之删除的。所以此程序用完了Uco1就删除之,然后才诞生Uco2对象,用完了Uco2就删除之,让Uco1Uco2Account对象占用最少的内存空间。提升了中间层Server资源的运用效率。

 

模式¿的特性──

    优点:简化的client程序,与thin-client的潮流相结合。

    缺点:又多出一种对象---- Uco,对于不熟悉组件式系统架构者而言,又徒增一些不习惯的事情。

 

俗语说:习惯成自然。希望您能逐渐熟悉N-tier、组件式、分散交易等系统之思维及技术,一日复一日,一旦旧习惯已经新陈代谢,换成习惯之后,就会驾轻就熟了。自然而然就养成新习惯了。

 

 

结语

    本文从模式¹º衍生出进阶模式»~¿,如图22所示。

 

22、从基本模式¹º衍生出进阶模式

 

    本文只是举例说明如何从基本模式「衍生」出较适合N-tier架构,也较能落实OOAD分析结果的应用模式。别忘了,本文的目的并不在于这些模式本身,而在于举例说明「衍生」的思考过程,培养您自己的衍生兴趣和能力。希望您能熟悉「衍生」的思维和技巧,能继续基于模式~¿而衍生出更多花样、更实用的新模式,然后与别人共享之。俗语说:独乐乐,不如众乐乐。希望您也能乐于与别人分享。

     例如,国内有数家软件公司,就将上述模式¿里的Uco对象由原来的stateful对象,更改为stateless对象,其主要理由条件是:clintWeb 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

 

就将组件式软件架构分为4tier,如下图19所示。

 

    

     244-tier分层方式

 

如果进一步将上图23的新模式里的对象,依据 Herzum & Sims的分层方式加以区分,其建构出来的组件式软件结构就如下图25所示。

    本文的重点在于介绍N-tier架构模式的基础观念与活用,也说明这些模式与Herzum & Sims等组件式架构之间的基本关系。至于Herzum & Sims4-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、依据Herzum4-tier结构将模式与对象加以分层

 



版权所有:UML软件工程组织