装饰者模式是一种对象结构型模式,是在不修改原有类的基础上,通过包装的方式动态的为对象添加额外职责。
代码实现
举个例子, 如果要生成不同订单的价格,有的订单只有基础价,有的订单是基础订单+运费, 还有订单是基础价+优惠价+运费,如下代码所示(继承的方式):
scala
/**
* 订单价格
* 基础价:商品本身的总价;
* 可选扩展:优惠券抵扣、运费、满减优惠、会员折扣、税费等;
* 不同订单的扩展规则组合不同(比如:普通订单 = 基础价 + 运费;优惠订单 = 基础价 + 优惠券 + 满减)。
*/
public class OrderService {
public static void main(String[] args) {
//基础价
Order order = new BaseOrder(new BigDecimal(10));
System.out.println("基础费用:"+order.calculateTotalPrice().toString());
//基础价+运费
Order freight =new OrderWithFreight(new BigDecimal(10),new BigDecimal(8));
System.out.println("基础费用+运费:"+freight.calculateTotalPrice().toString());
//基础价+优惠卷
Order coupon =new OrderWithCoupon(new BigDecimal(10),new BigDecimal(2));
System.out.println("基础费用+运费+优惠卷:"+coupon.calculateTotalPrice().toString());
//基础价+运费+优惠卷
Order freightAndCoupon =new OrderWithFreightAndCoupon(new BigDecimal(10),new BigDecimal(8),new BigDecimal(2));
System.out.println("基础费用+运费+优惠卷:"+freightAndCoupon.calculateTotalPrice().toString());
//基础价+运费+满减,基础价+运费+满减+会员折扣等等,多种不同组合的
}
}
interface Order {
BigDecimal calculateTotalPrice();
}
//基础价
class BaseOrder implements Order {
protected BigDecimal basePrice; // 商品基础价
public BaseOrder(BigDecimal basePrice) {
this.basePrice = basePrice;
}
@Override
public BigDecimal calculateTotalPrice() {
return basePrice;
}
}
// 基础价+运费
class OrderWithFreight extends BaseOrder {
private BigDecimal freight; // 运费
public OrderWithFreight(BigDecimal basePrice, BigDecimal freight) {
super(basePrice);
this.freight = freight;
}
@Override
public BigDecimal calculateTotalPrice() {
return basePrice.add(freight);
}
}
// 基础价+优惠卷
class OrderWithCoupon extends BaseOrder {
private BigDecimal coupon;//优惠卷
public OrderWithCoupon(BigDecimal basePrice, BigDecimal coupon) {
super(basePrice);
this.coupon = coupon;
}
@Override
public BigDecimal calculateTotalPrice() {
return basePrice.subtract(coupon);
}
}
// 基础价+运费+优惠卷
class OrderWithFreightAndCoupon extends BaseOrder {
private BigDecimal freight;//运费
private BigDecimal coupon;//优惠卷
public OrderWithFreightAndCoupon(BigDecimal basePrice, BigDecimal freight, BigDecimal coupon) {
super(basePrice);
this.freight = freight;
this.coupon = coupon;
}
@Override
public BigDecimal calculateTotalPrice() {
return basePrice.add(freight).subtract(coupon);
}
}
此时来了新的需求,要添加满减折扣、会员折扣。那么上面代码要再加满减折扣和会员折扣类,如果后续还有更多的折扣策略和组合,那么这个功能继承的类会多的爆炸。
我们想想,在不用直接继承BaseOrder类,不改变原类的基础上怎么添加新的功能(满减折扣、会员折扣)。遵循的设计原则:
类应该对扩展开放,对修改关闭
我们可以新增一个Decorator的抽象类(为什么是抽象类,因为是为了具体的装饰器提供通用模板,本身不提供任何的装饰功能,所以实例化没有意义),然后子类去实现这个Decorator抽象类,优惠卷、运费、满减都分别实现Decorator抽象类。这是将原来子类直接继承的方式改为继承Decorator装饰者类(包装类),由这个包装类的子类给BaseOrder类添加职责。
这样我们就可以在生成订单价格的时候,根据需要组合这些优惠卷、运费、满减等类。比如说要生成基础价+运费+优惠卷的价格,那么可以先生成基础价,再生成运费价格,最后包裹基础价格。在此基础上还可以叠加优惠卷的价格,生成优惠价格,最后在运行时通过包裹的方式添加价格,这样一层套一层,像是穿衣服一样的写法就是装饰者模式。如下图代码所示(装饰者模式):
scala
/**
* 订单装饰者
*/
public class OrderDecoratorService {
public static void main(String[] args) {
Order baseOrder = new BaseOrder(new BigDecimal(10));
System.out.println("基础价:" + baseOrder.calculateTotalPrice().toString());
//基础价+运费
baseOrder = new FreightDecorator(baseOrder,new BigDecimal(10));
System.out.println("基础价+运费:" + baseOrder.calculateTotalPrice().toString());
//基础价+运费+优惠卷,组合的方式,一个包裹一个
baseOrder = new CouponDecorator(baseOrder, new BigDecimal(6));
System.out.println("基础费用+运费+优惠卷:" + baseOrder.calculateTotalPrice().toString());
}
}
//原有的order、BaseOrder类不变,新增不同价格的装饰器
abstract class OrderDecorator implements Order {
protected Order order;
public OrderDecorator(Order order) {
this.order = order;
}
}
class CouponDecorator extends OrderDecorator {
protected BigDecimal coupon; //优惠价
public CouponDecorator(Order order, BigDecimal coupon) {
super(order);
this.coupon = coupon;
}
@Override
public BigDecimal calculateTotalPrice() {
return order.calculateTotalPrice().subtract(coupon);
}
}
class FreightDecorator extends OrderDecorator {
protected BigDecimal freight; //运费
public FreightDecorator(Order order, BigDecimal freight) {
super(order);
this.freight = freight;
}
@Override
public BigDecimal calculateTotalPrice() {
return order.calculateTotalPrice().add(freight);
}
}
这样的好处在于不必生成不同组合价格的类,可以根据需要生成想要的价格,可以带来更高的扩展性。并且如果想在原有不同价格基础上再加一种价格,只需要添加新的装饰者类,不必修改其他类,符合开闭原则。所以装饰者模式动态地将功能添加到对象上,如果需要扩展功能,装饰者模式比继承更具弹性。
核心结构(UML类图)

