本篇讲解Java设计模式中的桥接模式,分为定义、模式应用前案例、结构、模式应用后案例、适用场景、模式可能存在的困惑和本质探讨7个部分。
定义
桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。
在新的分类方式中,桥接模式被划分至类之间的交互类别中,其简化的是抽象类家族和实现类家族之间的交互,具体是通过组合方式而不是继承方式来解决类数量的爆炸式增长问题。
模式应用前案例
桥接模式中,我们以一个操作系统与文件系统的案例来进行说明。首先,先来看一下未使用桥接模式的案例。
文件系统的抽象类以及Windows操作系统版本和Linux操作系统版本的两个文件系统实现类代码如下。
public abstract class AbstractFileSystem {//文件系统抽象类
public abstract void readFile(String fileName);
}
public class WindowsFileSystem extends AbstractFileSystem {// Windows版本的FileSystem实现
@Override
public void readFile(String filename){
System.out.println("Reading the File using windows os");
}
}
public class LinuxFileSystem extends AbstractFileSystem {// Linux版本的FileSystem实现
@Override
public void readFile(String filename){
System.out.println("Reading the File using linux os");
}
}
接着,操作系统抽象类以及Windows和Linux操作系统两个实现类代码如下。
public abstract class AbstractOperatingSystem {//操作系统抽象类
protected String platform;
public AbstractOperatingSystem(String platform) {
this.platform = platform;
}
public abstract void openFile(String fileName);
}
public class WindowsOS extends AbstractOperatingSystem{// Windows操作系统类
public WindowsOS() {
super("Windows");
}
@Override
public void openFile(String fileName){
if(this.platform.equalsIgnoreCase("windows")){
new WindowsFileSystem().readFile(fileName);
} else{
System.out.println("Unsupported platform!");
}
}
}
public class LinuxOS extends AbstractOperatingSystem { // Linux操作系统类
public LinuxOS(){
super("Linux");
}
@Override
public void openFile(String filename){
if(this.platform.equalsIgnoreCase("linux")){
new LinuxFileSystem().readFile(filename);
} else{
System.out.println("Unsupported platform!");
}
}
}
最后,调用方代码如下。
public class Client {//调用方代码
public static void main(String[] args){
// 使用 Windows操作系统打开文件
//AbstractFileSystem windowsFileSystem = new WindowsFileSystem();
AbstractOperatingSystem windowsOS = new WindowsOS();
windowsOS.openFile("example.txt");
System.out.println();
// 使用 Linux 操作系统打开文件
//AbstractFileSystem linuxFileSystem = new LinuxFileSystem();
AbstractOperatingSystem linuxOS = new LinuxOS();
linuxOS.openFile("example.txt");
}
}
在未使用桥接模式之前,上述代码的主要问题在于操作系统如果实现类和文件系统实现类之间直接耦合,如果文件系统实现类后续有变化,与操作系统实现类之前无法实现独立的变化。因此,与桥接模式定义中的要求不符。
结构
桥接模式的示例代码如下。
public abstract class Abstraction {
protected Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
public abstract void operation();
}
public class RefinedAbstractionA extends Abstraction{
public RefinedAbstractionA(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
System.out.println("RefinedAbstractionA operation()");
implementor.operationImpl();
}
}
public class RefinedAbstractionB extends Abstraction{
public RefinedAbstractionB(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
System.out.println("RefinedAbstractionB operation()");
implementor.operationImpl();
}
}
public interface Implementor {
void operationImpl();
}
public class ConcreateImplementorA implements Implementor{
@Override
public void operationImpl() {
System.out.println("ConcreteImplementorA operationImpl()");
}
}
public class ConcreateImplementorB implements Implementor{
@Override
public void operationImpl() {
System.out.println("ConcreteImplementorB operationImpl()");
}
}
public class Client {
public static void main(String[] args) {
// 两个家族的顶层抽象类或接口组合,非实现类之间组合
Implementor implementorA = new ConcreateImplementorA();
Abstraction abstractionA = new RefinedAbstractionA(implementorA);
abstractionA.operation();
Implementor implementorB = new ConcreateImplementorB();
Abstraction abstractionB = new RefinedAbstractionA(implementorB);
abstractionB.operation();
}
}
参考桥接模式的结构,上面文件系统和操作系统案例的结构也可以绘制出来,如下图所示。
上述两种结构,可以发现主要差别在于抽象核心类家族和实现核心类家族的交互方式不同。
在桥接模式中,两者的交互在两个家族的抽象类之间进行。而在非桥接模式中,两者的交互在两个家族的具体类之间进行。交互方式不同导致两个家族是否可以独立变化。
模式应用后案例
上面操作系统与文件系统的案例。按照桥接模式的结构,如果将操作系统类家族作为抽象类家族,文件系统类家族作为实现类家族。那么完整代码实现如下。
首先是文件系统实现类家族,包括一个抽象类和两个实现类。
public abstract class AbstractFileSystem {//文件系统抽象类
public abstract void readFile(String fileName);
}
public class WindowsFileSystem extends AbstractFileSystem {// Windows版本的FileSystem实现
@Override
public void readFile(String filename){
System.out.println("Reading the File using windows os");
}
}
public class LinuxFileSystem extends AbstractFileSystem {// Linux版本的FileSystem实现
@Override
public void readFile(String filename){
System.out.println("Reading the File using linux os");
}
}
下面是操作系统抽象类家族,包括一个抽象类和两个实现类。
public abstract class AbstractOperatingSystem {//操作系统抽象类
protected AbstractFileSystem fileSystem;
public AbstractOperatingSystem(AbstractFileSystem fileSystem) {
this.fileSystem = fileSystem;
}
public abstract void openFile(String fileName);
}
public class WindowsOS extends AbstractOperatingSystem {// Windows操作系统类
public WindowsOS(AbstractFileSystem fileSystem) {
super(fileSystem);
}
@Override
public void openFile(String fileName){
System.out.println("Opening file in Windows OS");
this.fileSystem.readFile(fileName);
}
}
public class LinuxOS extends AbstractOperatingSystem { // Linux操作系统类
public LinuxOS(AbstractFileSystem fileSystem) {
super(fileSystem);
}
@Override
public void openFile(String fileName){
System.out.println("Opening file in Windows OS");
this.fileSystem.readFile(fileName);
}
}
最后,调用方代码如下。
public class Client {
public static void main(String[] args){
// 使用 Windows 操作系统打开文件
AbstractFileSystem windowsFileSystem = new WindowsFileSystem();
AbstractOperatingSystem windowsOS = new WindowsOS(windowsFileSystem);
windowsOS.openFile("example.txt");
System.out.println();
// 使用 Linux 操作系统打开文件
AbstractFileSystem linuxFileSystem = new LinuxFileSystem();
AbstractOperatingSystem linuxOS = new LinuxOS(linuxFileSystem);
linuxOS.openFile("example.txt");
}
}
在上面代码中,AbstractOperatingSystem与AbstractFileSystem之间通过组合的方式关联在一起。这种方式下,文件系统的实现类和操作系统的实现类之间是一种松耦合关系,未来可以独立变化。
适用场景
当程序中出现两个家族类之间需要相互交互的时候,就可以考虑通过桥接方式将两者联系起来。
那么,又是什么情况会造成两个家族类以及交互呢?按照说,需要交互的两个家族类也可以通过继承方式通过一个家族来实现。但是,继承方式会导致类数量的爆炸式增长。因此,通过组合,将一个大的家族通过组合拆分成两个家族,这样最终类的数量会减少。
因此,对于程序中继承会出现类数量爆炸式增长的情形时,也要考虑是否采用桥接模式。
模式可能存在的困惑
困惑1:桥接模式的定义中提到的抽象部分与它的实现部分分离,使它们都可以独立地变化。在很多设计模式中,都会出现抽象与实现的分离,有什么区别呢?
相较于其他设计模式,桥接模式中抽象与实现的分离其主要区别在于后半句:使它们都可以独立地变化。
大家可以想一下,如果仅仅是一个抽象类与多个实现类这种方式的分离,是无法达到抽象与实现独立地发生变化。
因此,桥接模式所说的抽象部分与实现部分都代表着一个家族,也就是抽象部分不是仅的一个抽象类,而是包括一个抽象类与多个实现类的家族。同样,对于实现部分也是如此。这样,才可实现抽象与实现独立地发生变化。
本质
在桥接模式中,我们可以看到两条编码设计原则的运用,这些原则的组合就构成了桥接模式的本质。
一是组合优于继承,解决继承会导致的类数量爆炸式增长问题;二是面向接口或抽象编程,组合时是将两个家族中的抽象类进行组合,这样确保两个家族的实现部分可以独立的变化。