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

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

代码实现

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

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

总结

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

相关推荐
神奇小汤圆7 分钟前
Unsafe魔法类深度解析:Java底层操作的终极指南
后端
神奇小汤圆40 分钟前
浅析二叉树、B树、B+树和MySQL索引底层原理
后端
文艺理科生1 小时前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构
千寻girling1 小时前
主管:”人家 Node 框架都用 Nest.js 了 , 你怎么还在用 Express ?“
前端·后端·面试
南极企鹅1 小时前
springBoot项目有几个端口
java·spring boot·后端
Luke君607971 小时前
Spring Flux方法总结
后端
define95271 小时前
高版本 MySQL 驱动的 DNS 陷阱
后端
忧郁的Mr.Li2 小时前
SpringBoot中实现多数据源配置
java·spring boot·后端
暮色妖娆丶2 小时前
SpringBoot 启动流程源码分析 ~ 它其实不复杂
spring boot·后端·spring
Coder_Boy_2 小时前
Deeplearning4j+ Spring Boot 电商用户复购预测案例中相关概念
java·人工智能·spring boot·后端·spring