参与者
• Component (Customer) ——组件
- 通过定义接口模拟出一个特定的基础抽象。
- 指定一组协议用于对角色对象的添加、移除、测试和查询。用户(client)为具体角色(ConcreteRole)
子类提供角色规范。在最简单的情况下,角色用string类做为标志。
• ComponentCore (CustomerCore) ——组件核心
- 实现Component接口,其中包括对角色管理协议的实现。
- 创建具体角色实例。
- 管理属于自己的角色对象。
• ComponentRole (CustomerRole) ——组件角色
- 保存自己所装饰(decorate)的组件核心对象的引用。
- 实现Component接口,把对此接口的请求转发给自己装饰的组件核心。
• ConcreteRole (Investor, Borrower) ——具体角色
- 模拟并实现根据环境指定的组件扩展接口。
- 可以用一个ComponetCore引用作参数被实例化。
协作
核心和角色对象进行如下协作:
ComponentRole将请求转发给它的ComponentCore对象。
ComponetCore创建并管理ConcreteRoles。
用户与角色、核心对象有如下交互:
用户可以给核心对象添加新的角色。它可以按照规范得到它希望的角色。
当用户需要一个核心对象扮演某种角色的时候,它向这个核心对象查询这个角色。如果该核心对象正在扮演这个角色,它就把角色的引用返回给用户。
如果该核心对象没有扮演这个角色,它将抛出一个错误。核心对象永远不会给自己创造角色对象。
效果
Role Object模式有如下的优点和效果:
简明地定义基础抽象。组件接口很好的集中精力于所模拟的基础抽象的本质性状态和行为,而不会因为环境指定的角色接口而变得臃肿。
角色可以被很容易被扩展,而且彼此之间互不依赖。扩展Component接口是很容易的,因为你不需要改变ComponentCore类。一个具体角色类就可以让你创建新的角色并实现它,同时保护了基础抽象。
角色对象可以被动态添加或是移除。你可以在运行时添加或移除角色对象,只需简单的将它附加到核心对象上或是从核心对象上分离即可。这样,只有在特定情况下需要的那些对象才会真正被创建。
应用程序得到更好的解耦。通过明确地把Component接口从它的角色中分离出来的手段,内含各种角
色的应用程序的耦合度被降低了。一个应用程序(ClientA)使用Component接口和一些环境指定的ConcreteRole类,另一个应用程序(ClientB)也是如此,那么ClientA不需要知道ClientB使用的那些具体的角色。
使用多继承(multiple inheritance)避免了“组合爆炸(combinatorial explosion)”。这个模式避免了类的组合爆炸,因为它使用多重继承,由此可以在一个类的基础上组合成多个角色。
Role Object模式有如下的短处和缺陷:
客户程序有可能变得更复杂。使用一个类的角色来处理这个类的对象需要做稍微多一些的编码工作——与组件自己提供接口相比。客户程序必须检查对象是否扮演自己需要的角色。如果得到肯定的结果,客户程序还必须请求这个角色;如果对象没有扮演这个角色,客户程序有责任在自己的环境中扩展核心对象使之能扮演这个角色。
维护角色之间的约束(constraints)变得困难。因为逻辑对象(logical object)由几个互相依赖的对象组成,维护它们之间的约束并保持全局一致性可能会变得困难。在“实现”一节中我们对出现的几个问题进行了讨论。
角色之间的约束不能由类型系统(type system)强制执行。在一个组合体中,你可能希望拒绝某些角色的添加。或者,某些角色的存在可能依赖于另外一些角色的存在。在Role Object模式中,你不能依靠类型系统帮你形成约束。你必须用运行时的检查来实现这些约束。
维护对象身份也会变得更复杂。核心对象和它的角色实例共同构成了一个概念上的单元,这个单元拥有它自己的概念上的身份。实际存在的对象的身份可以被任何程序语言直接处理(通过比较对象引用来实现),检查一个概念上的身份却需要Component接口的附加操作。这可以通过比较核心对象的引用来实现。
实现
实现Role Object模式必须遵循如下两个关键点:用角色对象透明地扩展基础抽象,动态管理这些角色。
为了达到透明扩展的目的,我们会使用Decorator模式[Gamma+95];为了创建和管理角色,我们使用Produce Trader模式[Bäumer+97]。这样,Role Object模式结合了两个广为人知的模式,并且增加了新的语义。
提供接口一致性(conformance)。因为我们希望无论在任何地方使用核心对象,角色对象都能被透明地使用,所以角色对象必须支持一个共同的接口。从以类为基础建模的观点来看,一个角色类是被看做它的核心类的特殊化版本的(即是说:一个Investor“是一个”(is-a)Customer)。从角色建模的观点——把角色看作建模第一位的类——来看,Borrower和Investor是Customer类的角色[RG98]。
首先,我们给所有的对象同样的接口,这样我们可以为它们动态添加角色。在结构图中,这个接口是由Component类提供的,ComponentCore类实现了这个接口。
隐藏角色对象的创建过程。角色的实例在运行时被用于装饰一个核心对象。一个核心的问题是:怎样创建一个具体角色的实例并将它附加到一个核心对象上。请注意,具体角色的创建不应该由客户程序来负责。更好的办法是:由ComponentCore来进行角色的创建,从而避免了一个角色对象单独存在(即:没有依赖于任何核心对象)的情况。这个方法还防止了客户程序知道怎样实例化一个角色对象。
将角色对象与核心对象解耦。角色的创建和管理都应该以普遍(generic)的形式进行。另外,如果允许用新的未知角色扩展ComponentCore,则很难保证不改变ComponentCore的实现。因此,角色的创建和管理都必须独立于任何具体的角色类;ComponentCore的代码中也不能静态引用任何具体角色类。
使用规范(specification)对象可以达到这样的效果。当客户需要请求一个角色时,它传递一个规范对象给核心对象。最简单的解决方案是:把类型名字当作规范使用(参见“代码示例”一节)。核心对象返回与规范相匹配的角色对象。在[Riehle95, Evans+97]中讨论了更多的详细的规范机制。
创建角色对象时也可以使用同样的规范。为了达到这个目的,你可以使用Product Trader模式