Categories

Links

VB 6-动态数组和 高级visual baisc编程 一书

在vb6 动态数组可能没有被分配,或者可能被erase,如果视图将一个未分配或 erase的数组复制给另一个数组,或者访问这个数组,则会发生错误。

在这种情况下 ,isarray返回true,但用lbound,ubound均会返回错误

http://dev.csdn.net/article/24/24016.shtm
这个帖子介绍了一种方法,这种方法应该说是相当不优雅的,也没有揭露其实质

我们来看 dictionary中keys和items数组,它实际上返回一个下界是0,上届是-1的数组,这个数组能正常的使用lbound,ubound,并且通过for each ,但你无法用keys(0)或keys(-1)访问(下标越界)

我认为这是一种更好表达一个空数组的方法,但显然,我们无法在vb 6中做到这点。
你无法用 redim l(0 to -1) 去声明一个数组

不过我们可以借助vboost来创建这样一个数组
    Dim items As SafeArray1d
    Dim key As Variant
    With items
        .cDims = 1
        .cElements = 0
        .lLbound = 0
        .cbElements = 4
    End With
    Dim l2() As Long
    vboost.Assign ByVal VarPtrArray(l2), VarPtr(items)
    Debug.Assert (UBound(l2) = -1)
    Debug.Assert (LBound(l2) = 0)
    Dim lv As Variant
    lv = l2
    For Each key In lv
        Debug.Assert (0)
    Next

对于一个safearray,其ubound计算方法是llbound+celements-1,因此,我们就得到了一个ubound为-1的数组

用vboost 的提供的数组功能可以可以更优雅的判断一个数组是否被分配,如果一个数组没有被分配或被erase,则无法取得其数组描述

dim l() as long
debug.assert
if vboost.deref(varptrarray(l))<>0 then
 '该数组已经被分配
end if

 

这些内容在第二章 使用数组中有描述

作为一个使用vb 6快4年的程序员,每每我感到对vb6了如指掌时,去翻翻这本 高级visual basic 编程,就有一种挫败感。现在我相信francesco balena 作的序是什么意思了:这是一本可以重用的书。从表面看,这本书描述的是vb中如果运用com技术,但实际上,这些技术都大大的促进了基于vb的程序开发。从我去年开始渐渐领悟其思想(不过,现在也才领悟 不到50%),我真正的体会到什么是语言无关。
真正的语言无关的含义应该是:尽可能发挥该语言的特长。


附:
http://dev.csdn.net/develop/article/12/12902.shtm AdamBear的这篇文章讲到相同的内容(应该是看了这书后的心得吧),不过我很奇怪,
引用:
    怎么样,是不是也明白了LBound实际上是SafeArray里的rgsabound的lLbound,而UBound实际上等于lLbound +cElements - 1,现在我提个问,下面iUBound应该等于几?   
    Dim aEmptyArray() As Long
    iUBound = UBound(aEmptyArray)
    正确的答案是-1,不奇怪,lLbound -cElements - 1 = 0 - 0 - 1 = -1   


这些代码显然是不能通过ide的

 

[2005-03-05 11:56:00 | Author:jiangjianxiao ] [] 14 comments

成功将osworkflow迁移到vb6

哈哈,先自我祝贺一下,迁移的是核心功能。花了整整二个工作日,顶着手臂的剧痛

包括配置、BasicWorkflow和MemoryWorkflowStore,JdbcWorkflowStore(现改为AdoWorkflowStore),还有很多事情要做

测试代码

 Dim workflow As IWorkflow
    '创建BasicWorkflow实现
    Set workflow = CreateBasicWorkflow("jjx")
   
    Dim Id As Long
    '初始化
    Id = workflow.Initialize("test", 1, Nothing)
    Debug.Assert Id > 1
    Dim steps As Collection
    Set steps = workflow.GetCurrentSteps(Id)
    Debug.Assert steps.Count = 1
    Dim actions As Variant
    actions = workflow.GetAvailableActions(Id)
    '有两个满足条件的记录
    Debug.Assert (UBound(actions) = 1)
    Debug.Assert (actions(0) = 1)
   
    Call workflow.DoAction(Id, actions(0), Nothing)
   
    Set steps = workflow.GetCurrentSteps(Id)
    Debug.Assert (steps.Count = 1)
     actions = workflow.GetAvailableActions(Id)
     '只有第二个
    Debug.Assert (UBound(actions) = 0)
     Debug.Assert (actions(0) = 2)
     '没有了
     Call workflow.DoAction(Id, actions(0), Nothing)
 
     Set steps = workflow.GetCurrentSteps(Id)
    Debug.Assert (steps.Count = 0)
     actions = workflow.GetAvailableActions(Id)
    Debug.Assert (UBound(actions) = -1)
   

 

手动创建工作流的代码

Dim wf As New WorkflowDescriptor
    Dim action As New actionDescriptor
    action.Id = 1
    action.name = "Start Workflow"
    Dim Result As New resultdescriptor
    Result.OldStatus = "Finished"
    Result.Status = "Queued"
    Result.step = 1
    Set action.UnconditionalResult = Result
    wf.InitialActions.Add action.Id, action
   
   
   
    Dim step As New StepDescriptor
    step.Id = 1
    step.name = "First Draft"
    Set action = New actionDescriptor
    action.Id = 1
    action.name = "Start First Draft"
   
    Dim c As New ConditionDescriptor
    c.ConditionType = "sfc.StatusCondition"
    c.Args.Add "status", "Queued"
    Dim re As New RestrictionDescriptor
    re.Conditions.Add c
    Set action.restriction = re
   
   
   
    Set Result = New resultdescriptor
    Result.OldStatus = "Finished"
    Result.Status = "Underway"
    Result.step = 1
    Set action.UnconditionalResult = Result
    step.actions.Add action.Id, action
   
    Set action = New actionDescriptor
    action.isFinish = True
    action.Id = 2
    action.name = "Finish First Draft"
    Set Result = New resultdescriptor
    Result.OldStatus = "Finished"
    Result.Status = "Queued"
     Set action.UnconditionalResult = Result
    step.actions.Add action.Id, action
    wf.steps.Add step.Id, step
   

