桥接模式
一、定义
- 桥接模式是将抽象部分与它的实现部分相分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体模式或接口模式。
二、业务场景
考虑到这样的一个需求:绘制矩形,圆形,三角形这三种图案。按照面向对象的理念,我们至少需要三个具体类,对应三种不同的图形。
抽象接口:IShape
public interface IShape {
void draw();
}
三个具体形状类:
class Rectangle implements IShape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
class Round implements IShape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class Triangle implements IShape {
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
接下来我们有了新的需求,每种形状都需要有四种不同的颜色:红、蓝、黄、绿。
这时我们很容易想到两种设计方案:
- 为了复用形状类,将每种形状定义为父类,每种不同颜色的图形继承自其形状父类。此时一共有 12 个类。
- 为了复用颜色类,将每种颜色定义为父类,每种不同颜色的图形继承自其颜色父类。此时一共有 12 个类。
乍一看没什么问题,我们使用了面向对象的继承特性,复用了父类的代码并扩展了新的功能。
但仔细想一想,如果以后要增加一种颜色,比如黑色,那么我们就需要增加三个类;如果再要增加一种形状,我们又需要增加五个类,对应 5 种颜色。
更不用说遇到增加 20 个形状,20 种颜色的需求,不同的排列组合将会使工作量变得无比的庞大。看来我们不得不重新思考设计方案。
形状和颜色,都是图形的两个属性。他们两者的关系是平等的,所以不属于继承关系。更好的的实现方式是:将形状和颜色分离,根据需要对形状和颜色进行组合,这就是桥接模式的思想。
官方定义非常精准、简练,但却有点不易理解。通俗地说,如果一个对象有两种或者多种分类方式,并且两种分类方式都容易变化,比如本例中的形状和颜色。这时使用继承很容易造成子类越来越多,所以更好的做法是把这种分类方式分离出来,让他们独立变化,使用时将不同的分类进行组合即可。
说到这里,不得不提一个设计原则:合成 / 聚合复用原则。虽然它没有被划分到六大设计原则中,但它在面向对象的设计中也非常的重要。
继承虽然是面向对象的三大特性之一,但继承会导致子类与父类有非常紧密的依赖关系,它会限制子类的灵活性和子类的复用性。而使用合成 / 聚合,也就是使用接口实现的方式,就不存在依赖问题,一个类可以实现多个接口,可以很方便地拓展功能。
注:合成/聚合复用原则:优先使用合成/聚合,而不是类继承。
让我们一起来看一下本例使用桥接模式的程序实现:
新建接口类 IColor,仅包含一个获取颜色的方法:
public interface IColor {
String getColor();
}
每种颜色都实现此接口:
public class Red implements IColor {
@Override
public String getColor() {
return "红";
}
}
public class Blue implements IColor {
@Override
public String getColor() {
return "蓝";
}
}
public class Yellow implements IColor {
@Override
public String getColor() {
return "黄";
}
}
public class Green implements IColor {
@Override
public String getColor() {
return "绿";
}
}
在每个形状类中,桥接 IColor 接口:
class Rectangle implements IShape {
private IColor color;
void setColor(IColor color) {
this.color = color;
}
@Override
public void draw() {
System.out.println("绘制" + color.getColor() + "矩形");
}
}
class Round implements IShape {
private IColor color;
void setColor(IColor color) {
this.color = color;
}
@Override
public void draw() {
System.out.println("绘制" + color.getColor() + "圆形");
}
}
class Triangle implements IShape {
private IColor color;
void setColor(IColor color) {
this.color = color;
}
@Override
public void draw() {
System.out.println("绘制" + color.getColor() + "三角形");
}
}
测试函数:
@Test
public void drawTest() {
Rectangle rectangle = new Rectangle();
rectangle.setColor(new Red());
rectangle.draw();
Round round = new Round();
round.setColor(new Blue());
round.draw();
Triangle triangle = new Triangle();
triangle.setColor(new Yellow());
triangle.draw();
}
//程序输出如下:
//绘制红矩形
//绘制蓝圆形
//绘制黄三角形
这时我们再来回顾一下官方定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。抽象部分指的是父类,对应本例中的形状类,实现部分指的是不同子类的区别之处。将子类的区别方式 —— 也就是本例中的颜色 —— 分离成接口,通过组合的方式桥接颜色和形状,这就是桥接模式,它主要用于两个或多个同等级的接口。
摘自地址:https://leetcode-cn.com/leetbook/read/design-patterns/99c3mc/
三、优缺点
1、优点
- 在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。
- 桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。
2、缺点
- 桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。