设计模式——Bridge(桥接)模式

本文深入解析桥接模式的概念,通过宠物与农场的生动案例,阐述如何利用桥接模式实现代码的解耦与扩展。同时,探讨了桥接模式在Java JDBC中的应用及其实现细节。

前言

体能状态先于精神状态,习惯先于决心,聚焦先于喜好。

桥接模式
书面用语

桥接模式的意图在于,将抽象和抽象方法的实现相互分离来实现解耦,以便二者可以相互独立地变化。

大白话

从实现上来说,桥接模式将代码分为了两个部分:
第一部门:一个接口和若干个接口的实现类。
第二部分:一个抽象类,并且该抽象类内部声明一个第一部门的接口,以及提供一个构造方法,允许将第一部分的子类传递进来。这个抽象类可以有很多的子类,子类的构造方法调用父类的抽象方法。
这样,第二部分只知道第一部分的接口,而不知道第一部分的子类具体的实现,与此同时,第二部的子类也可以有多个实现,从而实现了调用方和被调用方的解耦。

构造一个场景
场景描述
  • 你有两只宠物,一只小猫,一只小狗。
  • 你要带领你的两只宠物一起去参观农场。
  • 在将来,你可能会拥有其他的宠物,比如小猪、小鸟等等。
  • 农场分为很多的区域,当前有鱼塘、向日葵园。
  • 农场将来可能会有其他功能区域,比如种植大豆、白菜等。
  • 你可以在农场的特定区域喂你的宠物特定的食物,比如在鱼塘可以喂鱼,在向日葵园可以喂瓜子。并且,喂养哪一只宠物是你的自由。
  • 当小动物很开心的时候,会有一些反馈,比如小猫喵喵叫,小狗蹦蹦跳跳。
场景探究
  • 你有两只宠物,小猫、小狗,以后还可能有其他更多的小动物。
  • 你去参观农场,农场有不同的区域,目前有鱼塘、向日葵园,将来可能还有其他区域类型。
  • 你可以在农场的特定区域喂小动物吃东西,而且小动物会有行为上的反馈。
  • 小猫和小狗可以被认为是动物的抽象的具体实现,他们有一个功能行为——表示自己开心
  • 农场可以看做是鱼塘和向日葵院的抽象,在农场你可以喂小动物吃东西。
  • 你可以选择喂小猫,或者小狗,所以,小动物是农场中的一个变量,可通过构造函数传入具体的小动物。
  • 这正好符合桥接模式。多样的农场和多样的小动物的自由搭配。
桥接模式给出的解决方案
UML 图

Manor(农场) 就是桥
这里ManorFlashpond(农场的鱼塘) 和 ManorSunflower(农场的向日葵园) 在代码编写的时候不知道那只小动物会被传递进来,也不知道会喂那只小动物吃什么。
Animal(动物) 是小动物的接口,AnimalCat(动物-猫)和AnimalDog(动物-狗)是小动物的具体实现。

在这里插入图片描述

代码
接口:Animal (动物)
public interface Animal {
	/**小动物展示开心的行为*/
	void showHappy();
	/**获取小动物的名字*/
	String getName();
}
动物实现:AnimalDog(小狗)
/**
 * 动物:小狗
 * @author jie.wu
 */
public class AnimalDog implements Animal{

	@Override
	public void showHappy() {
		System.out.println("小狗开心的摇着尾巴,一蹦一跳");
	}
	@Override
	public String getName() {
		return "小狗";
	}
}
动物实现:AnimalCat(小猫)
/**
 * 动物:小猫
 * @author jie.wu
 */
public class AnimalCat implements Animal{

	@Override
	public void showHappy() {
		System.out.println("小猫开心的喵喵叫,然后去追毛线球了");
	}

	@Override
	public String getName() {
		return "小猫";
	}

}
抽象类: Manor (农场)

如果子类只有一个,可以不要抽象类,即不需要子类了,当然,这样调用者以后不容易扩展了。

/**
 * 庄园:抽象类
 * @author jie.wu
 *
 */
public abstract class Manor {
	protected Animal animal;
	public Manor(Animal animal) {
		this.animal=animal;
	}
	public abstract void feedSomething();
}
农场具体区域:ManorSunflower (向日葵园)
/**
 * 农场的向日葵园
 * @author jie.wu
 *
 */
public class ManorSunflower extends Manor{

	public ManorSunflower(Animal animal) {
		super(animal);
	}

	@Override
	public void feedSomething() {
		System.out.println("你在 向日葵园 送给 "+super.animal.getName()+" 一颗瓜子");
		super.animal.showHappy();
	}

}
农场具体区域:ManorFishpond (鱼塘)
/**
 * 农场的鱼塘
 * @author jie.wu
 */
