描述
适配器模式的意图是:
把一个类的接口转换成客户端期望的另一个接口。适配器可以让彼此不兼容的接口协同工作。
GOF给出了两种基本的适配器实现:类适配器和对象适配器。他们各有优缺点。
类适配器使用继承实现一个类到另一个类的适配。如下图所示适配器类继成了Adaptee和适配目标Target接口。客户端在适配目标类上调用具体的操作时,适配器类会从被适配的类中调用相应的继承方法。继承可以通过重载方法补充被适配类的行为,但是这样适配器类建立了与被适配类之间静态的依赖关系。这意味着适配器类不能被被适配类的子类重用。
对象适配器使用组合/代理而非继承作为适配实现的主要机制。如下图所示,Adapter类包含了一个指向被适配类的引用。客户端调用Request方法时,消息被传递给被适配类的对象。因为适配器只包含了一个指向被适配类的引用,所有它没有与其他类之间的静态依赖,适配器类可以被被适配类的子类重用。但代价是引入了自身问题(self problem),并且不能重载被适配类的行为。
两种适配器方式对客户端是透明的,客户端只使用目标接口处理他们想要适配的被适配接口。
分析
考虑对象适配器,上述第二种适配器方式更有效。
明确的自类型(self type)能消除继承适配器带来的适配器和具体被适配类之间依赖的问题。一种更加灵活的方式是用抽象成员声明所需的方法而非自类型。组合混入可以构建一个双重适配器,因为构建的复合类型可对目标类和被适配类都实现适配器功能。另外,这也消除了自身问题。
综合上述特性增强了GOF的两种模式方案,但是不能再运行时改变被适配的类,在某些场景下这是很重要的。
组件化
每次实例化不同的地方是适配器、被适配类的类型以及他们之间的通信。通信一般通过所涉一系列方法实现。在Scala中没有方式实现对一系列类成员的抽象,这意味着我们不能提供可用的组件。
适配器可被被适配类的任何子类复用,但是这也是有模式适用的特定上下文背景。
Scala解决方案
Scala方案结合了类适配器和对象适配器的优点,同时也有双重适配器的功能。下面代码是一个简单的例子。模式的关键是Adapter特质。它定义了明确的自类型——复合类型Target with Adapter,消除了适配器对被适配类的依赖。
trait Target {
def Request
}
class Adaptee {
def SpecificRequest = println("Specific Request!")
}
trait Adapter {
self : Target with Adaptee=>
def Request()=self.SpecificRequest()
}
def main(args:Array[String])= {
val adapter=new Adaptee with Target with Adapter
adapter.Request
adapter.SpecificRequest
}
main方法展示了如何使用适配器。它可以调用Request和SpecificRequest方法,证明其可以处理双重适配器。
总结
Scala方案结合了类适配器和对象适配器所有的优点,消除了自身问题。自类型的使用消除了对特定被适配类的依赖。另一种方案是在适配器类中声明抽象成员,这样能增加灵活性,因为只引入了结构上的依赖。这种方案仅有部分基于组合的灵活性,同时也说明在应用于Scala时没有第一种强大。因为无法抽象类的成员集合,所以无法组件化。
与GOF方案相比,Scala方案有如下特点:
- 没有self problem,但是几乎与对象适配器一样灵活
- 与类适配器相比,没有对特定被适配类的依赖
- 只适用于不需要动态改变被适配类的情况