《设计模式》3.结构型模式

点击进入我的博客

3.1 适配器模式

适配器模式把一个类的接口变换成客户端所期待的另一种接口,使得原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

3.1.1 类的适配器结构

《《设计模式》3.结构型模式》

  • 目标(Target)角色:这就是所期待得到的接口,由于是类适配器模式,因此目标不可以是类。
  • 源(Adaptee)角色:现有需要适配的接口。
  • 适配器(Adapter)角色:适配器类是本模式的核心,必须是具体类。
interface Target {
    void operation1();
    void operation2();
}

class Adaptee {
    public void operation1() {}
}

class Adapter extends Adaptee implements Target {
    @Override
    public void operation2() {}
}
类的适配器
  • 类的适配器模式把被的类的API转换成目标类的API。
  • 是通过继承实现的。
类的适配器效果
  • 使用一个具体类把源适配到目标中。这样如果源以及源的子类都使用此类适配,就行不通了。
  • 由于适配器是源的子类,因此可以在适配器中重写源的一些方法。
  • 由于引进了一个适配器类,因此只有一个线路到达目标类,是问题得到简化。

3.1.2 对象的适配器结构

《《设计模式》3.结构型模式》

  • 目标(Target)角色:这就是所期待得到的接口,因此目标可以是具体或抽象的类
  • 源(Adaptee)角色:现有需要适配的接口。
  • 适配器(Adapter)角色:适配器类是本模式的核心,必须是具体类。
interface Target {
    void operation1();
    void operation2();
}

class Adaptee {
    public void operation1() {}
}

class Adapter implements Target {
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void operation1() {
        adaptee.operation1();
    }

    @Override
    public void operation2() {
        // do something
    }
}
对象的适配器
  • 对象的适配器是通过依赖实现的
  • 推荐使用该方法
对象的适配器效果
  • 一个适配器可以把多种不同的源适配到同一个目标,即同一个适配器可以把源类和它的子类都适配到目标接口
  • 与类的适配器模式相比,要想置换源类的方法就不容易。
  • 增加新的方法方便的多

3.1.3 细节

使用场景
  1. 系统需要使用现有的类,而此类的接口不符合系统的需要
  2. 想要建立一个可以重复使用的类,用于与一些彼此间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
  3. 对对象的适配器儿模式而言,在设计里,需要改变多个已有的子类的接口,如果使用类的适配器模式,就需要针对每个子类做一个适配器类,这不太实际。
优点
  1. 可以让任何两个没有关联的类一起运行。
  2. 提高了类的复用。
  3. 增加了类的透明度。
  4. 灵活性好。
缺点
  1. 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
  2. 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
注意点
  1. 目标接口可以忽略,此时目标接口和源接口实际上是相同的
  2. 适配器类可以是抽象类
  3. 可以有带参数的适配器模式

3.1.4 一个充电器案例

// 充电器只能接受USB接口
public class Charger {
    public static void main(String[] args) throws Exception{
        USB usb = new SuperAdapter(new TypeC());
        connect(usb);
        usb = new SuperAdapter(new Lightning());
        connect(usb);
    }

    public static void connect(USB usb) {
        usb.power();
        usb.data();
    }
}

// 充电器的接口都是USB的,假设有两个方法分别是电源和数据
interface USB {
    void power();
    void data();
}

// IOS的Lightning接口
class Lightning {
    void iosPower() {
        System.out.println("IOS Power");
    }
    void iosData() {
        System.out.println("IOS Data");
    }
}

// TYPE-C接口
class TypeC {
    void typeCPower() {
        System.out.println("TypeC Power");
    }
    void typeCData() {
        System.out.println("TypeC Data");
    }
}

// 超级适配器,可以适配多种手机机型
class SuperAdapter implements USB {
    private Object obj;

    public SuperAdapter(Object obj) {
        this.obj = obj;
    }

    @Override
    public void power() {
        if(obj.getClass() == Lightning.class) {
            ((Lightning)obj).iosPower();
        } else if(obj.getClass() == TypeC.class) {
            ((TypeC)obj).typeCPower();
        }
    }

