Role Object(角色对象)模式

 

 

Dirk Bäumer等 著,透明

 

意图

 

       通过向组件对象加入对用户透明的角色对象(role object)——每个角色对象扮演了组件对象需要在一个用户的环境(context)中扮演的角色——使组件对象能适应不同用户的需要。每个“用户的环境”可能是它自己的一个应用程序,通过使用这个模式,应用程序与其它应用程序的耦合度将得到降低。

 

动机

 

       一个面向对象系统总是以一系列特征性的基本抽象(key abstract)为基础的。每个基本抽象都由一个相应的类根据其抽象状态和行为模拟实现。在设计小规模的应用程序时,这个机制通常运行得很好。但是,当我们要把系统的规模扩大成一套完整的应用程序时,我们就必须面对各种不同的客户,这些客户希望根据我们的基本抽象得到各自不同的、根据环境指定的视图(view)。

       假设我们正在为银行的投资部门开发一个支持软件,我们的基本抽象之一是表示“投资者”的概念。因此,我们的设计模型中应该有一个Customer类,这个类的接口提供了管理消费者的姓名、住址、储蓄、存款余额等属性的操作。

       现在,假设这个银行的贷款部门也需要软件支持。看起来,我们的设计不能满足处理一个贷款者客户的需要。很明显,我们必须为Customer类增加更多的状态和操作,使之能管理客户的贷款额、信用级别、债券等信息。

       把几个根据不同环境指定的视图包含在同一个类中,这样的做法最可能的后果是把基本抽象变成一个臃肿的接口。对这样一个臃肿的接口的理解和维护都是非常困难的。当一个未知的变化发生时,你无法温和的处理它,而通常只能任它引发对一系列程序模块的重新编译(recompilation)。而对用户指定的接口部分的变化则很可能影响到其他的子系统或应用程序。

       这里有一个简单的解决方案:扩展Customer类,加入BorrowerInvestor两个新的子类,让它们分别捕获贷款者和投资者各自特殊的事件。从对象身份的角度来考虑,子类化就意味着分属两个不同子类的对象是不同的。因此,一个既是投资者又是贷款者的客户就要扮演有截然不同的身份的两个对象。对象的“身份”只能以附加的机制来模拟(例如C++的运行时类型识别RTTI),如果两个对象有同样身份,它们必定有恒定不变且互不相同的继承属性。无论如何,假设要构造系统中所有客户的列表,我们将不可避免的陷入多态搜索(polymorphic search)的麻烦。除非我们很小心的避免复制(duplicate)对象,否则同一个客户对象将作为不同的角色重复出现。


       角色对象模式模拟了根据环境指定的对象视图,各个单独的角色对象被核心对象(core object)动态添加、移除。最终得到的对象聚合体作为一个逻辑对象进行它自己的行为。

1:银行环境中Customer类继承关系

       Customer这样的基本抽象被定义成一个抽象超类。它只提供一个接口,不做任何实现。Customer类指定了处理客户住址、存款余额这样的操作,并且定义了一个管理角色的最小化协议。子类CustomerCore实现了Customer的接口。

       CustomerRole则提供了用户指定角色的超类,并且它也支持Customer的接口。CustomerRole是一个抽象类,它不能被实例化。CustomerRole的具体子类——例如Borrower或者Investor——定义并实现了用户指定角色对象的接口,只有这些子类才能在运行时被实例化。Borrower类定义了贷款部门需要的客户对象视图,它定义了一些附加操作以管理客户的信用和债券等信息。同样,Investor类也添加了投资部门指定的客户视图需要的操作。

       象“贷款应用程序”这样的一个用户可能通过Customer的接口使用CustomerCore类,也可能使用某个具体CustomerRole子类的一个对象。假想这样的情况:贷款应用程序通过某个Customer实例的Customer接口了解到它,并且应用程序可能想检查这个Customer是否扮演着贷款者的角色。于是应用程序调用该对象的hasRole()方法,这个方法会告诉调用者该对象的角色。在本例中,我们假设我们的每个角色都有一个简单string类型的名字。如果这个Customer对象可以扮演名叫“Borrower”的角色,贷款应用程序会要求它传递一个自己的引用给相关的对象,然后应用程序就可以使用这个引用调用贷款者专用的操作了。


2:角色对象模式的一个对象图

       与此相似,“投资者”角色被它自己的应用程序处理。如果这两个应用程序不需要互相了解,那么角色对象的使用就可以帮助它们解耦。贷款应用程序不需要加载投资者角色,同时投资应用程序也不需要加载贷款者角色。

 

适用性

 

在以下情况下可以使用Role Object模式:

l         你希望在不同的环境中处理同一个基本抽象(每个环境可能是一个独立的应用程序),并且你不希望把所得到的环境相关的接口放到同一个类接口中去。

l         你希望能动态决定处理何种有用的对象,希望角色能根据要求在运行时被添加或移除,而不是在编译时被静态绑定。

l         你希望在不同的环境中保持逻辑对象的一致性。

l         你希望让角色/用户(role/client)互不依赖,这样对角色的修改不会影响到与之无关的用户。

如果有如下情况,不要使用这个模式:

l         你的潜在角色有很强的相互依赖。有其他的一些使用“角色”的方法,Fowler给出了使用这些方法的向导,并指出应该在什么时候使用什么模式[Fowler97]

 

结构

 

下面是Role Object模式的结构图。


图3:Role Object模式的结构图

 


参与者

 

· Component (Customer) ——组件

       - 通过定义接口模拟出一个特定的基础抽象。

       - 指定一组协议用于对角色对象的添加、移除、测试和查询。用户(client)为具体角色(ConcreteRole)子类提供角色规范。在最简单的情况下,角色用string类做为标志。

· ComponentCore (CustomerCore) ——组件核心

       - 实现Component接口,其中包括对角色管理协议的实现。

       - 创建具体角色实例。

       - 管理属于自己的角色对象。

· ComponentRole (CustomerRole) ——组件角色

       - 保存自己所装饰(decorate)的组件核心对象的引用。

       - 实现Component接口,把对此接口的请求转发给自己装饰的组件核心。

· ConcreteRole (Investor, Borrower) ——具体角色

       - 模拟并实现根据环境指定的组件扩展接口。

-          可以用一个ComponetCore引用作参数被实例化。

 

 

协作

 

       核心和角色对象进行如下协作:

l         ComponentRole将请求转发给它的ComponentCore对象。

l         ComponetCore创建并管理ConcreteRoles

用户与角色、核心对象有如下交互:

l         用户可以给核心对象添加新的角色。它可以按照规范得到它希望的角色。

l         当用户需要一个核心对象扮演某种角色的时候,它向这个核心对象查询这个角色。如果该核心对象正在扮演这个角色,它就把角色的引用返回给用户。

l         如果该核心对象没有扮演这个角色,它将抛出一个错误。核心对象永远不会给自己创造角色对象。

 

效果

 

Role Object模式有如下的优点和效果:

l         简明地定义基础抽象。组件接口很好的集中精力于所模拟的基础抽象的本质性状态和行为,而不会因为环境指定的角色接口而变得臃肿。

l         角色可以被很容易被扩展,而且彼此之间互不依赖。扩展Component接口是很容易的,因为你不需要改变ComponentCore类。一个具体角色类就可以让你创建新的角色并实现它,同时保护了基础抽象。

l         角色对象可以被动态添加或是移除。你可以在运行时添加或移除角色对象,只需简单的将它附加到核心对象上或是从核心对象上分离即可。这样,只有在特定情况下需要的那些对象才会真正被创建。

l         应用程序得到更好的解耦。通过明确地把Component接口从它的角色中分离出来的手段,内含各种角色的应用程序的耦合度被降低了。一个应用程序(ClientA)使用Component接口和一些环境指定的ConcreteRole类,另一个应用程序(ClientB)也是如此,那么ClientA不需要知道ClientB使用的那些具体的角色。

l         使用多继承(multiple inheritance)避免了“组合爆炸(combinatorial explosion)”。这个模式避免了类的组合爆炸,因为它使用多重继承,由此可以在一个类的基础上组合成多个角色。

Role Object模式有如下的短处和缺陷:

