装饰器设计模式

2021-01-30 设计模式架构设计

也叫包装设计模式,属于结构型模式,它是作为现有的类的一个包装,允许向一个现有的对象添加新的功能,同时又不改变其结构。给对象增加功能,一般两种方式继承或关联组合,将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为来增强功能,这个就是装饰器模式,比继承模式更加灵活。

# 应用举例

假设你正在开发一个提供通知功能的库, 其他程序可使用它向用户发送关于重要事件的通知。库的最初版本基于 通知器Notifier类, 其中只有很少的几个成员变量, 一个构造函数和一个 send发送方法。 该方法可以接收来自客户端的消息参数, 并将该消息发送给一系列的邮箱, 邮箱列表则是通过构造函数传递给通知器的。 作为客户端的第三方程序仅会创建和配置通知器对象一次, 然后在有重要事件发生时对其进行调用。

使用装饰模式前的库结构

程序可以使用通知器类向预定义的邮箱发送重要事件通知。此后某个时刻, 你会发现库的用户希望使用除邮件通知之外的功能。 许多用户会希望接收关于紧急事件的手机短信, 还有些用户希望在微信上接收消息, 而公司用户则希望在 QQ 上接收消息。

实现其他类型通知后的库结构

每种通知类型都将作为通知器的一个子类得以实现。这有什么难的呢? 首先扩展 通知器类, 然后在新的子类中加入额外的通知方法。 现在客户端要对所需通知形式的对应类进行初始化, 然后使用该类发送后续所有的通知消息。

但是很快有人会问: “为什么不同时使用多种通知形式呢? 如果房子着火了, 你大概会想在所有渠道中都收到相同的消息吧。”你可以尝试创建一个特殊子类来将多种通知方法组合在一起以解决该问题。 但这种方式会使得代码量迅速膨胀, 不仅仅是程序库代码, 客户端代码也会如此。

创建组合类后的程序库结构

子类组合数量爆炸。你必须找到其他方法来规划通知类的结构, 否则它们的数量会在不经意之间打破吉尼斯纪录。

# 角色(装饰者和被装饰者有相同的超类(Component))

  • 抽象组件(Component)
    • 定义装饰方法的规范,最初的自行车,仅仅定义了自行车的API;
  • 被装饰者(ConcreteComponent)
    • Component的具体实现,也就是我们要装饰的具体对象
    • 实现了核心角色的具体自行车
  • 装饰者组件(Decorator)
    • 定义具体装饰者的行为规范, 和Component角色有相同的接口,持有组件(Component)对象的实例引用
    • 自行车组件 都有 名称和价格
  • 具体装饰物(ConcreteDecorator)
    • 负责给构件对象装饰附加的功能
    • 比如 喇叭,防爆胎

# 编码实战

image-20210713140911292

顶层抽象组件:

public interface Bike {

    String getDescription();

    int getPrice();
}

被装饰者,大小号自行车

/**
 * 被装饰着_大号自行车
 * @author cv大魔王
 * @version 1.0
 * @date 2021/7/13 13:49
 */
public class BigBack implements Bike {

    private String description = "大号自行车";

    @Override
    public String getDescription() {
        return this.description;
    }

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


/**
 * 被装饰着_小号自行车
 * @author cv大魔王
 * @version 1.0
 * @date 2021/7/13 13:49
 */
public class SmallBack implements Bike {

    private String description = "小号自行车";

    @Override
    public String getDescription() {
        return this.description;
    }

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

装饰器

public class BikeDecorator implements Bike {


    private String description = "装饰器——无意义,由子类进行传递";

    @Override
    public String getDescription() {
        return description;
    }

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

装饰器实现类——具体的实现类,自行车的组件“防爆胎”和“喇叭”

/**
 * 具体的装饰器——防爆胎
 * @author cv大魔王
 * @version 1.0
 * @date 2021/7/13 13:59
 */
public class RSCBickDecorator extends BikeDecorator{


    private String description = "增加一个防爆胎";

    private Bike bike;

    public RSCBickDecorator(Bike bike) {
        this.bike = bike;
    }

    @Override
    public String getDescription() {
        return bike.getDescription() + "," + description;
    }

    @Override
    public int getPrice() {
        // 100元是防爆胎的价格
        return bike.getPrice() + 100;
    }
}

/**
 * 具体的装饰器——唢呐
 * @author cv大魔王
 * @version 1.0
 * @date 2021/7/13 13:59
 */
public class SuonaBickDecorator extends BikeDecorator{


    private String description = "增加一个喇叭";

    private Bike bike;

    public SuonaBickDecorator(Bike bike) {
        this.bike = bike;
    }

    @Override
    public String getDescription() {
        return bike.getDescription() + "," + description;
    }

    @Override
    public int getPrice() {
        // 50元是喇叭的价格
        return bike.getPrice() + 50;
    }
}

使用:

public static void main(String[] args) {
    // 选个自行车
    Bike bike = new BigBack();
    // 给自行车加一个喇叭
    bike = new SuonaBickDecorator(bike);
    // 给自行车再加上两个防爆胎
    bike = new RSCBickDecorator(bike);
    bike = new RSCBickDecorator(bike);

    // 查看配置
    System.out.println(bike.getDescription());
    // 结算价格
    System.out.println(bike.getPrice());
}

输出:

大号自行车,增加一个喇叭,增加一个防爆胎,增加一个防爆胎
450

# 优点

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但装饰模式可以提供比继承更多的灵活性。
  • 使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,原有代码无须改变,符合“开闭原则”

# 缺点

  • 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂 (多层包装)
  • 增加系统的复杂度,加大学习与理解的难度

# 装饰器模式和桥接模式对比

  • 相同点都是通过封装其他对象达到设计的目的,和对象适配器也类似,有时也叫半装饰设计模式

  • 没有装饰者和被装饰者的主次区别,桥接和被桥接者是平等的,桥接可以互换,

  • 不用继承自同一个父类

    比如例子里面的,可以是Phone持有Color,也可以是Color持有Phone

上次更新: 5 个月前