【设计模式】结构型-装饰器模式

文章目录

  • 前言
  • 一、概念
  • 二、核心思想
  • 三、Java代码实现
    • [1. 定义抽象组件(咖啡接口)](#1. 定义抽象组件(咖啡接口))
    • [2. 定义具体组件(基础咖啡类)](#2. 定义具体组件(基础咖啡类))
    • [3. 定义抽象装饰器(咖啡配料装饰器父类)](#3. 定义抽象装饰器(咖啡配料装饰器父类))
    • [4. 定义具体装饰器(各种配料)](#4. 定义具体装饰器(各种配料))
    • [5. 客户端使用代码(动态组合装饰器)](#5. 客户端使用代码(动态组合装饰器))
    • [6. 扩展:新增配料(符合开闭原则)](#6. 扩展:新增配料(符合开闭原则))
  • 四、优缺点
    • [1. 优点](#1. 优点)
    • [2. 缺点](#2. 缺点)
  • 五、应用场景
  • 六、注意事项
  • 总结

前言

在AI时代,代码的编写可以被大模型辅助甚至替代,但程序员真正的核心竞争力是技术思维------设计模式这类沉淀了数十年的"内功心法",决定了代码的可维护性、扩展性和稳定性,是AI无法完全替代的核心能力。装饰器模式作为结构型模式的重要成员,专注于"动态扩展对象功能",解决了继承扩展带来的类爆炸、灵活性低的问题,是灵活增强对象能力的最优范式。

一、概念

装饰器模式(Decorator Pattern)是一种结构型设计模式,核心目标是动态地给一个对象添加一些额外的职责,而不改变其原有结构和代码。简单来说,装饰器就像给手机贴膜、加手机壳、装挂件------手机本身的核心功能(通话、上网)不变,但通过一层层"装饰",新增了防刮、防摔、美观等额外功能,且这些装饰可以自由组合、动态增减。

在编程中,当需要给对象新增功能,但不想通过继承(会产生大量子类)或修改原有代码(违背开闭原则)时,装饰器模式是最优选择------它通过"包装"原有对象,在不改变其核心逻辑的前提下,动态添加新功能,且支持多个装饰器的组合使用。

二、核心思想

  1. 抽象组件(Component):定义对象的核心接口,是被装饰者和装饰器共同实现的接口,规范核心功能;
  2. 具体组件(Concrete Component):实现抽象组件接口,是需要被装饰的"原始对象",提供核心功能;
  3. 抽象装饰器(Decorator):实现抽象组件接口,同时持有抽象组件的引用(组合方式),作为所有具体装饰器的父类,定义装饰的基础结构;
  4. 具体装饰器(Concrete Decorator):继承抽象装饰器,重写核心方法,在调用原始对象方法的前后添加额外功能,实现"装饰"。

装饰器模式的核心本质是组合替代继承、动态扩展功能 ------通过组合而非继承的方式,给对象动态添加功能,既避免了继承导致的类膨胀,又支持灵活组合多个装饰器,符合"开闭原则"和"单一职责原则"。

三、Java代码实现

以"咖啡订单系统"场景为例:系统中有基础咖啡(美式咖啡、拿铁咖啡),用户可自定义添加配料(牛奶、糖、奶泡),每种配料都会增加咖啡的价格和描述。用装饰器模式实现"基础咖啡+动态添加配料",避免为每种组合创建单独的类(如"美式咖啡+牛奶+糖""拿铁+奶泡"等)。

1. 定义抽象组件(咖啡接口)

java 复制代码
/**
 * 抽象组件:咖啡接口
 * 定义咖啡的核心功能(获取描述、计算价格)
 */
public interface Coffee {
    // 获取咖啡描述(如"美式咖啡""拿铁+牛奶")
    String getDescription();
    // 计算咖啡价格
    double getPrice();
}

2. 定义具体组件(基础咖啡类)

java 复制代码
/**
 * 具体组件1:美式咖啡(基础咖啡,无装饰)
 */
public class Americano implements Coffee {
    @Override
    public String getDescription() {
        return "美式咖啡";
    }

    @Override
    public double getPrice() {
        return 15.0; // 基础价格
    }
}

/**
 * 具体组件2:拿铁咖啡(基础咖啡,无装饰)
 */
public class Latte implements Coffee {
    @Override
    public String getDescription() {
        return "拿铁咖啡";
    }

    @Override
    public double getPrice() {
        return 20.0; // 基础价格
    }
}

3. 定义抽象装饰器(咖啡配料装饰器父类)

java 复制代码
/**
 * 抽象装饰器:咖啡配料装饰器
 * 实现Coffee接口,持有Coffee引用,作为所有配料装饰器的父类
 */
public abstract class CoffeeDecorator implements Coffee {
    // 持有被装饰的咖啡对象(核心:组合方式)
    protected Coffee coffee;

    // 构造方法注入被装饰的咖啡
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    // 子类需重写这两个方法,添加装饰逻辑
    @Override
    public abstract String getDescription();

    @Override
    public abstract double getPrice();
}

4. 定义具体装饰器(各种配料)

java 复制代码
/**
 * 具体装饰器1:牛奶配料
 */
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        // 装饰逻辑:在原有描述后添加配料
        return coffee.getDescription() + "+牛奶";
    }

    @Override
    public double getPrice() {
        // 装饰逻辑:在原有价格上增加配料价格
        return coffee.getPrice() + 3.0;
    }
}

/**
 * 具体装饰器2:糖配料
 */
public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + "+糖";
    }

    @Override
    public double getPrice() {
        return coffee.getPrice() + 1.0;
    }
}

/**
 * 具体装饰器3:奶泡配料
 */
public class FoamDecorator extends CoffeeDecorator {
    public FoamDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + "+奶泡";
    }

    @Override
    public double getPrice() {
        return coffee.getPrice() + 2.5;
    }
}

5. 客户端使用代码(动态组合装饰器)

java 复制代码
/**
 * 客户端:咖啡订单系统
 * 动态给基础咖啡添加配料,组合不同的装饰器
 */
public class Client {
    public static void main(String[] args) {
        // 1. 基础美式咖啡(无装饰)
        Coffee americano = new Americano();
        System.out.println(americano.getDescription() + ":" + americano.getPrice() + "元");

        // 2. 美式咖啡+牛奶(单层装饰)
        Coffee americanoWithMilk = new MilkDecorator(americano);
        System.out.println(americanoWithMilk.getDescription() + ":" + americanoWithMilk.getPrice() + "元");

        // 3. 美式咖啡+牛奶+糖(多层装饰)
        Coffee americanoWithMilkAndSugar = new SugarDecorator(americanoWithMilk);
        System.out.println(americanoWithMilkAndSugar.getDescription() + ":" + americanoWithMilkAndSugar.getPrice() + "元");

        // 4. 拿铁咖啡+奶泡+牛奶(多层装饰)
        Coffee latteWithFoamAndMilk = new MilkDecorator(new FoamDecorator(new Latte()));
        System.out.println(latteWithFoamAndMilk.getDescription() + ":" + latteWithFoamAndMilk.getPrice() + "元");
    }
}

输出结果:

复制代码
美式咖啡:15.0元
美式咖啡+牛奶:18.0元
美式咖啡+牛奶+糖:19.0元
拿铁咖啡+奶泡+牛奶:25.5元

6. 扩展:新增配料(符合开闭原则)

java 复制代码
// 1. 新增具体装饰器:巧克力配料
public class ChocolateDecorator extends CoffeeDecorator {
    public ChocolateDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + "+巧克力";
    }

    @Override
    public double getPrice() {
        return coffee.getPrice() + 4.0;
    }
}

// 2. 客户端使用(无需修改原有代码,直接组合新装饰器)
public class Client {
    public static void main(String[] args) {
        // 拿铁+巧克力+奶泡
        Coffee latteWithChocolateAndFoam = new FoamDecorator(new ChocolateDecorator(new Latte()));
        System.out.println(latteWithChocolateAndFoam.getDescription() + ":" + latteWithChocolateAndFoam.getPrice() + "元");
    }
}

四、优缺点

1. 优点

  1. 动态扩展功能:可在运行时为对象添加/移除装饰器,灵活调整功能,无需修改原有代码(符合开闭原则);
  2. 避免类膨胀:相比继承(每种功能组合都需一个子类),装饰器通过组合实现功能扩展,大幅减少类数量;
  3. 功能组合灵活:多个装饰器可自由组合,实现不同的功能组合(如咖啡+牛奶+糖、咖啡+奶泡+巧克力);
  4. 单一职责:每个装饰器只负责一个额外功能,职责清晰,便于维护和扩展。

2. 缺点

  1. 增加系统复杂度 :多层装饰器嵌套时,代码可读性降低(如new MilkDecorator(new FoamDecorator(new Latte())));
  2. 调试难度增加:装饰器嵌套调用时,调试需逐层跟踪,定位问题较复杂;
  3. 装饰器顺序敏感:部分装饰器的组合顺序会影响结果(虽本示例无此问题,但如"先加折扣再加配料"和"先加配料再加折扣"价格不同);
  4. 仅适用于接口一致的扩展:装饰器必须实现与被装饰对象相同的接口,无法扩展接口不同的对象。

五、应用场景

装饰器模式适用于需动态扩展对象功能、功能可组合、避免继承类膨胀的场景:

  1. IO流扩展 :Java IO中的BufferedInputStream(给InputStream添加缓冲功能)、DataInputStream(给InputStream添加读取基本类型功能)都是装饰器模式的经典实现;
  2. 功能增强场景:如日志增强(给基础日志添加时间戳、链路ID)、权限增强(给基础接口添加鉴权、限流)、缓存增强(给数据查询添加缓存功能);
  3. 定制化产品:如咖啡、奶茶、披萨等定制化产品,基础产品+可选配料的组合场景;
  4. 框架中的应用 :Spring中的TransactionAwareCacheDecorator(给缓存添加事务支持)、MyBatis的Cache装饰器(给缓存添加日志、同步功能)。

六、注意事项

  1. 区分装饰器模式与适配器模式:装饰器不改变接口,仅增强功能;适配器改变接口,实现兼容;
  2. 控制装饰器嵌套层数:过多的装饰器嵌套会导致代码难以理解和调试,建议控制在3层以内;
  3. 抽象装饰器的必要性:若装饰器数量少,可省略抽象装饰器,直接让具体装饰器实现抽象组件接口,但会增加代码冗余;
  4. 确保装饰器与被装饰对象接口一致:装饰器必须实现与被装饰对象相同的接口,否则无法无缝替换被装饰对象。

总结

  1. 装饰器模式核心是组合替代继承、动态扩展对象功能,通过包装原有对象,在不改变其核心逻辑的前提下添加额外职责;
  2. 优势是灵活扩展、避免类膨胀、符合开闭原则,缺点是嵌套复杂、调试难度高;
  3. Java IO流是装饰器模式的最经典应用(如BufferedReader装饰FileReader),理解其实现逻辑可快速掌握装饰器模式的核心;
  4. 适用于功能需动态增减、可组合的场景,是日常开发中扩展对象功能的首选模式之一。
相关推荐
han_3 小时前
JavaScript设计模式(三):代理模式实现与应用
前端·javascript·设计模式
我的offer在哪里3 小时前
POM 设计模式深度解析|博客视角:从原理到落地,让自动化测试脚本 “活” 起来
设计模式
程序员Terry4 小时前
Java 代理模式:从生活中的"中介"到代码中的"代理人"
后端·设计模式
砍光二叉树4 小时前
【设计模式】结构型-适配器模式
设计模式·适配器模式
Yu_Lijing5 小时前
基于C++的《Head First设计模式》笔记——蝇量模式
c++·笔记·设计模式
敲代码的约德尔人21 小时前
JavaScript 设计模式完全指南
javascript·设计模式
短剑重铸之日1 天前
《ShardingSphere解读》16 改写引擎:如何理解装饰器模式下的 SQL 改写实现机制?
java·数据库·后端·sql·shardingsphere·分库分表·装饰器模式
han_1 天前
JavaScript设计模式(二):策略模式实现与应用
前端·javascript·设计模式
庞轩px2 天前
HotSpot详解——符号引用、句柄池、直接指针的终极解密
java·jvm·设计模式·内存·虚拟机·引用·klass