-
Component(Order)
- 定义一个公共接口,定义职责
-
ConcreteComponent(BaseOrder)
- 定义一个对象,实现接口的基础职责
-
Decorator(OrderDecorator)
- 定义抽象类,维持一个Component对象的引用
-
ConcreteDecorator(CouponDecorator)
- 对Component添加职责
框架中的应用
MyBatis中的插件系统就是应用了装饰者模式, 可以新建一个自定义执行器装饰器去实现Interceptor接口,在SQL执行的时候动态的添加监控、性能统计,慢查询告警等。实现代码如下:
less
@Mapper
public interface UserMapper {
@Select("SELECT id, username, age FROM user WHERE id = #{id}")
User getUserById(Long id);
}
/**
* 自定义SQL执行器装饰器 - 通过MyBatis插件机制实现
* 功能:添加SQL执行监控、性能统计、慢查询告警等
*/
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
@Component
@Slf4j
public class CustomSqlInterceptor implements Interceptor {
private long slowQueryThreshold = 1000;//慢查询阈值
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
//获取方法参数
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
String sqlId = mappedStatement.getId();
log.info("开始执行SQL: {}", sqlId);
//执行原方法
Object result = invocation.proceed();
//统计时间
long executionTime = System.currentTimeMillis() - startTime;
if (executionTime > slowQueryThreshold) {
log.warn("检测到慢查询 - SQL: {}, 执行时间: {}ms", sqlId, executionTime);
//可以在这里添加告警逻辑,如发送邮件、写入监控系统等
} else {
log.info("SQL执行完成 - SQL: {}, 执行时间: {}ms", sqlId, executionTime);
}
//SQL 执行成功统计
return result;
} catch (Exception e) {
long executionTime = System.currentTimeMillis() - startTime;
String sqlId = ((MappedStatement) invocation.getArgs()[0]).getId();
log.error("SQL执行失败 - SQL: {}, 执行时间: {}ms, 错误: {}",
sqlId, executionTime, e.getMessage());
// SQL执行失败统计
throw e;
}
}
/**
* 插件方法
* @param o
* @return
*/
@Override
public Object plugin(Object o) {
// 只有当目标对象是Executor时才进行包装
if (o instanceof Executor){
// 使用Plugin.wrap()方法创建装饰器代理
// 这实际上就是装饰器模式的实现
return Plugin.wrap(o, this);
}
return o;
}
@Override
public void setProperties(Properties properties) {
// 从配置文件读取慢查询阈值
String slowQueryThreshold = properties.getProperty("slowQueryThreshold", "1000");
this.slowQueryThreshold = Long.parseLong(slowQueryThreshold);
}
}
//配置类中注册
@Configuration
public class MybatisConfig {
@Bean
public ConfigurationCustomizer configurationCustomizer(){
return configuration -> configuration.setDefaultExecutorType(ExecutorType.SIMPLE);
}
}
@SpringBootTest
class DemoApplicationTests {
@Resource
private UserMapper userMapper;
@Test
void contextLoads() {
userMapper.getUserById(1L);
}
}
MyBatis 在启动时会扫描插件,通过@Intercepts注解找到所有插件,然后创建拦截器链InterceptorChain管理所有插件,最后为目标对象应用插件。有兴趣的可以看看这块的源码。
上面是我们自己实现的,而在MyBatis-Plus框架中也内置了一些拦截器的功能可以供我们参考,如下图所示:

总结
把装饰者模式类比生活中的穿搭,想要正式点就穿西装、去上班就要穿格子衫、天冷了就要加衣服、天热了就要脱衣服。所以装饰者模式是可以在不改变对象,不生成子类的方式,以动态(运行时)、透明(装饰类)的方式对对象功能进行增强。如果未来会有对某个对象添加很多功能的需求,可以考虑装饰者模式。