    @Override
    public void data() {
        if(obj.getClass() == Lightning.class) {
            ((Lightning)obj).iosData();
        } else if(obj.getClass() == TypeC.class) {
            ((TypeC)obj).typeCData();
        }
    }
}

3.2 缺省适配模式

缺省适配模式为一个接口提供缺省实现,这样子类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。

3.2.1 缺省适配模式结构

《《设计模式》3.结构型模式》

简单的例子
  • 下面程序中,Monk接口定义了两个方法,于是它的子类必须实现这两个方法。
  • 但出现了一个LuZhiShen,他只能实现一部分方法,另一部分方法无法实现
  • 所以需要一个抽象的适配类MonkAdapter实现此Monk接口,此抽象类给接口所有方法都提供一个空的方法,LuZhiShen只需要继承该适配类即可。
// 和尚
interface Monk {
    void practiceKungfu();
    void chantPrayer();
}

abstract class MonkAdapter implements Monk {
    @Override
    public void practiceKungfu() {}

    @Override
    public void chantPrayer() {}
}

class LuZhiShen extends MonkAdapter {
    @Override
    public void practiceKungfu() {
        System.out.println("拳打镇关西");
    }
}

3.2.2 细节

使用场景
  • 任何时候不准备实现一个接口中所有方法的时候
作用

缺省适配器模式可以使所需要的类不必实现不需要的接口。

核心点
  • 缺省适配的类必须是抽象类,因为这个类不应当被实例化
  • 缺省适配的类提供的方法必须是具体的方法,而不是抽象的方法。

3.3 组合模式

组合模式,就是在一个对象中包含其他对象,这些被包含的对象可能是终点对象(不再包含别的对象),也有可能是非终点对象(其内部还包含其他对象)。
我们将对象称为节点,即一个根节点包含许多子节点,这些子节点有的不再包含子节点,而有的仍然包含子节点,以此类推。很明显,这是树形结构,终结点叫叶子节点,非终节点叫树枝节点,第一个节点叫根节点。

3.3.1 安全式的合成模式结构

安全式的合成模式要求管理集合的方法只出现在树枝结点(Composite)中,而不出现在树叶结点中。
《《设计模式》3.结构型模式》

  • 抽象构建(Component)角色:这是一个抽象角色,他给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。
  • 树叶(Leaf)角色:树叶是没有子对象的对象,定义出参加组合的原始对象的行为。
  • 树枝(Composite)角色:代表参加组合的有下级子对象的对象。树枝构件类给出所有管理子对象的方法。

3.3.2 透明的合成模式结构

《《设计模式》3.结构型模式》
透明的合成模式要求所有的具体构建类,都符合一个固定的接口。

3.4 装饰器模式

装饰器模式(Decorator)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

3.4.1 装饰器结构

《《设计模式》3.结构型模式》

  • 抽象构件(Component)角色:给出一个抽象结构,以规范准备接受附加责任的对象。
  • 具体构件(Concrete Component)角色:定义一个要接受附加责任的类
  • 装饰(Decorator)角色:持有一个构件对象的实例,并定义一个与抽象构件接口一致的接口。
  • 具体装饰(Concrete Decorator)角色:负责给构件对象“贴上”附加的责任。

3.4.2 装饰器细节

使用场景
  1. 需要扩展一个类的功能
  2. 需要动态地给一个对象增加功能,这些功能可以再动态的插销
  3. 需要增加由一些基本功能的排列组合而产生非常大量的功能
优点
  1. 更加灵活:装饰模式和继承关系的目的都是要扩展对象的功能,但是装饰模式比继承更加灵活
  2. 多样性:通过使用不同具体装饰类及其排列组合,可以创造出不同的行为
  3. 动态扩展:装饰器可以动态扩展构件类
缺点
  1. 会产生比继承关系更多的对象
  2. 比继承更加容易出错
注意点
  1. 装饰类的接口必须与被装饰类的接口相容。
  2. 尽量保持抽象构件(Component)简单。
  3. 可以没有抽象的(Component),此时装饰器(Decorator)一般是具体构件(Concrete Component)的一个子类。
  4. 装饰(Decorator)和具体装饰(Concrete Decorator)可以合并。
