GOF设计模式——Bridge模式
一、什么是Bridge模式?
讲述之前,先介绍两个关于类层次的重要概念:类的功能层次结构和类的实现层次结构。
1、类的功能层次结构
假如现在有一个父类ClassFarther,它具有一些基本功能,现在希望在业务上新增新的功能时,可以通过编写一个子类ClassSon去继承父类ClassFarther,并将新的功能写在子类里面,这样就构成了类层次结构:
像上面这种层次结构,就称之为“类的功能层次结构”。如果继续想在ClassSon类的基础上增加新功能,那么可以再写一个子类去继承ClassSon。如此类推,当我们要新增功能时,我们可以从各个层次的类中找到最符合自己需求的类,然后以它为父类编写子类。
2、类的实现层次结构
有时候,类似的功能,想要有不同的实现手段,这是可以定义一个抽象类,将需要的方法写在抽象类里面,然后创建多个具体实现类去实现抽象类的方法。这时,类结构可以如下表示:
像上面这种层次结构,就被称为“类的实现层次结构”。
在编程时,为了使得类变得简洁,类功能明确,往往需要将“类的功能层次结构”与“类的实现层次结构”独立出来,那么这个时候就要事先明确开发的意图:“是要增加新功能?还是要增加实现?”,另一方面,将两种层次结构分离开,必然需要一种“媒介”将他们构成联系(因为上面的功能父类不是抽象类,用继承的话,就变成强关联),这个“媒介”,就是这里要说的“Bridge”。
二、Bridge模式的思想
左边是功能层次结构,右边是实现层次结构:
Abstraction类:抽象化的类(并非抽象类),位于“类的功能层次结构”的最上层,定义并实现了一些基本功能方法;
RefinedAbstraction类:改善后的抽象化的类(也并非抽象类),用于新增功能的类;
Implementor类:抽象类,此类位于“类的实现层次结构”的最上层,定义了一些抽象方法;
ConcreteImplementor类:具体实现类,实现了Implementor抽象类的方法。
Abstraction类还定义了一个属性,这是Implementor抽象类的引用,这也是Bridge模式的主角——“Bridge”。这样子就可以通过调用功能类的方法,做到调用实现类的实现手段,其实是使用了“委托”。
三、Bridge模式示例
这里有一个程序,用来“显示一些东西”。
1、Dipsplay类
package com.cjs.bridge;
import com.cjs.templateMethod.AbstractDisplay;
public class Display {
private AbstractDisplayImpl impl;
public Display(AbstractDisplayImpl impl) {
this.impl = impl;
}
public void open() {
impl.rawOpen();
}
public void print() {
impl.rawPrint();
}
public void close() {
impl.rawClose();
}
public final void display() {
open();
print();
close();
}
}
里面定义了AbstractDisplayImpl类型属性impl,在基本功能方法里面,其实是调用impl的方法,这种“委托”的设计,可以有效地降低对具体类的依赖。
2、CountDisplay类
package com.cjs.bridge;
public class CountDisplay extends Display{
public CountDisplay(AbstractDisplayImpl impl) {
super(impl);
}
public void multiDisplay(int times) {
open();
for (int i = 0; i < times; i++) {
print();
}
close();
}
}
multiDisplay()方法是一个增强方法,用于将内容循环打印。
3、AbstractDisplayImpl类
package com.cjs.bridge;
public abstract class AbstractDisplayImpl {
public abstract void rawOpen();
public abstract void rawPrint();
public abstract void rawClose();
}
4、StringDisplayImpl类
package com.cjs.bridge;
public class StringDisplayImpl extends AbstractDisplayImpl {
private String string;
private int width;
public StringDisplayImpl(String string) {
this.string = string;
this.width = string.getBytes().length;
}
@Override
public void rawOpen() {
printLine();
}
private void printLine() {
System.out.print("+");
for (int i = 0; i < width; i++) {
System.out.print("-");
}
System.out.println("+");
}
@Override
public void rawPrint() {
System.out.println("|"+ string + "|");
}
@Override
public void rawClose() {
printLine();
}
}
实现了AbstractDisplayImpl类的抽象方法,用于显示字符串。
5、Main类
package com.cjs.bridge;
public class Main {
public static void main(String[] args) {
Display display1 = new Display(new StringDisplayImpl("Hello, China"));
Display display2 = new Display(new StringDisplayImpl("Hello, Cjs"));
CountDisplay countDisplay = new CountDisplay(new StringDisplayImpl("Hello, IDEA"));
display1.display();
display2.display();
countDisplay.display();
countDisplay.multiDisplay(5);
}
}
输出结果:
四、拓展要点
Bridge模式将类的两种层次结构分离开,有利于独立对他们扩展。当想要增加功能时,只需要在“类的功能层次结构”一侧增加功能类即可,不必对“类的实现层次结构”进行修改,而且,增加后的功能可以被“所有的实现”使用。
继承是强关联,委托是弱关联。在示例的Main中,看似是Display类的对象调用它自己的方法,实际上是AbstractDisplayImpl的实现类对象来做具体动作,这种方式,叫做“委托”,Display类的对象委托AbstractDisplayImpl的实现类对象来完成任务。所谓的弱关联,就是在只有Display类的实例生成时,才与作为参数被传入的类构成关联,即为弱关联,这种关系使得代码很容易改变实现。
五、Bridge模式优点拓展示例
在原示例基础上,实现新的输出效果,如下:
示例一:
示例二:
对于示例一,可以理解为开头是“<”,结尾是“>”,中间内容是“*”的循环,每一行增加一个*;对于示例二,头部和结尾分别是“|”,“-”,中间是“#”,每一行递增两个。
回归到前面提及的一个问题,“是要增加新功能?还是要增加实现?”。已知的功能上,输出样式并不能满足现在的输出效果,所以要添加一个新的功能;另一方面,输出内容已经不是原来固定的“-”或者“+”,而是可以变换的,所以必须实现一个可以根据参数变化的输出内容;即,为了实现上面两个示例,要从类的功能结构和类的实现结构出发。
1、新增功能类:IncraseDisplay
package com.cjs.bridge;
public class IncreaseDisplay extends CountDisplay {
private int step;//设定步长
public IncreaseDisplay(AbstractDisplayImpl impl, int step) {
super(impl);
this.step = step;
}
public void increaseDisplay(int level) {
//level, 循环打印的次数
int count = 0;
for (int i = 0; i < level; i++) {
multiDisplay(count);
count += step;
}
}
}
2、新增实现类:CharDisplayImpl类
package com.cjs.bridge;
public class CharDisplayImpl extends AbstractDisplayImpl {
private char head;
private char body;
private char foot;
public CharDisplayImpl(char head, char body, char foot) {
this.head = head;
this.body = body;
this.foot = foot;
}
@Override
public void rawOpen() {
System.out.print(head);
}
@Override
public void rawPrint() {
System.out.print(body);
}
@Override
public void rawClose() {
System.out.println(foot);
}
}
3、Main类
package com.cjs.bridge;
public class Main {
public static void main(String[] args) {
// Display display1 = new Display(new StringDisplayImpl("Hello, China"));
// Display display2 = new Display(new StringDisplayImpl("Hello, Cjs"));
// CountDisplay countDisplay = new CountDisplay(new StringDisplayImpl("Hello, IDEA"));
// display1.display();
// display2.display();
// countDisplay.display();
// countDisplay.multiDisplay(5);
IncreaseDisplay d1 = new IncreaseDisplay(new CharDisplayImpl('<', '*', '>'), 1);
IncreaseDisplay d2 = new IncreaseDisplay(new CharDisplayImpl('|', '#', '-'), 2);
d1.increaseDisplay(4);
d2.increaseDisplay(6);
}
}
输出结果: