桥接模式
桥接模式(Bridge)能将抽象与实现分离,使二者可以各自单独变化而不受对方约束,使用时再将它们组合起来,就像架设桥梁一样连接它们的功能,如此降低了抽象与实现这两个可变维度的耦合度,以保证系统的可扩展性。
形与色的纠葛
既然基础建设如此重要,那么我们就用实例来分析一下桥接模式下的产业分工与合作。假设我们要画一幅抽象画,它主要由各种形状的色块组成,以此来表达世界的多样性
要完成这幅作品,不同颜色的画笔是必不可少的工具,那么相应地我们就得定义这些画笔工具类。首先抛开画笔的颜色,画笔本身一定是类似的,所以我们定义一个画笔抽象类.
package bridge;
public abstract class Pen {
public abstract void getColor();
public void draw(){
getColor();
System.out.println("▲");
}
}
画笔抽象类在第3行定义了抽象方法getColor()获取颜色,并交给子类实现不同的颜色;接着在第5行绘图方法draw()中先调用getColor()以获取具体的颜色,然后画出一个三角形。下面我们来看具体的黑色画笔类BlackPen。
package bridge;
public class BlackPen extends Pen {
@Override
public void getColor() {
System.out.println("黑");
}
}
黑色画笔类在第4行实现了获取颜色方法getColor(),并输出了字符串“黑”,以此来模拟黑色墨水的输出。我们先不急于进行过多的扩展,至少目前已经足以进行作画了,我们用客户端试着运行一下
package bridge;
public class Client {
public static void main(String[] args) {
Pen blackPen = new BlackPen();
blackPen.draw();
//输出:黑▲
}
}
我们在调用黑色画笔类的绘图方法draw()后成功输出了“黑△”(黑色三角形)。同理,我们可以继续定义白色画笔类,画出“白△”(白色三角形)。然而,不管我们制造多少种颜色的画笔,都只能画出三角形,这是因为我们在抽象类里硬编码了对“△”的输出,这就造成了形状被牢牢地捆绑于各类彩色画笔中,对于其他形状的绘制则无能为力,使系统丧失了灵活性与可扩展性。
架构产业链
我们已经利用画笔的抽象实现了颜色的多态,现在要解决的问题是对形状的抽离,将形状与颜色彻底分离开来,使它们各自扩展。既然颜色是由画笔来决定的,那么形状可以依赖尺子来规范其笔触线条走向。我们设想这样一个场景,画笔与尺子这两种工具分别产于南北两座孤岛,北岛擅长制造各色画笔,南岛则擅长制造各种形状的尺子
按照这种模式我们开始规划南岛文具产业。首先我们把可以规范形状的尺子类从画笔产业中独立出来,它们至少能够画出正方形、三角形和圆形,来看看南岛所制造的尺
尺子的功能是对笔触线条走向进行规范。为了让尺子各尽其能而不至于毫无章法地扩展,我们先定义一个尺子的高层接口
package bridge;
public interface Ruler {
public void regularize();
}
尺子接口定义了笔触线条走向规范方法regularize(),为各种形状的尺子实现留好了接口。为保持简单,我们在这里忽略形状的大小,假设一种形状对应一个类,那么应该有正方形尺子类、三角形尺子类以及圆形尺子类
package bridge;
public class SquareRuler implements Ruler {
@Override
public void regularize() {
System.out.println("|正方形|");
}
}
package bridge;
public class TriangleRuler implements Ruler {
@Override
public void regularize() {
System.out.println("|三角形|");
}
}
package bridge;
public class CircleRuler implements Ruler {
@Override
public void regularize() {
System.out.println("|圆|");
}
}
南岛文具产业已经被规划完成。接着我们来看处于产业链另一端的北岛文具产业,其擅长制造的是彩色画笔
依照南、北岛的产业合作模式,我们同样需要对北岛产业进行重新规划,也就是对之前的画笔类相关代码进行重构。因为画笔必须有尺子的协助才能完成漂亮的画作,所以我们假设北岛制造处于“产业链下游”,修改之前的画笔抽象类,使其能够用到尺子
package bridge;
public abstract class Pen {
protected Ruler ruler;//尺子的引用
public Pen(Ruler ruler){
this.ruler = ruler;
}
public abstract void draw();//抽象方法
}
package bridge;
public class BlackPen extends Pen {
public BlackPen(Ruler ruler) {
super(ruler);
}
@Override
public void draw() {
System.out.print("黑");
ruler.regularize();
}
}
package bridge;
public class WhitePen extends Pen {
public WhitePen(Ruler ruler) {
super(ruler);
}
@Override
public void draw() {
System.out.print("白");
ruler.regularize();
}
}
客户端Client
package bridge;
public class Client {
public static void main(String[] args) {
//白笔
new WhitePen(new CircleRuler()).draw();
new WhitePen(new SquareRuler()).draw();
new WhitePen(new TriangleRuler()).draw();
//黑笔
new BlackPen(new CircleRuler()).draw();
new BlackPen(new SquareRuler()).draw();
new BlackPen(new TriangleRuler()).draw();
}
}
笛卡儿积
在桥接的产业合作模式下,南、北岛勤劳的工人们继续扩大生产,制造了更多样式的尺子和画笔,让客户端能够更自由地作画。通过例程我们可以看到,桥接模式将原本对形状的继承关系改为聚合(组合)关系,使形状实现从颜色中分离出来,最终完成多类组件维度上的自由扩展与拼装,使形与色的自由搭配成为可能。
我们的例子其实比较简单,只是2色3形的笛卡儿积组合,如果再加入更多的颜色与形状,笛卡儿积的结果数量会大得惊人。举个例子,我们现有7种颜色和10种形状,组合起来就有70(7×10)种可能,假如设计程序时我们只用继承的方式去实现每种可能,那么至少需要70个类。如果颜色与形状不断增多,系统可能会出现代码冗余以及类泛滥的情况,之后每加一种颜色或形状都将举步维艰,系统扩展工作将会是一场灾难。如果利用桥接模式的设计,我们只需要17(7+10)个类便可以组装成任意可能了,并且之后对任何维度的扩展也是轻而易举的。
多姿多彩的世界
桥接模式构架了一种分化的结构模型,巧妙地将抽象与实现解耦,分离出了2个维度(尺子与画笔)并允许其各自延伸和扩展,最终使系统更加松散、灵活,请参看桥接模式的类结构
我们可以把桥接模式分为“抽象方”与“实现方”2个维度阵营,其中各角色的定义如下。
Abstraction(抽象方):抽象一方的高层接口,多以抽象类形式出现并持有实现方的接口引用,对应本章例程中的画笔类。
AbstractionImpl(抽象方实施):继承自抽象方的具体子类实现,可以有多种实施并在抽象方维度上自由扩展,对应本章例程中的黑色画笔和白色画笔。
Implementor(实现方):实现一方的接口规范,从抽象方中剥离出来成为另一个维度,独立于抽象方并不受其干扰,对应本章例程中的尺子接口。
ConcreteImplementor(实现方实施):实现一方的具体实施类,可以有多个实施并在实现方维度上自由扩展,对应本章例程中的正方形尺子类、三角形尺子类、圆形尺子类。
经济发展靠分工,系统扩展靠抽离,桥接模式将抽象与实现彻底解耦,使形状与颜色的纠葛终被化解,各自为营,互不侵扰。劳动分工实现了各种产品制造的自由扩展,使其能够在各自维度上达成多态,无限延伸。桥梁作为经贸发展的纽带更是不可或缺,它让贸易双方各尽其能,并达到合作共赢的状态。产业链的形成则使原本的产品再次组合,具备更多的功能。多姿多彩的世界,一定离不开形形色色的自由组合。
Go版本代码
package bridge
import "fmt"
type Ruler interface {
regularize()
}
type CircleRuler struct{}
func (c *CircleRuler) regularize() {
fmt.Println("|圆|")
}
type SquareRuler struct {
}
func (s *SquareRuler) regularize() {
fmt.Println("|正方形|")
}
type TriangleRuler struct {
}
func (t *TriangleRuler) regularize() {
fmt.Println("|三角形|")
}
package bridge
import "fmt"
type Pen struct {
Ruler
Draw func()
}
type BlackPen Pen
func NewBlackPen(ruler Ruler) *BlackPen {
return &BlackPen{
Ruler: ruler,
Draw: func() {
fmt.Print("黑")
ruler.regularize()
},
}
}
type WhitePen Pen
func NewWhitePen(ruler Ruler) *WhitePen {
return &WhitePen{
Ruler: ruler,
Draw: func() {
fmt.Print("白")
ruler.regularize()
},
}
}
package main
import "desginPatterns/bridge"
func main() {
bridge.NewWhitePen(&bridge.CircleRuler{}).Draw()
bridge.NewWhitePen(&bridge.SquareRuler{}).Draw()
bridge.NewWhitePen(&bridge.TriangleRuler{}).Draw()
//黑笔
bridge.NewBlackPen(&bridge.SquareRuler{}).Draw()
bridge.NewBlackPen(&bridge.CircleRuler{}).Draw()
bridge.NewBlackPen(&bridge.TriangleRuler{}).Draw()
}