先简单地介绍一下“桥接模式”:
用一个(抽象化的)接口将实体中的属性独立出来,以保持这个属性的变化独立于宿主,因为这个接口在两个定义(宿主和属性)之间起到桥接的作用,故名“桥接模式”,比如下文中的 Name 接口就是起到桥接 Party 类和 Name 属性之间的“桥”。
桥接模式给对象的灵活性带来了一些变化,它使得对象的某些部分的实现不再完全依赖父类,相反它可以从非继承关系中获的某些好处。就以文中的例子,我们先来看看当它以正面形象运行的时候是什么样的:
package pro.wangji;
interface Name{
String toString();
}
class OrganizationName implements Name{
String name;
public OrganizationName(String name){
this.name = name;
}
@Override
public String toString(){
return name;
}
}
class IndividualName implements Name{
String firstName;
String lastName;
public IndividualName(String firstName, String lastName){
this.firstName = firstName; this.lastName = lastName;
}
@Override
public String toString(){
return lastName + ", " + firstName;
}
}
class Party {
Name name;
}
class Organization extends Party {}
class Individual extends Party{}
public class BridgePattern {
public static void main(String[] args) {
Organization organization = new Organization();
organization.name = new OrganizationName("ABC Company");
Individual individual = new Individual();
individual.name = new IndividualName("Gates", "Bill");
System.out.println(String.format("Organization name is \"%s\"", organization.name));
System.out.println(String.format("Individual name is \"%s\"", individual.name)); }
}
很显然我们会得到预期的输出结果:
Organization name is "ABC Company" Individual name is "Bill, Gates"
但是需要注意的是“桥接模式”同样是恶魔的制造者,我们只需要对主代码做非常小的一点改动你就会发现问题:
public class BridgePattern {
public static void main(String[] args) {
Organization organization = new Organization();
organization.name = new OrganizationName("ABC Company");
// Exchange organization and individual name wouldn't causes error.
organization.name = new IndividualName("Gates”, "Bill");
individual.name = new OrganizationName("ABC Company");
System.out.println(String.format("Organization name is \"%s\"", organization.name));
System.out.println(String.format("Individual name is \"%s\"", individual.name));
}
}
我们“不小心”交换了传入参数的类型,原本应该传入 OrganizationName的地方实际上传入了IndividuaName,相反也是。而编译器居然完全没有发现这个错误,结果我们的到了完全相反的输出结果:
Organization name is "Gates, Bill" Individual name is "ABC Company"
为什么会这样?编译器为什么无法发现这个问题呢?难道是编译器开发者的疏忽吗?并不是的,其实这就是编译器做出的正确选择,虽然它不符合我们的预期。要理解这个“错误”,我们还是要从OO的最基础原理中去寻找答案。
OO 允许我们适应不同类型的最根本依据是“多态”,多态是 OO 三大特征中的一个,多态的主要体现是“重载”和“重写”,它们解决的核心问题是对象如何正确地寻找到目标。以 Java 语言规范为例,当一个对象被初始化的时候,它会对对象做“重载解析”(overload resolution)或称 “静态分派”,所谓 “静态” 指的是一个对象在声明的时候的类型,也被称为“静态类型”,在这个例子中也就是 “Name”类型,而等号后面的实例类型,OrganizationName 或 IndividualName,被称为“运行时类型”(runtime type)或“实际类型”(Actual type),顾名思义,实际类型在运行时决定,两者最大的不同是运行时类型可能会在运行时发生变化,但是这种变化不会影响静态类型,也就是说,你可以先给 organization.name 赋予 OrganizationName 类型值,然后再改变成 IndividualName 类型也不会导致程序运行的任何不舒服(而你舒服不舒服就不好说了)。
因为静态类型的判定属于编译期的事,而实际类型是属于运行期的事,编译器对OO 的多态依据是由静态类型决定的,也就是说是静态类型,而不是实际类型决定了重载解析时的变量类型!并且,在 Java 语言中,因为属性是不支持重写的,也就是说如果这个问题发生在属性 Name 上,我们希望通过重写来解决问题也是不可能的,也就是说指望经典多态理论来解决这个问题基本上不可行。
以下重写无法通过编译:
class Organization extends Party {
@Override
OrganizationName name;
}
综上所述,桥接模式其实是一个很危险的模式,这种危险性在于它的类型不安全性,那么有没有办法解决这个问题呢?答案当然是有的,就是“泛型”,泛型允许我们在编译期指定运行期类型的范围:
object GenericTest {
sealed trait Namne
final case class OrganizationName(name: String) extends Name
final case class IndividualName(firstName:String, lastName: String) extends Name
sealed trait Party[S <: Name] {
def name: S
}
trait Organization extends Party[OrganizationName]
trait Individual extends Party[IndividualName]
}
如此一来就避免了类型的不确定性,可以放心地使用 Organization 和 Individual了。
泛型并不排斥多态,甚至泛型弥补了经典多态的缺憾,产生了泛型多态的概念,丰富了多态的领域,它让语言变得更加安全,但是这种加强也导致了语言发展过程中的一次强烈的地震,这场地震的结果是将编程语言的世界一分为二,各自走上不同的道路:在一方面尽可能利用泛型来加强静态类型的约束力,让语言逐渐趋向静态类型,静态类型语言一个最明显的标志是逐渐淘汰了 cast(特指 downgrade cast)指令的使用,而对cast 指令的支持最初被认为是OO多态很重要的一个特征(自行Google cast 的四种模式中的 dynamic cast相关内容),Scala语言算的上是静态类型语言的巅峰之作;而另一方面有些语言则希望极大化地强调运行时侧的类型变化特征,这产生了语言的另一个分支:“动态类型语言”的出现,典型的动态类型语言包括 Python、JavaScript 等。当然也有语言尝试兼取两者优点,比如typescript 等。有关动态类型语言,以及它和泛函编程的渊源的内容有很多,基于时间和篇幅不在这里啰嗦,以后再单独介绍。但是可以提一下今天的 Java 也实现了对动态类型语言的支持,只不过这个支持出现的很晚,一直到 JVM7 才从虚拟机层面支持,而语言层面的支持则一直到 Java 8 发布以后。这个过程罕见地经过两个主版本的迭代,前后历时8年,影响深远。