《设计模式详解》结构型模式 - 装饰者模式

简介: 结构型模式 - 装饰者模式

结构型模式描述如何将类或对象按某种布局组成更大的结构,有以下两种:

  • 类结构型模式:采用继承机制来组织接口和类。
  • 对象结构型模式:釆用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足 “合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

结构型模式分为以下 7 种:

  • 代理模式
  • 适配器模式
  • ==装饰者模式==
  • 桥接模式
  • 外观模式
  • 组合模式
  • 享元模式

5.3 装饰者模式

5.3.1 概述

先来看一个快餐店的例子:快餐店有炒面、炒饭等快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。下面是使用继承方式的类图:

在这里插入图片描述

使用继承的方式存在的问题:

  • 扩展性不好:如果要再加一种配料(火腿肠),我们就会发现需要给 FriedRice 和 FriedNoodles 分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类。
  • 产生过多的子类

装饰者模式:在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。

5.3.2 结构

装饰者(Decorator)模式中的角色:

  • 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

5.3.3 案例

使用装饰者模式对快餐店案例进行改进,类图如下:

在这里插入图片描述

抽象构件角色:快餐类

@Data
public abstract class FastFood {
    private float price; // 价格
    private String desc; // 描述

    public FastFood(float price, String desc) {
        this.price = price;
        this.desc = desc;
    }

    public abstract float cost();
}

具体构件角色:炒饭类、炒面类

public class FriedRice extends FastFood {
    public FriedRice() {
        super(10, "炒饭");
    }

    public float cost() {
        return getPrice();
    }
}

public class FriedNoodles extends FastFood {
    public FriedNoodles() {
        super(12, "炒面");
    }

    public float cost() {
        return getPrice();
    }
}

抽象装饰者角色:配料类

@Data
public abstract class Garnish extends FastFood {
    // 声明快餐类的变量
    private FastFood fastFood;

    public Garnish(FastFood fastFood, float price, String desc) {
        super(price, desc);
        this.fastFood = fastFood;
    }
}

具体装饰者角色:鸡蛋配料类、培根配料类

public class Egg extends Garnish {
    public Egg(FastFood fastFood) {
        super(fastFood, 1, "鸡蛋");
    }
  
    // 计算价格
    public float cost() {
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

public class Bacon extends Garnish {
    public Bacon(FastFood fastFood) {
        super(fastFood, 2, "培根");
    }
    
      // 计算价格
    public float cost() {
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        //  点一份炒饭
        FastFood food = new FriedRice();
        System.out.println(food.getDesc() + "  " + food.cost() + "元");
        // 在上面的炒饭中加一个鸡蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + "  " + food.cost() + "元");
        // 再加一个鸡蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + "  " + food.cost() + "元");
        // 再加一个培根
        food = new Bacon(food);
        System.out.println(food.getDesc() + "  " + food.cost() + "元");
    }
}
炒饭  10.0元
鸡蛋炒饭  11.0元
鸡蛋鸡蛋炒饭  12.0元
培根鸡蛋鸡蛋炒饭  14.0元

5.3.4 使用场景

  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。

不能采用继承的情况主要有两类:

  • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。
  • 第二类是因为类定义不能继承(如 final 类)。
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

5.3.5 JDK源码解析

IO流中的包装类使用到了装饰者模式:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。

以 BufferedWriter 举例来说明,先看看如何使用 BufferedWriter:

// 创建FileWriter对象
FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
// 创建BufferedWriter对象
BufferedWriter bw = new BufferedWriter(fw);
// 写数据
bw.write("hello Buffered");
bw.close();

使用起来感觉确实像是装饰者模式,接下来看它们的结构:

在这里插入图片描述

BufferedWriter 使用装饰者模式对 Writer 子实现类进行了增强,添加了缓冲区,提高了写数据的效率。

5.3.6 代理和装饰者的区别

相同点

  • 都要实现与目标类相同的业务接口
  • 在两个类中都要声明目标对象
  • 都可以在不修改目标类的前提下增强目标方法

不同点

  • 目的不同

    装饰者是为了增强目标对象
    静态代理是为了保护和隐藏目标对象

装饰者可以迭代增强,代理只能增强一次
  • 获取目标对象构建的地方不同
    装饰者是由外界传递进来,可以通过构造方法传递
    静态代理是在代理类内部创建,以此来隐藏目标对象
相关文章
|
13天前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
|
1月前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
|
2月前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
42 0
|
2月前
|
设计模式 Java Kotlin
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
27 0
|
2月前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
23 0
|
4月前
|
设计模式 存储 Java
【十】设计模式~~~结构型模式~~~享元模式(Java)
文章详细介绍了享元模式(Flyweight Pattern),这是一种对象结构型模式,通过共享技术实现大量细粒度对象的重用,区分内部状态和外部状态来减少内存中对象的数量,提高系统性能。通过围棋棋子的设计案例,展示了享元模式的动机、定义、结构、优点、缺点以及适用场景,并探讨了单纯享元模式和复合享元模式以及与其他模式的联用。
【十】设计模式~~~结构型模式~~~享元模式(Java)
|
4月前
|
设计模式 存储 Java
【九】设计模式~~~结构型模式~~~外观模式(Java)
文章详细介绍了外观模式(Facade Pattern),这是一种对象结构型模式,通过引入一个外观类来简化客户端与多个子系统之间的交互,降低系统的耦合度,并提供一个统一的高层接口来使用子系统。通过文件加密模块的实例,展示了外观模式的动机、定义、结构、优点、缺点以及适用场景,并讨论了如何通过引入抽象外观类来提高系统的可扩展性。
【九】设计模式~~~结构型模式~~~外观模式(Java)
|
4月前
|
设计模式 Java
【八】设计模式~~~结构型模式~~~装饰模式(Java)
文章详细介绍了装饰模式(Decorator Pattern),这是一种对象结构型模式,用于在不使用继承的情况下动态地给对象添加额外的职责。装饰模式通过关联机制,使用装饰器类来包装原有对象,并在运行时通过组合的方式扩展对象的行为。文章通过图形界面构件库的设计案例,展示了装饰模式的动机、定义、结构、优点、缺点以及适用场景,并提供了Java代码实现和应用示例。装饰模式提高了系统的灵活性和可扩展性,适用于需要动态、透明地扩展对象功能的情况。
【八】设计模式~~~结构型模式~~~装饰模式(Java)
|
4月前
|
设计模式 XML 存储
【七】设计模式~~~结构型模式~~~桥接模式(Java)
文章详细介绍了桥接模式(Bridge Pattern),这是一种对象结构型模式,用于将抽象部分与实现部分分离,使它们可以独立地变化。通过实际的软件开发案例,如跨平台视频播放器的设计,文章阐述了桥接模式的动机、定义、结构、优点、缺点以及适用场景,并提供了完整的代码实现和测试结果。桥接模式适用于存在两个独立变化维度的系统,可以提高系统的可扩展性和灵活性。
【七】设计模式~~~结构型模式~~~桥接模式(Java)
|
4月前
|
设计模式 XML 存储
【六】设计模式~~~结构型模式~~~适配器模式(Java)
文章详细介绍了适配器模式(Adapter Pattern),这是一种结构型设计模式,用于将一个类的接口转换成客户期望的另一个接口,使原本不兼容的接口能够一起工作,提高了类的复用性和系统的灵活性。通过对象适配器和类适配器两种实现方式,展示了适配器模式的代码应用,并讨论了其优点、缺点以及适用场景。