设计模式之十四:策略模式详解及其在Spring与Java中的应用
引言
在软件开发中,我们经常会遇到这样的情况:实现同一个功能有多种算法或策略,而我们需要根据不同的情况选择不同的实现方式。传统的做法是将这些算法硬编码在业务逻辑中,通过大量的if-else或switch-case来判断使用哪种算法。这种做法不仅导致代码臃肿,而且难以维护和扩展。
策略模式(Strategy Pattern)正是为解决这类问题而生。本文将详细介绍策略模式的定义、结构、实现方式,并结合Java实际案例,探讨如何在Spring框架中优雅地应用策略模式。
一、策略模式概述
1.1 定义与核心思想
策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
核心思想:针对接口编程,而不是针对实现编程。将可变的部分从程序中抽象分离,形成算法的独立族,而在上下文(Context)中委派给具体的策略实现。
1.2 模式结构
策略模式包含三个核心角色:
- Context(上下文):持有一个策略对象的引用,负责调用策略对象的算法。
- Strategy(抽象策略):定义所有策略类必须实现的公共接口。
- ConcreteStrategy(具体策略):实现抽象策略接口,提供具体的算法实现。
1.3 类图
+---------------+ +-------------------+ | Context | | <<interface>> | +---------------+ | Strategy | | - strategy |------>+-------------------+ +---------------+ | + algorithm() | | + execute() | +-------------------+ +---------------+ ▲ | +---------------+---------------+ | | | +-----------+----+ +-----------+----+ +-----------+----+ |ConcreteStrategyA| |ConcreteStrategyB| |ConcreteStrategyC| +----------------+ +----------------+ +----------------+ | + algorithm() | | + algorithm() | | + algorithm() | +----------------+ +----------------+ +----------------+ 二、策略模式的Java实现示例
让我们通过一个电商促销活动的例子来演示策略模式的实现。
场景描述
一个电商平台需要支持多种促销策略:
- 满减促销
- 折扣促销
- 无促销(原价)
2.1 定义抽象策略接口
/** * 促销策略接口 */publicinterfacePromotionStrategy{/** * 计算促销后的价格 * @param originalPrice 原价 * @return 促销后价格 */doublecalculatePrice(double originalPrice);/** * 获取促销名称 * @return 促销名称 */StringgetPromotionName();}2.2 实现具体策略类
满减策略:满100减20
publicclassFullReductionStrategyimplementsPromotionStrategy{privatestaticfinaldouble FULL_AMOUNT =100;privatestaticfinaldouble REDUCTION_AMOUNT =20;@OverridepublicdoublecalculatePrice(double originalPrice){if(originalPrice >= FULL_AMOUNT){return originalPrice - REDUCTION_AMOUNT;}return originalPrice;}@OverridepublicStringgetPromotionName(){return"满减促销";}}折扣策略:8折优惠
publicclassDiscountStrategyimplementsPromotionStrategy{privatestaticfinaldouble DISCOUNT_RATE =0.8;@OverridepublicdoublecalculatePrice(double originalPrice){return originalPrice * DISCOUNT_RATE;}@OverridepublicStringgetPromotionName(){return"折扣促销";}}无促销策略:原价
publicclassNoPromotionStrategyimplementsPromotionStrategy{@OverridepublicdoublecalculatePrice(double originalPrice){return originalPrice;}@OverridepublicStringgetPromotionName(){return"无促销";}}2.3 创建上下文类
/** * 购物车 - 上下文角色 */publicclassShoppingCart{privatePromotionStrategy promotionStrategy;privateList<Double> items =newArrayList<>();publicShoppingCart(){this.promotionStrategy =newNoPromotionStrategy();// 默认无促销}publicvoidsetPromotionStrategy(PromotionStrategy promotionStrategy){this.promotionStrategy = promotionStrategy;}publicvoidaddItem(double price){ items.add(price);}publicdoublecalculateTotal(){double total = items.stream().mapToDouble(Double::doubleValue).sum();return promotionStrategy.calculatePrice(total);}publicvoidcheckout(){double finalPrice =calculateTotal();System.out.println("使用"+ promotionStrategy.getPromotionName()+",应付金额:"+ finalPrice);}}2.4 客户端使用示例
publicclassClient{publicstaticvoidmain(String[] args){ShoppingCart cart =newShoppingCart(); cart.addItem(80); cart.addItem(30);// 使用满减促销 cart.setPromotionStrategy(newFullReductionStrategy()); cart.checkout();// 使用折扣促销 cart.setPromotionStrategy(newDiscountStrategy()); cart.checkout();// 无促销 cart.setPromotionStrategy(newNoPromotionStrategy()); cart.checkout();}}输出结果:
使用满减促销,应付金额:90.0 使用折扣促销,应付金额:88.0 使用无促销,应付金额:110.0 三、策略模式在Spring框架中的应用
Spring框架中大量使用了策略模式,下面介绍几个典型的应用场景。
3.1 ResourceLoader - 资源加载策略
Spring的ResourceLoader接口使用了策略模式来加载不同类型的资源。
publicinterfaceResourceLoader{ResourcegetResource(String location);}// 不同协议的资源加载策略// ClassPathResource// FileSystemResource// UrlResource3.2 InstantiationStrategy - Bean实例化策略
Spring在创建Bean实例时,使用了不同的实例化策略。
publicinterfaceInstantiationStrategy{Objectinstantiate(RootBeanDefinition bd,String beanName,BeanFactory owner);// ... 其他方法}// 具体策略实现// SimpleInstantiationStrategy - 简单实例化// CglibSubclassingInstantiationStrategy - CGLIB动态代理实例化3.3 在Spring Boot中自定义策略模式的应用
让我们看一个在Spring Boot项目中优雅实现策略模式的例子。
场景:用户支付方式选择
// 1. 定义策略接口publicinterfacePaymentStrategy{StringgetPaymentType();PayResultpay(PayRequest request);}// 2. 定义策略注解@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfacePaymentType{Stringvalue();}// 3. 实现具体策略@Component@PaymentType("alipay")publicclassAlipayStrategyimplementsPaymentStrategy{@OverridepublicStringgetPaymentType(){return"alipay";}@OverridepublicPayResultpay(PayRequest request){System.out.println("使用支付宝支付:"+ request.getAmount());// 支付宝支付逻辑returnnewPayResult(true,"支付宝支付成功");}}@Component@PaymentType("wechat")publicclassWechatPayStrategyimplementsPaymentStrategy{@OverridepublicStringgetPaymentType(){return"wechat";}@OverridepublicPayResultpay(PayRequest request){System.out.println("使用微信支付:"+ request.getAmount());// 微信支付逻辑returnnewPayResult(true,"微信支付成功");}}@Component@PaymentType("card")publicclassCardPayStrategyimplementsPaymentStrategy{@OverridepublicStringgetPaymentType(){return"card";}@OverridepublicPayResultpay(PayRequest request){System.out.println("使用银行卡支付:"+ request.getAmount());// 银行卡支付逻辑returnnewPayResult(true,"银行卡支付成功");}}// 4. 策略工厂 - 注入所有策略@ComponentpublicclassPaymentStrategyFactoryimplementsApplicationContextAware{privatefinalMap<String,PaymentStrategy> strategyMap =newHashMap<>();@OverridepublicvoidsetApplicationContext(ApplicationContext applicationContext)throwsBeansException{Map<String,Object> beans = applicationContext.getBeansWithAnnotation(PaymentType.class);for(Object bean : beans.values()){PaymentStrategy strategy =(PaymentStrategy) bean;PaymentType paymentType = strategy.getClass().getAnnotation(PaymentType.class); strategyMap.put(paymentType.value(), strategy);}}publicPaymentStrategygetStrategy(String paymentType){PaymentStrategy strategy = strategyMap.get(paymentType);if(strategy ==null){thrownewIllegalArgumentException("不支持的支付类型:"+ paymentType);}return strategy;}}// 5. 服务类使用@ServicepublicclassPaymentService{@AutowiredprivatePaymentStrategyFactory strategyFactory;publicPayResultprocessPayment(String paymentType,PayRequest request){PaymentStrategy strategy = strategyFactory.getStrategy(paymentType);return strategy.pay(request);}}四、策略模式的优缺点
4.1 优点
- 开闭原则:可以在不修改原有代码的情况下,增加新的策略。
- 避免多重条件语句:消除大量的if-else或switch-case。
- 提高算法的复用性:策略可以被不同的上下文复用。
- 策略可以自由切换:由于策略类实现同一个接口,它们可以自由切换。
4.2 缺点
- 类数量增加:每个策略都是一个类,可能会产生很多策略类。
- 客户端必须了解策略:客户端需要知道有哪些策略可选,并选择合适的策略。
- 策略类之间无法共享数据:除非使用上下文传递数据。
五、使用总结与实践建议
5.1 适用场景
- 系统中有多个类,它们之间的区别仅在于行为不同。
- 需要动态选择算法或策略的场景。
- 算法需要使用if-else或switch-case进行选择的场景。
- 需要屏蔽算法实现细节的场景。
5.2 最佳实践
- 结合工厂模式:使用工厂模式创建策略对象,减少客户端对具体策略的依赖。
- 使用枚举定义策略类型:在策略工厂中使用枚举管理策略类型,提高代码可读性。
- Spring中利用依赖注入:充分利用Spring的IOC容器管理策略Bean,通过注解自动注册。
- 策略与状态模式的区别:
- 策略模式:客户端主动选择策略,策略之间相互独立。
- 状态模式:状态随上下文变化自动切换,状态之间有关联。
5.3 与Spring Boot的完美结合
在Spring Boot项目中,策略模式的实现可以更优雅:
// 使用枚举定义策略类型publicenumPaymentTypeEnum{ALIPAY("alipay","支付宝"),WECHAT("wechat","微信支付"),CARD("card","银行卡");privateString code;privateString desc;PaymentTypeEnum(String code,String desc){this.code = code;this.desc = desc;}publicStringgetCode(){return code;}}// 结合Optional处理空值publicPayResultprocessPayment(String paymentType,PayRequest request){returnOptional.ofNullable(strategyFactory.getStrategy(paymentType)).map(strategy -> strategy.pay(request)).orElseThrow(()->newIllegalArgumentException("不支持的支付类型"));}结语
策略模式是一种非常实用且常用的设计模式,它体现了"封装变化"的设计原则。在实际开发中,合理运用策略模式可以让代码更加优雅、易于维护和扩展。特别是在Spring框架的支持下,策略模式的实现变得更加简单和高效。