UML软件工程组织

Role分析模式(二)角色对象创建和管理
来自BBS水木清华站∶精华区

 

概要
Role Object(一)提出对解决Role问题的基本方法,但是我们还有许多问题需要深入,这些问题包括如何创建具体的Role实例,如何管理这些角色并实现角色之间的约束。
By 石一楹

本文从《ERP之道》www.erptao.org转载

======================================================================================

Role Object提出了对解决Role问题的基本方法,但是我们还有许多问题需要深入,其中的一个问题是如何创建具体的Role实例。

动机

对Role的创建有几个基本的需求。前面我们使用角色的名字作为Specification来创建Role.譬如我们用字符串”Customer”作为Specification来创建Customer这个具体的Role对象。在大多数情况下,这样做可能就足够了,但是有时候确不行。

假设我们有一个核心的抽象叫做Person。我们知道Person可以是员工,所以有一个叫”Employee”的角色。但是可能有不同类型的员工,如销售人员、开发人员等等。所以一般Employee会建模为一个接口,而由Employee的子类来实现具体的角色。当一个客户应用需要知道一个人的工资时,它从核心去获取一个”Employee”的角色,但是现在把它作为类型的名字显然不适合,因为实际的角色可能具有的名字是”Salesman”,”Developer”等等。

这个关于Specification的问题同样出现在对角色的管理过程,如果单独使用一个类名字不能建立或者获取到一个具体的Role,那么整个管理协议都适应这个Specification进行,而不是单独把类名字当作Key进行管理。

创建过程

对于创建过程来说,我们所要解决的核心问题是需要一个类,当我们向这个类提出某些需求或标准的时候,这个类就会返回满足我们需求的具体对象实例,这种满足可能具有层次关系、或者是其它的关系。

所以,有两个问题,一个是如何表达这个Specification,另一个问题是由谁来做这个中间类,我们是需要重新定义一个,还是使用原来就有的ComponentRole.我们怎样能够这个中间类的使用可以让用户对这个创建过程不可见。

我们对设计模式的认识显然排除了那种使用一个大case的可能性,这样的代码在我们需要动态加入一个角色的时候无能为力。工厂方法看起来适合我们的基本需求,但是你很快就发现我们上面的例子中对任何一个“Employee”,它只能返回一种类型的角色。

幸好,解决方案是存在的,我们有一个模式叫做Product Trader,下面是它的原理图:

在上面的图中,客户负责为每一个具体的产品类建立一个Specification,然后为ProductTrader提供这一

在上面的图中,客户负责为每一个具体的产品类建立一个Specification,然后为ProductTrader提供这一个Specification,Product是一个抽象的接口,它定义了需要实例化的子类的操作。Product Trader这个类则实现了从Specification到一个具体的Creator的映射。并且提供各种操作来管理这个映射关系。这里的映射关系可以是一个非常简单的HashMap或者是一个具有完备功能的中间对象trader.现在如何创建的任务落到了Creator上,Creator知道如何实例化一个具体的产品。Specification是一个表达满足关系的抽象,我们可以看到最简单的例子是一个字符串,可能是一个层次关系或者最复杂的情况下可能是一个需要经过很多计算的公式。它的目的是作为一个关键字搜索到具体的Creator.下面是具体的序列图:

针对我们的Role Object,可以看到ProductTrader的责任由ComponentRole来承担,而每一个具体的Role则是在上图中的ConcreteProduct.而客户实际上是Role Object中的ComponentCore.

为什么使用ProductTrader能够使得我们的角色对象可以被更好地创建呢?

首先,ProductTrader如同其它的创建性设计模式(Factory Method等),可以使得客户独立于具体的产品类。也就是使得ComponentCore独立于具体的Role.应用ProductTrader使得ComponentCore完全独立于具体Role的类层次。ComponentCore只需使用某一特定的Specification调用ComponentRole的createFor方法,由ComponentRole来做具体的创建。

其次,具体的角色类可以在运行时决定。ProductTrader的应用可以让ComponentRole在运行时把某些范围或者条件转化为一个Specification,然后使用这样一个Specification进行具体角色类的搜索。因此,你可以具有可变的运行时才决定的角色创建过程。这个优点带来的最直接的后果就是,你可以进行方便的配置,你可以任意增加、删除特定的角色,只要你通过ComponentRole的方法增加、替换及删除相应的Creator即可。这些Creator可以按照Specification搜索特定的角色类。

