@w1992wishes
2018-03-13T14:28:35.000000Z
字数 5849
阅读 960
设计模式
结构型模式
本文的结构如下:
以往出门都要带上钱包,准备一些现金,但自从有了支付宝,就再也没怎么拿过钱包,口袋揣个手机就可以了,除了付款,手机游戏也很多,也可以看小说,可以说,已经离不开手机。
既然离不开手机,当然会给手机很多保护了,比如贴膜,戴上手机壳等等。厂家在生产手机时,都不是把手机和手机壳作为一个整体生产,然后出售,都是分开生产,分开销售。
假如有三个品牌的手机vivo,oppo和小米,如果手机手机壳一体生产,会是这样的:
对应到相应的类中,将是1+3+6=10个有继承关系的类,如果这时再加一个华为手机,无疑是要多增加3个类,会带来类的急剧增长。
如果手机手机壳分开生产搭配(现实也的确是这样的),就是这样的:
对应到类设计中,只需要7个类,如果增加一类手机,只需增加一个类,增加一款手机壳,也只需增加一个类。
这种处理多维变化(手机和手机壳)的方式运用到软件设计中就是桥接模式。
桥接模式是一种很实用的结构型设计模式,在软件开发时,如果某个类存在两个独立变化的维度,可以运用桥接模式将这两个维度分离出来,使两者可以独立扩展,让系统更加符合“单一职责原则”。
与多层继承方案不同,它将两个独立变化的维度设计为两个独立的继承等级结构,并且在抽象层建立一个抽象关联,该关联关系就像一条桥一样,将两个独立继承结构的类联接起来,故名桥接模式。
可以明显看出,桥接模式使用组合代替了继承,将类之间的静态继承关系转换为动态的对象组合关系,使用组合而不用继承,会使系统更加灵活,并易于扩展,同时有效控制了系统中类的个数。
桥接定义如下:
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
上面的例子应该清楚说明了什么是桥接模式,但回来看定义却会发现有点模糊,大概是语义不是很清楚的缘故吧。
可以这样理解:
抽象部分:面向对象中,将对象的共同性质提取出来形成抽象类部分,比如上面的手机。
实现部分:是对抽象事物进一步具体化的产物,比抽象部分更具体,比如给手机戴上手机壳成为有手机壳的手机就是一个实现化过程。
说白了就是在多维变化中,分离出其中的一个变化为单独的继承结构,在多重的继承结构中这层变化就是抽象的一种实现,现在不用继承来扩展,而是改为组合来扩展其实现,也就是将抽象和实现分离。
桥接模式UML类图如下:
可以看到在桥接模式的结构图中,存在一条连接两个继承等级结构的桥。
在桥接模式结构图中包含如下几个角色:
桥接模式是一个非常有用的模式,在桥接模式中体现了很多面向对象设计原则的思想,包括“单一职责原则”、“开闭原则”、“合成复用原则”、“里氏代换原则”、“依赖倒转原则”等。
在使用桥接模式时,首先应该识别出一个类所具有的两个独立变化的维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合。通常情况下,将具有两个独立变化维度的类的一些普通业务方法和与之关系最密切的维度设计为“抽象类”层次结构(抽象部分),而将另一个维度设计为“实现类”层次结构(实现部分)。
比如上面的例子,手机可以打电话,可以玩游戏,这些时每个手机型号都具备的业务方法,所以将手机型号这一维度定为手机的抽象部分,而手机壳则是另一个维度,与手机更多是一种“设置”关系,定为手机的实现部分。
一个类两个独立变化的维度,为了降低耦合度,对两个不同的维度提取抽象类和实现类接口,并建立一个抽象关联关系。对于“实现部分”维度,实现类接口典型代码如下:
public interface Implementor {
void operationImpl();
}
具体实现类中实现了在实现类接中声明的方法,典型代码如下:
public class ConcreteImplementor implements Implementor {
public void operationImpl() {
//todo
}
}
另一维度,抽象类典型代码如下:
public abstract class Abstraction {
protected Implementor impl; //实现类接口
public void setImpl(Implementor impl){
this.impl = impl;
}
public abstract void operation(); //声明抽象业务方法
}
扩充抽象类继承抽象类,典型代码如下:
public class RefinedAbstraction extends Abstraction {
public void operation() {
//todo
impl.operationImpl();
//todo
}
}
看上去有点像“多维度的装饰者模式”。
还是以引言中的手机为例:
public abstract class Phone {
public abstract void playMusic();
}
public class VivoPhone extends Phone {
public void playMusic() {
System.out.println("音乐high起来");
}
}
public class OppoPhone extends Phone {
public void playMusic() {
System.out.println("音乐high起来");
}
}
public class XiaomiPhone extends Phone {
public void playMusic() {
System.out.println("音乐high起来");
}
}
public class SimpleVivoPhone extends VivoPhone {
public void playMusic() {
System.out.println("戴上简单手机壳");
System.out.println("音乐high起来");
}
}
public class CuteVivoPhone extends VivoPhone {
public void playMusic() {
System.out.println("戴上可爱手机壳");
System.out.println("音乐high起来");
}
}
public class SimpleOppoPhone extends OppoPhone {
public void playMusic() {
System.out.println("戴上简单手机壳");
System.out.println("音乐high起来");
}
}
public class CuteOppoPhone extends OppoPhone{
public void playMusic() {
System.out.println("戴上可爱手机壳");
System.out.println("音乐high起来");
}
}
public class SimpleXiaomiPhone extends XiaomiPhone {
public void playMusic() {
System.out.println("戴上简单手机壳");
System.out.println("音乐high起来");
}
}
public class CuteXiaomiPhone extends XiaomiPhone {
public void playMusic() {
System.out.println("戴上简单手机壳");
System.out.println("音乐high起来");
}
}
可以发现:
先定出抽象部分:
public abstract class Phone {
protected ShellImplementor shellImplementor;
public void setShellImplementor(ShellImplementor shellImplementor){
this.shellImplementor = shellImplementor;
}
public void call(){
System.out.println("打电话");
}
public abstract void playMusic();
}
扩展抽象部分:
public class VivoPhone extends Phone {
public void playMusic() {
shellImplementor.wearShell();
System.out.println("音乐High起来");
}
}
public class OppoPhone extends Phone {
public void playMusic() {
shellImplementor.wearShell();
System.out.println("音乐high起来");
}
}
public class XiaomiPhone extends Phone {
public void playMusic() {
shellImplementor.wearShell();
System.out.println("音乐high起来");
}
}
将手机壳部分定义为实现部分:
public interface ShellImplementor {
void wearShell();
}
具体实现:
public class SimpleShell implements ShellImplementor {
public void wearShell() {
System.out.println("戴上简单手机壳");
}
}
public class CuteShell implements ShellImplementor {
public void wearShell() {
System.out.println("戴上可爱手机壳");
}
}
客户端测试:
public class Client {
public static void main(String[] args) {
Phone phone = new VivoPhone();
ShellImplementor shell = new SimpleShell();
phone.setShellImplementor(shell);
phone.playMusic();
}
}
新增手机型号只需继承自Phone就可以,新增手机壳也只需实现ShellImplementor,而且更换简单,可以用XML配置文件实现,只需修改配置,不需修改源码。
而且如果还要多一个实现,即三重维度,比如手机膜,多重继承会直接炸掉,而桥接模式则相对简洁很多。
桥接模式的主要优点如下:
桥接模式的主要缺点如下:
在以下情况下可以考虑使用桥接模式: