|
1 | 1 | # **策略模式在项目设计中的应用** |
| 2 | + |
| 3 | +## 摘要: |
| 4 | +在软件开发中,策略模式(Strategy Pattern)是一种行为设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户,从而实现了业务逻辑与具体实现的解耦,为软件的可扩展性、可维护性和复用性提供了坚实的基础。 |
| 5 | + |
| 6 | +本文旨在探讨如何在12306项目中运用策略模式来优化业务流程,并通过实例分析其优势和实施步骤。 |
| 7 | + |
| 8 | +## 引言: |
| 9 | +随着业务的不断发展变化,软件系统需要具备灵活应对各种复杂场景的能力。传统的条件分支语句或类型继承体系往往导致系统结构臃肿,难以适应快速变化的业务需求。策略模式提供了一种更为优雅的解决方案,允许在运行时动态选择最适合的算法或流程。 |
| 10 | + |
| 11 | +## 1. 策略模式简介: |
| 12 | +策略模式定义了一族算法,分别封装起来,让它们之间可以互相替换。这些算法通常具有相同的接口,使得客户端在使用算法时无需知道具体的实现细节。 |
| 13 | + |
| 14 | +策略模式主要包含三个角色:上下文(Context)、策略(Strategy)和客户端(Client)。 |
| 15 | + |
| 16 | +## 2. 策略模式的优势: |
| 17 | +- 提高系统的灵活性和可扩展性。 |
| 18 | +- 避免使用多重条件转移语句。 |
| 19 | +- 提供管理相关的算法族的方法。 |
| 20 | +- 符合开放封闭原则,易于扩展新的操作。 |
| 21 | +- 符合单一职责原则,每个类只负责一件事情。 |
| 22 | + |
| 23 | +## 3. 策略模式在项目中的应用: |
| 24 | +以下是12306项目中策略模式的实际使用案例,展示如何利用策略模式进行业务代码的优化。 |
| 25 | + |
| 26 | +### 案例分析: |
| 27 | +对12306项目中支付宝支付组件的策略模式使用场景进行案例分析。 |
| 28 | + |
| 29 | +- 第一步:定义策略接口 |
| 30 | +```java |
| 31 | +/** |
| 32 | + * 策略执行抽象 |
| 33 | + */ |
| 34 | +public interface AbstractExecuteStrategy<REQUEST, RESPONSE> { |
| 35 | + |
| 36 | + /** |
| 37 | + * 执行策略标识 |
| 38 | + */ |
| 39 | + default String mark() { |
| 40 | + return null; |
| 41 | + } |
| 42 | + |
| 43 | + /** |
| 44 | + * 执行策略范匹配标识 |
| 45 | + */ |
| 46 | + default String patternMatchMark() { |
| 47 | + return null; |
| 48 | + } |
| 49 | + |
| 50 | + /** |
| 51 | + * 执行策略 |
| 52 | + * |
| 53 | + * @param requestParam 执行策略入参 |
| 54 | + */ |
| 55 | + default void execute(REQUEST requestParam) { |
| 56 | + |
| 57 | + } |
| 58 | + |
| 59 | + /** |
| 60 | + * 执行策略,带返回值 |
| 61 | + * |
| 62 | + * @param requestParam 执行策略入参 |
| 63 | + * @return 执行策略后返回值 |
| 64 | + */ |
| 65 | + default RESPONSE executeResp(REQUEST requestParam) { |
| 66 | + return null; |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +``` |
| 71 | +- 第二步:实现不同的具体策略类 |
| 72 | +```java |
| 73 | +/** |
| 74 | + * 阿里支付回调组件 |
| 75 | + */ |
| 76 | +@Slf4j |
| 77 | +@Service |
| 78 | +@RequiredArgsConstructor |
| 79 | +public final class AliPayCallbackHandler extends AbstractPayCallbackHandler implements AbstractExecuteStrategy<PayCallbackRequest, Void> { |
| 80 | + |
| 81 | + private final PayService payService; |
| 82 | + |
| 83 | + @Override |
| 84 | + public void callback(PayCallbackRequest payCallbackRequest) { |
| 85 | + AliPayCallbackRequest aliPayCallBackRequest = payCallbackRequest.getAliPayCallBackRequest(); |
| 86 | + PayCallbackReqDTO payCallbackRequestParam = PayCallbackReqDTO.builder() |
| 87 | + .status(TradeStatusEnum.queryActualTradeStatusCode(aliPayCallBackRequest.getTradeStatus())) |
| 88 | + .payAmount(aliPayCallBackRequest.getBuyerPayAmount()) |
| 89 | + .tradeNo(aliPayCallBackRequest.getTradeNo()) |
| 90 | + .gmtPayment(aliPayCallBackRequest.getGmtPayment()) |
| 91 | + .orderSn(aliPayCallBackRequest.getOrderRequestId()) |
| 92 | + .build(); |
| 93 | + payService.callbackPay(payCallbackRequestParam); |
| 94 | + } |
| 95 | + |
| 96 | + @Override |
| 97 | + public String mark() { |
| 98 | + return PayChannelEnum.ALI_PAY.name(); |
| 99 | + } |
| 100 | + |
| 101 | + public void execute(PayCallbackRequest requestParam) { |
| 102 | + callback(requestParam); |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +/** |
| 107 | + * 阿里支付组件 |
| 108 | + */ |
| 109 | +@Slf4j |
| 110 | +@Service |
| 111 | +@RequiredArgsConstructor |
| 112 | +public class AliPayNativeHandler extends AbstractPayHandler implements AbstractExecuteStrategy<PayRequest, PayResponse> { |
| 113 | + |
| 114 | + private final AliPayProperties aliPayProperties; |
| 115 | + |
| 116 | + @SneakyThrows(value = AlipayApiException.class) |
| 117 | + @Override |
| 118 | + @Retryable(value = ServiceException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 1.5)) |
| 119 | + public PayResponse pay(PayRequest payRequest) { |
| 120 | + AliPayRequest aliPayRequest = payRequest.getAliPayRequest(); |
| 121 | + AlipayConfig alipayConfig = BeanUtil.convert(aliPayProperties, AlipayConfig.class); |
| 122 | + AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig); |
| 123 | + AlipayTradePagePayModel model = new AlipayTradePagePayModel(); |
| 124 | + model.setOutTradeNo(aliPayRequest.getOrderSn()); |
| 125 | + model.setTotalAmount(aliPayRequest.getTotalAmount().toString()); |
| 126 | + model.setSubject(aliPayRequest.getSubject()); |
| 127 | + model.setProductCode("FAST_INSTANT_TRADE_PAY"); |
| 128 | + AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); |
| 129 | + request.setNotifyUrl(aliPayProperties.getNotifyUrl()); |
| 130 | + request.setBizModel(model); |
| 131 | + try { |
| 132 | + AlipayTradePagePayResponse response = alipayClient.pageExecute(request); |
| 133 | + log.info("发起支付宝支付,订单号:{},子订单号:{},订单请求号:{},订单金额:{} \n调用支付返回:\n\n{}\n", |
| 134 | + aliPayRequest.getOrderSn(), |
| 135 | + aliPayRequest.getOutOrderSn(), |
| 136 | + aliPayRequest.getOrderRequestId(), |
| 137 | + aliPayRequest.getTotalAmount(), |
| 138 | + JSONObject.toJSONString(response)); |
| 139 | + if (!response.isSuccess()) { |
| 140 | + throw new ServiceException("调用支付宝发起支付异常"); |
| 141 | + } |
| 142 | + return new PayResponse(StrUtil.replace(StrUtil.replace(response.getBody(), "\"", "'"), "\n", "")); |
| 143 | + } catch (AlipayApiException ex) { |
| 144 | + throw new ServiceException("调用支付宝支付异常"); |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + @Override |
| 149 | + public String mark() { |
| 150 | + return StrBuilder.create() |
| 151 | + .append(PayChannelEnum.ALI_PAY.name()) |
| 152 | + .append("_") |
| 153 | + .append(PayTradeTypeEnum.NATIVE.name()) |
| 154 | + .toString(); |
| 155 | + } |
| 156 | + |
| 157 | + @Override |
| 158 | + public PayResponse executeResp(PayRequest requestParam) { |
| 159 | + return pay(requestParam); |
| 160 | + } |
| 161 | +} |
| 162 | + |
| 163 | +/** |
| 164 | + * 阿里支付组件 |
| 165 | + * |
| 166 | + */ |
| 167 | +@Slf4j |
| 168 | +@Service |
| 169 | +@RequiredArgsConstructor |
| 170 | +public class AliRefundNativeHandler extends AbstractRefundHandler implements AbstractExecuteStrategy<RefundRequest, RefundResponse> { |
| 171 | + |
| 172 | + private final AliPayProperties aliPayProperties; |
| 173 | + |
| 174 | + private final static String SUCCESS_CODE = "10000"; |
| 175 | + |
| 176 | + private final static String FUND_CHANGE = "Y"; |
| 177 | + |
| 178 | + @Retryable(value = {ServiceException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 1.5)) |
| 179 | + @SneakyThrows(value = AlipayApiException.class) |
| 180 | + @Override |
| 181 | + public RefundResponse refund(RefundRequest payRequest) { |
| 182 | + AliRefundRequest aliRefundRequest = payRequest.getAliRefundRequest(); |
| 183 | + AlipayConfig alipayConfig = BeanUtil.convert(aliPayProperties, AlipayConfig.class); |
| 184 | + AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig); |
| 185 | + AlipayTradeRefundModel model = new AlipayTradeRefundModel(); |
| 186 | + model.setOutTradeNo(aliRefundRequest.getOrderSn()); |
| 187 | + model.setTradeNo(aliRefundRequest.getTradeNo()); |
| 188 | + BigDecimal payAmount = aliRefundRequest.getPayAmount(); |
| 189 | + BigDecimal refundAmount = payAmount.divide(new BigDecimal(100)); |
| 190 | + model.setRefundAmount(refundAmount.toString()); |
| 191 | + model.setOutRequestNo(SnowflakeIdUtil.nextIdStr()); |
| 192 | + AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); |
| 193 | + request.setBizModel(model); |
| 194 | + try { |
| 195 | + AlipayTradeRefundResponse response = alipayClient.execute(request); |
| 196 | + String responseJson = JSONObject.toJSONString(response); |
| 197 | + log.info("发起支付宝退款,订单号:{},交易凭证号:{},退款金额:{} \n调用退款响应:\n\n{}\n", |
| 198 | + aliRefundRequest.getOrderSn(), |
| 199 | + aliRefundRequest.getTradeNo(), |
| 200 | + aliRefundRequest.getPayAmount(), |
| 201 | + responseJson); |
| 202 | + if (!StrUtil.equals(SUCCESS_CODE, response.getCode()) || !StrUtil.equals(FUND_CHANGE, response.getFundChange())) { |
| 203 | + throw new ServiceException("退款失败"); |
| 204 | + } |
| 205 | + return new RefundResponse(TradeStatusEnum.TRADE_CLOSED.tradeCode(), response.getTradeNo()); |
| 206 | + } catch (AlipayApiException e) { |
| 207 | + throw new ServiceException("调用支付宝退款异常"); |
| 208 | + } |
| 209 | + } |
| 210 | + |
| 211 | + @Override |
| 212 | + public String mark() { |
| 213 | + return StrBuilder.create() |
| 214 | + .append(PayChannelEnum.ALI_PAY.name()) |
| 215 | + .append("_") |
| 216 | + .append(PayTradeTypeEnum.NATIVE.name()) |
| 217 | + .append("_") |
| 218 | + .append(TradeStatusEnum.TRADE_CLOSED.tradeCode()) |
| 219 | + .toString(); |
| 220 | + } |
| 221 | + |
| 222 | + @Override |
| 223 | + public RefundResponse executeResp(RefundRequest requestParam) { |
| 224 | + return refund(requestParam); |
| 225 | + } |
| 226 | +} |
| 227 | +``` |
| 228 | +- 第三步:创建上下文环境类 |
| 229 | +```java |
| 230 | +/** |
| 231 | + * 策略选择器 |
| 232 | + **/ |
| 233 | +public class AbstractStrategyChoose implements ApplicationListener<ApplicationInitializingEvent> { |
| 234 | + |
| 235 | + /** |
| 236 | + * 执行策略集合 |
| 237 | + */ |
| 238 | + private final Map<String, AbstractExecuteStrategy> abstractExecuteStrategyMap = new HashMap<>(); |
| 239 | + |
| 240 | + /** |
| 241 | + * 根据 mark 查询具体策略 |
| 242 | + * |
| 243 | + * @param mark 策略标识 |
| 244 | + * @param predicateFlag 匹配范解析标识 |
| 245 | + * @return 实际执行策略 |
| 246 | + */ |
| 247 | + public AbstractExecuteStrategy choose(String mark, Boolean predicateFlag) { |
| 248 | + if (predicateFlag != null && predicateFlag) { |
| 249 | + return abstractExecuteStrategyMap.values().stream() |
| 250 | + .filter(each -> StringUtils.hasText(each.patternMatchMark())) |
| 251 | + .filter(each -> Pattern.compile(each.patternMatchMark()).matcher(mark).matches()) |
| 252 | + .findFirst() |
| 253 | + .orElseThrow(() -> new ServiceException("策略未定义")); |
| 254 | + } |
| 255 | + return Optional.ofNullable(abstractExecuteStrategyMap.get(mark)) |
| 256 | + .orElseThrow(() -> new ServiceException(String.format("[%s] 策略未定义", mark))); |
| 257 | + } |
| 258 | + |
| 259 | + /** |
| 260 | + * 根据 mark 查询具体策略并执行 |
| 261 | + * |
| 262 | + * @param mark 策略标识 |
| 263 | + * @param requestParam 执行策略入参 |
| 264 | + * @param <REQUEST> 执行策略入参范型 |
| 265 | + */ |
| 266 | + public <REQUEST> void chooseAndExecute(String mark, REQUEST requestParam) { |
| 267 | + AbstractExecuteStrategy executeStrategy = choose(mark, null); |
| 268 | + executeStrategy.execute(requestParam); |
| 269 | + } |
| 270 | + |
| 271 | + /** |
| 272 | + * 根据 mark 查询具体策略并执行 |
| 273 | + * |
| 274 | + * @param mark 策略标识 |
| 275 | + * @param requestParam 执行策略入参 |
| 276 | + * @param predicateFlag 匹配范解析标识 |
| 277 | + * @param <REQUEST> 执行策略入参范型 |
| 278 | + */ |
| 279 | + public <REQUEST> void chooseAndExecute(String mark, REQUEST requestParam, Boolean predicateFlag) { |
| 280 | + AbstractExecuteStrategy executeStrategy = choose(mark, predicateFlag); |
| 281 | + executeStrategy.execute(requestParam); |
| 282 | + } |
| 283 | + |
| 284 | + /** |
| 285 | + * 根据 mark 查询具体策略并执行,带返回结果 |
| 286 | + * |
| 287 | + * @param mark 策略标识 |
| 288 | + * @param requestParam 执行策略入参 |
| 289 | + * @param <REQUEST> 执行策略入参范型 |
| 290 | + * @param <RESPONSE> 执行策略出参范型 |
| 291 | + * @return |
| 292 | + */ |
| 293 | + public <REQUEST, RESPONSE> RESPONSE chooseAndExecuteResp(String mark, REQUEST requestParam) { |
| 294 | + AbstractExecuteStrategy executeStrategy = choose(mark, null); |
| 295 | + return (RESPONSE) executeStrategy.executeResp(requestParam); |
| 296 | + } |
| 297 | + |
| 298 | + @Override |
| 299 | + public void onApplicationEvent(ApplicationInitializingEvent event) { |
| 300 | + Map<String, AbstractExecuteStrategy> actual = ApplicationContextHolder.getBeansOfType(AbstractExecuteStrategy.class); |
| 301 | + actual.forEach((beanName, bean) -> { |
| 302 | + AbstractExecuteStrategy beanExist = abstractExecuteStrategyMap.get(bean.mark()); |
| 303 | + if (beanExist != null) { |
| 304 | + throw new ServiceException(String.format("[%s] Duplicate execution policy", bean.mark())); |
| 305 | + } |
| 306 | + abstractExecuteStrategyMap.put(bean.mark(), bean); |
| 307 | + }); |
| 308 | + } |
| 309 | +} |
| 310 | +``` |
| 311 | +- 第四步:通过策略模式封装支付回调渠道,支付回调时动态选择对应的支付回调组件 |
| 312 | +```java |
| 313 | + @PostMapping("/api/pay-service/callback/alipay") |
| 314 | + public void callbackAlipay(@RequestParam Map<String, Object> requestParam) { |
| 315 | + PayCallbackCommand payCallbackCommand = BeanUtil.mapToBean(requestParam, PayCallbackCommand.class, true, CopyOptions.create()); |
| 316 | + payCallbackCommand.setChannel(PayChannelEnum.ALI_PAY.getCode()); |
| 317 | + payCallbackCommand.setOrderRequestId(requestParam.get("out_trade_no").toString()); |
| 318 | + payCallbackCommand.setGmtPayment(DateUtil.parse(requestParam.get("gmt_payment").toString())); |
| 319 | + PayCallbackRequest payCallbackRequest = PayCallbackRequestConvert.command2PayCallbackRequest(payCallbackCommand); |
| 320 | + /** |
| 321 | + * {@link AliPayCallbackHandler} |
| 322 | + */ |
| 323 | + // 策略模式:通过策略模式封装支付回调渠道,支付回调时动态选择对应的支付回调组件 |
| 324 | + abstractStrategyChoose.chooseAndExecute(payCallbackRequest.buildMark(), payCallbackRequest); |
| 325 | + } |
| 326 | +``` |
| 327 | + |
| 328 | +## 4. 实施步骤: |
| 329 | +- 识别可变的部分并将其封装成独立的策略类。 |
| 330 | +- 确保所有的策略类遵循同一个策略接口。 |
| 331 | +- 在上下文环境中设置策略对象,并在运行时决定使用哪一种策略。 |
| 332 | +- 客户端代码调用上下文环境的执行方法,由上下文环境负责使用当前的策略对象来执行请求的操作。 |
| 333 | + |
| 334 | +## 结论: |
| 335 | +策略模式通过将算法的定义从使用它们的客户端代码中分离出来,不仅提高了代码的复用性与可维护性,还极大地提升了系统的灵活性。在项目实践过程中,适时引入策略模式,可以帮助开发者有效地管理和优化复杂的业务流程,以适应不断变化的业务需求。通过对策略模式的合理应用,项目团队可以更高效地响应市场变化,提升客户满意度,最终推动业务的持续增长和成功。 |
0 commit comments