我们引入ProductTrader的一个最重要的原因是它可以让你轻松地加入具体角色的类层次。由于ComponentCore成功地和具体角色的类层次、类名字、层次结构和实现策略分离,所以对上述内容的改变不会影响到ComponentCore创建具体角色的接口和方法。

能够很好地配置以及改变具体角色类层次使得加入和删除一个具体的角色类变得什么简单,我们不需要修改任何存在的代码,只要对配置文件或脚本修改即可。


实现的考虑

在角色对象中应用ProductTrader需要考虑四个主要的问题,他们包括: l 如何实现从Specification到Creator的映射

我们需要在一个ProductTrader中维护一个Specification、Creator和它们之间的一个映射关系。

l 如何实现Creator

有很多设计模式可以用来处理这样的麻烦,我们耳熟能详的就有prototype、类对象等等。总而言之,无论用何种方法,你都不会希望每次增加一个新的角色,都需要手工去建立一个Creator。所以,对于Creator的要求就是这个类里面由一种机制可以直接创建一个角色实例.


class Creator {

public Creator(Specification aSpec) {

aSpecification =aSpec;

}

public Specification getSpecification() {

return aSpecification;

}

public ComponentRole create() ;

private Specification aSpec;

}


class ConcreteCreator extends Creator {

private class concreteRoleType;

public ConcreteRole(class concreteRoleType,Specification spec) {

super(spec);

this.concreteRoleType = concreteRoleType;

}

public ComponentRole create() {

return new concreteRoleType.newInstance();

}

}

这个Creator相当简单,注意其实在第一个抽象类Creator中,Specification的实例应当放在ComponentRole抽象类里面用于映射到某个Creator,但是我们处于管理方面的目的,在这里加入了这个成员,你可以在后面看到。

其次,要注意的是,如果使用象C++一样的template,我们可以做到类型检查,但是在这里所有的Specification是抽象的Specification,而具体的Creator生成的是ComponentRole而不是ConcreteRole.

再仔细看一下这个Creator,你会发现create过程没有参数,某些时候,你可能需要其它的创建参数,那么也许你要通过反射得到concreteRoleType这个class中符合你参数的构建方法,然后用你的参数进行创建,而不是直接使用newInstance().当然,前提是在create方法中假如你的参数。

l 如何实现Specification

Specification可以使用某些原语类型来实现,如String代表Class name.当然,复杂的情况需要使用一个自有的对象。如果使用一个专门的接口而并非简单的字符串,Specification的接口看起来如下:


public interface Specification {

public void newFormClient(Object anObject);

public void newFromManager();

public void adaptTo();

public boolean matches(Specification spec);

}

这些接口需要由具体的Specification来实现,其中getRoleClass得到为该Specification对应的Role Class. Matches用于判断两个specification之间的匹配性。NewFromClient则由客户使用。它可以创建一个Specification然后交给ProductTrader. 而adaptTo方法用于把某一个具体的specification和该roleClass相对应,它由ComponentRole使用,ComponentRole通过该方法直接从roleClass中获取到建立Specification所需要的信息。

Specification中newFromManager可以直接缺省实现为adaptTo即可,matches可以实现为相等。我喜欢使用的方法之一先定义接口,然后实现一个abstract类:

public abstract class AbstractSpecification implements {

public void newFormClient(Object anObject);

public void newFromManager() {

adaptTo();

}

public void adaptTo();

public boolean matches(Specification spec) {

getClass().equals(spec.getClass());

}

}

这时候,我们可以实现ComponentRoleSpecification的如下:

