7 『桥接模式』 - 图1
image.png

1. 「引例」

image.png

  • 有两个公司,房地产公司和服装制造公司。

  • 「抽象公司」

    1. public abstract class Corp {
    2. protected abstract void produce(); // 生产产品
    3. protected abstract void sell(); // 销售
    4. public void makeMoney() {
    5. // 先生产,再销售,从中获利
    6. this.produce();
    7. this.sell();
    8. }
    9. }
  • 「房地产公司实现」

    1. public class HouseCorp extends Corp {
    2. // 盖房子
    3. protected void produce() {
    4. System.out.println("房地产公司盖房子...");
    5. }
    6. // 卖房子
    7. protected void sell() {
    8. System.out.println("房地产公司出售房子...");
    9. }
    10. // 计算利润
    11. public void makeMoney() {
    12. super.makeMoney();
    13. System.out.println("房地产公司赚大钱...");
    14. }
    15. }
  • 「服装公司实现」

    1. public class ClothesCorp extends Corp {
    2. // 生产衣服了
    3. protected void produce() {
    4. System.out.println("服装公司生产衣服...");
    5. }
    6. // 卖服装
    7. protected void sell() {
    8. System.out.println("服装公司出售衣服...");
    9. }
    10. //赚钱
    11. public void makeMoney() {
    12. super.makeMoney();
    13. System.out.println("服装公司赚小钱...");
    14. }
    15. }
  • 「测试」

    1. @Test
    2. public void test() {
    3. System.out.println("-------房地产公司是这样运行的-------");
    4. HouseCorp houseCorp = new HouseCorp();
    5. houseCorp.makeMoney();
    6. System.out.println("-------服装公司是这样运行的-------");
    7. ClothesCorp clothesCorp = new ClothesCorp();
    8. clothesCorp.makeMoney();
    9. }

    ———-房地产公司是这样运行的———- 房地产公司盖房子… 房地产公司出售房子… 房地产公司赚大钱… ———-服装公司是这样运行的———- 服装公司生产衣服… 服装公司出售衣服… 服装公司赚小钱…

  • 服装厂转到小钱后,决定转型,于是开始生产山寨IPod,其他不变

    1. public class IPodCorp extends Corp {
    2. protected void produce() {
    3. System.out.println("生产IPod...");
    4. }
    5. protected void sell() {
    6. System.out.println("IPod畅销...");
    7. }
    8. public void makeMoney() {
    9. super.makeMoney();
    10. System.out.println("IPod赚钱...");
    11. }
    12. }
  • 「测试」

    1. @Test
    2. public void test1() {
    3. System.out.println("-------房地产公司是按这样运行的-------");
    4. HouseCorp houseCorp = new HouseCorp();
    5. houseCorp.makeMoney();
    6. System.out.println("-------山寨公司是按这样运行的-------");
    7. IPodCorp iPodCorp = new IPodCorp();
    8. iPodCorp.makeMoney();
    9. }
  • 这样的小厂,随时都会根据市场的动向进行业务调整,而且是相对比较大的改变。所以不要把产品和工厂绑得太死。

image.png-

  • 具体的实现就省略了。
  • public abstract class Product
  • public class House extends Product
  • public class IPod extends Product
  • public abstract class Corp中添加一个有参构造,传入产品,子类都要重写父类的构造,以传入产品进行相应的业务。

  • 这种情况下,假如房地产公司进行业务细化,将公司分为公寓房公司别墅公司等等,对类图的也不会有太大的修改。

  • 增加公司,要么继承Corp类,要么继承HouseCorpShanZhaiCorp,不用修改原有的类。
  • 增加产品,继承Product类,或者继承House类,要把房子分为公寓房、别墅、商业 用房等。
  • 唯一要修改的就是Client类。类都增加了,高层模块也需要修改,也就是说Corp类和 Product类都可以自由地扩展,而不会对整个应用产生太大的变更,这就是桥梁模式

2. 「定义」

Decouple an abstraction from its implementation so that the two can vary independently.

  • 将抽象和实现解耦,使得两者可以独立地变化。

通用类图
image.png

  • Abstraction——抽象化角色
    • 定义出该角色的行为,同时保存一个对实现化角色的引用,该角色一般 是抽象类
  • Implementor——实现化角色
    • 它是接口或者抽象类,定义角色必需的行为和属性。
  • RefinedAbstraction——修正抽象化角色
    • 它引用实现化角色对抽象化角色进行修正。
  • ConcreteImplementor——具体实现化角色
    • 它实现接口或抽象类定义的方法和属性。