InputStream及其子类

《《设计模式》3.结构型模式》

  • 抽象构件(Component)角色:InputStream
  • 具体构件(Concrete Component)角色:ByteArrayInputStreamPipedInputStreamStringBufferInputStream等原始流处理器。
  • 装饰(Decorator)角色:FilterInputStream
  • 具体装饰(Concrete Decorator)角色:DateInputStreamBufferedInputStreamLineNumberInputStream
OutputStream及其子类

也用到类装饰器模式

3.4.3 例子

// Component:一个艺人
interface Artist {
    void show();
}

// Concrete Component:一个歌手
class Singer implements Artist {
    @Override
    public void show() {
        System.out.println("Let It Go");
    }
}

// 装饰后的歌手:不仅会唱歌,还会讲笑话和跳舞
class SuperSinger implements Artist {
    private Artist role;
    
    public SuperSinger(Artist role) {
        this.role = role;
    }
    @Override
    public void show() {
        System.out.println("Tell Jokes!");
        role.show();
        System.out.println("Dance!");
    }
}

3.5 代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象对引用

3.5.1 代理模式结构

《《设计模式》3.结构型模式》

  • 抽象主题(Subject)角色:声明了真实主题和代理主题的共同接口
  • 代理主题(Proxy)角色:代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主题控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);代理角色通常在将客户端调用传递给真实的主题之前或之后,都要执行某个操作,而不是单纯地将调用传递给真实主题对象。
  • 真实主题(RealSubject)角色:定义了代理角色所代表的真实对象。
public class Test {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Subject proxy = new ProxySubject(subject);
        proxy.request(); // 此处通过代理类来执行
    }
}

interface Subject {
    void request();
}

class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject");
    }
}

class ProxySubject implements Subject {
    private Subject subject;

    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void request() {
        System.out.println("ProxySubject");
    }
}

3.5.2 动态代理

自从JDK 1.3以后,Java在java.lang.reflect库中提供了一下三个类直接支持代理模式:ProxyInvocationHanderMethod

动态代理步骤
  1. 创建一个真实对象
  2. 创建一个与真实对象有关的调用处理器对象InvocationHandler
  3. 创建代理,把调用处理器和要代理的类联系起来Proxy.newInstance()
  4. 在调用处理对象的invoke()方法中执行相应操作
public class Test {
    public static void main(String[] args) {
        // 创建要被代理的实例对象
        Subject subject = new RealSubject();
        // 创建一个与被代理实例对象有关的InvocationHandler
        InvocationHandler handler = new ProxySubject(subject);
        // 创建一个代理对象来代理subject,被代理的对象subject的每个方法执行都会调用代理对象proxySubject的invoke方法
        Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, handler);
        // 代理对象执行
        proxySubject.request();
    }
}

interface Subject {
    void request();
}

class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject");
    }
}

