“老板娘,来一个圆饼加鸡蛋!...”
今天,我要说说我的早餐。每天清晨,我都忍不住去公司前面那家超市买早餐。最让我垂涎三尺的就是他们家的饼,形式多样,口感俱佳。他们家有长条形的土豆饼,圆形的白菜饼,圆形的茄子饼,方形的鸡蛋饼,方形的香肠饼,还有大煎饼。。。
那么我就说一说这个圆形和方形的白菜饼,鸡蛋饼,香肠饼。若采用自上而下的继承关系,程序设计时只需要定义一个抽象的“饼”,定义制作饼的加鸡蛋、加香肠、加青菜的方法,子类去继承和实现相应的方法即可。
这种实现方式的UML图如下:
类图很简单,声明了一个cake抽象类,定义一个"饼"的抽象模型,定义了制作饼的几种方式方法,不同的饼实现这些方法,源代码如下:
package com.pattern.bridge.v1;
/**
* 有两种饼:圆饼和方饼,分别可以加三种辅料:鸡蛋、香肠、青菜
* 饼--抽象类:定义饼的抽象方法,每一种继承“饼”的子类都需要实现所有抽象方法
* 根据不同的抽象方法,实现“饼”的不同制作方式
*
* 第一种实现方式:
* 把圆饼、方饼分别加鸡蛋、香肠、青菜的各备上一些,客户需要时直接拿给TA
* @author
*
*/
public abstract class Cake {
protected abstract void addEgg();
protected abstract void addSausage();
protected abstract void addGreens();
public void makeCake(String addWhat){
if(addWhat.equalsIgnoreCase("鸡蛋")){
this.addEgg();
}else if(addWhat.equalsIgnoreCase("香肠")){
this.addSausage();
}else if(addWhat.equalsIgnoreCase("青菜")){
this.addGreens();
}
}
}
package com.pattern.bridge.v1;
/**
* 圆饼具体实现类--根据客户的要求提供不同的制作方法,但是无论客户选择哪种方式,其他未选择的方式也需要实现
* @author
*
*/
public class RoundCake extends Cake {
@Override
protected void addEgg() {
System.out.println("【圆饼】加【鸡蛋】嘞...\n");
}
@Override
protected void addSausage() {
System.out.println("【圆饼】加【香肠】嘞...\n");
}
@Override
protected void addGreens() {
System.out.println("【圆饼】加【青菜】嘞...\n");
}
}
package com.pattern.bridge.v1;
/**
* 方饼具体实现类--根据客户的要求提供不同的制作方法,但是无论客户选择哪种方式,其他未选择的方式也需要实现
* @author
*
*/
public class SquareCake extends Cake {
@Override
protected void addEgg() {
System.out.println("【方饼】加【鸡蛋】嘞...\n");
}
@Override
protected void addSausage() {
System.out.println("【方饼】加【香肠】嘞...\n");
}
@Override
protected void addGreens() {
System.out.println("【方饼】加【青菜】嘞...\n");
}
}
package com.pattern.bridge.v1;
/**
* 客户端测试类:
* 老板,给我来3个饼:一个圆饼加鸡蛋,一个方饼加香肠,另一个方饼加青菜
* 好嘞~
* @author
*
*/
public class Test {
public static void main(String[] args){
Cake c1=new RoundCake();
c1.makeCake("鸡蛋");
Cake c2=new SquareCake();
c2.makeCake("香肠");
Cake c3=new SquareCake();
c3.makeCake("青菜");
}
}
上述代码只是描述了早餐店目前的情况,但是任何事情都是变化莫测和向前发展的,早餐店也在不断地改善和发展,比如早餐店即将推出一款新的制作方法,圆饼和方饼还可以加韩国烤肉,开张时吸引了一大批顾客。我们都是吃货呀,好吃的当然不会错过!但是如果按照上述代码的模式,我们需要新增一个子类,且原来的接口需要新增一种制作方法,继承的所有子类都需要改动。而且每一种饼都要实现这么多方式方法,可能并不一定会用到,违反了单一职责原则和依赖倒置原则。
那么,什么样的方式可以不违背基本的设计原则,又实现上述功能呢?
这就是我们今天要说的”桥梁模式“,所谓桥梁模式,将抽象部分和实现部分分离,各自独立,但又能动态结合。实现桥梁模式,重点是如何做到将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
早餐饼的桥梁模式UML图如下:
从类图可以看出,将”饼“和”加辅料“的动作分别抽象出来,分别独立,他们之间架起了一座桥,又能动态结合。若新增加一种类型的饼或者制作工艺,只需要在各自的体系中添加代码,具有良好的扩展性。源代码实现:
package com.pattern.bridge.v2;
/**
* 定义“饼”的抽象类:
*
* 第二种实现方式:
* 将“饼”的种类(抽象)和制作方式(具体实现)分开,抽象出饼的种类特征和制作工艺特征,
* 然后让每一种饼都有制作工艺接口,根据客户的需求,采用对应的工艺,制作出需要的饼
* @author
*
*/
public abstract class Cake {
protected AddImplement addImplement=null;
protected void setAddImplement(AddImplement addImplement){
this.addImplement=addImplement;
}
public abstract void startMakeCake();
}
package com.pattern.bridge.v2;
/**
* 饼的具体实现类--圆饼
* @author
*
*/
public class RoundCake extends Cake {
@Override
public void startMakeCake() {
System.out.println("【圆饼】加什么呢?先备着再说吧...");
}
}
package com.pattern.bridge.v2;
/**
* 饼的具体实现类--方饼
* @author
*
*/
public class SquareCake extends Cake {
@Override
public void startMakeCake() {
System.out.println("【方饼】加什么呢?先备着再说吧...");
}
}
package com.pattern.bridge.v2;
/**
* 饼的制作方式-抽象类:定义抽象方法,根据客户的需求来采用对应的制作工艺
* @author
*
*/
public abstract class AddImplement {
protected String addWhat;
}
package com.pattern.bridge.v2;
/**
* 饼的制作方式-加鸡蛋 具体实现类
* @author
*
*/
public class AddEgg extends AddImplement {
public AddEgg(){
this.addWhat="鸡蛋";
System.out.println("加【"+addWhat+"】咯...\n");
}
}
package com.pattern.bridge.v2;
/**
* 饼的制作方式-加香肠 具体实现类
* @author
*
*/
public class AddSausage extends AddImplement {
public AddSausage(){
this.addWhat="香肠";
System.out.println("加【"+addWhat+"】咯...\n");
}
}
package com.pattern.bridge.v2;
/**
* 饼的制作方式-加青菜 具体实现类
* @author
*
*/
public class AddGreens extends AddImplement {
public AddGreens(){
this.addWhat="青菜";
System.out.println("加【"+addWhat+"】咯...\n");
}
}
package com.pattern.bridge.v2;
/**
* 客户端测试类:
* 老板,给我来3个饼:一个圆饼加鸡蛋,一个方饼加香肠,另一个方饼加青菜
* 好嘞~
* @author
*
*/
public class Test {
public static void main(String[] args){
Cake c1=new RoundCake();
c1.startMakeCake();
c1.setAddImplement(new AddEgg());
Cake c2=new SquareCake();
c2.startMakeCake();
c2.setAddImplement(new AddSausage());
Cake c3=new SquareCake();
c3.startMakeCake();
c2.setAddImplement(new AddGreens());
}
}
综上所述,Bridge模式的优缺点如下:
优点:
1) 分离接口及其实现部分:一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。将Abstraction与Implementor分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译 Abstraction类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道Abstraction和Implementor即可。
2) 提高可扩充性:你可以独立地对Abstraction和Implementor层次结构进行扩充。
3 ) 实现细节对客户透明: 你可以对客户隐藏实现细节,例如共享 Implementor对象以及相应的引用计数机制(如果有的话) 。