public class ManorFishpond extends Manor{

	public ManorFishpond(Animal animal) {
		super(animal);
	}

	@Override
	public void feedSomething() {
		System.out.println("你在 鱼塘 送给 "+super.animal.getName()+" 一条鱼");
		super.animal.showHappy();
	}

}
测试类
import org.junit.Test;

/**
 * 桥接模式 测试类
 * @author jie.wu
 */
public class BridgeTest {

	/**
	 * 农场有很多个具体场地,比如鱼塘,向日葵园等-如果农场只有单一农作物,你可以不实用抽象类
	 * 你可以带不同的小动物,在喂食小动物后,小动物有不同的表现——但是都是高兴的表现
	 * */
	@Test
	public void testFeedSomething() {
		//选择小动物
		Animal animal=new AnimalCat();
		//选择农场的具体位置
		Manor manor=new ManorFishpond(animal);
		//给小动物喂食
		manor.feedSomething();
	}
	
	@Test
	public void testFeedSomething2() {
		//选择小动物
		Animal animal=new AnimalDog();
		//选择农场的具体位置
		Manor manor=new ManorSunflower(animal);
		//给小动物喂食
		manor.feedSomething();	
		
	}
}
桥接模式的专有名词

上面的动物、小猫、农场和鱼塘在桥接模式中有专门的名字

  • Abstraction(抽象类):就是农场。用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
  • RefinedAbstraction(扩充抽象类):就是鱼塘。扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以调用在Implementor中定义的业务方法。
  • Implementor(实现类接口):就是动物。定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。
  • ConcreteImplementor(具体实现类):就是小猫。具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。

Abstraction(抽象类):农场
RefinedAbstraction(扩充抽象类):鱼塘、向日葵园
Implementor(实现类接口):动物
Implementor(实现类接口):小猫、小狗

java.sql 中的桥接模式

相信读者在学习桥接模式的过程中又看到过类似的介绍了。Java 中通过 JDBC 调用不同数据库厂商驱动来获取数据库服务就是桥接模式的一个体现。
但是需要注意的是,相比于本文提到了完整的模型,java.sql 中的桥接模式有一些小的调整,比如它只有抽象类,而没有扩充抽象类——事实上就是一个普通类 DriverManager

java.sql 驱动相关UML 图

在这里插入图片描述

从桥接模式看 java.sql 类关系

Abstraction(抽象类):DriverManager
RefinedAbstraction(扩充抽象类):无
Implementor(实现类接口):java.sql.Driver
Implementor(实现类接口):com.mysql.jdbc.Driver

java.sql 对桥接模式的调整

加载驱动,建立数据库连接的代码

@Test
	public void test() throws ClassNotFoundException, SQLException {
		//加载 mysql 驱动器
		Class.forName("com.mysql.jdbc.Driver");
		//new com.mysql.jdbc.Driver();
		Connection conn = DriverManager.getConnection(
		"jdbc:mysql://127.0.0.1:3306/database", "user", "password");
		conn.createStatement();
	}

看起来,好像没有出现 com.mysql.jdbc.Driver 被作为构造函数的入参传递到 DriverManager,尽管你可以自由的选择Oracle的驱动——类比小猫、小狗等

我们看下源码

  • DriverManager 内部有一个 static
static {
  //用于获得所有可以获得的 数据库驱动
  loadInitialDrivers();
  println("JDBC DriverManager initialized");
}
  • loadInitialDrivers();内部有一个 DriverInfo 列表变量
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers 
= new CopyOnWriteArrayList<>();
  • DriverInfo
class DriverInfo {
  final Driver driver;
  DriverAction da;
  DriverInfo(Driver driver, DriverAction action) {
      this.driver = driver;
      da = action;
  }
  ···
 }

所以,可以看到,在 DriverManager 的类加载阶段,其通过 static 代码块获得了可以获得的驱动,然后通过构造方法注入到 DriverInfo 中,可以看出是桥接模式的变种。

桥接模式 适用场景

桥接模式使用于调用方和实现方比较灵活的情况,这种灵活有两种类别,第一种就是类别比较丰富,丰富到用继承的方式来实现会增加太多的类——M*N;第二种类别就是不确定,在不确定的时候使用桥接模式进行解耦,在调用的时候再传入具体的实现,为后面的改造留下余地——只需要增加一个抽象的实现,然后修改构造方法的入参对象而不用修改调用方的代码。

参考链接

[1]、https://blog.youkuaiyun.com/lemon_tree12138/article/details/51024127#commentsedit
[2]、https://blog.youkuaiyun.com/qq_25827845/article/details/52490611
[3]、https://blog.youkuaiyun.com/a910626/article/details/50766084

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值