class ComponentRoleSpecification extends AbstractSpecification {

private String roleType;

//假设这里的表示方法是一个字符串roleType,你可以使用其它的如roleClass等等

public void newFormClient(Object anObject) {

roleType = (String)anObject;

}

public void newFromManager() {

adaptTo();

}

//注意这里需要ComponentRole有一个静态的getRoleType方法

public void adaptTo() {

roleType = ComponentRole.getRoleType();

}

public boolean matches(Specification spec) {

if (!(super.match(spec))

return false;

if (!(spec instanceOf ComponentRoleSpecification))

return false;

return roleType.equals( (ComponentRoleSpecification)spec.getRoleType());

}

public String getRoleType() {

return roleType;

}

}

最后的问题是ComponentRole如何建立映射,从而把一个Specification映射到一个Creator,这些方法包括addCreator(Creator),removeCreator(Creator),substitue(Creator),我们可以看到前面的Creator中包含了getSpecification这个方法,这是我们可以用如下代码:

addCreator(Creator creator) {

mapping.put(creator.getSpecification(), creator);

}

除了这些维护方法外,还需要有lookup,这个lookup仅仅就是通过一个Specification找到creator的过程,相当简单。

ProductTrader模式的应用可能走得更远,它甚至可以用于整个应用系统的所有对象的创建,对ProductTrader的深入讨论可能会超出Role角色所需要的内容,如果读者需要进一步研究该模式,请参见PLOP3.

Role角色管理

在ComponentRole中,还需要管理那些角色,最重要的操作包括:

hasRole(Spec)

getRole(Spec)

removeRole(Spec)

addRole(Spec)

我们看到,前面的match用于两个Specification之间的精确匹配,理由是我们用它来精确匹配对应的Creator。同时,我们看到,这固然解决了ComponentRole具有多层次的问题,但是却不能提供上面的这些管理接口所需要的所有操作。

回忆一下我们在动机一节提出的问题:

“假设我们有一个核心的抽象叫做Person。我们知道Person可以是员工,所以有一个叫”Employee”的角色。但是可能有不同类型的员工,如销售人员、开发人员等等。所以一般Employee会建模为一个接口,而由Employee的子类来实现具体的角色。当一个客户应用需要知道一个人的工资时,它从核心去获取一个”Employee”的角色,但是现在把它作为类型的名字显然不适合,因为实际的角色可能具有的名字是”Salesman”,”Developer”等等。“

这里的问题是我们并不明确指定一个完全的对等关系,我们需要类层次中一样的概念,如果一个Salesman继承自Employee,那么所有Salesman的对象都是Employee,所以我们需要在Specification接口中加入两个新的操作,分别是:

isSpecialCaseOf(Specification)和isGeneralizationOf(Specification).

这时两个Specification之间可以比较。Specification B是Specification A的一个特殊情况当且仅当对于任何对象X,如果X满足A,那么X就满足B.从我们的角色对象来看,如果它所可能具有的任何一个角色的Specification是用户查询的Specification的一个特殊情况,那么该角色对象就具有用户指定Specification所代表的角色。IsGeneralizationOf则正好相反。

在我们的角色对象中,每次用户通过一个addRole(Spec)加入一个角色对象,ComponentCore在自己维护的一个Map中包存了spec实例到ComponentRole的一个映射,当用户使用hasRole(spec)或getRole(sepc)进行查询时,我们可以遍历该Map,如果发现有某一个spec满足用户的参数,则可以返回。对getRole来讲,可能有两种情况,一种情况是找到一个即返回,另外一种情况则是返回所有满足Specification的角色。


Role和Role之间的约束

我们在一开始就讲到,Role Object很难处理具体Role之间具有约束的问题。这是由Role Object本身的特点所决定的。具体Role 之间没有相互的关联,他们不象Decroator一样一个指向一个,最后指向ComponentCore。

如果Role具体对象之间的关系非常复杂,我们可能需要一个知识层(knowledge level)来处理Role之间的关系。我们在我以后讲到Party[待写]的时候看到,TAO BBC如何使用知识层和Role组合完成一个具有Role的Accountability模式。

其实,我们的Specification也可以看作是一个知识层,对于某些约束,Role可以通过Specification来解决。Specification可以解决具体Role具有一个类层次的问题。那么我们可以递归使用Role Object模式来处理这样的约束。

在实际应用中碰到最多的约束问题可能是,某一个对象的一个角色A可能是该对象能够充当其它角色的一个前提,在银行业务中,如果一个人希望充当贷款人、投资者,那么它首先必须是一个客户。

你可以看到,在上图中我们重复应用了Role Object模式,从而限制了一个Investor角色首先就必须是一个Customer角色,从而形成如下的对象链。

Role的属性列表

Role可能需要动态增加属性,并且处理这些属性之间相互约束,请参见专栏中的文章动态属性(属性列表)

Role的存储

我们在这里没有关心Role的实际存储,我们将Role设计成具体的存储无关,具体的存储方法可能需要另一个包装。譬如使用O/R mapping,或者EJB,我们需要实现对应的RoleEntity层次,然后把RoleEntity实现的行为分派到具体的Role模型。这是我们将在TAO J2EE模式研究中看到的课题。TAO BBC将使用这样的模式来实现具体的存储。


本文使用Product Trader模式和Specification模式讨论了Role的一些具体实现和管理问题。在下一篇文章中,我将描述实现Role的另一种方法。新的方法将采用类Decorator模式来处理Role,这种方式和Role Object各有自己的优缺点。


关于作者
石一楹是一个专注于面向对象领域的系统设计师。目前的工作是国内一家ERP公司的CTO.他住在浙江杭州,有一个两个月的女儿。




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