动手实现第一个桥接:从接口到具体类
简单说,桥接模式就是:把“做什么”和“怎么做”彻底分开,让它们各自独立变化。
先来一个生活类比:遥控器和电视机
想象一下,你坐在沙发上,手里拿着一个万能遥控器。这个遥控器上只有三个按钮:开机、调音量、换频道。你根本不需要知道电视机里面是液晶屏还是老式显像管,也不关心它是索尼还是创维。你只要按按钮,电视就执行对应的动作。
现在问题来了:如果有一天,你家换了一台投影仪,遥控器还能用吗?如果遥控器本身从红外线升级成了蓝牙,电视机需要跟着换吗?
答案是:都不需要。因为遥控器只关心“按什么键”,电视机只关心“执行什么动作”。它们之间通过一个通用的“接口”(比如红外信号协议)沟通,彼此完全独立。
这就是桥接模式的核心思想:把抽象(遥控器上的按钮)和实现(电视机怎么响应)拆成两条独立的轨道,中间用一座桥连接起来。
为什么会有这个发明?——一个程序员的血泪故事
让我们回到一个真实的开发场景。
假设你接到了一个任务:为一个画图软件实现“形状”功能。老板说:“先支持圆形和方形,颜色嘛,先支持红色和蓝色。”
你一拍脑袋,很快就写出来了:
- 一个
RedCircle(红圆)类 - 一个
BlueCircle(蓝圆)类 - 一个
RedSquare(红方)类 - 一个
BlueSquare(蓝方)类
看起来没问题,对吧?但老板第二天说:“再加一个绿色。” 你就要新增两个类:GreenCircle和GreenSquare。第三天老板又说:“再加一个三角形。” 你又要新增三个类:RedTriangle、BlueTriangle、GreenTriangle。
你发现规律了吗?如果有 M 种形状,N 种颜色,你就要写 M × N 个类。这个数字会像野草一样疯长。更可怕的是,每次新增一种颜色或者一种形状,你都要修改已有的代码——这就像每次换灯泡都要重新装修整个房子。
这就是“类爆炸”问题。根本原因在于:你把“形状是什么”和“颜色怎么画”这两个本该独立变化的东西,强行绑在了一起。
桥接模式怎么解决?——拆成两条轨道
桥接模式的做法很简单:把形状和颜色拆成两个独立的“维度”。
- 维度一(抽象层):形状 —— 只关心“我是什么形状”,不关心“怎么画颜色”
- 维度二(实现层):颜色 —— 只关心“怎么画颜色”,不关心“画在什么形状上”
然后,用一座“桥”把它们连接起来:每个形状对象里,持有一个颜色对象的引用。
第一步:定义颜色接口(实现层)
先定义“颜色”这个维度。它只负责一件事:告诉别人怎么填充颜色。
// 颜色接口 —— 只关心“怎么画颜色” interface Color { void applyColor(); // 应用颜色 }这个接口就像遥控器上的“红外信号协议”。它只规定了“你要能发送信号”,但不管信号是控制电视还是空调。
第二步:实现具体颜色
现在我们可以随意添加具体颜色,每个颜色只需要实现applyColor()方法。
// 红色实现 class RedColor implements Color { public void applyColor() { System.out.println("应用红色"); } } // 蓝色实现 class BlueColor implements Color { public void applyColor() { System.out.println("应用蓝色"); } }注意:这里没有任何形状的影子。颜色类完全不知道自己是画在圆形上还是方形上。它只负责“把红色涂上去”。
第三步:定义形状抽象(抽象层)
现在定义“形状”这个维度。它不直接画颜色,而是委托给颜色对象去做。
// 形状抽象 —— 只关心“我是什么形状”,但需要颜色帮忙 abstract class Shape { protected Color color; // 桥!形状里持有颜色的引用 // 构造函数:把颜色“注入”进来 public Shape(Color color) { this.color = color; } abstract void draw(); // 画形状,但具体怎么画颜色?交给 color 去做 }这里的Color color就是那座桥。形状类不自己实现颜色,而是通过这座桥,把“画颜色”的任务委托给传入的颜色对象。
第四步:实现具体形状
具体形状只需要实现draw()方法,并在里面调用color.applyColor()。
// 圆形 class Circle extends Shape { public Circle(Color color) { super(color); // 通过桥传入颜色 } public void draw() { System.out.print("画圆形,然后"); color.applyColor(); // 委托给颜色对象 } } // 方形 class Square extends Shape { public Square(Color color) { super(color); } public void draw() { System.out.print("画方形,然后"); color.applyColor(); } }第五步:组装使用
现在,创建对象的方式变成了“组合式”的:
// 创建一个红色圆形 Shape redCircle = new Circle(new RedColor()); redCircle.draw(); // 输出:画圆形,然后应用红色 // 创建一个蓝色方形 Shape blueSquare = new Square(new BlueColor()); blueSquare.draw(); // 输出:画方形,然后应用蓝色关键变化:当你需要新增一种颜色(比如绿色)时,你只需要写一个GreenColor类,所有形状自动就能用绿色。当你需要新增一种形状(比如三角形)时,你只需要写一个Triangle类,所有颜色自动就能用在这个形状上。
类数量从 M × N 变成了 M + N。这才是真正的“解耦”(分开独立)。
为什么叫“桥接”?
想象一下,形状和颜色原本是两座孤岛。桥接模式就是在它们之间建了一座桥:形状对象里持有颜色对象的引用。这座桥让它们可以通信,但彼此又不需要知道对方的内部细节。
- 形状说:“我要画了,颜色你负责上色。”
- 颜色说:“好的,我只管上色,不管画什么形状。”
这就是桥接的核心:用组合(持有引用)代替继承(父子关系),让两个维度可以独立变化。
真实世界的场景:图形编辑器
假设你正在开发一个图形编辑器,用户可以选择形状(圆形、方形、三角形)和颜色(红、蓝、绿、渐变)。如果没有桥接模式,每增加一种颜色,你就要为每种形状都写一个新类。很快,代码就会变成一团乱麻。
有了桥接模式:
- 形状团队只需要关注“怎么画形状的轮廓”,完全不用管颜色。
- 颜色团队只需要关注“怎么填充颜色”,完全不用管形状。
- 两个团队可以并行工作,各自添加新功能,互不干扰。
更妙的是,如果未来要支持“纹理”(比如木纹、大理石纹),你只需要新增一个“纹理”维度,再建一座桥连接到形状上。系统可以无限扩展,而不会爆炸。
小结:桥接模式的三个关键点
- 拆成两个维度:把“抽象”(形状)和“实现”(颜色)彻底分开。
- 用桥连接:抽象层持有实现层的引用,通过委托完成工作。
- 独立变化:任何一方新增变体,都不需要修改另一方。
最后留一个思考:桥接模式和“策略模式”很像,但有一个核心区别——策略模式是“换算法”,桥接模式是“拆维度”。你能感觉到它们的区别吗?
推荐一个学习网站,http://easelearningai.com 输入学习主题,会根据你的知识背景,帮你把学习内容讲得通俗易懂。