[2005-02-27 22:59:10 | Author:jiangjianxiao ] [] 3 comments

vb6框架设计 - asp 支持

先谈谈我对asp一些看法:

有了asp.net,为什么还要用asp,因为简单。asp.net太重了,想想jsp失败(在java 领域现在很少人直接使用jsp,原因是什么?jspasp.net事实上还要轻量)。如果你明确应用程序的分界,将 asp作为一个view端。那asp还是相当可用的。

Asp的后端是com,从体系结构来说,自然比不上.net ,但这并不能阻止你设计好的系统,我想,这个框架的设计很改变你的一些看法。

通过订单管道,结合对象导航功能,我们在asp中提供 了类似webwork的框架。因此在下面,我们会用webwork中的action来代指pipelinepipelinecomponent的概念

 

Asp支持享有订单管道的全部优点,由于通过PipelineContext web解除了耦合,所有实现都是易测试,可重用于桌面环境。另外,Parameter,Validation等拦截器的实现使得asp编程体验更为简单。

当在asp中运行时,管道和管道组件、拦截器必须设置为singleton

PipelineContext

 

PipeLineContext的生存范围类似于一个静态变量,一旦创建就存在,而且由于vb的单线程特性,它生来就是Threadstatic 的。

AspPipelineDispatcher每次活动都会创建或破坏PipeLinecontext(如果已经存在)。在win32应用中,你可以简单的传递一个dictionary实例来破坏它。如

Dim dic as new dictionary

Dic.comparemode=vbtetcompare

Dim ctx as new PipelineContext

Set ctx.Context=dic

 

由于pipelinecontext这这个特性,你可以在一个链中任意使用上下文数据。这导致我修改IpipelineComponentExecute方法签名,将datacontext参数去除。

PipelineContextweb无关, AspPipelineDispatcher执行时会将asp中的application,session,formquerystring分别置入PipeLineContext的对应词典。

 

PipelineContext本身是一个词典,其中又包括以下词典

l         Application – asp应用中,存储application.contents词典的值

l         Session – asp应用中,存储session.Contents 词典的值

l         Parameters – asp应用中,存储querystring,form的值

l         ValueStack – 存储action

l         ConversionErrors -存储转换拦截器的错误信息

l         ValidationErrors - 存储验证的错误信息

 

win32应用中,这些词典可按其名字需要自由使用

 

ValueStack

ValueStack顾名思义是个先进先出的Stack,它主要用来保存action

ValueStack提供GetValueSetValue方法,这两个方法有如下特色

    一般,总是从stack顶部开始查找,如果对应属性或方法不存在(错误 438)则向下查找,直到找到或遍历结束

    如果只要stack某一对象,则可以通过集合索引,从1开始,1表示stack 顶部,2表示顶部下一个对象,其它类推。

Dim valueStack As New valueStack

   

    Dim emp As New Employee

    emp.Name = "jjx"

    Dim emp2 As New Employee

    emp2.Name = "jxb"

    Dim c As New Customer

    c.userName = "customer"

    valueStack.Push c

    valueStack.Push emp2

  

    valueStack.Push emp

   

    oTestResult.Assert (valueStack.count = 3)

    oTestResult.Assert (valueStack.GetValue("UserName") = "customer")

    Call valueStack.SetValue("UserName", "jjx")

    oTestResult.Assert (valueStack.GetValue("UserName") = "jjx")

   

    valueStack.Pop

    oTestResult.Assert (valueStack.count = 2)

   

    oTestResult.Assert (valueStack.GetValue("Name") = "jjx")

    Call valueStack.SetValue("Name", "jjx1")

    oTestResult.Assert (valueStack.GetValue("Name") = "jjx1")

   

    oTestResult.Assert (valueStack.GetValue("[2].Name") = "jxb")

     

 

 

AspPipelineDispatcher

 

我们需要一个核心的控制器,Asp没有Servlet或是HttpHandler的概念,但我们可以创建一个asp 组件来模拟这种概念。取名为servlet是因为其api类似servlet

AspPipelineDispatcher

有三个方法ProcessRequest,doGet,doPost,主要是使用doGetdoPost,当你需要使用post方法递交时,使用doPost,否则使用doGet,如果不想理会request_method,就使用ProcessRequest。在dogetdoPost中,如果页面提交方式不同于方法签名,则方法不会执行。

显然,我们需要手动在加入代码

<%

Dim dispatcher

Set dispatcher =Server.CreateObject(“sfc.AspPipeLineDispatcher”)

dispatcher.DoPost(action) ‘dispatcher.DoGet(action) dispatcher.ProcessRequest(action)

 

%>

关于要执行的action,因为asp无法之间提交给一个action或是直接执行一个action,我考虑过用hidden字段来表示,这样可以通过获取这个参数来得到实际执行的action。最后,我没有这样做。

 

asppipelinedispatcher是通过com+获得asp内置对象,然后,它执行这样一些动作

1.          通过Request.ServerVariables(“REQUEST_METHOD”)检查与方法签名是否符合

2.          创建PipeLineContext,将

a)         Application词典的值置入pipelinecontext.Application词典