一句话就是:抽象角色引用实现角色, 或者说抽象角色的部分实现是由实现角色完成的。

  • 「实现化角色」

    1. public interface Implementor {
    2. //基本方法
    3. public void doSomething();
    4. public void doAnything();
    5. }
  • 「具体实现化角色」

    1. public class ConcreteImplementor1 implements Implementor {
    2. public void doSomething() {
    3. //业务逻辑处理
    4. }
    5. public void doAnything() {
    6. //业务逻辑处理
    7. }
    8. }
    9. public class ConcreteImplementor2 implements Implementor {
    10. public void doSomething() {
    11. // 业务逻辑处理
    12. }
    13. public void doAnything() {
    14. // 业务逻辑处理
    15. }
    16. }
  • 「抽象化角色」

    1. public abstract class Abstraction {
    2. // 定义对实现化角色的引用
    3. private Implementor imp;
    4. // 约束子类必须实现该构造函数
    5. public Abstraction(Implementor _imp) {
    6. this.imp = _imp;
    7. }
    8. // 自身的行为和属性
    9. public void request() {
    10. this.imp.doSomething();
    11. }
    12. // 获得实现化角色
    13. public Implementor getImp() {
    14. return imp;
    15. }
    16. }

    添加一个有参构造,是为了让子类传入实现类,明确实现者。

  • 「具体抽象化角色」

    1. public class RefinedAbstraction extends Abstraction {
    2. //覆写构造函数
    3. public RefinedAbstraction(Implementor _imp) {
    4. super(_imp);
    5. }
    6. //修正父类的行为
    7. @Override
    8. public void request() {
    9. super.request();
    10. super.getImp().doAnything();
    11. }
    12. }
  • 「测试」

    1. @Test
    2. public void test() {
    3. Implementor imp = new ConcreteImplementor1(); //定义一个实现化角色
    4. Abstraction abs = new RefinedAbstraction(imp); // 定义一个抽象化角色
    5. abs.request(); // 执行行文
    6. }

    桥梁模式是一个非常简单的模式,它只是使用了类间的聚合关系、继承、覆写等常用功 能,但是它却提供了一个非常清晰、稳定的架构。


3. 「优点」

  • 抽象和实现分离
    • 这也是主要特点,它完全是为了解决继承的缺点而提出的设计模式。
    • 实现可以不受抽象的约束,不用再绑定在一个固定的抽象层次上。
  • 优秀的扩充能力
  • 实现细节对客户透明
    • 细节已经由抽象层通过聚合关系完成了封装。

4. 「使用场景」

  • 不希望或不适用使用继承的场景
    • 例如继承层次过渡、无法更细化设计颗粒等场景,需要考虑使用桥梁模式。
  • 接口或抽象类不稳定的场景
    • 明知道接口不稳定还想通过实现或继承来实现业务需求,那是得不偿失的,也是比较失 败的做法。
  • 重用性要求较高的场景
    • 设计的颗粒度越细,则被重用的可能性就越大,而采用继承则受父类的限制,不可能出 现太细的颗粒度。

5. 「注意事项」

📌:桥梁模式非常简单,使用该模式时主要考虑如何拆分抽象和实现,并不是一涉及继 承就要考虑使用该模式,那还要继承干什么呢?桥梁模式的意图还是对变化的封装,尽量把可能变化的因素封装到最细、最小的逻辑单元中,避免风险扩散。因此在进行系统设计时,发现类的继承有N层时,可以考虑使用桥梁模式。


6. 「最佳实践」

  • 继承的优点有很多,可以把公共的方法或属性抽取,父 类封装共性,子类实现特性,这是继承的基本功能。
  • 缺点是强侵入,父类有一个方法,子类也必须有这个方法。这是不可选择的,会带来扩展性的问题。
    • 我举个例子🌰来说明:Father类有一个方法A,Son继承了这个方法,然后GrandSon也继承了这个方法, 问题是突然有一天Son要重写父类的这个方法,他敢做吗?绝对不敢!GrandSon要用从Father继承过来的方法A,如果修改了,那就要修改Son和GrandSon之间的关系,那这个风险就太大了!
    • 桥梁模式描述了类间弱关联关系。Father类完全可以把可能会变化的方法放出去,Son子类要拥有这个方法很简单,桥梁搭过去,获得这个方法,GrandSon也一样,即使你Son子类不想使用这个方法也没关系,对GrandSon不产生影响,它不是从Son中继承来的方法!
  • 对于比较明确不发生变 化的,则通过继承来完成;若不能确定是否会发生变化的,那就认为是会发生变化,则通过桥梁模式来解决,这才是一个完美的世界。