l         客户程序有可能变得更复杂。使用一个类的角色来处理这个类的对象需要做稍微多一些的编码工作——与组件自己提供接口相比。客户程序必须检查对象是否扮演自己需要的角色。如果得到肯定的结果,客户程序还必须请求这个角色;如果对象没有扮演这个角色,客户程序有责任在自己的环境中扩展核心对象使之能扮演这个角色。

l         维护角色之间的约束(constraints)变得困难。因为逻辑对象(logical object)由几个互相依赖的对象组成,维护它们之间的约束并保持全局一致性可能会变得困难。在“实现”一节中我们对出现的几个问题进行了讨论。

l         角色之间的约束不能由类型系统(type system)强制执行。在一个组合体中,你可能希望拒绝某些角色的添加。或者,某些角色的存在可能依赖于另外一些角色的存在。在Role Object模式中,你不能依靠类型系统帮你形成约束。你必须用运行时的检查来实现这些约束。

l         维护对象身份也会变得更复杂。核心对象和它的角色实例共同构成了一个概念上的单元,这个单元拥有它自己的概念上的身份。实际存在的对象的身份可以被任何程序语言直接处理(通过比较对象引用来实现),检查一个概念上的身份却需要Component接口的附加操作。这可以通过比较核心对象的引用来实现。

 

实现

 

       实现Role Object模式必须遵循如下两个关键点:用角色对象透明地扩展基础抽象,动态管理这些角色。为了达到透明扩展的目的,我们会使用Decorator模式[Gamma+95];为了创建和管理角色,我们使用Produce Trader模式[Bäumer+97]。这样,Role Object模式结合了两个广为人知的模式,并且增加了新的语义。