b)         Session词典的值置入pipelinecontext.Session

c)         FormqueryString置入pipelinecontext.Parameters

3.          ioc获取管道配置

4.          执行该管道

AspDispatcherResult

实现Iresult接口的类执行转向功能,该组件有两个属性

1.          Mode 字符串转向的方式redirect或是transfer

2.          url 转向的页面

获取Action的数据并呈现到web页面

显然,我们只解决了递交的过程,如果一个页面需要返回值,比方说一个记录集,一个集合或一个对象,怎么获得。

jsp,asp.net中,一个页面就是一个对象,在spring.netweb 框架中,一个页面是ioc中的一个对象,因此,你可以直接赋值给控件(当然,你需要attribute来指示)

jsp中,通常使用模板或标记语言来展现。

asp中,这两者都无法获得(asp没有好的模板框架)

因此,这时做法就原始一些,你需要主动去获取值

ListCustomer 组件返回一个Customers的记录集,你就可以这样去获取它

 <%

Dim context

Set context=Server.createObject(“sfc.PipelineContext”)

Dim rs

Set rs=context.ValueStack.getValue(“Customers”)

%>

接下去怎么使用是你的事情了

 

 

拦截器

没有下面增加的拦截器,这个asp框架也没有什么大的作用。当前,我们已经创建了以下拦截器

 

ParametersInterceptor

参数拦截器将PipeLineContextParameter词典的值通过ObjectNavigator设置到目标组件上。参数拦截器的代码非常简单。但完成的事情美妙无法用语言表达。无需在做获取值、设置值的工作,拦截器自动会帮你完成。

拦截器的实现代码

Private Function IInterceptor_Invoke(invocation As PipelineComponentInvocation) As String

 

    Dim context As PipelineContext

    Set context =new PipelineContext

   

    Dim Target As Object

    Set Target = invocation.PipelineComponentProxy.Target

   

   

    Dim parameters As Dictionary

    Set parameters = context.parameters

    Dim keys As Variant, key As Variant

    keys = parameters.keys

    Dim objectNavigator As New objectNavigator

   

    For Each key In keys

 

        Call objectNavigator.SetValue(Target, key, parameters(key))

       

    Next

   

    Set objectNavigator = Nothing

   IInterceptor_Invoke = invocation.Invoke

   

End Function

例子配置

<objects>

    <object name="dataSource" class="sfc.SqlOledbDataSource">

        <property name="database">mis2</property>

        <property name="server">(local)</property>

        <property name="userid">sa</property>

    </object>

    <object name="pipeLine" class="sfc.PipeLineComponentProxy" signleton="true">

        <property name="target">

            <object name="target" class="ObjectsTest.AddCustomer" singleton="true">

                <property name="DataSource">

                    <ref object="dataSource"/>

                </property>

            </object>

        </property>

        <property name="Interceptors">

            <list>

            <object name="parametersInterceptor" class="sfc.ParametersInterceptor" singleton="true"/>

            </list>

        </property>

 

    </object>

</objects>

如何使测试便的容易的

   Dim pipeline As IPipelineComponent

    Dim addCustomer As addCustomer

   

    Dim ctx As IApplicationContext

    Set ctx = createxmlapplicationcontext(App.Path & "\parameters.xml")

    Dim context As New PipelineContext

   

    Dim parameters As Dictionary

    Set parameters = context.parameters

    Call parameters.Add("Username", "jjx")

    Call parameters.Add("loginname", "jjx")

    Call parameters.Add("password", "123456")

   

    Set pipeline = ctx.GetObject("pipeLine")

   

    oTestResult.Assert (pipeline.Execute() = "success")

    Dim proxy As PipelineComponentProxy

    Set proxy = pipeline

   

    Set addCustomer = proxy.Target

   

    oTestResult.Assert (addCustomer.username = "jjx")

    oTestResult.Assert (addCustomer.LoginName = "jjx")

    oTestResult.Assert (addCustomer.Password = "123456")

   

    destroyxmlapplicationcontext ctx

TypeConversionInterceptor

类型转换器

原始思路参考 http://forum.javaeye.com/viewtopic.php?t=10507

 

ValidationInterceptor

ValidationInterceptor利用我们的Validation框架对目标字段进行验证处理

如果Validation没有通过,则拦截器不在继续往下执行管道组件,即不调用invocation.invoke。并将一个validation字符串返回。并且将没有通过validationkey和错误信息存储在PipelineContextValidationErrors词典中。

 

Todo

1Request.Form,Request.QueryString每个值实际上是一个IstringList的实例

这个对象有一个缺省的Item属性和一个count属性,有一个可选的参数变体,如果你忽略它,就返回全部值

解决方法:

如果count1,取其字符串,否则,新建一个StringList对象(自己实现)加入parameters词典

 

2、如何结合xmlhttpjs,改善validationInterceptor的体验

[2005-02-22 13:12:13 | Author:jiangjianxiao ] [] 1 comments

vb6框架设计-对象导航

ObjectNavigation最早从xwork中了解的。Xwork捆绑了ognl。后来,spring.net 也增加了Object Navigation功能。与ognl相比, spring.net的实现非常的弱,而且还没有完成(MethodNode/SelectorNode)。但其结构简单,容易迁移到vb6。整个工作花了近一天时间,除了SelectorNode应无法推测其用意,其余的基本通过测试。

Vb中的callbyname非常有用,但其层次仅局限于1层,无法应付复杂的业务需求,我们需要能处理多层的表达式的取值和设置值,如一个employee对象,有一个Mananger属性(也是个employee对象)我们可能希望获取Manager对象的name属性,用ObjectNavigator对象就是这样

Dim navigator as new ObjectNavigator