class ProxySubject implements InvocationHandler {
    private Subject subject;

    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    /**
     * @param proxy 要代理的
     * @param method
     * @param args
     * @return
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before Proxy");
        Object obj = method.invoke(subject, args);
        System.out.println("After Proxy");
        return obj;
    }
}
  • 可以使用范型来创建ProxySubject
  • 可以使用匿名内部类减少代码数量请查看14.7节

3.5.3 细节

优点
  1. 代理类和真实类分离,职责清晰。
  2. 在不改变真是累代码的基础上扩展了功能。
缺点
  1. 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
和适配器模式的关系

适配器模式的用意是改变所考虑对象的接口,而代理模式不能改变。

和装饰模式
  • 装饰模式应当为所装饰的对象提供增强功能
  • 代理模式对对象的使用施加控制,并不提供对象本身的增强功能
虚拟代理
  • 虚拟代理模式(Virtual PRoxy)会推迟真正所需对象实例化时间。在需要真正的对象工作之前,如果代理对象能够处理,那么暂时不需要真正对象来出手。
  • 当一个真实主题对象的加载需要耗费资源时,一个虚拟代理对象可以代替真实对象接受请求,并展示“正在加载”的信息,并在适当的时候加载真实主题对象。

3.6 享元模式

享元模式以共享的方式高效地支持大量的细粒度对象。

3.6.1 单纯享元模式

单纯享元模式中,所有的享元对象都是可以共享的。
《《设计模式》3.结构型模式》

  • 抽象享元(Flyweight)角色:是所有具体享元角色的超类,并为这些类规定公共接口。
  • 具体享元(Concrete Flyweight)角色:实现抽象享元的接口。如果由内蕴状态的话,必须负责为内蕴状态提供空间。
  • 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。如果系统中有了则返回该角色,没有则创建。
  • 客户端(Client)角色:维护一个所有享元对象的引用。存储所有享元对象的外蕴状态。

3.6.2 复合享元模式

《《设计模式》3.结构型模式》

  • 抽象享元(Flyweight)角色 :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法。
  • 具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定出的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。
  • 复合享元(ConcreteCompositeFlyweight)角色 :复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称作不可共享的享元对象(UnsharedConcreteFlyweight)。
  • 享元工厂(FlyweightFactory)角色 :负责创建和管理享元角色。当一个客户端对象调用一个享元对象的时候,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。

3.6.3 细节

内蕴状态和外蕴状态

内蕴状态:是存储在享元对象内部的,不会随环境改变而改变的。一个享元可以具有内蕴状态并可以共享。
外蕴状态:随环境改变而改变、不可以共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。

不变模式

享元模式中的对象不一定非要是不变对象,但大多数享元对象的确是这么设计的。

享元工厂
  1. 使用单例模式:一般只需要一个享元工厂,可以设计成单例的。
  2. 备忘录模式:享元工厂负责维护一个表,通过这个表把很多相同的实例与它们的一个对象联系起来。
优点

减少对象的创建,降低内存消耗

缺点
  1. 提高了系统的复杂度,为了使对象可以共享,需要将一些状态外部化
  2. 需要将一些状态外部化,而读取外部状态是的运行时间稍微变长
使用场景
  1. 一个系统中有大量对象。
  2. 这些对象消耗大量内存。
  3. 这些对象的状态大部分可以外部化。
  4. 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。
  5. 系统不依赖于这些对象身份,换言之,这些对象是不可分辨的。
Java应用
  1. String对象,有则返回,没有则创建一个字符串并保存
  2. 数据库的连接池

3.6.4 案例

依旧是熟悉的KFC点餐为例:

  • 外蕴状态:点餐的顾客
  • 内蕴状态:顾客点的食物
  • 具体享元角色:维护内蕴状态(客人要点的食物)。
public class KFC {
    public static void main(String[] args) {
        OrderFactory orderFactory = OrderFactory.getInstance();
        Order order = orderFactory.getOrder(Food.MiniBurger);
        order.operation("李雷");
        order = orderFactory.getOrder(Food.MiniBurger);
        order.operation("韩梅梅");
    }
}

enum Food {
    MiniBurger,
    MexicanTwister,
    CornSalad,
    HotWing,
    PepsiCola
}

// Flyweight角色
interface Order {
    // 传入的是外蕴对象:顾客
    void operation(String customer);
}

// ConcreteFlyweight角色
class FoodOrder implements Order {
    // 内蕴状态
    private Food food;
    // 构造方法,传入享元对象的内部状态的数据
    public FoodOrder(Food food) {
        this.food = food;
    }

    @Override
    public void operation(String customer) {
        System.out.println("顾客[" + customer + "]点的是" + food.toString());
    }
}

// FlyweightFactory角色
class OrderFactory {
    private Map<Food, Order> orderPool = new HashMap<>();
    private static OrderFactory instance = new OrderFactory();

    private OrderFactory() {}

    public static OrderFactory getInstance() {
        return instance;
    }

    // 获取Food对应的享元对象
    public Order getOrder(Food food) {
        Order order = orderPool.get(food);
        if (null == order) {
            order = new FoodOrder(food);
            orderPool.put(food, order);
        }
        return order;
    }
}

3.7 门面模式

门面模式(Facade Pattern)要求一个子系统的外部与其内部通信,必须通过一个统一的门面对象进行。

3.7.1 门面模式结构

门面模式没有一个一般化的类图描述,可以用下面的例子来说明。
《《设计模式》3.结构型模式》

  • 门面(Facade)角色:外部可以调用这个角色的方法。此角色知道子系统的功能和责任。
  • 子系统(Subsystem)角色:可以有多个子系统,子系统不需要知道门面的存在。

3.7.2 细节

门面数量

通常只需要一个门面类,而且只有一个实例,因此可以设计称单例模式。当然也可有多个类。

使用场景
  1. 为一个复杂的子系统提供一个简单的接口
  2. 使子系统和外部分离开来
  3. 构建一个层次化系统时,可以使使用Facade模式定义系统中每一层,实现分层。
优点
  1. 减少系统之间的相互依赖。
  2. 提高了安全性。
缺点
  1. 不符合开闭原则
  2. 如果要改东西很麻烦,继承重写都不合适。
Java例子

MVC三层结构

3.7.3 KFC例子

假如没有服务员(门面),顾客(外部系统)要点一个套餐需要知道每个套餐包含的食物(子系统)种类,这样就会非常麻烦,所以最好的方式是直接告诉服务员套餐名称就好了。

public class Customer {
    public static void main(String[] args) {
        Waiter waiter = new Waiter();
        List<Food> foodList = waiter.orderCombo("Combo1");
    }
}

abstract class Food {}
class MiniBurger extends Food {}
class MexicanTwister extends Food {}
class CornSalad extends Food {}
class HotWing extends Food {}
class PepsiCola extends Food {}

class Waiter {
    public List<Food> orderCombo(String comboName) {
        List<Food> foodList;
        switch (comboName) {
            case "Combo1" : 
                foodList = Arrays.asList(new MiniBurger(), new CornSalad(), new PepsiCola()); 
                break;
            case "Combo2":
                foodList = Arrays.asList(new MexicanTwister(), new HotWing(), new PepsiCola());
                break;
            default:
                foodList = new ArrayList<>();
        }
        return foodList;
    }
}

3.8 过滤器模式

过滤器模式使用不同的条件过滤一组对象,并通过逻辑操作以解耦方式将其链接。这种类型的设计模式属于结构模式,因为该模式组合多个标准以获得单个标准。

3.8.1 细节

步骤
  1. 创建一个要过滤的普通类,要有获得其私有属性的get方法
  2. 创建一个接口,规定过滤方法
  3. 实现接口,可以依需要重写过滤方法,参数传递的一般是存储过滤类的容器类
  4. 复杂过滤类可以通过设置传递接口参数(复用其他基础过滤类)来实现多重过滤
Java8

Java8中的lambda表达式可以更简单的实现过滤器

List<Movie> movies = Stream.of(
                new Movie("大话西游","comedy"),
                new Movie("泰囧", "comedy"),
                new Movie("禁闭岛", "suspense"))
                .filter(var -> "comedy".equals(var.getType()))
                .collect(Collectors.toList());

3.8.2 电影的例子

  1. 创建被过滤的类Movie,根据它的type属性实现过滤
  2. 创建接口Criteria,规定过滤方法
  3. 创建喜剧电影过滤器ComedyMovieCriteria,根据comedy==movie.type来过滤出需要的喜剧电影
public class Test {
    public static void main(String[] args) {
        List<Movie> movies = new ArrayList(){{
            add(new Movie("大话西游","comedy"));
            add(new Movie("泰囧", "comedy"));
            add(new Movie("禁闭岛", "suspense"));
        }};
        System.out.println(new ComedyMovieCriteria().meetCriteria(movies));
    }
}

// 被筛选的对象
class Movie {
    private String name;
    // 电影类型
    private String type;

    public Movie(String name, String type) {
        this.name = name;
        this.type = type;
    }
    // getters & setters & toString
}

// 过滤器接口
interface Criteria {
    /**
     * @param movies 要被筛选的电影
     * @return 筛选后的结果
     */
    List<Movie> meetCriteria(List<Movie> movies);
}

