查看: 87|回复: 0

11 设计模式自学笔记(Java)-桥接模式Bridge[结构型模式]

[复制链接]

5

主题

11

帖子

21

积分

新手上路

Rank: 1

积分
21
发表于 2023-7-20 18:27:07 | 显示全部楼层 |阅读模式
一、桥接模式的本质和目的

桥接模式的本质是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展,基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。
在这儿不理解没关系,通过后面的例子就可以深刻理解了。
二、示例场景

一个咖啡店卖咖啡,卖出的咖啡可以从几个角度来看:
1. 口味:摩卡、拿铁、美式等;
2. 容量:大杯、中杯、小杯等;
3. 添加物:牛奶、糖、蜂蜜等;
4. 温度:热、常温、冷等;
如果需要设计一个点餐系统,来生成咖啡订单,如何在系统中将上面说到的拥有4个属性的咖啡生成呢?
第一种方法

为每种咖啡组合编写不同的类,如下
1. 大杯热牛奶摩卡类;
2. 中杯热牛奶摩卡类;
3. 小杯热牛奶摩卡类;
4. 大杯热蜂蜜摩卡类; ......
试想一下,需要多少各类?假设4个维度分别可选的值是a、b、c、d,那么需要定义的类就是a x b x c x d种。而且每种属性都是固化到特定的类,没法复用,按照这个方法要定义3x3x3x3=81个类。
第二种方法

针对第一种方法,我们能不能单独定义每种属性,并让每种属性能够复用,然后将他们组合/聚合起来形成一杯咖啡呢?这样就减少每个属性的重复定义了。 例如: 1. 口味:摩卡、拿铁、美式等分别定义一个类;3个类 2. 容量:大杯、中杯、小杯等分别定义一个类;3个类 3. 添加物:牛奶、糖、蜂蜜等分别定义一个类;3个类 4. 温度:热、常温、冷等分别定义一个类;3个类
如果能够找到一种方法把这4个维度组合起来,是不是总的类数就变成a+b+c+d=3+3+3+3=12个类?类的数量锐减。
那么如何将这个4个维度组合起来实现一杯咖啡呢?
三、桥接模式的原理

桥接模式就是为了实现上面的第二种方法的,先进行抽象,然后通过桥接将属性连接起来。
看一下下面原理图(初步思路):



桥接模式思想示意图

这个思路是否可以再优化一下?
进一步思路如下图:



桥接模式思想优化示意图

想一下,咖啡的最重要的属性就是口味(也可以说是本质的属性),其它的属性相对来说不是最重要的,那么我们就让口味直接通过继承的方式实现属性的集成,其它三个属性为了提升替换性/重用性,可以也进行一个抽象类的定义,然后去具体实现。
这样设计后,通过组合/聚合实现了咖啡多个属性的集成,减少了类的数量。
图中组合/聚合关系就是桥接模式中Bridge的核心要义,通过组合/聚合将多个属性连接起来的。
最后就形成了桥接模式的通用示意图,如下:



桥接模式UML示意图

桥接模式的角色: * 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用,就是上面所说的Coffe抽象类。

  • 修正抽象化(RefinedAbstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义,就是上面的口味实现类。
  • 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。就是上面的容量、温度、添加物抽象类。
  • 具体实现化(ConcreteImplementor)角色:这个角色给出实现化角色接口的具体实现,就是上面的容量、温度、添加物实现类。



桥接模式UML示意图(与实例映射版)

四、 代码实现与分析

结合上面的示例场景和原理,我们开始实现代码。
1. 抽象化(Abstraction)角色:Coffe抽象类-AbstractCoffe类的定义

public abstract class AbstractCoffe {
    // 重点:此处就是连接其它属性的关键,通过成员变量的方式聚合了其它属性
    ICapacity capacity;
    IAdditives additives;
    ITemperature temperature;

    // 此处通过有参构造的方式接受客户端想要的咖啡属性
    public AbstractCoffe(ICapacity capacity,IAdditives additives,ITemperature temperature){
        this.capacity = capacity;
        this.additives = additives;
        this.temperature = temperature;
    }

    public abstract void Order(int count);
}2. 修正抽象化(RefinedAbstraction)角色:口味实现类-MocaCoffe类定义

public class MocaCoffe extends AbstractCoffe{

        public MocaCoffe(ICapacity capacity,IAdditives additives,ITemperature temperature){
        super(capacity, additives, temperature);
    }

    public void Order(int count) {
        System.out.println("开始制作摩卡咖啡:");
        capacity.SetCupCapacity();
        additives.AddAdditives();
        temperature.SetTemperature();
        System.out.println(count+" 杯Moca Coffe制作完成!");
    }
}本例中就实现了一种类型,就是摩卡,还可以实现其它的口味类型,代码类似,此处就省略了。
3. 实现化(Implementor)角色:容量、温度、添加物抽象类-ICapacity、ITemperature、IAdditives抽象类定义

(1)容量抽象类ICapacity
public interface ICapacity {
    public void SetCupCapacity();
}(2)温度抽象类ITemperature
public interface ITemperature {
    public void SetTemperature();
}(3)添加物抽象类IAdditives
public interface IAdditives {
    public void AddAdditives();
}4. 具体实现化(ConcreteImplementor)角色:容量、温度、添加物实现类-Capacity、Temperature、Additives抽象类定义

(1)Capacity实现类-3种
// 大杯
public class LargeCapacity implements ICapacity{
    @Override
    public void SetCupCapacity() {
        System.out.println("制作杯子尺寸:Large");
    }
}

//中杯
public class MiddleCapacity implements ICapacity{
    @Override
    public void SetCupCapacity() {
        System.out.println("制作杯子尺寸:Middle");
    }
}

//小杯
public class SmallCapacity implements ICapacity{
    @Override
    public void SetCupCapacity() {
        System.out.println("制作杯子尺寸:Small");
    }
}(2)Temperature实现类-3种
// 热饮
public class HotTemperature implements ITemperature{
    @Override
    public void SetTemperature() {
        System.out.println("加热温度至:Hot");
    }
}

// 冷饮
public class NormalTemperature implements ITemperature{
    @Override
    public void SetTemperature() {
        System.out.println("加热温度至:Normal");
    }
}

// 常温
public class ColdTemperature implements ITemperature{
    @Override
    public void SetTemperature() {
        System.out.println("加热温度至:Cold");
    }
}(3)Additives实现类-2种
// 加奶
public class MilkAdditives implements IAdditives{

    @Override
    public void AddAdditives() {
        System.out.println("添加 MILK 成功!");
    }
}

// 加糖
public class SugarAdditives implements IAdditives{
    @Override
    public void AddAdditives() {
        System.out.println("添加 SUGAR 成功!");
    }
}5. 客户端类定义

public class Main {
    public static void main(String[] args) {
        // 客户端传入想要的咖啡各个属性的具体对象
        AbstractCoffe mocaCoffe = new MocaCoffe(new MiddleCapacity(),new SugarAdditives(),new ColdTemperature());
        mocaCoffe.Order(4);
    }
}五、桥接模式的实现关键点

桥接模式实现需要注意一下几点:

  • 抽象化角色提取,就是所有属性中找到最本质的那个属性,例如咖啡最本质的属性就是口味。其实,这个有没有都没关系,可以将所有属性都作为实现化(Implementor)角色;如下面所示:
public class Coffe{
    ICapacity capacity;
    IAdditives additives;
    ITemperature temperature;
    ITaste taste;

    public Coffe(ICapacity capacity, IAdditives additives, ITemperature temperature,ITaste taste) {
        this.capacity = capacity;
        this.additives = additives;
        this.temperature = temperature;
        this.taste = taste;
    }

    public void Order(int count) {
        taste.makeCoffeTaste();
        capacity.SetCupCapacity();
        additives.AddAdditives();
        temperature.SetTemperature();
        System.out.println(count+" 杯Coffe制作完成!");
    }
}
// 这个例子中就去掉了抽象化角色,体会一下。只不过扩展下就会降低。

  • 对属性进行抽象,需要能够识别某个对象更高一级的属性,例如将大杯、中杯、小杯识别为容量这个抽象概念;
  • 设计桥接方法,需要将抽象后的属性连接进咖啡实现类,就是上面例子中的成员变量和构造函数的用途;
六、桥接模式的优缺点

优点


  • 抽象与实现分离,扩展能力强,符合开闭原则。
  • 实现细节对客户透明。
  • 减少了因为继承带来的类爆炸。
缺点


  • 抽象难度大;
  • 类之间的关系变得复杂。
适用场景


  • 某个类有多个的维度的变化,如果用继承就会使项目变的臃肿,会产生许多的子类。
  • 抽象的部分和实现的部分都应该可以扩展。
<< 10 设计模式自学笔记(Java)-装饰器模式Decorator[结构型模式]
>> 12 设计模式自学笔记(Java)-组合模式Composite[结构型模式]
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表