摘要
因为业务需求迅速变化着,规则也每天都在变化。如何处理这些变化,从而使我们的系统更加有效的可维护、可重用和可扩展?如何为这些规则建模以及处理(表现)它们,以得到系统更大重用性、可维护性和性能?
业务规则的变动往往比它们关联业务对象的其他部分要频繁,这些规则通常在一个业务对象的规则方法中实现,并且它们也引用该业务对象周围相关的其他业务对象,这就建立了一个隐含的网络,它们的依赖关系日益增加并难以维护。这种情况下,改变一条业务规则会影响一系列依赖该规则的对象,特别是当实现一条规则的代码在分散类的若干方法中,甚至是若干协作类的方法中,平均信息量就将大大增加。缺少集中控制导致了波纹效应,并且改变一条规则的if-else语句元素还会产生副作用。
规则对象模式语言包含18个模式,这里只介绍主要的模式:规则对象以及另外两个与之密切相关的两个模式,判定器(Assessor)和动作(Action)。其他模式只作简单描述,不在本文重点讨论。这个模式语言权衡上面问题域的先决条件,并提供一组连贯且不断展开的模式,来描述处理可伸缩性、可适配性和复杂性的渐进需求。对于服务提供商域模式(Service Provider domain pattern)的上下文环境中[Arsanjani99a;b],规则对象模式语言可以作为解决问题的最佳方案。
简介:规则设计
在软件系统中大部分都会涉及到规则,有各种各样的类型、领域和规模。为了不断满足新的需求,这些规则经常需要改变。很多作者曾提出了规则分类法,特别是针对业务规则[Odell96]的分类方法。此外,随着日益加快的业务变化和演进,规则也在不断进化。
让我们在一种编程语言的上下文中定义一个简单规则,形式为:”if <condition> then <action>
else <action>”。如果用这些嵌套的if-else语句长链用于代码的不同部分来实现类,将导致代码非常混乱,在需求不断重复变更时,代码实质上是不可维护的。为了避免这种波纹效应,基于面向变更的设计原则[GHJV95],[Arsanjani99;b],我们以一种非统一的形式把那些经常变化的部分封装起来。同时,我们将if语句和case语句链从应用程序的层代码(例如业务逻辑)中解脱出来,使得规则结构能够根据业务需要迅速替换。
这使我们在很大程度上隔离系统,从而避免因改变个别对象或为完成某个业务目标而协作的一组对象而产生的波纹效应。
规则具有多种不同的类型,下面有一个场景列表,提供了定义一种规则分类法的依据,这个分类法是基于复杂度、规模、分层和功能的。特别注意,1-7条指出了依赖于分层的规则类型,8包含一些非层特定的规则列表。
1.
表单上的输入域需要校验是否非空、是否正确的域值等。这里有专门对象来校验,UI可通过向这个应用对象发送一个消息来校验这些数据值。
2.
一组互相依赖的字段必须一起校验。
3.
跨表单的复合数据需要交叉校验。一个表单的数据不应和另一相关表单的其他数据冲突。这里可以使用一个中间人(Mediator)模式来实现,它将检查是否所有的数据都是完整的,而不需要各个表单互相知道对方。
3.1
非法的组合需要识别出来并报告或完全禁止;
3.2
合法的组合要测试它们的有效性;
4
在中间层进行策略检查和业务逻辑验证以形成大批应用(或业务)逻辑,它们可以被多种不同类型的客户端访问:例如PDA、瘦客户端、胖客户端等。
5
在数据层用触发器和存储过程的方式检查规则。
6
中间件通讯规则。
7
多种安全性规则;授权、验证和认可等。
8
不管所在何层,规则都能关联于计算方法(函数、映射、转换,通常可以由数学公式来表示)。例如:当选定一个保险险种(加入到一个保单),它可以与损失原因相关联,这是固定不可改变的。如果一个险种有若干损失原因,当计算该险种保险费时,计算每种损失原因的保险费并汇总。如果一个险种没有损失原因,那么该险种具有$0.00保险费。
8.1
一些规则限制并约束业务过程以及输入中驱动规则的数据;
8.2
一些规则属于工作流顺序、“路由”、“规则”和“角色”;
8.3
一些规则设立“同意条款”或“前提条件”,例如实际中更具法律性质的“服务级别协议”;
8.4
特定类型规则的执行导致“正包含”,如一个险种当且仅当…是合法的。有时,还有“负包含”:例如如果申请者的邮编在<高欺诈区域邮编列表>中时,那么我们将必须做一个预付费者的名片。另外一些时候,我们还有“非法组合”情况,它联合一组条件导致一个非法的状态,如假设申请者欲申请其第二栋房子的购房贷款,而他的主房屋位于高地震率地区且没有保险,再如果他的信用值低于<xxx>值,那么我们将不得不拒绝这个贷款。
9
规则的设计和实现是两码事。例如,规则可以设计成业务约束,而在数据层中,可以使用参照完整性规则来实现,用特定类型的关联/关系来建模。
模式1:模式对象
意图
使计算机化的业务过程设计和实现具有可扩展性、可适配性。通过可插拔的规则,使整个业务过程不因规则的内部变化而受到影响。
动机
例1
写一个程序实现温度的摄氏与华氏之间的互相转换:FàC和CàF。
第一眼看来这非常简单。实际上,在实现这个设计过程中,有很多“规则”出现。输入一个数,点击OK按钮,在另一个文本框中显示另一种单位的温度值。可以实现如下:有两个文本框(一个用于显示摄氏,一个显示华氏)和一个按钮(用于开始计算)。你可以在某个文本框中输入数值,并点击“计算”按钮。计算过程要检查在另一个文本框中是否已经有一个被转换的值,如果有,它是否是一个合法的转换;如果不合法(或没有数值),重新转换;否则,弹出一个信息提示“已经转换。”
所以,在真正转换前,需要做一些验证和检查。对象经常不会“无知地”执行某些动作(执行方法),更确切地说,通常要检查一些条件,如果那些条件满足了,对象才能执行动作。当然,对于更大型软件系统中更重要的问题,这是一个有点微不足道的示例。
接着,我们决定加入开氏温度转换。首先检查特定文本框中是否已经输入值并且另外两个是否为空。如果其他文本框非空,要提示用户清除。而在提示清除文本框内容前,如果它们不是合法的转换,那么它们将无法获得焦点。
想象一下如果不是温度值,而是货币值,那么问题是类似的,且解决方法也是类似的。
例2
你正在设计一个财产保险的应用。保险公司有一些客户(保户),它们购买了一些保单。每份保单都关联着若干保险险种,以针对某些损失原因(COL)来保护客户的建筑物和其他财产。建筑物以位于一个地理区域为前提,保险险种可以分成四种:房屋保险、个人财产保险、Debris Removal和Special Provision,每种分类都有一组COL关联着。
这个需求的核心部分是业务规则,你会发现无数的规则操纵着一个业务应用程序,这个也是一样。这些规则往往分散在各个层中,如GUI、支持业务过程的中间业务逻辑层和数据库层,每层都可以有各自的规则集合。
让我们看看一些对域问题无需过多说明的业务层规则:
Ÿ
没有重复的保险险种;
Ÿ
为了在保单中增加一个个人财产保险,必须已经存在房屋保险;
Ÿ
或者加入InflationGuard保险, 这是一个附加的险种,可以用于当房屋保险是保单的一部分,并且没有包含一个Debris Removal保险的情况;
Ÿ
保单中不应包含重叠日期的险种;
保单、险种和单个险种关联的规则经常发生变化,为了确保市场份额和收益,需要经常改变这些规则以适应迅速变化的业务需求和竞争。为了快速改变业务需求、分析不变量(analysis invairiants)(全体业务规则)的设计,我们需要在不产生副作用的前提下定位和修改规则。具有代表性地,我们也许希望在以后某个时间恢复规则先前的某个条件,例如当一种优惠过期。因此,为了适应需求的变化,必须要让规则能够可插拔,可适应。为此,必须要很好的组织规则。如果规则分散在应用程序的各个角落,那么改变它们和避免副作用的代价会很昂贵,那样不仅麻烦,在某种程度上,根本就是不可行的,这样业务过程的可适应性也遭到破坏。
使用一组硬编码的if语句来构成规则是难以维护和改变的。所以我们建立了一个小型框架,允许我们进行规则检查,它可以根据业务需要,对条件和动作进行插拔。
不过规则一般不需要全部改变,有时仅需要增加新条件来增强原来的条件判断,新的和修改过的动作需要被替换或重新组合成新规则。因此,为了可插拔性的要求,我们对规则进行组件化。我们从封装它们的要素部分开始:条件(condition)、动作(action)、传递给它们的属性(property)(上下文)、结果对象(result),它们构成一个规则对象,它本身就是一个可适应的组件。
这让我们可以迅速改变、扩展、重组和重用规则、规则要素以及组件。例如,为了检查一种新的财产保险类型,可以方便地插入一些条件,如个人财产。如果新条件应用了,我们将执行一些动作,即使是一些简单的,如显示一个警告信息告诉用户它们输入了一个日期重叠的险种,因此不能加入到同一个保单中。
典型业务规则实现
规则在一个对象的实现中通常位于多个方法的函数体里,它们通常是分散的,并且通常是嵌套的if-else语句,这都造成代码混乱且不够智能,且难以维护。
规则作为方法(规则方法)
为了可以适当地实现,首先将确定下来的业务规则分列出来、分类,然后实现成一个单一方法,常常返回一个布尔值表示这个规则是否应用。如果规则过于复杂,可能会调用其他的规则方法,并试图和其他业务对象进行协作执行它们的规则判定。例如,在财产保险例子中,有一个叫Debris Removal的保险,这是一个附加的险种,可以用于以下情况:
Ÿ
保单中已包含房屋保险;
Ÿ
保单中没有通货膨胀保护;
Ÿ
对业务服务没有应用SpecialProvision,并且DebrisRemoval限度已经增加。
规则对象对等模式(Peer Pattern)概览
对等模式
规则对象在判定器(Assessor)、动作(Action)、属性(Property)(上下文(Context))和结果之间充当中间人(Mediator)的角色。规则对象决定哪些Assessor用于哪些Property的判定,并在判定结果中产生可能的Result。如果判定成功,相应的动作将被调用,这可能会更新或改变属性的状态,结果也将记录入一个动作结果对象。
判定器
Assessor封装一组输入Property并基于它们进行条件判定,并将判定结果记录在它们的AssessmentResult中。Assessor类似于命令(Command),一个Command执行一个操作,而一个Assessor基于输入状态或属性进行条件判定,不同的地方在于它拥有一个evaluate()方法而不是execute()方法,并且与一个属性列表、一个错误日志(ErrorLog)或结果集一起协作。
图2:判定器(条件求值器)
动作
一个Action通过状态的更新或调用其他协作对象的行为执行,执行结果将记录入一个ActionResult对象中,这个对象可以包含一个ErrorResult对象或ErrorLog对象。动作对象可以按策略(Strategy)模式实现,也可以按访问者(Visitor)、解释器(Intepereter)、命令(Command)等模式实现。下面是它的集群结构:
图:Action 模式
动作对象和判定器对象的关系如下:
图:动作和判定器
规则对象的渐进展开:简单规则对象
如何提供一种可以用于简单情况的设计同时又具备一定的伸缩性呢?规则对象根据复杂度的渐进展开可以得到最好的理解,平衡各个附加层的复杂度和功能性。
通过透明地嫁接规则对象,能让规则对象可扩展地、可适应地管理对象行为。每个规则对象表示一条对象在域中必须实施的规则。对象动态地管理它的规则对象,以独立的对象来表示规则,不同的业务过程流与管理规则以及它们的交互都互相独立开来,这样改变它们的过程就简单的多了。
规则对象从一个简单的校验器入手,它使用方法来进行条件判定和动作执行,校验器有一个判定条件的方法,当所有条件值为真值,那么调用一个和更多的动作方法。
图:校验器
下一步是一个简单规则对象。现在你发现自己开始不得不定义数量很多或复杂的方法来判定条件,并且这些条件有些还正在变化。所以,你想将这些方法封装一下,以易于修改,动作也是如此。所以决定将条件抽象成Assessor对象,让动作成为Action对象,规则对象作为Assessor和Action的中间人([GOF])。这可能会使用一个工厂模式(Factory)(构建者(Builder)或抽象工厂(Abstract Factory),亦或是单单工厂方法(Factory Method))从序列化对象[Riehle98]来创建合适的Assessor和Action对象。另外还有一些在规则集群模式语言中的模式,解决对规则对象、或它所包含的Assessor及Action对象进行“哈希和缓冲(Hash&Cache)”的问题,以支持按需创建或插入规则对象。现在你得出了如下设计,请注意SimpleRuleObject是一个抽象类,Assessor和Action可以是接口和抽象类,具有一些缺省的行为。
图:有判定器和动作的简单规则对象
第三级复杂度/伸缩度可以满足经常改变需要进行判定的字段或属性的目的,使用Beck的可变状态(Variable State)和Yoder&Foote的属性列表(Property List)模式可以很方便地创建一组名-值对,把它们传入到Assessor中进行判定。这样Assessor的代码也不会因引用个别(视图级)的对象实例而过于混乱,它将引用一组模型级的名-值队实例,反映输入到规则对象Assessor中的字段和属性集合。同时也可能发现你的Action对象需要以某种方式更新字段,如将一个州名缩写扩展成全名,根据一个邮编得出州名等,另外一些步骤的复杂度将可以得到如下的设计:
图:具有Properties的简单规则对象
第四步是你何时需要记录和返回智能的信息以表示处理的结果。这样,当一个规则对象判定了一组属性,结果需要被记录成一个AssessmentResult,例如:
if( assessor.evaluate(“BillPayMethod”) )
action.performAction();
else
assessmentResult.addErrorFor( “BillPayMethod”);
用户更关注在它们的数据录入时什么地方出错了,以便于纠正。还有很多更多原因需要记录和报告结果,例如调试(为程序员)、性能(架构师)、数据挖掘(市场、业务人员)、问题解决(用户)等等。
下图是对简单规则对象的简单的装饰:
图:具有Result体系的规则对象
这个模式实现的变种包括规则对象拥有指向AssessmentResult和ActionResult的引用,而不是通过Assessor或Action对象引用。
让我们将上面类的集群称作简单规则对象集群,表示如下:
图:规则对象集群
这意味着集群充当一个facade的角色,拥有方法applyRule(),以及要素成员或协作的规则对象、判定器、动作、属性和结果对象。这个集群可以实现成一个组件,可以相应调用参与者的公共方法。
复合规则对象
至此,我们已经讨论了组成一个简单规则对象结构的四个复杂度级别,对于更复杂的情况,当一个组织想存储规则并能够访问一组可重用的条件(判定器)和动作时,可以使用合成模式(Composite)来设计这样一个复合规则对象:
图:复合规则对象,视图1
下图是一个更加展开的视图:CompoundRuleObjectCluster包含一个复合抽象规则对象。它的叶节点是简单规则对象,这在上面已经讨论过了,它的部件可以包含嵌套的规则对象聚合结构,这些规则对象可能合成Assessor和Action对象。这种更加复杂的方案用处之一是用来描述类似电信条款的规则,这需要一组嵌套的规则对象,以及它们相应嵌套的判定器和动作对象组。
图:复合规则对象集群
先决条件
Ÿ
业务需求的改变致使规则设计实现的变化。业务规则通常比业务对象其他部分的变动更加频繁。在分析、构架、设计和实现阶段,业务规则可能需要重新定义和更新以反映策略和业务的变化,并且随着新业务需求增加而不断演进。改变已有的规则必须保持未用规则的完整性并与之保持一致的状态,一致性意味着对一组条件的修改不会影响到系统的其他部分从而造成不必要的副作用。
Ÿ
规则也许对时间非常敏感。当规则在一个受时间限制的域中就是如此。例如,应用于服务提供的规则只在某个限定的时间段内有效。在这种域中,拥有一些迅速扩展和变化的规则,它们可能频繁改变,甚至有时候可能每天都会变化。虽然在程序中改变规则是有必要的,但是代价也颇昂贵,这种修改通常扰乱代码并具有副作用,而且需要进一步的调试和测试。进行扰乱代码的修改是不安全的并且代价昂贵,潜在的副作用导致需要更广泛的回归测试和组件的重新鉴定。
Ÿ
规则可能被集中化,以使它们易于定位和修改。通常,规则是分散在设计和实现过程中,一般都是if-then-else语句的形式,并且伴随很多依赖关系。把规则需求跟踪到规则的设计和实现阶段中,将是一个非常易错的并且是资源密集型的项目。
Ÿ
规则可能被非集中化,将它们分配到相关的类和集群中。对象具有manner,manner管理对象的哪些方法被调用,以确保状态集的一致性和合法性。这样,规则可以分配到类中,每个类(业务对象)有一组方法,规则管理它们合法的状态,以及发送给协作对象或自身方法的合法消息序列。规则通常检查一个对象或一组协作对象的状态,操作于在上下文中提交给它们的数据,我们需要跟踪对规则应用于这些数据的状态。规则应该知道相关的属性(数据),但是对于从业务角度,这些属性应该被那些用于检查它们完整性的规则所忽略。
Ÿ
规则应该具备伸缩性。为一个小型应用而设计的规则结构应该也适用于一个更大型应用的需要和非功能性需求。初期,它们可能只应用在很小的环境中,而很快就需要处理更大规模的事务,因此,规则的设计要求简单而又可缩放。
Ÿ
规则类型的非统一对待。对不同类型规则的不同对待导致很多不必要的后果,因此需要尽量一致对待,不过也要注意每个业务对象都具有各自的manner。从不同角度看,有不同类型的业务规则应用于不同的应用层,如用户界面、应用逻辑层和持久层。规则要对所有类型有一个整体的视图,无论它们是否是一组数据元素,都可以在应用服务器的中间层来确定(报告)它们合法和不合法的组合、值或复杂的业务逻辑。
Ÿ
代码混乱:嵌套的if-then-else语句扰乱了代码,使之难以维护。规则经常通过嵌套的if-then-else结构来实现,但实际上规则的增加是一段一段的,并非自顶而下的设计。
Ÿ
架构分层和规则:不同的规则可以应用于架构的不同层中,规则可以应用每个不同的层:GUI、Webserver、应用层、中间件、数据库等。位于不同层中的规则有不同的实现机制,如GUI中的简单校验、应用层中复杂的交叉规则检查、数据库层中的存储过程和触发器。但是它们基本的结构要类似,以易于跟踪和一致的应用。
Ÿ
业务域包含由一组业务规则集控制的业务过程。这些规则从公司的策略、工作流、操作和手续过程中提炼而来。虽然要求是标准的过程,但仍然经常需要改变。这些规则通常设计于那些要随着业务需求变化而改变规则的信息系统中。
Ÿ
规则由管理层创建且是可见的,规则以及规则修改对程序员也要是可见的。在业务人员改变业务规则时,开发人员将它们实现成代码,管理层必须知道哪些规则已经实现了。
Ÿ
规则是一个域中的对象相互交互和改变状态的合法途径,它们应该被看作是对象思维模式(object paradigm)最好的概念,并且早在分析过程中就要被标识出来,而非事后的想法。在这个新的思维模式中,除了有对象标识、状态和行为,一个类还有manner(控制行为或方法的规则)。Manner是规则加上方法(对象行为),再加上应用规则和对象中控制交互和协作协议的元数据。
应用性
使用规则对象
Ÿ
为业务对象动态且透明地增加/修改条件、动作和规则,就是说,增改时不会影响其他对象和规则。
Ÿ
当复杂性和规模使得规则方法不可再用时,规则可以通过if语句或返回布尔值的方法(如isCompatible())实现。而当它们增长到不再是简单的检查时,就要将他们实现成规则对象了,特别是需要经常改变规则时更为适用。
Ÿ
当通过实现需求变化来维护一个系统时,需求可以被分析成规则、不变式(invariant)、业务需求(谓词和条件)、策略、协议条款等,这样我们可以进行不扰乱代码的修改。重用现有的条件和动作来创建一个新的规则,只稍稍异于现有的规则,但是它只在短期内起作用(高度易变的需求)。
Ÿ
业务对象应该知道它们自己的manner:如何使用它们的方法和其他协作的业务对象一致工作,什么是合法的状态,什么是业务对象间非法的状态组合。这样,控制方法使用的规律,需要元数据来存储这些信息,以及和具体表现这些规律的条件和动作一起称作业务对象的manner[Arsanjani99;a]。规则不是作为每个业务对象中逻辑通过它的表达式来单独访问,而是被整理在各自的对象中,以利于非干扰式的修改和插拔,因此业务对象将包含一系列业务规则。
Ÿ
当描述一组业务对象的特性时,需要根据不同规模和复杂度的限制进行改编和定制,规则对象可以对GUI数据录入字段简化成一个简单的校验器,或者可以升级到一个具有复合判定器(条件)和动作,具有属性和结果的合成规则对象,下面有完整的图示。
结构
业务规则要比业务对象中其他部分的变动要频繁,如果这些规则封装在自己的类中并且分离控制,插拔地使用或重用,那么这些变化可以减少到最少。因此,我们对规则和它们的条件、动作进行抽象,以使它们可以互换、可以插拔。
通过对规则对象的非干扰式修改来维护规则,增加和修改已有的条件和动作,通过使用规则对象增加属性(上下文)来检查合法或非法的状态组合。一个规则对象集群是条件(Assessor)和动作,以及一些辅助类的合成物。辅助类包括提供“字段”的名-值对,这些“字段”用于条件(Assessor)的判定,判定结果记录在一个Asscessment Result中,如果这些条件都应用了,那么Action将被执行,以改变对象或其他对象的状态。这些动作产生的结果记录在一个Action Result中,以供报告或分析用。
图:复合规则对象
参与者
Ÿ
抽象规则对象
定义对象的接口,可以让规则动态地加入。
Ÿ
具体规则对象
定义对象,让附加的规则可以加入。
Ÿ
规则对象
条件和动作的中间人,应用规则
Ÿ
判定器(Assessor)
检查条件并存放结果
Ÿ
动作(Action)
基于相应判定器的成功或失败执行动作,记录结果并且改变协作对象的状态
Ÿ
属性(上下文)
传入的信息或状态,用于条件判定(判定器)或状态更新(动作)。
Ÿ
结果(Result)
提供一个AssessorResult、ActionResult、Error的父类,其他的参与者可以在此记录结果并报告给客户端。
Ÿ
AssessorResult
Result的子类
Ÿ
ActionResult
用于记录动作结果,Result的子类
Ÿ
MediationStrategy
在复合规则对象、判定器和动作的情况下,决定我们将如何执行判定器和动作,或是规则对象。
简单规则对象:静态视图
图:规则对象及协作者的静态视图
复合规则对象:静态视图
规则集群持有合成规则的一个集群,每个合成规则都有它们各自可能的合成条件和动作。一个简单规则是合成规则的一个叶节点,它可以独自存在来处理一些诸如GUI字段的编辑和校验。
图:复合规则对象和合成体系的静态视图
下面还有一个复合规则对象的另一种视图,着重表现规则对象以及它的组成要素的动态可插拔性,而上图主要表示它的合成结构。
图:复合规则对象的另一种视图
协作
下面是一组示例协作:
1. 设定规则(例如缓冲到一个哈希表中:“缓冲和哈希”)
2. 提交候选的状态(传入属性或哈希表,或仅将你想检查一致性的状态作为参数)
3. 使用判定器。基于提交的状态检查条件,一个判定器通常会遍历列表或哈希表以检查每个条件,或者有一个策略,使用某种算法来检查每个条件,最简单的就是一次循环进行。你可以通过创建、扩充一个特定判定策略来选择或定义你自己优化的判定器算法。
4. 对每个判定器组,会有一组对应的动作组,它们必须被执行,或有一组ResultState或ErrorLog对象,必须写入、创建或反馈,对于不重要或未实现ErrorResult或ResultLog的情况,它们可以是Null对象。
下面的时序图描述了上面设定规则的步骤:
图:设定规则
下面是一些缺省协作的变种:
规则对象的一个策略(多对多)
if(assessorList.assess(instate))
actionList.perform(inState, outState);
else
errorLog.reportOutcome(outState);
|
简单策略:(一对一)
if(assessor.evaluate(instate))
action.performAction(inState, outState);
else
errorLog.reportOutcome(outState);
|
合法的组合:
一个组合是一个状态,此状态耦合于一个策略和一个规则对象。当规则应用了,一个初始化的InvalidState状态迁移到ValidState状态,否则,ErrorLog记录非法状态错误和原因 |
非法的组合:
如果这里inState是一个InvalidState状态,那么,调用errorLog.logThisAsInvalidState()
遍历所有的规则对象(使用特定或缺省的策略),看这个inState是否匹配一个InvalidState组合,如果是,reportInvalidState
outState = assessor.assess(inState);
if(outState.isInvalidState())
errorLog.reportError(outState);
else
continue;//继续下一可能非法状态的检查 |
结论
Ÿ
规则变得更易于改变和重用,简化了维护工作,非干扰式的修改可以用来维护规则以及它们的判定器和动作。单独的判定器和动作可能在多个不同的场景中被重用。以这种方式构建的系统更加符合开放封闭原则(open/closed principle)[Meyer84]。对业务规则的改变将很少有波纹效应,它们封装在一个规则对象中。通过在一个规则对象的上下文中创建复合规则,或在简单规则的情况下改变检验器的策略,就可以通过增加更多的规则对象来得到一个新的规则。
Ÿ
规则可伸缩性提高了。对于规则、判定器和动作的合成物,它们可以存储在数据库中,或者根据增长量、需求和适用性进行缓冲,与单个规则的增长相对称。
Ÿ
规则类型的统一对待。设计和实现业务规则没有一个最好的方式,实际上,规则具有多种类别:业务规则、校验规则、使用规则、协作规则等。校验规则多用于GUI,一些输入域是必填的,有些输入域的值必须在某个范围中,有些则与其他输入域有相关的依赖关系,还有输入特定的值,其他的某些输入域变成有效或无效等。业务规则检查合法的输入组合及非法组合。每一层的规则都以相似的方式对待,即使每层在定制规则时都有它们各自的独特性。例如,在GUI中校验一个文本框可以不用合成的规则,而使用简单的校验器。而一个具备合成判定器和合成动作的合成规则可以用来实现一些客户关系和电信条款的需求。
Ÿ
规则变得易用测试。对业务规则严格要求也意味着每个捕捉到的业务规则必定有测试手段。
Ÿ
需要新的子系统来管理规则对象,并且让特权用户来改变这些规则。
Ÿ
公司的策略库。规则对象为软件开发组织或公司提供了一个集中的规则库这个基础设施。虽然规则(和公司策略)分散在组织的结构中,它们可以被集中管理和浏览,在一个集中的地方进行定义和修改,允许所有对规则或规则类型感兴趣的人收到它们改变的通知,这可以通过观察者(Observer)或发布者-订阅者(Publisher-Subscriber)模式实现。
Ÿ
规则对象库可以让公司的业务主管通过基于GUI的规则浏览器来定义和操纵规则和策略,接着,它们可以发布到整个组织结构中,这样,负责不断将规则实现成业务对象的开发人员有了共同基本原则和可追溯的参考点。
实现
考虑下列实现问题:
Ÿ
避免将简单的规则的逻辑放入规则对象,除非它们需要经常变化。在规则不需插拔、适应或扩展的情况下,规则方法是一个更简单的方式。
Ÿ
重用现有的条件和动作相对于重用一个规则对象而言,是一种更好的选择。
Ÿ
设定规则对象可能需要一些工作,不过一旦框架设置完毕(见下面的代码),那么定义新规则、动作和条件将非常简单。
Ÿ
策略模式可以用来检查规则,因为可能会有一组相关的规则系列,根据特定对象的状态而应用。命令模式也可以用于检查规则,或在条件检查后执行一个动作。判定器抽象了一组需要检查的条件。例如,一个集群的状态由组成它的对象状态构成,每个状态都需要进行合法条件检查,或可选地检查非法条件的存在,因此需要检查合法与非法条件的排列。
Ÿ
规则应用的结果。规则对象的应用可以被跟踪,表现成规则应用的结果,因此它们的条件和动作都需要日志记录下来。当然为效率考虑,可以禁止为每个简单条件和动作组进行日志记录。
Ÿ
规则复杂度:规则可以很简单,或很复杂(复合),它们可以看作合成物,或简单的校验器。可以使用一个合成、策略或命令模式来实现。
在业务规则设计中较少考虑到的一个因素是错误处理是与之紧密耦合的,这非常重要,也许只是为了知道为什么一条规则失败了或成功了,因此,错误处理将作为实现部分考虑的一部分。
实现指导
每个业务规则封装在它们各自的类中,可以是简单规则对象或是复合规则对象。一个合成的规则对象由合成结构构成,包含判定器和动作。RuleObject包含一个合成判定器、一个合成判定器和ErrorResult,ErrorResult是为了记录在判定器求值后,禁止规则触发动作的错误。一个判定器类似于命令,他有一个方法”assess()”,返回布尔值,如果是一个合成结构,判定器在返回true前必须成功判定所有的构成元素,如果有一个问题,判定器就将一个错误信息/条件记录入ErrorResult。
如果所有的都没有问题,判定器也已经求出条件的值为true,接着将执行动作。动作是一个命令,也可能是一个合成结构,会改变当前正使用或包含规则对象的客户对象的状态,还有可能是和其他对象协作,根据判定器的求值结果为系统创建一个合法的状态。在任何时候有错误发生,它们都将记录到动作对象引用的ErrorResult对象中。
一个规则对象通常构成一个集群,这个集群由RuleObject、Property、Assessor、Action和Result构成。传入RuleObject的属性或状态被判定器进行判定和求值。如果这个判定成功,RuleObject(在Property、Assessor、Action和Result之间充当中间人)将判定结果放入结果对象中,然后在向Action请求执行,以改变某些Property的状态。
动作执行的结果也放入Result对象,这可以为一些实际情况提供元数据。例如,一个系统状态发生变化,属性通常从一组协作或单一对象的属性值封装成状态。因此,我们不仅只想对一组属性进行判定,根据结果执行动作,我们还想得到求值的中间结果和动作执行的中间步骤,这些信息记录在Result对象中。状态或属性组,以及那些作为判定器求值基础的值,把它们传入规则对象或规则对象集合中,并创建它自己的Property对象,再在需求驱动的基础上,把它们传入它的Assessor和Action对象中。
示例代码
这里有一个应用于GUI层的简单规则对象例子,虽然如此,规则对象可适用于所有的层:应用、协议和持久层等。
class BusinessRule implements ActionListener{
private JDialog theDialog;
private Frame theFrame;
public void setTheDialogJDialog( aDialog){
theDialog = aDialog;
}
public BusinessRule(){
}
public boolean assess( int
number ){
if (MIN_SERVICES <= number &&
number <= MAX_SERVICES )
return true;
return false;
}
public void actionPerformed(ActionEvent event){
if (event.getActionCommand() == “SubmitButton”){
theDialog.dispose();
}
}
} |
这是一个最简单的例子,规则对象仅仅是一个类,作为一个Listener使用,每次在对话框中的一个输入域按键,assess()都将被调用来判定是否输入了合法的值,这个例子中将检查它的范围。
下面是使用的实例
/**
* 注册输入人数的输入域
* @param field 输入人数的文本框
*/
public void registerNumberField( final JTextComponent field){
numberOfServices = filed;
DocumentAdapter documentAdapter = new DocumentAdapter(){
protected void parseDocument(){
int count = 0;
try{
count = Integer.parseInt( filed.getText());
} catch (NumberFormatException e){
}
if (rangeRule.assess(count))
serviceCount = count;
else
serviceCount = 0;
} // parseDocument()
};
}
将规则对象注册成ok按钮的listener
public void registerOKButton(final JButton btn){
submitButton = btn;
submitButton.addActionListener(rangeRule);
submitButton.setActionCommand(“SubmitButton”);
rangeRule.setTheDialog(myParentDialog);
} |
示例代码:规则对象框架
定义一个规则对象,包含它的Assessor和Action,Property和一个ActionAssessor Map辅助决定哪个Action和哪些Assessor相关。
public abstract class RuleObject
{
private Vector assessorVector;
private Vector actionVector;
private RuleProperties ruleProperties;
private ActionAssessorMap actionAssessorMap;
public RuleObject(){
actionVector = new Vector();
assessorVector = new Vector();
actionAssessorMap = new ActionAssessorMap();
ruleProperties = new RuleProperties();
}
public Boolean applyRule(){
Boolean assessorReturns = true;
Assessor tempAssessor;
Action tempAction;
//
Vector tempVector = getAssessors();
Enumeration e = tempVector.elements();
// 对每个判定器,调用求值方法返回值
while (e.hasMoreElements()){
tempAssessor = (Assessor)(e.nextElement());
assessorReturns = assessorReturns &&
tempAssessor.evaluateAssessor(getRuleProperties());
// 决定这个判定器是否有动作执行
Vector tempActionVector = getActionForAssessor(tempAssessor);
if(tempActionVector != null){
Enumeration tempEnum
= tempActionVector.elements();
while(tempEnum.hasMoreElements()){
tempAction
= (Action)(tempEnum.nextElement());
tempAction.performAction(getRuleProperties(),
new Boolean(assessorReturns));
}
}
}
return assessorReturns;
}
} |
已知应用
本文作者曾参与的多个团队的多个项目中都使用了规则对象。包括电信领域(客户关系和计费),医药保健、保险、汽车高等教育、销售、以及电子经纪等领域。
规则对象已经用于Java业务框架[Arsanjani99b]的实现中。
IBM San Francisco用到的策略公共业务对象和模式(Policy Common Business Object and
Pattern),使用类似的概念。
规则对象被Paul Corazza应用于“If-Then-Else”框架中。
IBM WebSphere Application
Server 企业版中,组件代理的Managed Object 框架中实现了规则对象。
David Taylor在它的对象杂志专栏中提到对业务规则相似的结构。
相关模式
对等模式
规则对象作为判定器和动作,属性(上下文)和结果的中间人,决定哪些判定器应该用于属性的判定,并可能将结果记录入一个判定结果中。如果判定成功了,相应的动作被调用,这可能会更新和改变属性的状态,它的结果也会被记录入一个动作结果中。
其他模式
规则对象使用了若干个基本设计模式。因此它可以被认为是一个复合模式(或一个合成模式),但是并不是所有的复合模式就是模式本身,复合模式仅仅是一个名字空间,标记一组在很多不同场合下重复使用的模式集。为了实现Condition参与者,我们建议使用一个Assessor[Arsanjani98],为实现action(简单或复合的),使用Command。复合本身是一个Composite模式。SimpleRule使用一个Strategy来实现它的Validator参与者。规则集群有一个Builder,它使用一个Abstract Factory来创建规则、条件和动作的单个实例。
Assessor实际上是Command的一种特殊形式,在多种环境中可以发现,它并非是执行一个命令,而是有一个assess()方法,返回一个布尔值(在简单判定器的情况下)或者返回一个合成物(在复合判定器的情况下)。判定器还可以进一步实现成Interpreter模式,如需要判定一个“规则串”的合法性的时候,即一个字符串包含一个“规则语言”的句子。
规则对象和Visitor相联,在很大程度上共享了下面的适用性:很多不同且无关的操作需要在对象结构中被执行,你可能不想让这些操作”污染”你的类,Visitor让你将相关的操作放在一起定义在一个类中。当对象结构被很多应用共享时,使用Visitor就可以让只有使用特定操作的应用才拥有那些操作。
另外,判定器还可以使用在面向语法编程(Grammar-Oriented Programming [Arsanjani89])的环境中,它的域分析确定了一种域语言。这种域语言以域语法来进行描述。域对象的交互完全由域语法来描述。触发协作、触发域语法和消息的用例以输入流的形式传入到解析器中,这个解析器负责解释和分解语法。对象的manner以元模型来描述并表示为语法。
参考
[Arsanjani99;a]
Ali Arsanjani. "Service Provider: A Domain
Pattern and Its Business Framework Implementation," presented to
PloP '99. http://st-www.cs.uiuc.edu/~plop/plop99/proceedings/Arsanjani/provider3.pdf
[Arsanjani99;b]
Ali Arsanjani. "Analysis, Design, and
Implementation of Distributed Java Business Frameworks Using Domain
Patterns" in Proceedings of Technology of Object-oriented Languages
and Systems 30, IEEE Computer Society Press 1999, pp. 490-500.
[Arsanjani89]
Concepts of Grammar-Oriented Programming, Azad
University
Technical Report, 1989.
[Corazza] Paul Corazza. Using the if-then-else framework, Part 1: Code
maintainable branching logic with the if-then-else framework. Available
at: http://www.javaworld.com/javaworld/jw-03-2000/jw-0324-ifthenelse.html
[Fow96]
Martin
Fowler. Analysis Patterns. Reading,
MA:
Addison-Wesley, 1996.
[GHJV95]
E. Gamma, R. Helm, R. Johnson and J. Vlissides. Design Patterns: Elements of Reusable
Object-oriented Software. Reading,
MA:
Addison-Wesley, 1995.
[MRB98]
Robert
Martin, Dirk Riehle, and Frank Buschmann (eds.). Pattern
Languages of Program Design 3. Reading,
MA:
Addison-Wesley, 1998.
[Odell96]
James
Odell and James Martin, Object-oriented Methods: Pragmatic Considerations.
Prentice-Hall, 1996.
[Riehle98]
Dirk
Bäumer, Dirk Riehle,
Wolf Siberski, and Martina Wulf. "Role Object." In Proceedings of the 1997 Conference
on Pattern Languages of Programs (PLoP '97). Technical Report WUCS-97-34. Washington
University
Dept. of Computer Science, 1997. Paper 2.1, 10 pages.
[VCK96]
John
M. Vlissides, James O. Coplien, and Norman L. Kerth
(eds.). Pattern Languages of Program Design 2. Reading,
MA:
Addison-Wesley, 1996.
附录A:规则对象:一种用于可适应、可伸缩规则设计和构建的模式语言(管理)
下表是本模式语言的模式的总结,为每种模式提供初始定义和上下文环境,下一节列出此模式语言的模式图,表示各个模式之间的关系,并给出转换标准(从一种模式到另一个模式),以及在转换前和转换后所遇到的问题。
ROPL 模式
1、规则对象-为业务过程提供扩展性和适应性,不会因干扰式的修改而影响,使用规则可以插拔使用。 |
2、Assessor-基于一组输入的属性进行条件求值判断,记录求值结果。 |
3、Action-在条件判定后执行动作,记录结果并更新属性以及相关协作对象的状态。 |
4、Rule Cluster-组件化规则对象的合成定义和应用,通过一个规则应用策略的定义优化规则应用。 |
5、Rules have State - 维护规则检查和应用之间的状态 |
6、Rules are Tracked - 跟踪历史、修改和条件/动作对。 |
7、Document Rules as Patterns-把规则捕捉成模式,跟踪报告问题解决方法和结果的原因 |
8、Rule Object Repository-把规则集中在公司知识库中 |
9、Rules Access Rights-经理应该能够创建规则,提供访问权限,以控制不必要或滥用的规则。 |
10、Rules Change Process - 新规则影响旧过程 |
11、Clusters Have Manners-协作对象的集群具有管理对象行为的规律以及这些规律的元数据。 |
12、Rules as First-class Constructs
- 基于对象“manner”进行分析和设计 |
13、Rules as Production Rules作为应用域语法的产品规则-面向语法的对象设计;为某种域设计一个域语言,使用解析器来实现,接收来自域中的应用程序输入。 |
14、Persistent Rules-以数据方式处理子类和对象的增殖。 |
15、Hash and Cache-当子类和对象数目增加很多时,提供高效、快速的访问。 |
16、Remedy Rule Proliferation-处理对象增殖综合症。 |
17、Rules Evole-为业务拓展进行规则演进 |
18、Rule Change Imapct Architecture-信息系统构架、功能和非功能性需求受规则改变影响。 |
模式语言图
规则对象模式语言能够简要地用以下方式来表述,接着我们来看看每个模式之间地关系,以及如何转换到另一个模式。
注意有些模式表面上是小型模式语言:规则对象由Validator、简单规则对象、Assessor、Action、ErrorResult、Property、复合规则对象和MediationStrategy组成。(表示用什么算法来依次应用规则、条件或动作,这可以是简单的循环或更复杂的算法,如Rete算法,缺省情况是简单循环。它将遍历规则列表,并依次应用。你也许想有一个加权的vector或哈希表,可以有优先顺序地应用规则的判定器或动作,它们可以是集合结构或者是合成结构)
你可以从ROPL模式的不同部分入手,以你自己的方式应用模式来解决问题域的问题,对我们的例子,这里有若干用例,描述本模式语言中模式应用的过程和解决引起问题的先决条件。
用例1:你想构建一个保险应用程序,需要实现作为需求规范中特定部分的业务规则。
用例2:组织想整理它的业务规则,你正收集业务规则并将它们记录成模式,创建一个公司规则库以存放规则。
用例3:你有一个公司规则库,并想配置成通用库。为经理和需要访问库中规则的开发人员分配规则访问权限(Rule Access Right)。当管理变化时,你对规则变化进行日志记录,以满足市场和操作需要。这个库影响着业务如何进行,所以新规则改变旧业务过程(Rules Change Business Process)。当对规则进行改变,架构也受到影响,也许规则集中在一个中间层而不是分散和重复在若干中间层中,如数据库触发器、GUI等。
用例4:在规则对象开发过程中,规则演进(Rule Evolve)和改变可以通过创建新的Assessor和Action,或重用现有对象进行重组来反映新规则和过程。Property被创建成判定器的原始输入,Action可能会更新Property,产生一个Result和ErrorLog向用户报告,或者记录到持久存储中。
用例5:当规则开始增殖,我们通过将相关规则封装在规则集群(Rule Cluster)中,并用简单规则组成复合规则,开始重用Assessor和Action,我们可以把它们哈希和缓冲(Hash and Cache)在内存和一个中间层中,以优化性能。当它们增殖时,我们也许想持久化Assessor、Action和Rule、Property和ErrorResult等,于是创建了持久规则对象(Persistent Rule Object)。
用例6:在使用规则对象的过程中,我们也许需要跟踪规则状态(Track Rule State),这可以通过将它们作为Momento或其他机制来实现,也许一个简单的静态AssessorResult就可以做到。
用例7:当我们进行更多需求分析并创建更多的规则对象后,我们发现可以有协作的类形成集群共同工作完成某个业务目标。这些业务对象组成了集群(Cluster)。这些Cluster让规则管理它们的交互,我们称之为它们的“Manner”,于是Claster和业务对象都具有Manner。
把上面的用例记在脑中,下面的模式语言图将帮助你在语言中选择路径:
图:规则对象模式语言图
序号 |
转换标准、前提 |
1 |
复杂性、可适应性、可合成性、组织结构 |
2 |
规则频繁变化的需要;设计、规则、条件和动作的重用;当复杂度增加时的维护问题;伸缩性。 |
3 |
使用;可互换的条件需要 |
4 |
使用;动作的可互换、重用和重现,如错误信息、数据库更新等。 |
5 |
子类和对象增殖 |
6 |
大量增殖(对象增殖综合症);对对象更快速地访问;需要管理和维护对象资源。 |
7 |
更大量地增殖;更快地访问;将对象当作数据,数据库可以很好地处理它 |
8 |
需要跟踪变化并报告 |
9 |
很多人访问,需要被控制;法律和安全目的;稳定性和控制 |
10 |
公司规则的公共入口;有组织、可浏览;开发团队需要对新规则变化更新的参考框架;动态,交叉域重用; |
11 |
多域重用;产品线架构;创建一个域语法;使用面向语法的对象设计[Arsanjani90] |
12 |
架构和业务过程由规则驱动 |
13 |
改变规则和业务;需要保持业务,维护市场份额和受益;业务促进、新服务、新供应;新法律;违规等。 |
14 |
在功能性和非功能性需求(可用性、安全性、性能、持久性、伸缩性等)方面,规则变化影响架构 |
15 |
公司需要知道规则背后的原理,发布这个规则是为了解决什么需求、什么原因,这些都要文档记载下来;这些规则如何解决问题;会导致什么样的结果。 |
16 |
<同上> |
17 |
从最适当的设计或实现机制开始实现规则 |
|