Java设计模式应用--装饰器模式

一、概念

装饰器模式概念:是指允许对一个现有的对象加入其它额外的功能并且不改变其原来的结构,属于结构型模式。这种模式通常会创建一个装饰类来包装原有的类以达到装饰的效果。

装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能。

意图​:动态地给一个对象添加额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。

二、应用

装饰器模式在实际项目中的应用:以我从事的支付行业为例。

核心角色​:

Component(抽象组件)​​:通常是接口或抽象类,定义了核心对象的行为。在支付系统中,这就是最核心的"支付"操作。

ConcreteComponent(具体组件)​ ​:实现了Component接口的核心对象,是我们要动态添加功能的对象。例如,一个基础的、只完成最基本支付流程的实现。

Decorator(抽象装饰器)​ ​:持有一个Component对象的引用,并实现Component接口。它是所有具体装饰器的父类。

ConcreteDecorator(具体装饰器)​​:向组件添加具体职责的类。

在支付系统中的常用应用场景

在支付中,支付流程是主流程,可以用装饰器模式给主流程进行扩展。

示例1:如一个简单的示例:日志记录,验证逻辑。当然在整个支付中,这样的应用很少,只是做个简单的引用。

简化代码:

java 复制代码
// 1. 抽象组件 (Component)
public interface PaymentService {
    PayResult pay(PaymentRequest request);
}
java 复制代码
// 2. 具体组件 (ConcreteComponent) - 基础支付实现
public class BasicPaymentService implements PaymentService {
    @Override
    public PayResult pay(PaymentRequest request) {
        // 模拟调用支付网关的核心逻辑
        System.out.println("调用支付网关,扣款 " + request.getAmount() + " 元...");
        // ... 实际网络调用
        return new PayResult(200, "支付成功", "T123456");
    }
}
java 复制代码
// 3. 抽象装饰器 (Decorator)
public abstract class PaymentDecorator implements PaymentService {
    protected PaymentService wrapped;

    public PaymentDecorator(PaymentService wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public PayResult pay(PaymentRequest request) {
        return wrapped.pay(request);
    }
}
java 复制代码
// 4. 具体装饰器 (ConcreteDecorator) - 日志记录
public class LoggingPaymentDecorator extends PaymentDecorator {
    public LoggingPaymentDecorator(PaymentService wrapped) {
        super(wrapped);
    }

    @Override
    public PayResult pay(PaymentRequest request) {
        System.out.println("[INFO] 开始支付请求: " + request);
        try {
            PayResult result = super.pay(request);
            System.out.println("[INFO] 支付请求完成: " + result);
            return result;
        } catch (Exception e) {
            System.out.println("[ERROR] 支付请求异常: " + e.getMessage());
            throw e;
        }
    }
}
java 复制代码
// 5. 具体装饰器 (ConcreteDecorator) - 参数验证
public class ValidationPaymentDecorator extends PaymentDecorator {
    public ValidationPaymentDecorator(PaymentService wrapped) {
        super(wrapped);
    }

    @Override
    public PayResult pay(PaymentRequest request) {
        // 简单的验证逻辑
        if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("支付金额必须大于0");
        }
        if (request.getToUserId() == null) {
            throw new IllegalArgumentException("收款方不能为空");
        }
        System.out.println("[VALID] 参数验证通过");
        return super.pay(request);
    }
}
java 复制代码
// 测试类
public class PaymentDemo {
    public static void main(String[] args) {
        // 构建一个被"验证"和"日志"装饰的基础支付服务
        PaymentService paymentService = new BasicPaymentService();
        paymentService = new ValidationPaymentDecorator(paymentService);
        paymentService = new LoggingPaymentDecorator(paymentService);

        // 创建支付请求
        PaymentRequest request = new PaymentRequest(new BigDecimal("100.00"), "userA", "userB");

        // 执行支付
        PayResult result = paymentService.pay(request);
    }
}
java 复制代码
输出结果:
[INFO] 开始支付请求: PaymentRequest(amount=100.00, fromUserId=userA, toUserId=userB)
[VALID] 参数验证通过
调用支付网关,扣款 100.00 元...
[INFO] 支付请求完成: PayResult(success=true, msg=支付成功, transactionId=T123456)

