穿搭式的设计模式-装饰者

装饰者模式是一种对象结构型模式,是在不修改原有类的基础上,通过包装的方式动态的为对象添加额外职责。

代码实现

举个例子, 如果要生成不同订单的价格,有的订单只有基础价,有的订单是基础订单+运费, 还有订单是基础价+优惠价+运费,如下代码所示(继承的方式):

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框架中也内置了一些拦截器的功能可以供我们参考,如下图所示:

总结

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

相关推荐
venton2 小时前
前端也能轻松上手:Express + MongoDB 搭建你的第一个后端服务
后端
a努力。2 小时前
中国电网Java面试被问:Dubbo的服务目录和路由链实现
java·开发语言·jvm·后端·面试·职场和发展·dubbo
爬山算法2 小时前
Hibernate(42)在Hibernate中如何实现分页?
java·后端·hibernate
爱码猿3 小时前
Springboot结合thymeleaf模板生成pdf文件
spring boot·后端·pdf
IT_陈寒3 小时前
SpringBoot 3.2实战:5个性能优化技巧让你的应用提速50%
前端·人工智能·后端
上进小菜猪3 小时前
基于 YOLOv8 的农作物叶片病害、叶片病斑精准识别项目 [目标检测完整源码]
后端
老毛肚3 小时前
Spring源码探究2.0
java·后端·spring
程序员鱼皮3 小时前
你的 IP 归属地,是咋被挖出来的?
前端·后端·计算机·程序员·互联网·编程经验
fisher_sky4 小时前
流媒体服务mediamtx和FFMpeg工具链联合实验
后端