12,桥接模式-露娜的召唤师技能

191 阅读7分钟

一,前言

7种结构型设计模式:桥接模式,适配器模式,装饰模式,组合模式,享元模式,外观模式,代理模式

上篇我们说了装饰模式:动态地将责任附加到对象上,在不修改任何底层代码的情况下,为对象赋予新的职责

开发中,我们经常会遇到一个类有两个或两个以上的维度经常在变化
如果我们使用继承的方式实现:对一个抽象做多种实现
我们会发现由于变化的维度太多,可能无法实现或是设计变得十分臃肿

这种时候,我们特别希望这个两个维度可以独立变化
即将实现和抽象放在两个不同的类层次中,使他们可以独立拓展,不会影响到对方

桥接模式将变化的部分抽象出来,使变化的部分与主类分离,从而将多个维度的变化彻底分离。
桥接模式提供一个管理类组合不同维度上的变化,通过这种组合完成灵活的设计

今天我们来说桥接模式,有了桥接的存在,就可以独立的改变这两个层次了

二,问题&解决

1,两个维度的例子:

上边我们说受两个维度的变化,为了方便理解,我们举一个具体例子做分析:

以学校选课为例:
学校有数学系和计算机系
课程包含数学课和英语课
数学课分数学分析和高等数学
英语课分数学英语和计算机英语

选课要求:
数学系只能选择数学分析和数学英语
计算机系只能选择高等数学和计算机数学

两个维度为系和课程,多个系和多种课程(eg:数学,计算机)及其不同子类(eg:数学分析和数学英语)

2,问题所在

如果使用继承的方式实现,我们可能要实现多种子类:
计算机系->高等数学
计算机系->计算机英语
数学系->数学分析
数学系->数学英语
...
相当于排列组合的结果,假设我们有10个系,20门课程,子类数量就相当大了
最主要的问题在于,如果其中的一个纬度发生变化,那么涉及到这个维度的所有子类都需要修改代码
对于开发者来说,无论是维度的新增或者是修改,都会设计很大的修改量

3,继承的局限性

有时候过分的使用继承会带来一些麻烦

对象的继承关系是在变异的时候就规定好了,运行时子类无法改变从父类继承来的实现
子类和父类之前是一种强依赖关系,所以我们说,继承从某种意义上讲是一种耦合

这就导致了父类中的某个实现的改变会影响子类的实现
当子类需要被复用时,会由于其继承关系的实现是它不适合解决新的问题

4,我们期望的

对于这种多维度变化的需求,我们希望可以将多个维度拆开,使他们可以独立拓展,而不会影响到对方

5,合成/聚合复用原则

这里我们引入一个设计上的原则:合成/聚合复用原则
尽量使用合成/聚合,尽量不要使用类继承
优先使用合成/聚合,在合成/聚合解决不了问题的情况下再使用继承

聚合:表示一种弱"拥有"关系
比如,对象A可以包含对象B,但对象B不一定是对象A的一部分
合成:表示一种强"拥有"关系
是严格的部分与整体的关系,具有相同的生命周期

合成/聚合复用原则的优点:
类与类继承的层次规模较小,每个类独立封装且职责单一

拿选课的例子来分析:
每个系给学生准备的可选课程可以包含数学课程,也可以不包含数学课程
所以系和课程之间的关系为聚合关系

类A会使用类B的对象,那么类A就算是类B的一个聚合
即,如果类A持有类B对象的引用,那么类A就算是类B的一个聚合

三,桥接模式

有了以上的知识储备,我们可以来看一下桥接模式了

1,桥接模式的定义:

桥接模式通过将实现和抽象放在两个不同的类层次中而使他们可以独立改变

2,桥接模式类图

桥接模式类图

从类图我们可以看出:
    Abstraaction抽象类是implementor接口的一个聚合(持有implementor的引用)
    通过这种聚合关系创建出一个桥
    将两个维度的具体实现进行组合,即抽象的类层次与实现的类层次
    使他们可以独立维护,互不影响,解除耦合

3,桥接模式的理解

到这里,对于桥接的认识还是相对抽象的,不能理解他的概念
通过学生选课的这个例子,主要为了说明,桥接模式适用于这种排列组合式的需求

现在我们假设有一座桥
桥的A侧,有A1,A2,A3
桥的B侧,有B1,B2,B3

A----------------B
A1              B1
A2              B2
A3              B3

从A侧到B测,可以有以下方式:
A1B1,A1B2,A1B3,A2B1,A2B2,A2B3,A3B1,A3B2,A3B3这九种情况
这就是桥接模式所要解决的问题

4,桥接模式的方向性

桥接模式应对的场景是有方向性的
桥绑定的一方都是被调用者,属于被动方
抽象方属于主动方

5,jdk中的桥接模式

