桥接模式(Bridge Pattern)
概念
桥接(Bridge Pattern)是用于把抽象化与实现化解耦,使得二者可以独立变化。然后两者通过一个抽象类来进行桥接;这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。可以理解为 A功能类、B功能类,然后一个抽象类C作为桥接者,将A和B两者连接起来,这样A、B两者可以修改自己的类的行为,然后C不去桥接两者,这样可以灵活的配置;如果出现很多组合,然而每一个类都实现不同的服务,可能会出现笛卡儿积,这个时候用桥接就特别爽了!
简介
作用及优势
- 将抽象部分与实现部分分离,使它们都可以独立的变化。
- 具有较强的扩展能力,单独加实现类就可以多出几个组合。
- 可以解决子类过多的情况,其实是通过聚合去解决的。
不足之处
其实桥接模式没什么不足之处,就是需要将关联关系或者聚合关系建立在抽象之上,而真正的处理逻辑又在这个抽象的实现中;所以会有点难理解;
场景
- 需要两两组合又不想通过if else去判断的情况(2 * 2类似的场景)
- 两边组合都希望能够很好扩展性,且能多出组合关系
代码
案例描述
我是个吃货😂,假设有家店,他有很多菜品(Cuisine),这些菜品呢,就是那个桥接者,然后呢,有面食(Pasta)、米粉(RiceFlour);然后厨师会两种做法水煮(Boiled)、爆炒(StirFry);那么笛卡尔积就来,2 * 2 = 4,就存在四个菜品:水煮面、爆炒面、水煮粉、爆炒粉;突然厨子A离职了,然后厨子B来了,假设他是个菜鸟,只会水煮,那么菜品只能减少了(Cuisine);但是只要厨子A又回来了,或者将爆炒改良了成魔鬼辣椒爆炒,但是对于菜品来说,只需要将面食丢给厨子就行了,我菜品上又可以加两道菜;这就是桥接的好处就是应对这种会存在组合关系,会出现笛卡尔积的场景;
工程目录
项目类图
具体实现
烹饪方式的接口(CookingMethod) :
/**
* 功能描述: 烹饪方式
*
* @author: WuChengXing
* @create: 2021-06-26 10:25
**/
public interface CookingMethod {
/**
* 制作
*/
String doThings();
}
具体的烹饪方式:Boiled、StirFry
/**
* 功能描述: 水煮
*
* @author: WuChengXing
* @create: 2021-06-26 10:27
**/
public class Boiled implements CookingMethod {
@Override
public String doThings() {
System.out.println("=== 放入水中开始煮 ===");
return "水煮";
}
}
---------------------------------------
/**
* 功能描述: 爆炒
*
* @author: WuChengXing
* @create: 2021-06-26 10:26
**/
public class StirFry implements CookingMethod {
@Override
public String doThings() {
System.out.println("=== 放入大料中开始爆炒 ===");
return "爆炒";
}
}
菜系的抽象(Cuisine):
/**
* 功能描述: 菜系
*
* @author: WuChengXing
* @create: 2021-06-26 10:19
**/
public abstract class Cuisine {
protected CookingMethod cookingMethod;
public Cuisine(CookingMethod cookingMethod) {
this.cookingMethod = cookingMethod;
}
/**
* 制作完成
*/
public abstract void finishedProduct();
}
这里就是放聚合关系的地方,将烹饪方式 通过聚合方式引入,然后在子类再去处理或者叫使用这个引入的功能,这样烹饪方式和菜品都可以随意的扩展,这个菜系抽象类就相当于一个桥一样,将两者串联起来;
具体的菜系:Pasta、RiceFlour
/**
* 功能描述: 面食
*
* @author: WuChengXing
* @create: 2021-06-26 10:23
**/
public class Pasta extends Cuisine {
public Pasta(CookingMethod cookingMethod) {
super(cookingMethod);
}
@Override
public void finishedProduct() {
System.out.println("=== 擀面、醒面、清洗面 ===");
String name = cookingMethod.doThings();
System.out.println("=== 制作完成, " + name + "面 ===");
}
}
-------------------------------------------
/**
* 功能描述: 米粉
*
* @author: WuChengXing
* @create: 2021-06-26 10:24
**/
public class RiceFlour extends Cuisine {
/**
* 引申出一个问题:为什么子类一定要先调用父类的构造器去协助完成初始化?
* 道理很简单,就是有一些字段需要父类自己去完成初始化,然后子类才可以去使用
*
* @param cookingMethod
*/
public RiceFlour(CookingMethod cookingMethod) {
super(cookingMethod);
}
@Override
public void finishedProduct() {
System.out.println("=== 清洗米粉、浸泡一段时间 ===");
String name = cookingMethod.doThings();
System.out.println("=== 制作完成, " + name + "米粉 ===");
}
}
测试
/**
* 功能描述: 桥接模式
*
* @author: WuChengXing
* @create: 2021-06-20 13:07
**/
public class BridgingTest {
public static void main(String[] args) {
Cuisine boiledRiceFlour = new RiceFlour(new Boiled());
boiledRiceFlour.finishedProduct();
System.out.println("-----------------------------");
Cuisine stirFryRiceFlour = new RiceFlour(new StirFry());
stirFryRiceFlour.finishedProduct();
System.out.println("-----------------------------");
Cuisine boiledPasta = new Pasta(new Boiled());
boiledPasta.finishedProduct();
System.out.println("-----------------------------");
Cuisine riceFlourStirFry = new RiceFlour(new StirFry());
riceFlourStirFry.finishedProduct();
}
}
结果: 这里就组合出了四种情况,四种菜品;
=== 清洗米粉、浸泡一段时间 ===
=== 放入水中开始煮 ===
=== 制作完成, 水煮米粉 ===
-----------------------------
=== 清洗米粉、浸泡一段时间 ===
=== 放入大料中开始爆炒 ===
=== 制作完成, 爆炒米粉 ===
-----------------------------
=== 擀面、醒面、清洗面 ===
=== 放入水中开始煮 ===
=== 制作完成, 水煮面 ===
-----------------------------
=== 清洗米粉、浸泡一段时间 ===
=== 放入大料中开始爆炒 ===
=== 制作完成, 爆炒米粉 ===