《大话设计模式》第二十二章
0.《设计模式》摘录
-
问题:
GUI工具箱需要从一个平台迁移到另一个平台,希望可以用尽量少的代码。
-
解决:
将GUI工具箱中与平台有关的部分分解为抽象和实现两个部分。
工具箱只与抽象部分的接口有关系
抽象部分通过实现接口连接具体实现部分,具体平台上的类定义这些具体实现如图:左边window是抽象部分,右边windowImp是实现部分,表示不同平台的接口
【就是把不变的放在抽象类,变的放在另一个层次】
-
桥接模式分解了不同的类层次关系
例如在一个应用中会用到各种类型的窗口,这些类型可能构成一个类层次
另一方面,同一种窗口在各种不同的平台上又会有不同的具体实现类,这构成另一个类层次
如果有m种窗口,n个平台,共需要m*n种窗口类
如果所有这些窗口中用到的与具体平台有关的功能能够被抽象成一个共同的接口(即Implementor),就可采用桥接模式:
只需要m种窗口类和n种Implementor,即总共m+n个类
两个清晰的相互独立的类层次,也容易管理。
1. 问题
一个手机的软件在另一个手机上可能不能运行,一个解决是使用类继承:每个手机品牌都有游戏和通讯录,但这样如果要增加其他功能会很麻烦:
客户端要使用两种手机,就分别实例化两种品牌的各个功能。
如果换一种方式,让软件作为父类:
但这样的话如果要增加手机品牌,也比较麻烦
-
使用继承的局限性:
对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。即增加一个类,会影响其他类。
例如在上面的例子中,如果想增加品牌或者功能,则类的数量越来越多。
2. 合成/聚合复用原则(CARP)
-
面向对象设计中一个重要的原则:合成/聚合复用原则——应该优先使用对象合成/聚合,而不是类继承
-
合成(Composition,也有翻译成组合)和聚合(Aggregation) 都是关联的特殊种类。
聚合表示一种弱的’拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;
合成则是一种 强的’拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。
举例:雁群包含多个大雁,但大雁不是雁群的一部分。翅膀是雁群的一部分,和雁群生命周期一样
-
优点:优先使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能増长为不可控制的庞然大物。
3.用合成/聚合原则解决上面问题
-
将软件和手机硬件分离,减少耦合。手机和软件通过聚合来表示关系。
通过在手机类中声明一个内部属性:
protected Soft soft;
就相当于安装软件了,可以调用soft的函数。当要扩展手机品牌或者功能时,不会影响之前的类,因为之前Phone里面就有Soft soft了,现在仍然适用。
-
代码
手机端:
//抽象的手机 abstract class Phone { protected Soft soft; public Phone(Soft soft) { this.soft=soft; } public abstract void Run(); } //具体的手机 class Phone1 extends Phone { public Phone1(Soft soft) { super(soft); } @Override public void Run() { soft.Run();//是抽象的soft里面声明的,实际传进来的是具体的软件。 } } class Phone2 extends Phone { public Phone2(Soft soft) { super(soft); } @Override public void Run() { soft.Run();//是抽象的soft里面声明的,实际传进来的是具体的软件。 } }
抽象的软件:
//抽象的软件 abstract class Soft { public abstract void Run(); } //具体的两个软件——游戏和通讯录 class Game extends Soft { @Override public void Run() { System.out.println("运行游戏"); } } class AddressList extends Soft { @Override public void Run() { System.out.println("运行通讯录"); } }
客户端:
//客户端 class Client { public static void main(String[] args) { Soft game=new Game(); Phone1 phone=new Phone1(game); phone.Run(); } }
4.桥接模式
-
定义:将抽象部分和它的实现部分分离,使他们都可以独立地变化。例如:上面的手机硬件就是抽象部分,没有具体的功能,而软件就是实现部分。
-
理解:将抽象和实现分离,并不是说将抽象类和它的派生类分开。而是实现系统可能有多角度的分类(比如有手机品牌和手机软件两种分类),每一种分类都有自己的变化,不好将某一种分类作为父类,而其他类作为继承父类的子类,应该把这种多角度分离出来让他自己变化,减少他们之间的耦合。
-
结构图:
Abstraction和Implementor都有Operation,但Abstraction里的Operation是通过Impementor的Operation来具体化
-
代码
Implementor类:
abstract classs Implementor { public abstract void Operation(); }
ConcreteImplementorA和ConcreteImplementorB:实现具体的方法
class ConcreteImplementorA extends Implementor { public void Operation() { System.out.println("具体实现A的方法执行"); } }
Absraction类:
class Abstration { protected Implementor implementor; public void SetImplementor(Implementor) { this.implementor=implementor; } public void Opereation() { implementor.Operation(); } }
RefinedAbstration类:
class RefineddAbstration extends Abstration { public void Operation() { implement.Operation(); } }
客户端代码:
static void main(String[] args[]) { Abstraction ab=new RefinedAbstraction(); ab.SetImplementor(new ConcreteImplementorA()); ab.Operation; ab.SetImplementor(new ConcreteImplementorB)); ab.Operation; }