Navigator.GetValue(employee,”Manager.Name)

如果我们要设置其值

代码就是这样

Navigator.SetValue(employee,”Manager.Name”,”jjx”)

对象导航的广泛的用于

l         表达式的运算

l         模板

l         特定的场景:多语言,数据绑定,编号规则等

 

属性

获取员工的名称

objectNavigator.GetValue(emp,”Name”)

获取员工的管理者名称

objectNavigator.GetValue(emp,”Manager.Name”)

获取管理者的管理者,如果不存在者返回nothing

objectNavigation.GetValue(emp,”Manager.Manager”,nothing)

设置值

objectNavigator.setValue(emp,”name”,”jjx”)

objectNavigator.setValue(emp,”Manager.name”,”jjx”)

objectNavigator.setValue(emp,”Manager.Manager”,new employee)

例子代码

  Dim navigator As New ObjectNavigator

   

    Dim emp As New Employee

    emp.Name = "jjx"

    emp.Address = "zj"

    oTestResult.Assert (navigator.GetValue(emp, "Name") = "jjx")

    oTestResult.Assert (navigator.GetValue(emp, "Address") = "zj")

   

    Dim m As New Employee

    m.Name = "m"

    Set emp.Manager = m

   

    oTestResult.Assert (navigator.GetValue(emp, "Manager.Name") = "m")

    Dim m2 As New Employee

    m2.Name = "m2"

    Set emp.Manager.Manager = m2

    oTestResult.Assert (navigator.GetValue(emp, "Manager.Manager.Name") = "m2")

   

    Call navigator.SetValue(emp, "Name", "jxb")

    oTestResult.Assert (navigator.GetValue(emp, "Name") = "jxb")

    Call navigator.SetValue(emp, "Manager.Name", "m1")

    oTestResult.Assert (navigator.GetValue(emp, "Manager.Name") = "m1")

    Call navigator.SetValue(emp, "Manager.Manager.Name", "m3")

   

    oTestResult.Assert (navigator.GetValue(emp, "Manager.Manager.Name") = "m3")

   

    Dim m4 As New Employee

    m4.Name = "m4"

   

    Call navigator.SetValue(emp, "Manager", m4)

   

    oTestResult.Assert (navigator.GetValue(emp, "Manager.Name") = "m4")

 

集合和词典

如果你定义了一个缺省方法,命名为Item,则可以按集合和词典的方式访问。

 

在写Indexer的实现代码时,我发现collectionItem是以缺省方法表示的,而Scripting.Dicitonary却是属性,这是因为collection是只读的缘故

如果你有自定义集合,请遵循这个规则,就是将Item写成方法,而不是属性

读取值

用索引值

objectNavigator.GetValue(emp,”employees[1]”)

key,注意,字符串用单引号括起来

objectNavigator.GetValue(emp,”employee[‘jjx’]”)

设置值(当前仅词典能被更新)

objectNavigator.setValue(emp,”Phone[‘work’]”,”123456)

comunit测试代码

   Dim navigator As New ObjectNavigator

   

    Dim emp As New Employee

    emp.Name = "jjx"

    Dim emp2 As New Employee

    emp2.Name = "jjx"

   

    Call emp.Employees.Add(emp2, emp2.Name)

    emp.Phone("home") = "11111111"

    emp.Phone("work") = "22222222"

   

   

    oTestResult.Assert (navigator.GetValue(emp, "Employees[1].Name") = "jjx")

    oTestResult.Assert (navigator.GetValue(emp, "Employees['jjx'].Name") = "jjx")

   

    Call oTestResult.Assert(navigator.GetValue(emp, "Phone['home']") = "11111111")

    Call oTestResult.Assert(navigator.GetValue(emp, "Phone['work']") = "22222222")

   

   

    Call navigator.SetValue(emp, "Phone['home']", "123456")

    Call navigator.SetValue(emp, "Phone['work']", "654321")

    Call oTestResult.Assert(navigator.GetValue(emp, "Phone['home']") = "123456")

    Call oTestResult.Assert(navigator.GetValue(emp, "Phone['work']") = "654321")

   

   

方法

注意点:

l         参数不能嵌套(

GetSalary((9))

l         字符型参数使用单引号括起来,日期型参数使用#框起来,其它不处理,如

GetSalary(‘jjx’,#2001-1-1#,9)

l         方法节点不能嵌套

l         如果你的组件定义了缺省方法,当前请显式说明缺省方法,如

objectNavigator.GetValue(category,”Item(‘name’)”)

例子代码

  Dim navigator As New ObjectNavigator

   

    Dim emp As New Employee

    oTestResult.Assert (navigator.GetValue(emp, "GetSalary(9)") = 9)

   

    Call navigator.GetValue(emp, "dosub('jjx',#2005-1-1#,999)")

Selector

todo

设计

借鉴了spring.netnavigation的设计,将多级表达式中的每一级抽象为一个InavigationNode接口(spring.net是一个抽象类),实现根据节点不同分为

l         PropertyOrFieldNode – 该级是字段或属性 pass

l         MethodNode – 该级是方法 pass

l         IndexerNode –  对集合(collection) 词典(Scripting.Dictionary)提供支持 pass

l         SelectorNode – 目前我都不了解spring.net这个代表什么

ExpressionParser负责解析表达式到InavigationNode 数组,是个私有类。

ObjectExpression 负责实际的运算,该类是私有类

ObjectNavigator 是一个助手类,是被外部使用的对象

 

需要说明的举止和行为

当某一中间节点值是nothing时,框架做以下处理

 

    GetValue时,如果你传递了一个缺省值,则系统屏蔽可能的错误,简单的返回缺省值。否则则抛出错误

    setValue时,总是抛出错误

    集合不能设置值

    方法节点不能设置值

 

注:为方便在外部项目测试

一些类在项目中都修改为公共(ObjectExpression)或公共不可创建(InavigationNode和其实现),除了ObjectNavigator外,不要在外部使用这些类

 

重构Expresion

这个类实际上在很早以前就存在,是为了解决自定义编号问题,如一个订单号可写作{dates:yyyymmdd}{no:000},一个条码可写作{orderno}01{coilno:000000}

新版本的Expression可以充分利用对象导航的能力,像传入对象不在必须是词典(原来),原来只能使用属性和字段(但也允许多级),但就根本谈不上设计了。

 

例子

Dim dic As New Dictionary

    dic.Add "dates", #1/1/2005#

    dic.Add "no", "1"

    Dim exp As New expression

   

    oTestResult.Assert (exp.Parse2("{['dates']:yyyy-mm-dd}{['no']:000}", dic) = "2005-01-01001")

   

    Set dic = New Dictionary

    dic.Add "orderno", 1

    dic.Add "coilno", 1

    oTestResult.Assert (exp.Parse2("{['orderno']}01{['coilno']:000000}", dic) = "101000001")

   

    Dim rs As Recordset

    Set rs = New Recordset

    rs.CursorLocation = adUseClient

    rs.Fields.Append "orderno", adInteger

    rs.Fields.Append "coilno", adInteger

    rs.Open

   

    rs.AddNew

   

    Dim objectNavigator As New objectNavigator

    Call objectNavigator.SetValue(rs, "fields['orderno'].value", 1)

    Call objectNavigator.SetValue(rs, "fields['coilno'].value", 1)

   

    rs.Update

   

    oTestResult.Assert (exp.Parse2("{fields['orderno'].value}01{fields['coilno'].value:000000}", rs) = "101000001")

[2005-02-21 23:40:33 | Author:jiangjianxiao ] [] 1 comments

vb6框架设计- 订单管道(修订中)

Commerce server中的订单管道给我很大启发,继续结合xworkjava)的一些特征。形成了我现在的订单管道框架。

这个框架集成了这样一些特点

l         来自Commerce server的订单管道的基本概念

l         来自xwork的拦截器思想和命令模式

l         结合ioc的配置功能

现在,订单管道是edf 框架(e2 erp/e2 erp lite的开发框架)的核心技术,简单的设计和模式带来了高度的灵活性和可定制能力

基于订单管道开发程序,业务逻辑完全是装配式的,可自由的任何时候进行增加、删除、修改。代理模式和拦截器的运用使不修改原有模块而增加新 功能的实现变成可能(这达到了类似于aop的功效,一般而言,aop是建立在动态代理的基础上的,vb 6无法实现动态代理,通过显式实现命令模式的代理也是一种途径,因为命令模式简单)。

一个订单管道是一个大的逻辑单位,像单据的处理就是一个订单管道。单据的处理的各个步骤由管道中的组件完成。

订单管道与工作流的关系比较模糊,通常,我认为订单管道是无需人工干预,而工作流的典型特征是人工干预(当然,订单管道也可以实现这点)。另外,工作流具有多种模式,而订单管道往往是一条线(虽然也是可以编程来达成的)

虽然如此,但订单管道得确使系统拥有高度的灵活性,完成了工作流在mis系统中的作用,就是我前面所说的

l         业务逻辑被文字所描述

l         业务逻辑被隔离,以便于进一步重用

l         业务逻辑可以在外部变动

l         拦截器可以用来修饰一个管道组件的功能,而无需修改原组件的代码

 

IpipelineComponent

一个订单管道由若干个实现IpipelineComponent的组件组成,IpipelineComponent遵循简单的Command模式,只有一个方法Execute

 

一个订单管道由若干个实现IpipelineComponent的组件组成,IpipelineComponent遵循简单的Command模式,只有一个方法Execute

Scriptor

Scriptor实现IpipelineComponent接口,它提供一个根据脚本执行业务逻辑的功能

Scriptorscripttype属性使你能够指定脚本的类型,默认是vbscript,你也可以使用任何符合ActiveScript规范的脚本,在内部,Scriptor使用microsoft script control 执行

用你的脚本语言定义一个名为Execute 的函数,无参数,在结束时返回一个字符串,如果用vbscript就是如下

Function Execute(byref data,byref context)

Execute=”success”

End function

下面是一个具体的例子

<objects>

    <object name="pipeLine" class="sfc.PipeLine">

        <property name="PipelineComponents">

            <list>

                <object name="vbsdemo" class="sfc.Scriptor">

                    <property name="ScriptType">vbscript</property>

                    <property name="ScriptText">

                        <![CDATA[

                        function Execute()

                            Execute="success"

                        end function

                        ]]>

                        </property>

                </object>

                <object name="jsdemo" class="sfc.Scriptor">

                    <property name="ScriptType">JScript</property>

                    <property name="ScriptText">

                        <![CDATA[

                        function Execute(){

 

                            return "success";

                        }

                        ]]>

                    </property>

                </object>

            </list>

        </property>

    </object>

</objects>

Comunit测试代码

Dim ctx As IApplicationContext

    Set ctx = createxmlapplicationcontext("c:\my works\sfc\test\pipeline4.xml")

   

    Dim pl As pipeline

   

    Set pl = ctx.GetObject("pipeLine")

    oTestResult.Assert (pl.PipelineComponents.count = 2)

   

    oTestResult.Assert (TypeOf pl.PipelineComponents(1) Is Scriptor)

    oTestResult.Assert (pl.Execute(Nothing, Nothing) = "success")

   

destroyxmlapplicationcontext ctx

 

WorkflowPipelineComponent

一个workflowPipeLineComponent持有管道中上一个组件的返回值,在执行时根据这个返回值执行从一个PipelineComponents词典(注意不是集合)中执行对应的组件。

这个组件实现了workflow patternsplit,其实就是个条件判断J

WorkflowPipelineComponent获得上一个组件的返回值是通过IResultAware接口来实现的。这是依赖接口,不依赖具体类和反转控制的一个具体运用,如果你的组件需要获得上一个返回值,你可以实现这个接口。

例子

<objects>

    <object name="pipeLine" class="sfc.PipeLine">

        <property name="PipelineComponents">

            <list>

                <object name="answer" class="ObjectsTest.AnswerComponent"/>

                <object name="workflow" class="sfc.WorkflowPipelineComponent">

                    <property name="PipelineComponents">

                        <map>

                            <entry key="yes">

                                <object name="yes" class="ObjectsTest.YesComponent"/>

                             </entry>

                            <entry key="no">

                                <object name="no" class="ObjectsTest.NoComponent"/>

                            </entry>

                        </map>

                    </property>

                </object>

            </list>

        </property>

    </object>

</objects>

Comunit测试代码

Dim ctx As IApplicationContext

    Set ctx = CreateXmlApplicationContext(App.Path & "\pipeline5.xml")

   

    Dim p As pipeline

    Set p = ctx.GetObject("pipeLine")

   

    Dim s As String

    Dim p1 As AnswerComponent

   

    Set p1 = p.PipelineComponents(1)

    p1.Answer = "yes"

   

    oTestResult.Assert (p.Execute(Nothing, Nothing) = "yes")

    p1.Answer = "no"

    oTestResult.Assert (p.Execute(Nothing, Nothing) = "no")

   

    p1.Answer = ""

   

    oTestResult.Assert (p.Execute(Nothing, Nothing) = "success")

destroyxmlapplicationcontext ctx

 

你可以在一个组件中使用对话框,通过用户的动作执行下一个组件。

Todo:用于asp页面的思考

Pipeline

 详细见下

Iresult

当一个action执行结束后,可选择的根据执行结果执行一个Iresult的实例。注意IpipelineComponent不拥有Results,只有代理才拥有Results的词典,这样就可以灵活的配置IpipelineComponent的执行结束后的动作

PipelineComponetProxy

这个一个代理类,使用代理模式,代理一个IpipelineComponentPipelineComponentProxy是执行拦截器的前提。

PipelineComponentProxy还拥有ExecuteResult属性和Results词典,前者说明是否在执行后执行Result,后者保存以系列Iresult 的实现列表。

PipelineComponentProxytarget代表实际的IPipelineComponent

为什么要把Target设置为object ,而又在内部检查是否符合IpipelineComponent接口,原因是vb 6的局限性

 

    Dim AddCustomer As AddCustomer

    Dim pipeline As IPipelineComponent

  

    Set AddCustomer = New AddCustomer

    Dim obj As Object

    Set obj = AddCustomer

    Debug.Print AddCustomer.username

  

    Debug.Print obj.username

  

    Set pipeline = AddCustomer

  

    Set obj = pipeline

Debug.Print obj.username  '后期绑定失败

 

显然,在vb.net中没有问题

   Dim addCustomer As New AddCustomer

         Dim obj As Object

 

        Dim s As String

        s = "jjx"

        addCustomer.Username = s

 

        AssertEquals(addCustomer.Username, s)

 

        obj = addCustomer

        AssertEquals(obj.username, s)

        Dim pc As Pipeline.IPipelineComponent

        pc = addCustomer

 

        obj = pc

        AssertEquals(obj.username, s)

Iinterceptor

拦截器接口,PipeLineCompoenntProxy将维持一个拦截器的集合

PipelineComponentInvocation

PipelineComponentInvocation 调度一个拦截器集合的执行,并进行具体方法的执行

Pipeline

Pipeline 维护一个IpipelineComponent的集合,不过其本身也实现了IpipelineComponent,因此,也可以在其上施加拦截器

 

Pipeline实现IpipeLineComponent带来的灵活性难以估量,除了享受PipelineComponentProxy带来的好处外,另外,一个管道也可以由多个管道组成,一个workflowPipelineComponent可以转向一个管道而不是一个管道组件。

避免直接在代码中使用PipeLine对象,尽量使用 IpipelineComponent来引用一个Pipeline,这样以保持最大的灵活性,因为当前PipelineComponentProxy不代理Pipeline

 

上下文数据

PipelineContext

返回值

每个管道组件返回一个约定的值,对于订单管道而言,以下值为内部保留(均为小写)

Success  完成,继续运行

Failure  失败,不再运行

Warning  警告,继续运行

Validation  validation失败,不再运行

这里,Failure/validatin中止管道的运行,如果有一个致命错误发生,需要中止执行,请返回该值,validation通常由ValidationInterceptor拦截器返回。当然,你也可以手动返回该值

管道组件中错误处理

默认的订单管道处理实现非常简单,它不隐藏任何错误,因此,你需要在执行订单管道的外围进行处理代码

Asp支持

todo

 

配置

目前,管道的配置使用ioc进行, 提供一个简单的图形界面也是可行的,你也可以手动配置一个管道。

Ioc配置例子一

<objects>

   <object name="validateQuantity" class="ObjectsTest.ValidateQuantity"/>

   <object name="validateFlowno" class="ObjectsTest.ValidateFlowNo"/>

   <object name="pipeLine" class="sfc.PipeLine">

       <property name="PipelineComponents">

           <list>

               <ref object="validateFlowno"/>

               <ref object="validateQuantity"/>

           </list>

       </property>

   </object>

</objects>

Ioc配置例子二—代理

<objects>

   <object name="validateQuantityTarget" class="ObjectsTest.ValidateQuantity"/>

   <object name="bug" class="sfc.DebugInterceptor"/>

   <object name="timing" class="sfc.TimingInterceptor"/>

   <object name="validateQuantity" class="sfc.PipelineComponentProxy">

       <property name="Target"><ref object="validateQuantityTarget"/></property>

       <property name="Interceptors">

           <list>

               <ref object="bug"/>

               <ref object="timing"/>

           </list>

       </property>

   </object>

   <object name="validateFlowno" class="ObjectsTest.ValidateFlowNo"/>

   <object name="pipeLine" class="sfc.PipeLine">

       <property name="PipelineComponents">

           <list>

               <ref object="validateFlowno"/>

               <ref object="validateQuantity"/>

           </list>

       </property>

   </object>

</objects>

Ioc 配置三—这个配置说明了可以在管道上施加代理

<?xml version="1.0" encoding="GB2312" ?>

<objects>

   <object name="validateQuantityTarget" class="ObjectsTest.ValidateQuantity"/>

   <object name="bug" class="sfc.DebugInterceptor"/>

   <object name="timing" class="sfc.TimingInterceptor"/>

   <object name="validateQuantity" class="sfc.PipelineComponentProxy">

       <property name="Target"><ref object="validateQuantityTarget"/></property>

       <property name="Interceptors">

           <list>

               <ref object="bug"/>

               <ref object="timing"/>

           </list>

       </property>

   </object>

   <object name="validateFlowno" class="ObjectsTest.ValidateFlowNo"/>

   <object name="pipeLineTarget" class="sfc.PipeLine">

       <property name="PipelineComponents">

           <list>

               <ref object="validateFlowno"/>

               <ref object="validateQuantity"/>

           </list>

       </property>

   </object>

   <object name="pipeLine" class="sfc.PipeLineComponentProxy">

       <property name="Target"><ref object="pipeLineTarget"/></property>

       <property name="Interceptors">

           <list>

               <ref object="bug"/>

               <ref object="timing"/>

           </list>

       </property>

 

   </object>

</objects>

ComUnit测试代码

Dim ctx As IApplicationContext

    Set ctx = createxmlapplicationcontext("c:\my works\sfc\test\pipeline3.xml")

    Dim pipeline As IPipelineComponent

   

    Set pipeline = ctx.GetObject("pipeLine")

   

    oTestResult.Assert (TypeOf pipeline Is PipelineComponentProxy)

    Dim proxy As PipelineComponentProxy

    Set proxy = pipeline

    oTestResult.Assert (TypeOf proxy.Target Is pipeline)

    oTestResult.Assert (proxy.Interceptors.count = 2)

   

    oTestResult.Assert (pipeline.Execute(Nothing, Nothing) = "success")

 

手动配置(摘自comunit测试代码)

Set pipeline = New pipeline

   

     Dim c1 As IPipelineComponent

    Set c1 = New ValidateFlowNo

    Dim c2 As IPipelineComponent

    Set c2 = New ValidateQuantity

    Call pipeline.PipelineComponents.Add(c1)

Call pipeline.PipelineComponents.Add(c2)

手动配置—代理(摘自comunit测试代码)

Dim proxy As New PipelineComponentProxy

    Set proxy.Target = New ValidateQuantity

    Dim t As New TimingInterceptor

    Dim di As New DebugInterceptor

    Call proxy.Interceptors.Add(t)

    Call proxy.Interceptors.Add(di)

    Dim s As String

    Call pipeline.PipelineComponents.Add(proxy)

    s = pipeline.Execute(Nothing, Nothing)

    oTestResult.Assert (LCase$(s) = "success")

    oTestResult.Assert (di.count = 1)

    'validateflowno sleep(5)

    oTestResult.Assert (t.ElaspedTime >= 5)

应用

准备

业务过程分析

对业务过程进行分析,划分处理的步骤,一个步骤一个管道组件

可以预料的是,在一开始,通常只有一二个步骤,一般是规则验证,保存。随着开发的深入和用户需求的变更,管道步骤会增多。

Data,context约定

在配置每个管道前,首先要对管道中传递的数据进行约定。比方说,一个销售订单管道传递的数据是订单信息,至于这个数据是以class 还是用dictionary或是记录集。则根据具体情况而定,edf 没有强制要求

除了需要处理的数据外,还需要约定其它一些信息,比方错误信息的保存,其它的上下文信息。

在下面的例子中,context传递的是IapplicationContext 的实例,而data则是一个词典,其中有一个errors词典用来保存所有的错误信息

 

规则验证实例

一个实际例子,在一个计划单保存中,需要有两个验证过程

vb 6代码

  If ValidateQuantity = False Then

        If MsgBox("颜色分配数量超过需要生产的数量,是否继续?", vbQuestion + vbYesNo, "提示") = vbNo Then

            Exit Sub

        End If

    End If

   

    If OrderService.ValidateFlowNo(orderid, txtFlowNo) Then

        MsgBox "流程卡号不能重复", vbInformation, "提示"

        txtFlowNo.SetFocus

        Exit Sub

End If

这是个不好的例子,第一个规则在已有数据上展开,无需访问数据库,第二个例子需要访问数据库。

 

这里,就需要有两个组件,你可以选择用vb来实现,也可以使用Scriptor直接用脚本来实现。下面我们例子包括两者

启动管道

在这个例子中,data是一个词典,初始化的过程如下

Dim data as new dictionary

Set Data(“order”)=rsOrder

Set data(“gongyi”)=rsgongyi

Set data(“color”)=rsColor

Set data(“renyuan”)=rsrenyuan

Set data(“spoilage”)=rsSpoilage

Set data(“errors”)=new dictionary

Dim pipeline as IpipelineComponent
set pipeline=context.GetObject(“planPipeLine”)

If Pipeline.Execute(data,contenxt)=” failure” then

遍历错误词典,以了解详细信息

Msgbox “错误

Else

Msgbox “执行结束

End if

编写第一个管道组件实现

todo

编写第二个管道组件实现

todo

拦截器实现举例

Timing拦截器

Timing拦截器记录一个管道组件的执行时间

'##MODULE_SUMMARY 执行时间拦截器

Option Explicit

 

Implements IInterceptor

 

Private et As Long

 

'## 总执行时间

Public Property Get ElaspedTime() As Long

 

    ElaspedTime = et

 

End Property

 

'## 执行拦截器

'##param invocation PipelineComponentInvocation实例

'##returns 执行结果

Private Function IInterceptor_Invoke(invocation As PipelineComponentInvocation) As String

 

    Dim t As New Timing

    t.start

    IInterceptor_Invoke = invocation.Invoke

    t.finish

    et = t.ElaspedTime

    Set t = Nothing

 

End Function

事务拦截器

事务拦截器需要使用edfdata框架,这个简单的拦截器具有两个属性

rollbackOnError 如果错误触发是否执行回退

rollbackName 这个定义那个返回值需要回退,默认是 rollback

代码

''##MODULE_SUMMARY 事务拦截器

Option Explicit

Implements IInterceptor

 

'## 定义回退的返回值,默认为rollback

Public RollbackName As String

'## 如果错误触发是否回退,默认为true

Public RollbackOnError As Boolean

'## ITransaction实例

Public Transaction As ITransaction

 

Private Sub Class_Initialize()

 

    RollbackName = "rollback"

    RollbackOnError = True

   

End Sub

 

'## 执行

Private Function IInterceptor_Invoke(invocation As PipelineComponentInvocation) As String

 

    If Transaction Is Nothing Then

 

        IInterceptor_Invoke = invocation.Invoke

        Exit Function

 

    End If

 

    Transaction.BeginTransaction

    Dim retval As String

    retval = invocation.Invoke

 

    If retval = RollbackName Then

 

        Transaction.RollbackTransaction

 

    Else

 

        Transaction.CommitTransaction

 

    End If

 

    IInterceptor_Invoke = retval

 

    Exit Function

sub_end:

    Dim ex As Exception

    Set ex = New Exception

   

    If RollbackOnError Then Transaction.RollbackTransaction

   

    ex.throw

   

End Function

 

例子配置

        

<objects>

    <!-- 数据源-->

    <object name="dataSource" class="sfc.SqlOledbDataSource">

        <property name="server">(local)</property>

        <property name="userid">sa</property>

        <property name="database">mis2</property>

    </object>

    <!-- 事务对象 -->

    <object name="transaction" class="sfc.AdoTransaction">

        <property name="dataSource"><ref object="dataSource"/></property>

    </object>

    <!--事务拦截器-->

    <object name="transactionInterceptor" class="sfc.TransactionInterceptor">

        <property name="transaction"><ref object="transaction"/></property>

    </object>

    <!--管道组件-->

    <object name="addCustomer" class="ObjectsTest.AddCustomer">

        <property name="dataSource"><ref object="dataSource"/></property>

    </object>

    <object name="addCustomer2" class="ObjectsTest.AddCustomer">

        <property name="dataSource"><ref object="dataSource"/></property>

    </object>

       <!--管道-->

    <object name="pipeLineTarget" class="sfc.PipeLine">

        <property name="pipelineComponents">

            <list>

                <ref object="addCustomer"/>

                <ref object="addCustomer2"/>

            </list>

        </property>

    </object>

    <!-- 管道代理 -->

    <object name="pipeLine" class="sfc.PipeLineComponentProxy">

        <property name="Target"><ref object="pipeLineTarget"/></property>

        <property name="Interceptors">

            <list>

                <ref object="transactionInterceptor"/>

            </list>

        </property>

    </object>

 </objects>

 

测试代码

Dim ctx As IApplicationContext

    Set ctx = CreateXmlApplicationContext(App.Path & "\pipeline7.xml")

   

    Dim dataSource As IDataSource

    Set dataSource = ctx.GetObject("dataSource")

    Dim sql  As adotemplate

    Set sql = New adotemplate

    Set sql.dataSource = dataSource

    Dim count As Long

    Dim c As String

    c = "select count(*) from customerinfo"

    count = sql.ExecuteScalar(c)

   

    Dim p As IPipelineComponent

   

    Set p = ctx.GetObject("pipeLine")

   

    oTestResult.Assert (p.Execute(Nothing, Nothing) = "success")

    oTestResult.Assert (sql.ExecuteScalar(c) = (count + 2))

   

    count = sql.ExecuteScalar(c)

   

    '增加一个回退

    Dim r As RollbackMock

    Set r = New RollbackMock

     Dim pl As pipeline

     Set pl = ctx.GetObject("pipeLineTarget")

    

   

    Call pl.PipelineComponents.Add(r)

 

   

    oTestResult.Assert (p.Execute(Nothing, Nothing) = r.Result)

    '没有变化

    oTestResult.Assert (sql.ExecuteScalar(c) = count)

   

    destroyxmlapplicationcontext ctx

[2005-02-19 20:46:21 | Author:jiangjianxiao ] [] 1 comments

Total 91 Display 61 of 65
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Powered by Google App Engine