实际上在支付领域中,对于支付渠道路由/组合一般会采用装饰器和责任链设计模式。还有营销活动的叠加的技术选型中采用装饰器/策略模式。

示例2:如:电商系统中常见的优惠叠加装饰器

java 复制代码
// 支付金额计算接口
public interface AmountCalculator {
    CalculationResult calculate(Order order);
}
java 复制代码
// 基础计算器 - 原价计算
@Component
public class BaseAmountCalculator implements AmountCalculator {
    @Override
    public CalculationResult calculate(Order order) {
        BigDecimal originalAmount = order.getTotalAmount();
        return new CalculationResult(originalAmount, originalAmount, "原价计算");
    }
}
java 复制代码
// 优惠券装饰器
@Component
public class CouponCalculatorDecorator implements AmountCalculator {
    private final AmountCalculator wrapped;
    private final CouponService couponService;
    
    public CouponCalculatorDecorator(AmountCalculator wrapped, CouponService couponService) {
        this.wrapped = wrapped;
        this.couponService = couponService;
    }
    
    @Override
    public CalculationResult calculate(Order order) {
        // 先计算基础金额
        CalculationResult baseResult = wrapped.calculate(order);
        
        // 应用优惠券
        Coupon coupon = couponService.getApplicableCoupon(order);
        if (coupon != null) {
            BigDecimal discountAmount = coupon.calculateDiscount(baseResult.getFinalAmount());
            BigDecimal finalAmount = baseResult.getFinalAmount().subtract(discountAmount);
            
            return new CalculationResult(
                baseResult.getOriginalAmount(),
                finalAmount,
                baseResult.getDescription() + ", 优惠券减免: " + discountAmount
            );
        }
        
        return baseResult;
    }
}
java 复制代码
// 积分抵扣装饰器
@Component
public class PointsCalculatorDecorator implements AmountCalculator {
    private final AmountCalculator wrapped;
    private final PointsService pointsService;
    
    public PointsCalculatorDecorator(AmountCalculator wrapped, PointsService pointsService) {
        this.wrapped = wrapped;
        this.pointsService = pointsService;
    }
    
    @Override
    public CalculationResult calculate(Order order) {
        CalculationResult baseResult = wrapped.calculate(order);
        
        // 计算可用积分抵扣
        int availablePoints = pointsService.getAvailablePoints(order.getUserId());
        BigDecimal pointsDiscount = pointsService.calculatePointsValue(availablePoints);
        
        if (pointsDiscount.compareTo(BigDecimal.ZERO) > 0) {
            BigDecimal finalAmount = baseResult.getFinalAmount().subtract(pointsDiscount);
            
            return new CalculationResult(
                baseResult.getOriginalAmount(),
                finalAmount.max(BigDecimal.ZERO), // 确保不低于0
                baseResult.getDescription() + ", 积分抵扣: " + pointsDiscount
            );
        }
        
        return baseResult;
    }
}
java 复制代码
// 会员折扣装饰器
@Component
public class MemberDiscountCalculatorDecorator implements AmountCalculator {
    private final AmountCalculator wrapped;
    private final MemberService memberService;
    
    public MemberDiscountCalculatorDecorator(AmountCalculator wrapped, MemberService memberService) {
        this.wrapped = wrapped;
        this.memberService = memberService;
    }
    
    @Override
    public CalculationResult calculate(Order order) {
        CalculationResult baseResult = wrapped.calculate(order);
        
        // 获取会员折扣
        BigDecimal discountRate = memberService.getMemberDiscount(order.getUserId());
        if (discountRate.compareTo(BigDecimal.ONE) < 0) {
            BigDecimal discountAmount = baseResult.getFinalAmount()
                .multiply(BigDecimal.ONE.subtract(discountRate));
            BigDecimal finalAmount = baseResult.getFinalAmount().multiply(discountRate);
            
            return new CalculationResult(
                baseResult.getOriginalAmount(),
                finalAmount,
                baseResult.getDescription() + ", 会员折扣: " + discountRate + "折"
            );
        }
        
        return baseResult;
    }
}
java 复制代码
// 配置和使用
@Configuration
public class CalculatorConfig {
    
