九、装饰者模式


1 基本介绍

装饰者模式(Decorator Pattern)是一种 结构型 设计模式,它 允许向一个现有的对象动态地添加新的功能同时不改变其结构。该模式通过创建一个 包装对象(即装饰者)来 包裹 真实对象,并在 保持类方法签名完整性 的前提下,给对象 添加一些新的职责

2 案例

2.1 Noodles 抽象类

public abstract class Noodles { // 面食
    public abstract String desc(); // 获取面的描述
}

2.2 ShavedNoodles 类

public class ShavedNoodles extends Noodles { // 刀削面
    @Override
    public String desc() {
        return "刀削面";
    }
}

2.3 HandPulledNoodles 类

public class HandPulledNoodles extends Noodles { // 拉面
    @Override
    public String desc() {
        return "拉面";
    }
}

2.4 Flavoring 抽象类

public abstract class Flavoring extends Noodles { // 调味料
    protected Noodles noodles; // 面食
    public Flavoring(Noodles noodles) {
        this.noodles = noodles;
    }
}

2.5 Pepper 类

public class Pepper extends Flavoring { // 辣椒
    public Pepper(Noodles noodles) {
        super(noodles);
    }

    @Override
    public String desc() {
        return "多加一份[辣椒]的 " + noodles.desc();
    }
}

2.6 Salt 类

public class Salt extends Flavoring { // 食盐
    public Salt(Noodles noodles) {
        super(noodles);
    }

    @Override
    public String desc() {
        return "多加一份[食盐]的 " + noodles.desc();
    }
}

2.7 Vinegar 类

public class Vinegar extends Flavoring { // 醋
    public Vinegar(Noodles noodles) {
        super(noodles);
    }

    @Override
    public String desc() {
        return "多加一份[醋]的 " + noodles.desc();
    }
}

2.8 Client 类

public class Client { // 创建 加调味料的 刀削面 和 拉面
    public static void main(String[] args) {
        // 一份刀削面
        Noodles shavedNoodles = new ShavedNoodles();
        System.out.println(shavedNoodles.desc());

        // 多加 两份辣椒 和 一份食盐
        shavedNoodles = new Pepper(shavedNoodles);
        shavedNoodles = new Pepper(shavedNoodles);
        shavedNoodles = new Salt(shavedNoodles);
        System.out.println(shavedNoodles.desc());

		// 分隔
        System.out.println("===========================================================");

        // 一份拉面
        Noodles handPulledNoodles = new HandPulledNoodles();
        System.out.println(handPulledNoodles.desc());

        // 多加 一份辣椒 和 一份醋
        handPulledNoodles = new Pepper(handPulledNoodles);
        handPulledNoodles = new Vinegar(handPulledNoodles);
        System.out.println(handPulledNoodles.desc());
    }
}

2.9 Client 类的运行结果

刀削面
多加一份[食盐]的 多加一份[辣椒]的 多加一份[辣椒]的 刀削面
===========================================================
拉面
多加一份[醋]的 多加一份[辣椒]的 拉面

2.10 总结

在本案例中,可以随意组合 两种基本面食 和 三种调味料,这就是 通过创建一个 包装对象(调味料)来 包裹 真实对象(基本面食),并在 保持类方法签名完整性(保持 desc() 的方法签名完整)的前提下,给对象 添加一些新的职责(给面食调味)。

3 各角色之间的关系

3.1 角色

3.1.1 Component ( 抽象零件 )

该角色负责 定义 基本对象的 接口。在本案例中,Noodles 抽象类扮演该角色。

3.1.2 ConcreteComponent ( 具体零件 )

该角色负责 实现 Component 角色所定义的 接口。在本案例中,ShavedNoodles, HandPulledNoodles 类都在扮演该角色。

3.1.3 Decorator ( 抽象装饰者 )

该角色负责 聚合 Component 角色,并 持有与 Component 角色相同的接口。在本案例中,Flavoring 抽象类扮演该角色。

3.1.4 ConcreteDecorator ( 具体装饰者 )

该角色负责 使用 Decorator 聚合的 Component,来 实现 Decorator 中未实现的 接口。在本案例中,Pepper, Salt, Vinegar 类都在扮演该角色。

3.2 类图

alt text
说明:在 Component 角色中,method1(), method2(), method3() 都是抽象的,交给其子类实现。在 Decorator 角色中,不需要实现 method1(), method2(), method3(),只需要 聚合 Component 即可,这些方法交给 ConcreteDecorator 实现。此外,Decorator 角色的 Component 属性是 protected 的,因为要给子类使用。