JDK提供的JDBC数据库访问接口API是经典的桥接模式实现,
接口内部可以通过实现接口来扩展针对不同数据库的具体实现来进行扩展,
而对外的仅仅只是一个统一的接口调用

四,桥接模式的场景

下面我们选一个场景来使用桥接模式
当然这次我又选取了王者荣耀里的一个场景,可能不是非常的恰当,但刚好可以使用上这个模式

选择游戏英雄时,我们还会为英雄选择一个召唤师技能
游戏中有多个英雄,多个召唤师技能可以组合搭配

露娜

召唤师技能


四,桥接模式的例子

项目结构图如下:

桥接模式项目结构

1,创建抽象层–召唤师技能

package com.brave.bridge.pesticide.skill;

/**
 * 抽象技能
 * 
 * @author Brave
 *
 */
public interface Skill {

    String getName();

}

2,创建抽象层–英雄,持有召唤师技能的引用(二者是聚合关系)

package com.brave.bridge.pesticide.hero;

import com.brave.bridge.pesticide.skill.Skill;

public abstract class Hero {

    protected Skill skill;

    public void setHeroSkill(Skill skill){
        this.skill = skill;
    }

    public abstract void showHeroSkill();

}

3,继承抽象英雄创建具体英雄-露娜,孙悟空

package com.brave.bridge.pesticide.hero;

import com.brave.bridge.pesticide.skill.Skill;

/**
 * 具体英雄-露娜
 * @author Brave
 *
 */
public class Luna extends Hero {

    private String name = "露娜";

    @Override
    public void showHeroSkill() {
        System.out.println("英雄 : " + name + ", 装备技能 : " + skill.getName());
    }

}
package com.brave.bridge.pesticide.hero;

import com.brave.bridge.pesticide.skill.Skill;

/**
 * 具体英雄-孙悟空
 * 初始暴击率为20%,暴击伤害150%;
 * 
 * @author Brave
 *
 */
public class MonkeyKing extends Hero {

    private String name = "孙悟空";

    @Override
    public void showHeroSkill() {
        System.out.println("英雄 : " + name + ", 装备技能 : " + skill.getName());
    }

}

4,实现召唤师技能接口,创建具体召唤师技能-闪现,打野,加速

package com.brave.bridge.pesticide.skill;

/**
 * 具体技能-闪现
 * @author Brave
 *
 */
public class Flash implements Skill {

    @Override
    public String getName() {
        return "闪现";
    }

}
package com.brave.bridge.pesticide.skill;

/**
 * 具体技能-打野
 * @author Brave
 *
 */
public class Jungle implements Skill {

    @Override
    public String getName() {
        return "打野";
    }

}
package com.brave.bridge.pesticide.skill;

/**
 * 具体技能-加速
 * @author Brave
 *
 */
public class SpeedUp implements Skill {

    @Override
    public String getName() {
        return "加速";
    }

}

5,测试桥接模式

package com.brave.bridge.pesticide;

import com.brave.bridge.pesticide.hero.Hero;
import com.brave.bridge.pesticide.hero.Luna;
import com.brave.bridge.pesticide.hero.MonkeyKing;
import com.brave.bridge.pesticide.skill.Flash;
import com.brave.bridge.pesticide.skill.Jungle;
import com.brave.bridge.pesticide.skill.Skill;
import com.brave.bridge.pesticide.skill.SpeedUp;

/**
 * 测试桥接模式
 * 
 * @author Brave
 *
 */
public class Client {

    public static void main(String[] args) {

        // 实例化召唤师技能
        Skill flash = new Flash();//闪现
        Skill jungle = new Jungle();//打野
        Skill speedUp = new SpeedUp();//加速

        // 实例化英雄
        Hero Luna = new Luna();
        Hero monkeyKing = new MonkeyKing();

        // 为英雄桥接召唤师技能
        Luna.setHeroSkill(flash);
        Luna.showHeroSkill();
        Luna.setHeroSkill(jungle);
        Luna.showHeroSkill();
        Luna.setHeroSkill(speedUp);
        Luna.showHeroSkill();

        monkeyKing.setHeroSkill(flash);
        monkeyKing.showHeroSkill();
        monkeyKing.setHeroSkill(jungle);
        monkeyKing.showHeroSkill();
        monkeyKing.setHeroSkill(speedUp);
        monkeyKing.showHeroSkill();
    }

}

打印输出:

英雄 : 露娜, 装备技能 : 闪现
英雄 : 露娜, 装备技能 : 打野
英雄 : 露娜, 装备技能 : 加速
英雄 : 孙悟空, 装备技能 : 闪现
英雄 : 孙悟空, 装备技能 : 打野
英雄 : 孙悟空, 装备技能 : 加速

五,桥接模式的优点与缺点

优点:

将实现解耦,让他和界面之间不再永久绑定
抽象和实现可以独立拓展,不会影响到对方
对于"具体的抽象类"所做的改变.不会影响到客户端

缺点:

增加了代码复杂度