    @Bean
    public AmountCalculator compositeCalculator(CouponService couponService,
                                               PointsService pointsService,
                                               MemberService memberService) {
        // 构建计算链:原价 -> 会员折扣 -> 优惠券 -> 积分抵扣
        AmountCalculator calculator = new BaseAmountCalculator();
        calculator = new MemberDiscountCalculatorDecorator(calculator, memberService);
        calculator = new CouponCalculatorDecorator(calculator, couponService);
        calculator = new PointsCalculatorDecorator(calculator, pointsService);
        
        return calculator;
    }
}

配置类利用spring启动加载一次初始化完成时构建好了完整的装饰器链,并把calculator这个对象交给spring进行管理。

如果不好理解,可以使用另一种方式。

java 复制代码
// 方法链方式(更易读)
public class CalculatorBuilder {
    private AmountCalculator calculator;
    
    public CalculatorBuilder base() {
        this.calculator = new BaseAmountCalculator();
        return this;
    }
    
    public CalculatorBuilder withMemberDiscount(MemberService service) {
        this.calculator = new MemberDiscountCalculatorDecorator(calculator, service);
        return this;
    }
    
    public CalculatorBuilder withCoupon(CouponService service) {
        this.calculator = new CouponCalculatorDecorator(calculator, service);
        return this;
    }
    
    public CalculatorBuilder withPoints(PointsService service) {
        this.calculator = new PointsCalculatorDecorator(calculator, service);
        return this;
    }
    
    public AmountCalculator build() {
        return calculator;
    }
}

// 使用方式(更清晰)
AmountCalculator calculator = new CalculatorBuilder()
    .base()
    .withMemberDiscount(memberService)
    .withCoupon(couponService)
    .withPoints(pointsService)
    .build();
java 复制代码
// 业务使用
@Service
public class OrderService {
    private final AmountCalculator amountCalculator;
    
    public CreateOrderResult createOrder(CreateOrderRequest request) {
        Order order = buildOrder(request);
        
        // 自动应用所有优惠规则
        CalculationResult result = amountCalculator.calculate(order);
        
        order.setFinalAmount(result.getFinalAmount());
        order.setDiscountDescription(result.getDescription());
        
        return saveOrder(order);
    }
}

OrderService进行bean依赖注入,通过构造器注入。在OderService实例化时完成。

相关推荐
兔兔爱学习兔兔爱学习9 小时前
Spring Al学习7:ImageModel
java·学习·spring
LoveXming9 小时前
Chapter14—中介者模式
c++·microsoft·设计模式·中介者模式·开闭原则
lang2015092810 小时前
Spring远程调用与Web服务全解析
java·前端·spring
m0_5642641811 小时前
IDEA DEBUG调试时如何获取 MyBatis-Plus 动态拼接的 SQL?
java·数据库·spring boot·sql·mybatis·debug·mybatis-plus
崎岖Qiu11 小时前
【设计模式笔记06】:单一职责原则
java·笔记·设计模式·单一职责原则
Yeniden11 小时前
【设计模式】适配器模式大白话讲解!
设计模式·适配器模式
Hello.Reader11 小时前
Flink ExecutionConfig 实战并行度、序列化、对象重用与全局参数
java·大数据·flink
熊小猿12 小时前
在 Spring Boot 项目中使用分页插件的两种常见方式
java·spring boot·后端
paopaokaka_luck12 小时前
基于SpringBoot+Vue的助农扶贫平台(AI问答、WebSocket实时聊天、快递物流API、协同过滤算法、Echarts图形化分析、分享链接到微博)
java·vue.js·spring boot·后端·websocket·spring