4 注意事项

  • 接口一致性:尽量保持 装饰类的接口被装饰类的接口 相同。这样,对于客户端而言,无论是装饰之前的对象还是装饰之后的对象,都可以一致对待。这有助于保持代码的透明性和可替换性。
  • 轻量级设计:尽量保持 具体装饰类 是一个“轻量级”类,不要将太多的行为放在 具体装饰类 中。装饰类的主要职责是为被装饰类添加新的功能或行为,而 不是承载过多的业务逻辑。相反,应该通过装饰类的组合来实现复杂的功能。
  • 使用 聚合 代替 继承:在装饰者模式中,通常使用 聚合 而不是 继承 来关联 装饰类被装饰类。这是因为 聚合 可以提供更好的 灵活性扩展性
  • 减少装饰层次:尽量减少装饰者的数量和层次,避免过度使用装饰者模式。过多的装饰层次会增加系统的 复杂性调试难度
  • 简化调用:可以使用 工厂方法模式 或 建造者模式来创建 装饰者对象,以简化客户端的调用。这有助于降低客户端与装饰者之间的耦合度,并提高系统的可维护性。

5 在源码中的使用

装饰者模式在 Java 的 IO 流中得到了广泛的应用。这种设计模式允许 向一个现有的对象添加新的功能同时又不改变其结构。在 IO 流中,装饰者模式通过 包装现有的流对象增强其功能,如 增加缓冲

  • 四大角色
    • 抽象零件InputStream / OutputStreamReader / Writer 抽象类。
    • 具体零件:直接继承自 InputStream / OutputStreamReader / Writer 的流,用于 数据的实际读写操作,也称作节点流,如 FileInputStreamFileOutputStream 等。
    • 抽象装饰者FilterInputStream / FilterOutputStreamFilterReader / FilterWriter 类,它们 内部持有一个节点流对象的引用
    • 具体装饰者:继承自 FilterInputStream / FilterOutputStreamFilterReader / FilterWriter 的流,通过调用节点流的方法来实现数据的读写,也称作过滤流,可以添加额外的功能,如 BufferedInputStream 流增加的 缓冲 功能。
  • 动态扩展功能:装饰者模式允许在运行时动态地为 IO 流添加新的功能,而不需要修改原有的流类。例如,可以通过包装一个 FileInputStream 对象来创建一个 BufferedInputStream 对象,从而增加缓冲功能。
  • 透明性:对于客户端而言,无论是直接使用节点流还是使用经过装饰的过滤流,它们的 接口都是相同的。这使得客户端可以透明地使用这些流,而无需关心它们是否被装饰过。

alt text
如果只观察 InputStream, FileInputStream, FilterInputStream, BufferedInputStream 这四个类,则可以形成如上的类图,可以将其映射到该模式的类图中。

6 优缺点

优点

  • 扩展性强:通过使用不同的 装饰类 以及这些装饰类的 组合,可以创造出很多 不同行为 的组合。这意味着可以在运行时 动态地 给一个对象添加一些额外的职责,而不需要修改这个对象本身。
  • 灵活性高:具体构件类与装饰类可以 独立变化,用户可以根据需要增加新的装饰类或取消装饰类,在使用增加和删除装饰类时,不需要修改具体构件类的代码,装饰者模式提供了比 继承 更多的灵活性
  • 符合开闭原则:对扩展开放,对修改关闭。通过装饰者模式可以在不修改原有类的基础上,增加新的功能。
  • 透明性:对于使用装饰者模式的客户来说,它可以 透明地 使用装饰后的对象,无需关心对象是如何被装饰的。

缺点

  • 多层装饰时会更复杂:如果多层装饰比较复杂,可能会 影响代码的可读性。特别是对于 装饰类的排列顺序对结果有影响的情况下,需要特别注意。
  • 可能会产生很多小的对象:如果过度使用装饰者模式,可能会产生 大量的装饰类,增加了系统的复杂性,特别是在一些复杂的系统中,这些装饰类可能难以理解和维护。
  • 如果 装饰者之间 有关联,可能会增加系统的复杂性:在某些情况下,装饰者之间可能会存在 依赖关系,这种依赖关系可能会使系统的设计和理解变得更加复杂。
  • 过度使用可能违反单一职责原则:虽然装饰者模式可以提高类的灵活性,但如果 一个类装饰了过多的功能,可能会违反单一职责原则,导致类的职责不够清晰。

7 适用场景

  • 为类扩展功能:当需要为一个类添加 额外的功能或职责,但 不想改变其原有的类结构 时,可以使用装饰者模式。这样可以在不修改原有类代码的情况下,通过组合的方式为对象添加新的功能。
  • 动态 添加 或 撤销 功能:装饰者模式允许在运行时动态地给对象 添加新的功能 或 撤销之前添加的功能。这意味着可以根据需要,在对象上添加或移除装饰器,从而改变对象的行为。
  • 组合多种功能:通过组合不同的装饰器,可以创建出具有 多种功能组合 的对象。这种灵活性使得装饰者模式在需要 高度可配置性的系统 中非常有用。

8 总结

装饰者模式是一种 结构型 设计模式,它通过创建一个 包装对象(即装饰者)来 包裹 真实对象,并在 保持类方法签名完整性 的前提下,给对象 添加一些新的职责

装饰者模式是一种很好的模式,在设计系统时推荐使用,它在 高度可配置性的系统 中尤为重要。在实现的时候,注意 不要让具体装饰者的功能太过复杂,从而违背单一职责原则;也不要让具体装饰者之间产生依赖,从而使系统变得复杂。

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值