l         提供接口一致性(conformance)。因为我们希望无论在任何地方使用核心对象,角色对象都能被透明地使用,所以角色对象必须支持一个共同的接口。从以类为基础建模的观点来看,一个角色类是被看做它的核心类的特殊化版本的(即是说:一个Investor“是一个”(is-aCustomer)。从角色建模的观点——把角色看作建模第一位的类——来看,BorrowerInvestorCustomer类的角色[RG98]
首先,我们给所有的对象同样的接口,这样我们可以为它们动态添加角色。在结构图中,这个接口是由Component类提供的,ComponentCore类实现了这个接口。

l         隐藏角色对象的创建过程。角色的实例在运行时被用于装饰一个核心对象。一个核心的问题是:怎样创建一个具体角色的实例并将它附加到一个核心对象上。请注意,具体角色的创建不应该由客户程序来负责。更好的办法是:由ComponentCore来进行角色的创建,从而避免了一个角色对象单独存在(即:没有依赖于任何核心对象)的情况。这个方法还防止了客户程序知道怎样实例化一个角色对象。

l         将角色对象与核心对象解耦。角色的创建和管理都应该以普遍(generic)的形式进行。另外,如果允许用新的未知角色扩展ComponentCore,则很难保证不改变ComponentCore的实现。因此,角色的创建和管理都必须独立于任何具体的角色类;ComponentCore的代码中也不能静态引用任何具体角色类。
使用规范(specification)对象可以达到这样的效果。当客户需要请求一个角色时,它传递一个规范对象给核心对象。最简单的解决方案是:把类型名字当作规范使用(参见“代码示例”一节)。核心对象返回与规范相匹配的角色对象。在[Riehle95, Evans+97]中讨论了更多的详细的规范机制。
创建角色对象时也可以使用同样的规范。为了达到这个目的,你可以使用Product Trader模式[Bäumer+97]。角色对象的trader维护了一个容器,其中包含了规范对象及与之相关的creator对象(例如类对象、原型(prototype)或是标本(exemplar))。当一个客户程序想添加一个新的角色时,它把一个规范对象传递给核心对象。核心对象会把创建过程委托给这个trader

l         选择合适的规范对象。在很多案例中,使用类型名称作为规范对象就已经足够了。但有时候也值得使用更加复杂的规范:
假设你设计了一个叫做Person的核心类。一些人(person)可能是雇员(employee),所以这里应该有一个Employee角色。因为雇员也有各种各样的,你可能还希望把Employee设计成一个接口并且设计一些具体角色作为Employee的子类,比如售货员(Salesman)、开发者(Developer)和经理(Manager)。当客户程序需要获得一个人的工资信息时,它会向核心对象请求“Employee”角色。如果只用类型名称作为规范你将不能这么做,因为这些具体的角色对象的类型名称将是“Salesman”之类的名称,而不是“Employee”。在这种情况下,你可以使用类型对象(Type Objects[Johnson+97]作为规范。通过对子类/超类关系的评估,核心对象就可以找回客户程序请求的角色对象了。

l         管理角色对象。为了让核心对象管理它的角色,Component接口声明了一个角色管理协议,其中包括对角色对象的添加、移除、测试、查询等操作。为了支持角色管理协议,核心对象维护一个字典(dictionary),其中的内容是角色规范到具体角色实例的映射表。当一个角色对象被附加到核心对象上时,这个新的角色对象与它的角色规范一起被注册到角色字典中。
请注意:一个核心对象是通过ComponentRole类型的角色对象引用来管理它们的。绝对不要在这里使用ComponentRole的实例!因为这样做会使核心对象无法鉴别它们真实的类型。另外,由于核心对象拥有它的角色,它必须细心管理它们。特别是,核心对象在自己被销毁(delete)的时候必须负责销毁它拥有的角色对象。

l         维护核心对象和角色对象的状态一致性。核心对象或角色对象发生改变时,其他的角色对象也能也需要更新。下面是一个例子:改变一个Person的名字,而这个Person同时还是一个Borrower。当这个Person接收到它的新名字的时候,在Borrow的面前应该有一个摇晃着小旗的人告诉它:“你的名字被改变了!”这个摇旗者同时也告诉系统:“一个贷款者更改了他的名字,你应该把这个事件报告给管理贷款者信用的国家机构。”——对于银行来说,这样的通知是必须的。有几种可能的解决方案可以确保这些约束,但是每种方案都会带来额外的开销;并且这些依赖会导致编码的困难。我们将在下一个条款中讨论一个更详细的解决方案。

l         PropertyObserver模式来维护角色的属性约束。如果很多的相互依赖使状态的集合变得复杂,核心对象的属性(或其中的一部分)可以用一个属性列表(property list[Riehle97])来实现——属性列表也被称为变量状态(Variable State[Beck96])。属性列表是一个键/值(key/value)二元组列表,我们用它来分别表现属性的名称和值。它常见的实现方式是一个字典,在其中将属性名称映射到属性值(对象)上。
然后,一个角色对象与一个特定的属性——这个属性被定义为核心对象状态的一部分——联系在一起。当一个改变此属性的方法被调用时,角色对象会得到一个通知。为了做到这一点,每个可以改变核心对象属性值的角色对象都必须在它这样做的时候通知核心对象,这样核心对象才能向相关的角色对象发出通告。
属性列表通常被看成一种不好的东西,因为它们破坏了对实现的封装。如果属性的名称发生改变,可能所有与之相关的角色类都需要相应改变。而且,差劲的代码可能会带来隐蔽的错误。但是,只要小心处理,这些问题都是可以避免的。这个模式最广为人知、应用最广泛的例子可能是抽象语法树上的带修饰节点(decorated nodes),这是许多编译器和软件开发环境的基础抽象。

l         维护概念上的身份(conceptual identity)。Role Object模式让你可以象管理一个有综合状态的概念对象(conceptual object)一样管理核心和它的角色。因此,你必须让客户有办法确定两个截然不同的技术对象(technical object)是否是同一个逻辑对象(logical object)的部分,它们是否拥有同样的概念身份。当不同的角色对象拥有同样的核心对象时,它们将共享同一个概念对象身份。
比如说,假设这样的一个客户应用程序:它使用Component接口直接处理核心对象,并通过一个具体的角色接口引用核心对象唯一的角色实例。从技术的观点来看,这两个引用不可能指向同一个对象,因为它们引用了两个——在技术上——截然不同的对象。因此,为了发现“这两个引用实际上表示同一个概念对象”这一事实,Component接口必须提供特殊的身份比较方法,然后由客户程序来使用这一方法。通常这种身份比较的实现方法是:直接对两个核心对象的引用进行比较。

l         维护角色之间的约束。在角色(不仅仅是它们的状态)之间可能有一些约束。一个常见的案例是:核心对象只有在扮演角色A的情况下才能再扮演角色B。例如:如果CustomerBorrower都是Person的角色,那么Customer角色对象的存在就是Person能扮演Borrower角色的前提。这些是角色级别的约束。对象扮演角色B的能力受限于对象是否已经扮演角色A。如果对象没有扮演角色A,则它一定不能扮演角色B。如同我们在上面的例子中看到的,这些约束是来自应用领域的。
通常说来,角色B不会仅仅依赖于角色A的存在,而是会依赖于角色A存在于某种特定的状态。在最复杂的案例中,你将不得不使用一个约束解决系统(constraint solving system)。幸运的是,在实际应用中,情况几乎永远不会变得那么复杂,很多实际用到的解决方案都足以应付。在典型案例的解决中,我们用了一个两阶段提交协议(two-phase commit protocol),这个协议在最终做出什么请求——比如对一个角色对象的移除——之前先询问所有的角色,从而保证了约束的完整性。

l         递归使用Role Object模式以维护角色之间的约束。许多角色级别约束的问题都可以通过递归使用Role Object模式得到解决。如果角色A是角色BCD……的前提,那么角色A可以被看成这些角色(BCD)的基础抽象。“贷款者(Borrower)”和“投资者(Investor)”可以看成是“客户(Customer)”的角色,而“客户”和“担保人(Guarantor)”又可以看成是“人(Person)”的角色。因为做一个担保人不需要首先是一个客户,所以你不必把Guarantor设计成Customer的角色。Role Object对象递归使用的一个范例见下图:


4:递归使用Role Object模式


       在运行时,这种递归使用形成了一条角色对象和核心对象的链(chain)。下面的图展示出了这个情况:

5:角色上再附加角色形成的动态对象图

       我们的角色级约束是:除非Customer角色已经存在,否则不允许BorrowerInvestor的角色对象出现。在上面的解决方案中,这个约束已经很简单的被实现了。由此可见,把Customer设计成更具体角色的另一个基础抽象,这样可以为Person基础抽象提供很重要的角色约束保证。

 

代码示例

 

       下面的C++代码展示了“动机”一节中讨论的客户范例的实现。我们假定存在一个叫做CustomerComponent类。

class CustomerRole;

class Customer {

public:

// customer specific operations

virtual list<Account *> getAccounts() = 0;

// Role management

virtual CustomerRole * getRole(string aSpec) = 0;

virtual void addRole(string aSpec) = 0;

virtual void removeRole(string aSpec) = 0;

virtual bool hasRole(string aSpec) = 0;

};

       CustomerCore类的实现可能是这样:

class CustomerCore : public Customer {

public:

CustomerRole * getRole(string aSpec)

{

return roles[aSpec];

};

void addRole(string aSpec)

{

CustomerRole * role = NULL;

if ((role = getRole(aSpec)) == NULL)

{

if(role = CustomerRole :: createFor(aSpec, this))

roles[Spec] = role;

}

};

list<Account *> getAccounts() { ... };

private:

map<string, CustomerRole *> roles;

};

       角色的规范被实现为与其具体角色类名相同的字符串。角色规范与角色对象之间的映射关系用一个字典来实现。

       然后,我们定义Customer的一个名叫CustomerRole的子类,我们将从CustomerRole再衍生出子类而得到不同的具体角色。CustomerRole通过核心对象的引用变量对CustomerCore进行装饰。对于Customer接口的操作请求,CustomerRole将之转发给核心对象。请注意,核心对象引用变量的类型是CustomerCore,这样可以保证客户角色不会被用做核心对象。角色规范和相应creator对象之间的映射表可以实现特定角色的实例化,这个映射关系可以用查找表(lookup table)来实现。在[Bäumer+97]中有对creator对象的实现和管理的详细讨论。

class CustomerRole : public Customer {

public:

list<Account *> getAccounts() { return core->getAccounts() };

CustomerRole * addRole(string aSpec) { return core->addRole(aSpec); };

static CustomerRole * createFor(string aSpec, CustomerCore * aCore)

{

CustomerRole * newRole = NULL;

if (newRole = lookup(aSpec)->create()) newRole->core = aCore;

return newRole;

};

private:

CustomerCore * core;

};

       CustomerRole的子类定义了特定的角色,比如Borrower类加入了一些操作用以处理债券、信用值等信息。子类不能对所继承的角色管理操作进行重载。

class Borrower : public CustomerRole {

public:

list<Security *> getSecurities() { return securities; };

private:

list<Security *> securities;

};

       请注意,客户程序必须将从核心组件返回的角色引用进行向下转换,这样它们才能调用这个角色对象特有的操作:

Foo()

{

...

Customer * aCoustomer = Database :: load(“Tom Jones”);

Borrower * aBorrower = NULL;

if (aBorrower = dynamic_cast<Borrower *> aCustomer->getRole(“Borrower”))

{

// access securities

list<Security *> securities = aBorrower->getSecurities();

}

}

...

 

已知应用

 

       这个模式被广泛应用于GEBOS系列面向对象银行项目中[Bäumer+97a]。这个项目为银行的出纳、贷款、投资、内部服务、记帐等几个部门提供了软件支持。GEBOS系统基于一个通用的商业领域层,并且对银行的核心概念进行了模拟。具体的应用程序则用Role Object模式扩展了这些核心概念。

       [Riehle+95a, Riehle+95b]中描述了实现Role Object模式所用的工具及原料框架,并对角色建模设计空间的问题进行了探索,其中用到了copy&paste协议、多继承、修饰和包装等等方法。Fowler对这些变化做了更简明的描述[Fowler97]

       [Kristensen+96]中有KristensenØsterbye关于使用Decorator模式为编程语言引入角色的报告。但是,他们没有细致讨论角色对象的创建、管理的问题。作为例证,我们用了一个领域相关的例子——Person及其角色。这个例子是如此通用,以至它实际上已经成为了一个单独的模式。因为在许多环境中都需要一个“人(Person)”的抽象,都会要求它扮演各种各样的角色。Schoenfeld讨论了这个问题的一些例子,比如在以文档为中心的商务处理中的人及其角色[Schoenfeld96]。我们则用“客户”的角色把“人”和它的角色拿到银行商务的环境中。另外一个例子则是在政府官员体系及其相关的薪水管理问题中的人及其角色。

       Role Object模式的一个与“人”无关的应用是对抽象语法树(abstract syntax treeAST)的节点的修饰。AST是大多数软件开发环境最重要的抽象,许多不同的工具——例如语法导向的编辑器、符号浏览器、交叉引用、编译支持、分析依赖、变化影响——从不同的角度展示和使用了它。这些工具通常只对AST的一小方面感兴趣,并且需要用自己特定的信息对AST节点进行解释。使用Role Object模式可以很好的帮助这些工具为节点提供工具特殊的接口。Mitsui et al[Mitsui+93]讨论了在C++编程环境下使用Role Object模式的情况,虽然这是对于具体情况的讨论,但它也实用于更广泛的目的。

 

相关模式

 

       Extension Object模式[Gamma97]有同样的出发点:用满足环境需求的方式来扩展对象,从而使组件得到扩展。但是这个模式没有告诉我们一个实现上的关键方面:怎样透明地处理ComponentComponentRole的对象。而且Extension Object模式只关心扩展对象(角色对象)的创建和管理的问题。我们可以把Role Object模式的核心部分看成是Decorator模式和Product Trader模式的综合。

       ZhaoFoster[Zhao+97]以及Schoenfeld[Schoenfeld96]都曾经在角色建模(role modeling)中使用过Extension Object模式。ZhaoFoster把角色对象当做扩展对象来讨论,也就是说他们并不对核心对象进行透明的包装。他们的基本范例是一个运输软件系统,其中的关键对象是Point(及其角色)。Schoenfeld则选择了与我们相同的主要范例(Person及其角色),但他也使用了Extension Objects模式,而没有用Decorator来对核心进行透明包装。

       [Fowler96]中的Post模式描述了Role Object模式的一个有趣的变化。与Extension Object模式相似,Post模式描述了某个特定应用程序环境中核心对象的职责。但是,一个Post对象的存在独立于核心对象,即使不被赋值给一个核心对象,Post对象也可以存在。

 

致谢

 

       我们要感谢Ari Schoenfeld,我们的导师。他帮助我们对这个模式的表达和内容做了改进。

 

参考文献

 

[Bäumer+97] Dirk Bäumer and Dirk Riehle. “Product Trader.” In [Martin+97]. Chapter 3.

[Bäumer+97a] Dirk Bäumer, Guido Gryczan, Rolf Knoll, Carola Lilienthal, Dirk Riehle, and Heinz Züllighoven. “Framework Development for Large Systems.” Communications of the ACM 40, 10 (October 1997).

[Beck96] Kent Beck. Smalltalk Best Practice Patterns. Prentice-Hall, 1996.

[Coplien+95] James O. Coplien and Douglas C. Schmidt (editors). Pattern Languages of Program Design. Addison-Wesley, 1995.

[Evans+97] Eric Evans and Martin Fowler. “Specification Patterns.” Submitted to PLoP ’97.

[Fowler96] Martin Fowler. Analysis Patterns. Addison-Wesley, 1996.

[Fowler97] Martin Fowler. “Role Patterns.” Submitted to PLoP ’97.

[Gamma+95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns. Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995. 中文版:《设计模式:可复用面向对象软件的基础》,李英军等译,机械工业出版社20009月。

[Gamma97] Erich Gamma. “Extension Object.” In [Martin+97]. Chapter 6.

[Johnson+97] Ralph Johnson and Bobby Woolf. “Type Object.” In [Martin+97]. Chapter 4.

[Kristensen+96] Bent Bruun Kristensen and Kasper Østerbye. “Roles: Conceptual Abstraction Theory and Practical Language Issues.” Theory and Practice of Object System 2, 3 (1996): 143-160.

[Martin+97] Robert C. Martin, Dirk Riehle, and Frank Buschmann (editors). Pattern Languages of Program Design 3. Addison-Wesley, 1997.

[Mitsui+93] Kin'ichi Mitsui, Hiroaki Nakamura, Theodore C. Law, and Shahram Javey. “Design of an Integrated and Extensible C++ Programming Environment.” Object Technology for Advanced Software (ISOTAS-93, LNCS-742). Edited by Shojiro Nishio and Akinori Yonezawa. New York: Springer-Verlag, 1993. Page 95-109.

[Riehle+95a] Dirk Riehle. “How and Why to Encapsulate Class Trees.” In Proceedings of the 1995 Conference on Object-Oriented Programming Systems, Languages and Applications (OOPSLA ’95). ACM Press, 1995. Page 251-264.

[Riehle+95a] Dirk Riehle and Heinz Züllighoven. “A Pattern Language for Tool Construction and Integration Based on the Tools and Materials Metaphor.” In [Coplien+95]. Chapter 2.

[Riehle+95b] Dirk Riehle and Martin Schnyder. Design and Implementation of a Smalltalk Framework for the Tools and Materials Metaphor. Ubilab Technical Report 95.7.1, 1995.

[Riehle97] Dirk Riehle. A Role-Based Design Pattern Catalog of Atomic and Composite Patterns Structured by Pattern Purpose. Ubilab Technical Report 97.1.1. Zürich, Switzerland: Union Bank of Switzerland, 1997.

[Riehle+98] Dirk Riehle and Thomas Gross. “Role Model Based Framework Design and Integration.” In Proceedings of the 1998 Conference on Object-Oriented Programming Systems, Languages and Applications (OOPSLA ’98). ACM Press, 1998.

[Schoenfeld96] Ari Schoenfeld. “Domain Specific Patterns: Conversions, Persons and Roles, and Documents and Roles.” In Proceedings of the 1996 Conference on Pattern Languages of Programming (PLoP ’96). Washington University Department of Computer Science, Technical Report WUCS-97-07, 1997.

[Zhao+97] Liping Zhao and Ted Foster. “A Pattern Language of Transport Systems (Point and Route).” In [Martin+97]. Chapter 23.