桥接模式

《大话设计模式》第22章读书笔记,介绍桥接模式

Posted by GrayWind on November 20, 2018

继承的误区

现在有两个手机品牌M和N,它们都有游戏和通讯录功能,设计它们的代码结构图

方案一:父类是手机品牌,下有手机品牌M和手机品牌N,每个子类下各有通讯录和游戏子类

手机1

方案二:父类是手机软件,下面是子类通讯录和游戏,每个子类下面是不同手机品牌的具体软件

手机2

但是,我们发现,对于上面两种设计,如果要增加新的手机品牌S或者新增新的软件音乐播放器都需要大幅修改已有的结构。

继承的误区:对象的继承关系在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

合成/聚合复用原则

合成/聚合复用原则(CARP)尽量使用合成/聚合,尽量不要使用类继承。优先使用对象的合成/聚合有助于保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持比较小的规模,并且不太可能增长为不可控制的庞然大物。

聚合(Aggregation)表示一种弱的拥有关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成(Composition也称组合)则是强的拥有关系,体现了严格的部分和整体的关系,部分和整体的声明周期一样。比如说,大雁有两个翅膀,翅膀和大雁是部分和整体的关系,并且它们的生命周期相同,于是大雁和翅膀是合成关系。而每只大雁属于一样雁群,雁群中可以有多只大雁,所以大雁和雁群是聚合关系。

基于合成/聚合复用原则的手机软件结构如下

手机3

手机软件接口

public interface HandsetSoft {
	public void run();
}

游戏和通讯录

public class HandsetGame implements HandsetSoft {

	@Override
	public void run() {
		System.out.println("运行手机游戏");
	}

}

public class HandsetAddressList implements HandsetSoft {

	@Override
	public void run() {
		System.out.println("运行手机通讯录");
	}

}

手机品牌抽象类,需要关注软件,所以在机器中安装软件(设置手机软件),以备运行

public abstract class HandsetBrand {
	protected HandsetSoft soft;
	
	public void setHandsetSoft(HandsetSoft soft) {
		this.soft = soft;
	}
	
	public abstract void run();
}

品牌具体类

public class HandsetBrandM extends HandsetBrand {

	@Override
	public void run() {
		soft.run();
	}

}

public class HandsetBrandN extends HandsetBrand {

	@Override
	public void run() {
		soft.run();
	}

}

客户端代码

public class Test {
	public static void main(String[] args) {
		HandsetBrand ab;
        ab = new HandsetBrandN();

        ab.setHandsetSoft(new HandsetGame());
        ab.run();//运行手机游戏

        ab.setHandsetSoft(new HandsetAddressList());
        ab.run();//运行手机通讯录

        ab = new HandsetBrandM();

        ab.setHandsetSoft(new HandsetGame());
        ab.run();//运行手机游戏

        ab.setHandsetSoft(new HandsetAddressList());
        ab.run();//运行手机通讯录
	}
}

要新增手机播放器只需要增加一个手机软件的继承类

public class HandsetMusic implements HandsetSoft {

	@Override
	public void run() {
		System.out.println("运行音乐播放器");
	}

}

要增加一个手机品牌S,也只需要增加一个手机品牌的继承类

public class HandsetBrandS extends HandsetBrand {

	@Override
	public void run() {
		soft.run();
	}

}

桥接模式

上面的结构图中手机软件与手机品牌之间通过桥梁连接,被称为桥接模式(Bridge),它将抽象部分与实现部分分离,使它们都可以独立的变化。抽象与它的实现分离,这并不是说,让抽象类与其派生类分离,因为这没有任何意义,实现指的是抽象类和它的派生类用来实现自己的对象。对于这个例子中,就是让手机既可以按品牌来分类,也可以按照功能来分类。

bridge

Implementor类

public interface Implementor {
	public void operation();
}

具体方法实现类

public class ConcreteImplementorA implements Implementor {

	@Override
	public void operation() {
		System.out.println("具体实现A的方法执行");
	}

}

public class ConcreteImplementorB implements Implementor {

	@Override
	public void operation() {
		System.out.println("具体实现B的方法执行");
	}

}

Abstraction类

public abstract class Abstraction {
	protected Implementor implementor;

	public void setImplementor(Implementor implementor) {
		this.implementor = implementor;
	}
	
	public abstract void operation();
}

RefinedAbstraction类

public class RefinedAbstraction extends Abstraction{

	@Override
	public void operation() {
		implementor.operation();
	}

}

客户端代码

public class BridgeTest {
	public static void main(String[] args) {
		Abstraction ab = new RefinedAbstraction();

        ab.setImplementor(new ConcreteImplementorA());
        ab.operation();//具体实现A的方法执行

        ab.setImplementor(new ConcreteImplementorB());
        ab.operation();//具体实现B的方法执行
	}
}

实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合,这就是将抽象部分与它的实现部分分离。