// 过滤喜剧电影的过滤器,要求是movie.type==comedy
class ComedyMovieCriteria implements Criteria {
    @Override
    public List<Movie> meetCriteria(List<Movie> movies) {
        List<Movie> result = new ArrayList<>();
        for (Movie movie : movies) {
            if ("comedy".equals(movie.getType())) {
                result.add(movie);
            }
        }
        return result;
    }
}

3.9 桥接模式

桥接模式是将抽象化实现化解耦,使两者可以独立地变化。桥接模式有助于理解面向对象的设计原则,包括开闭原则以及组合聚合复用原则

3.9.1 桥接模式结构

《《设计模式》3.结构型模式》

这个系统含有两个等级结构
  • 由抽象化角色和修正抽象化角色组成的抽象化等级结构。
  • 由实现化角色和两个具体实现化角色所组成的实现化等级结构。
桥接模式所涉及的角色
  • 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。
  • 修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
  • 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应该只给出底层操作,而抽象化角色应该只给出基于底层操作的更高一层的操作。
  • 具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现。

3.9.2 细节

抽象化、实现化、解耦

抽象化:存在于多个实体中的共同的概念性联系;通过忽略一些信息,把不同的实体当作相同的实体来对待。
实现化:抽象化给出的具体实现就是实现化。一个类的实例就是这个类的实现化,一个子类就是它超类的实现化。
解耦:耦合就是两个实体的某种强关联,把它们的强关联去掉就是解耦。
强关联与弱关联:所谓强关联,就是在编译期已经确定的,无法在运行期动态改变的关联;所谓弱关联,就是可以动态地确定并且可以在运行期动态地改变的关联。继承是强关联,而聚合关系是弱关联

核心理解

桥接模式中的脱耦,就是在抽象化和实现化之间使用组合关系而不是继承关系,从而使两者可以相对独立的变化。

优点
  1. 实现抽象化和实现化的分离。
  2. 提高了代码的扩展能力。
  3. 实现细节对客户透明。
缺点
  1. 桥接模式的引入会增加系统的理解与设计难度
  2. 由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
使用场景
  1. 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
  2. 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  3. 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
  4. 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
  5. 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
Java例子

大多数的驱动器(Driver)都是桥接模式的应用,使用驱动程序的应用系统就是抽象化角色,而驱动器本身扮演实现化角色。
《《设计模式》3.结构型模式》

3.9.3 发送消息的案例

  • 下面案例中,SendMsg及其子类是按照发送消息的方式进行扩展的;而Send是按照发送消息的时间进行扩展的,两者互不影响。
  • Send持有类一个SendMsg对象,并可以使用此对象的方法。
// Implementor角色
interface SendMsg {
    void sendMsg();
}

// Concrete Implementor角色
class EmailSendMsg implements SendMsg {
    @Override
    public void sendMsg() {
        System.out.println("Send Msg By Email");
    }
}

// Concrete Implementor角色
class WeChatSendMsg implements SendMsg {
    @Override
    public void sendMsg() {
        System.out.println("Send Msg By WeChat");
    }
}

// Abstraction 角色
abstract class Send {
    protected SendMsg sendMsg;

    public Send(SendMsg sendMsg) {
        this.sendMsg = sendMsg;
    }

    public abstract void send();
}

// Concrete Implementor角色
class ImmediatelySend extends Send {
    public ImmediatelySend(SendMsg sendMsg) {
        super(sendMsg);
    }

    @Override
    public void send() {
        sendMsg.sendMsg();
        System.out.println("Send Msg Immediately");
    }
}

// Concrete Implementor角色
class DelayedlySend extends Send {
    public DelayedlySend(SendMsg sendMsg) {
        super(sendMsg);
    }

    @Override
    public void send() {
        sendMsg.sendMsg();
        System.out.println("Send Msg DelayedlySend");
    }
}
    原文作者:设计模式
    原文地址: https://segmentfault.com/a/1190000017210573
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