默认分类··By/蜜汁炒酸奶

漫谈桥接模式

定义与目的

桥接模式(Bridge Pattern)将抽象部分与具体实现部分分离,使它们可以独立变化。

桥接模式也被称为桥梁模式( Bridge Design Pattern)、接口(Interface)模式或者柄体(Handle and Body)模式。

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

在桥接模式中的抽象不是指抽象类和接口这些高层概念,实现也不是指对抽象类的继承或接口的实现。

《设计模式之美》中有一种的解释方式:

定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。

定义中的“实现”,也并非“接口的实现类”,而是一套独立的“类库”。

“抽象”和“实现”独立开发,通过对象之间的组合关系,组装在一起

在很多设计模式书中还有一种简单的解释:当一个类内部具有两种(或多种)变化维度时,通过组合的方式可以解耦这些变化的维度,让其独立进行扩展。

在这种理解中,将类中存在的多种变化维度当作了抽象,将对这些变化维度的扩展当作实现。这种理解有些类似“组合优于继承”的设计原则。

应用场景

Java中的JDBC 驱动本身是一个桥接模式的典型。这里抽象是JDBC本身,它指定了Java中数据库操作的一套标准,具体实现是MySQL、Oracle等驱动。JDBC 和 Driver 独立开发,通过对象之间的组合关系,组装在一起。JDBC 的所有逻辑操作,最终都委托给 具体 Driver 来执行。大致关系如下:

umlbridgedriver.png

基于第二种解释,可以整理处如下使用场景:

  1. 抽象与具体实现之间需要增加更多灵活性的场景。
  2. 一个类存在两种(或多种)变化维度,这些维度需要独立扩展的时候。
  3. 不希望使用继承,或者因为多层继承导致系统类的个数剧增的时候。

UML类图

第一种更像是一组类负责抽象,另一种组负责实现,从而实现桥接。这里简单描述下基于第二种理解的UML图:

umlbridgebasea.png

由上图可见,桥接模式一般包括4个角色:

1.  抽象化角色(Abstraction):该类持有一个实现角色的引用,抽象角色中的方法需要实现角色来实现。抽象角色一般为抽象类(构造函数强制子类需要传入一个实现对象)。

2.  修正抽象化角色(Refined Abstraction):该类是抽象化角色(Abstraction)的具体实现,扩展抽象化角色,对其方法进行完善和扩展。从而可以改变和修正父类对抽象化的定义。

  1. 实现化角色(Implementor): 用于确定实现维度的基本操作。该类一般为接口或者抽象类。Implementor和RefinedAbstraction抽象部分并不一定完全一致,Implementor常用于提供基本操作,抽象部分定义的是基于实现部分基础操作的业务方法。
  2. 具体实现(ConcreteImplementor):Implementor的具体实现。

通用写法

给予上面UML图我们先写列一个通用写法:

/**
 * 抽象化角色
 */
public abstract class Abstraction {
    Implementor implementor;
    public Abstraction(Implementor implementor) {
        this.implementor = implementor;
    }
    public abstract void opterator();
}
/**
 *修正抽象化角色
 */
public class RefinedAbstraction extends Abstraction {

    public RefinedAbstraction(Implementor implementor) {
        super(implementor);
    }

    public void opterator() {
        this.implementor.operationImpl();
        PrintUtill.println("做的另一些事情");
    }
}
/**
 * 实现化角色
 */
public interface Implementor {
    void operationImpl();
}
/**
 *具体实现
 */
public class ConcreteImplementor implements Implementor{
    @Override
    public void operationImpl() {
        PrintUtill.println("实际做一些事情");
    }
}
/**
 * 客户端
 */
public class BridgeClient {
    public static void main(String[] args) {
        RefinedAbstraction obj = new RefinedAbstraction(new ConcreteImplementor());
        obj.opterator();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

卖奶茶

一杯奶茶可以加仙草、珍珠、青稞等各种配料,奶茶做完是需要卖的,自然也有就很多品牌,如COCO、喜茶、茶颜悦色等,除此之外还有大杯/中杯/小杯等规格。

一个奶茶店除了奶茶,还有果茶、纯茶、咖啡等,它们都可以称为饮品Drink。

如果简单地把配料换成品牌或者规格,继续用装饰器模式实现,可能会出现类似的情况:

umlbridgedecorator.png

可以预料一旦新增品牌或者饮品类型等将造成类指数性增长,维护起来也很麻烦。

一种奶茶可以加多种配料,或者不添加任何配料,但一定属于某个品牌的某种规格。基于此,我们将这两个转成组合的形式:

umlbridgecomposite.png

代码实现如下:

饮品相关:

public abstract class Drink {
    private BrandBase brand;
    private SkuBase sku;

    public Drink(BrandBase brand,SkuBase sku) {
        this.brand = brand;
        this.sku = sku;
    }


    protected int getPriceBase() {
        return 8;
    }
    public abstract  int getCost() ;

    protected BrandBase getBrand() {
        return brand;
    }

    protected SkuBase getSku() {
        return sku;
    }
}


public class Boba extends Drink{


    public Boba(BrandBase brand, SkuBase sku) {
        super(brand, sku);
    }

    public String desc() {
       return getBrand().getName()+ "奶茶";
    }

    @Override
    public int  getCost() {
        return getPriceBase()+getBrand().getPrice()+getSku().getPrice();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

规格相关:

public interface SkuBase {
    String getSku();
    int getPrice();
}


public class BigSku implements SkuBase{
    @Override
    public String getSku() {
        return "大杯";
    }

    @Override
    public int getPrice() {
        return 3;
    }
}

public class MediumSku implements SkuBase{
    @Override
    public String getSku() {
        return "中杯";
    }

    @Override
    public int getPrice() {
        return 2;
    }
}
// 小杯类似,不再展示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

品牌相关:

public interface BrandBase {
    String getName();
    int getPrice();
}


public class SexyTeaBrand implements BrandBase {

    @Override
    public String getName() {
        return "茶颜悦色";
    }

    @Override
    public int getPrice() {
        return 8;
    }
}

public class XichaBrand implements BrandBase{
    @Override
    public String getName() {
        return "喜茶";
    }

    @Override
    public int getPrice() {
        return 9;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

客户端使用:

public class BoboClient {
    public static void main(String[] args) {
        Boba boba = new Boba(new SexyTeaBrand(),new BigSku());
        PrintUtill.println(boba.desc()+":"+boba.getCost());
    }
}
1
2
3
4
5
6

当需要新增品牌时,只需要新增一个BrandBase实现类即可,无需修改其他代码。

总结

通过上面,可以感知桥接模式解耦了抽象与具体实现,使两者可以独立扩展,互不干扰彼此。桥接模式遵循了里氏替换原则和依赖倒置原则,最终实现开闭原则。

优点

  1. 分离抽象与具体实现,方便两者独立扩展,两者的改变互不干扰。
  2. 符合开闭原则
  3. 符合合成复用原则。
  4. 避免扩展了特定抽象的类导致嵌套泛化:嵌套泛化即指使用继承或者因为多层次继承导致系统类的组合个数急剧增加。

缺点

  1. 最大的缺点是增加了系统的复杂度。
  2. 需要正确理解抽象与具体实现,或者正确识别系统中两个或多个独立变化的唯独。

参考资料:

  1. 设计模式之美
  2. 《Head First 设计模式》
  3. 《设计模式就该这么学》
  4. 《Java设计模式及实践》

JK_DesignPattern.png

预览
Loading comments...
0 条评论

暂无数据

example
预览