文章目录
- 前言
- 一、概念
- 二、核心思想
- 三、Java代码实现
-
- [1. 定义抽象组件(咖啡接口)](#1. 定义抽象组件(咖啡接口))
- [2. 定义具体组件(基础咖啡类)](#2. 定义具体组件(基础咖啡类))
- [3. 定义抽象装饰器(咖啡配料装饰器父类)](#3. 定义抽象装饰器(咖啡配料装饰器父类))
- [4. 定义具体装饰器(各种配料)](#4. 定义具体装饰器(各种配料))
- [5. 客户端使用代码(动态组合装饰器)](#5. 客户端使用代码(动态组合装饰器))
- [6. 扩展:新增配料(符合开闭原则)](#6. 扩展:新增配料(符合开闭原则))
- 四、优缺点
-
- [1. 优点](#1. 优点)
- [2. 缺点](#2. 缺点)
- 五、应用场景
- 六、注意事项
- 总结
前言
在AI时代,代码的编写可以被大模型辅助甚至替代,但程序员真正的核心竞争力是技术思维------设计模式这类沉淀了数十年的"内功心法",决定了代码的可维护性、扩展性和稳定性,是AI无法完全替代的核心能力。装饰器模式作为结构型模式的重要成员,专注于"动态扩展对象功能",解决了继承扩展带来的类爆炸、灵活性低的问题,是灵活增强对象能力的最优范式。
一、概念
装饰器模式(Decorator Pattern)是一种结构型设计模式,核心目标是动态地给一个对象添加一些额外的职责,而不改变其原有结构和代码。简单来说,装饰器就像给手机贴膜、加手机壳、装挂件------手机本身的核心功能(通话、上网)不变,但通过一层层"装饰",新增了防刮、防摔、美观等额外功能,且这些装饰可以自由组合、动态增减。
在编程中,当需要给对象新增功能,但不想通过继承(会产生大量子类)或修改原有代码(违背开闭原则)时,装饰器模式是最优选择------它通过"包装"原有对象,在不改变其核心逻辑的前提下,动态添加新功能,且支持多个装饰器的组合使用。
二、核心思想
- 抽象组件(Component):定义对象的核心接口,是被装饰者和装饰器共同实现的接口,规范核心功能;
- 具体组件(Concrete Component):实现抽象组件接口,是需要被装饰的"原始对象",提供核心功能;
- 抽象装饰器(Decorator):实现抽象组件接口,同时持有抽象组件的引用(组合方式),作为所有具体装饰器的父类,定义装饰的基础结构;
- 具体装饰器(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. 优点
- 动态扩展功能:可在运行时为对象添加/移除装饰器,灵活调整功能,无需修改原有代码(符合开闭原则);
- 避免类膨胀:相比继承(每种功能组合都需一个子类),装饰器通过组合实现功能扩展,大幅减少类数量;
- 功能组合灵活:多个装饰器可自由组合,实现不同的功能组合(如咖啡+牛奶+糖、咖啡+奶泡+巧克力);
- 单一职责:每个装饰器只负责一个额外功能,职责清晰,便于维护和扩展。
2. 缺点
- 增加系统复杂度 :多层装饰器嵌套时,代码可读性降低(如
new MilkDecorator(new FoamDecorator(new Latte()))); - 调试难度增加:装饰器嵌套调用时,调试需逐层跟踪,定位问题较复杂;
- 装饰器顺序敏感:部分装饰器的组合顺序会影响结果(虽本示例无此问题,但如"先加折扣再加配料"和"先加配料再加折扣"价格不同);
- 仅适用于接口一致的扩展:装饰器必须实现与被装饰对象相同的接口,无法扩展接口不同的对象。
五、应用场景
装饰器模式适用于需动态扩展对象功能、功能可组合、避免继承类膨胀的场景:
- IO流扩展 :Java IO中的
BufferedInputStream(给InputStream添加缓冲功能)、DataInputStream(给InputStream添加读取基本类型功能)都是装饰器模式的经典实现; - 功能增强场景:如日志增强(给基础日志添加时间戳、链路ID)、权限增强(给基础接口添加鉴权、限流)、缓存增强(给数据查询添加缓存功能);
- 定制化产品:如咖啡、奶茶、披萨等定制化产品,基础产品+可选配料的组合场景;
- 框架中的应用 :Spring中的
TransactionAwareCacheDecorator(给缓存添加事务支持)、MyBatis的Cache装饰器(给缓存添加日志、同步功能)。
六、注意事项
- 区分装饰器模式与适配器模式:装饰器不改变接口,仅增强功能;适配器改变接口,实现兼容;
- 控制装饰器嵌套层数:过多的装饰器嵌套会导致代码难以理解和调试,建议控制在3层以内;
- 抽象装饰器的必要性:若装饰器数量少,可省略抽象装饰器,直接让具体装饰器实现抽象组件接口,但会增加代码冗余;
- 确保装饰器与被装饰对象接口一致:装饰器必须实现与被装饰对象相同的接口,否则无法无缝替换被装饰对象。
总结
- 装饰器模式核心是组合替代继承、动态扩展对象功能,通过包装原有对象,在不改变其核心逻辑的前提下添加额外职责;
- 优势是灵活扩展、避免类膨胀、符合开闭原则,缺点是嵌套复杂、调试难度高;
- Java IO流是装饰器模式的最经典应用(如
BufferedReader装饰FileReader),理解其实现逻辑可快速掌握装饰器模式的核心; - 适用于功能需动态增减、可组合的场景,是日常开发中扩展对象功能的首选模式之一。