意图
从实现中分离抽象,使得两者都可以独立变化。
桥接模式可以避免抽象与其实现之间的永久绑定。桥接模式对于一系列可扩展类非常重要,一般来说从一个类层次定义若干核心抽象,通过继承具体实现,每个都需要不同的实现。典型的例子是UI框架的窗口抽象。核心抽是窗口,它被子类具体实现。然后每个子类又被不同平台相应的窗口子类继承,这导致了子类的增生。这创建了抽象和实现之间永久的绑定,使得系统硬性绑定在一起。抽象的改变会导致所有子类的改变。解决方法是把类层次分解成为两个独立的层次,一个用于抽象另外一个用于实现。这减少了系统中类的数量,使得每个层次互相独立。下图展示了两个层次直接的关系。
Abstraction中所有的操作被Implementor中的抽象方法具体实现。这是两者解耦的关键。
分析
桥接模式本身通过组合和委托实现。抽象包含了指向实现的引用。实现的具体类型通过抽象类隐藏,这种组合实现了解耦,符合了GOF的第二准则(对象组合优于类继承)。
使用组合、委托替代类继承的重构方式,可以使用Scala的self type。这样做的代价是无法在运行时改变绑定。
组件化
在讲适配器模式时,我们讲过无法以一种有意义的方式抽象类之间的通信。抽象和实现层次之间的通信在不同的模式实例中差别巨大。不同实例之间唯一保持相同的是概念层次上的相关角色,这意味着没有可组件化的内容。
Scala方案
在下面代码中,根抽象是Window,它有一个指向根实现的self type叫做WindowImp。
trait Window {
self:WindowImp=>
def drawRect(x1:Int,x2:Int,x3:Int,x4:Int) = {
drawLine(x1,x2)
drawLine(x1,x3)
drawLine(x2,x4)
drawLine(x3,x4)
}
}
//抽象
trait TransientWindow {
self:Window=>
def drawCloseBox = drawRect(4,3,2,1)
}
trait IconWindow {
self:Window=>
def drawBorder = drawRect(1,2,3,4)
}
//所有实现的通用接口
trait WindowImp {
def drawLine(x:Int,y:Int)
}
//实现
trait WindowOSX extends WindowImp {
def drawLine(x:Int,y:Int)=println("drawing line in OSX")
}
trait WindowVista entends WindowImp {
def drawLine(x:Int,y:Int)=println("drawing line in Vista")
}
def main(args:Array[String])={
val windowOSX:Window = new Window with WindowOSX
windowOSX.drawRect(1,2,3,4)
}
-----
drawing l ine in OSX
drawing l ine in OSX
drawing l ine in OSX
drawing l ine in OSX
我们需要在Window的子类中重复声明自身类型。另外一种较为繁琐的使用自身类型的方式是将定义所需的操作定义为抽象成员。这样更加灵活,因为无需命名具体的特质。