设计模式-总览https://mp.csdn.net/mp_blog/creation/editor/122202507目录
在项目进行重构的时候,会发现有很多的判断,或者枚举类型的处理。则策略模式应该是比较好的方式,基于上一篇博客所述, 策略模式使其可以理解为Java的多态实现(父类引用指向不同实现的子类对象),就Spring的DispatcherServlet的九大件的默认配置,使用的则相应于是一个事先定义好规则的Map,根据不同的key或者不同的value。 但其实,在项目中进行使用的时候,发现单一的使用策略模式并不能解决问题,或者说很鸡肋。除非情况比较复杂时,策略模式和 工厂模式呀,代理呀等混合进行使用。下面是一次项目代码的重构。刚开始想的很简单,但是后面发现自己却写不好,所以触动很大。觉得还记录一下。
1、背景介绍
订单系统中根据不同类型(枚举),会调用支付系统提供的不同微服务接口,但是都会返回相同的结果对象。但是呢并不像简单的Map一样一个key对应一个value,即一个或者多个枚举会调用一个Feign方法。
支付类型枚举类如下:
public enum PaymentType {
PAY_ON_DELIVERY(1, "货到付款"),
ALIPAY(21, "支付宝"),
WECHAT(22, "微信"),
OFFLINE(0, "线下退款"),
POS(23,"POS机"),
RECHARGE_PAY(24,"预存挂账"),
BANK_TRANSFER(25,"银行转账"),
CREDIT(26,"赊账结算"),
CASH(40,"现金"),
SYNTHESIZE(41,"混合支付"),
CITIC(42, "全付通"),
CREDIT_PAY(43, "授信挂账"),
ONLINE_PAY(44, "在线支付"),
PLATFORM_COLLECTION(45, "平台代收"),
OTHER_PAY(46, "其他支付");
}
调用如下:
// 原来的调用方式
PaymentRespVo paymentRespVo = storePay(reqVo.getStoreId(), reqVo.getAuthCode(), reqVo.getIp(), reqVo.getShopOrderPaymentVos(), order, reqVo.getAdminVoInDto());
// 使用策略加工厂的调用方式
PaymentRespVo paymentRespVo = PaymentStrategyFactory.create(order.getPaymentType())
.getPaymentRespVO(order, reqVo);
// 使用策略加枚举的调用方式
PaymentRespVo paymentRespVo = PaymentStrategy.doGet(order, reqVo, paymentFeignClient);
原来的写法如下:
private PaymentRespVo storePay(Long storeId, String authCode, String ip, List<ShopOrderPaymentVo> vos, Order order, AdminVoInDto adminVoInDto) {
SystemAsserts.isTrue(order.isPayableInStore(), "该订单无法继续支付:订单状态不合法");
logger.info("门店支付:订单id:{}, 订单编码:{}, 支付方式:{}", order.getId(), order.getOrderCode(), order.getPaymentType());
PaymentRespVo paymentRespVo;
if (Objects.equals(order.getPaymentType(), PaymentType.CITIC)) {
PayAdminVoInDto payAdminVoInDto = PayAdminVoInDtoBuilder.createBuilder(adminVoInDto).build();
CiticPayRequest request = CiticPayRequest.builder()
.orderVo(PaymentOrderVoBuilder.createBuilder(order).setShopOrderPaymentVos(Lists.transform(vos, new ShopOrderPayment2PShopOrderPaymentVo())).builder()).authCode(authCode).ip(ip).storeId(storeId)
.adminVoInDto(payAdminVoInDto).build();
logger.info("门店发起全付通支付:{}", JSON.toJSONString(request));
paymentRespVo = paymentFeignClient.citicMicroPay(request);
} else if (Objects.equals(order.getPaymentType(), PaymentType.CASH)
|| Objects.equals(order.getPaymentType(), PaymentType.RECHARGE_PAY)
|| Objects.equals(order.getPaymentType(), PaymentType.POS)
|| Objects.equals(order.getPaymentType(), PaymentType.BANK_TRANSFER)
|| Objects.equals(order.getPaymentType(), PaymentType.ALIPAY)
|| Objects.equals(order.getPaymentType(), PaymentType.WECHAT)
|| Objects.equals(order.getPaymentType(), PaymentType.CREDIT_PAY)
|| Objects.equals(order.getPaymentType(), PaymentType.OTHER_PAY)) {
// 现金、挂账支付、POS、银行转账、支付宝、微信、授信挂账
BackendPayReqVo backendPayReqVo = BackendPayReqVo.builder()
.orderId(order.getId())
.orderVo(PaymentOrderVoBuilder.createBuilder(order).builder())
.shopOrderPaymentVos(Lists.transform(vos, new ShopOrderPayment2PShopOrderPaymentVo())).build();
logger.info("门店发起后台支付:{}", JSON.toJSONString(backendPayReqVo));
paymentRespVo = paymentFeignClient.backendPay(backendPayReqVo);
} else if (Objects.equals(order.getPaymentType(), PaymentType.SYNTHESIZE)) {
PayAdminVoInDto payAdminVoInDto = PayAdminVoInDtoBuilder.createBuilder(adminVoInDto).build();
SynthesizePayReqVo reqVo = new SynthesizePayReqVo();
reqVo.setStoreId(storeId);
reqVo.setAuthCode(authCode);
reqVo.setIp(ip);
reqVo.setAdminVoInDto(payAdminVoInDto);
reqVo.setOrderVo(PaymentOrderVoBuilder.createBuilder(order).builder());
reqVo.setOrderId(order.getId());
reqVo.setShopOrderPaymentVos(Lists.transform(vos, new ShopOrderPayment2PShopOrderPaymentVo()));
logger.info("门店发起混合支付:{}", JSON.toJSONString(reqVo));
paymentRespVo = paymentFeignClient.synthesizePay(reqVo);
} else {
throw new DepotNextDoorException(500, "无效的支付方式");
}
return paymentRespVo;
}
按照原来使用模仿Spring DispatcherServlet 中策略模式的方式,在项目里写过使用一个 HashMap在静态代码块中初始化key、value值。 但是现在发现这并不是单纯的一个key对应一个value的情况。 于是想到了Spring(apache common也有)中有一个MultiValueMap类型的Map(一个key对应多个值),发现也不是这种情况,但是还好apache common还有一个 MultiKeyMap类型的Map(多个key对应同一个value)。
2、使用策略加工厂的实现方式
1)、抽象一个支付方式的调用接口:
public interface PaymentGet {
PaymentRespVo getPaymentRespVO(Order order, ConfirmOrderCreateReqVo reqVo);
}
2)、抽象实现,(注入其余子类都要用到的 远程调用feign的bean)
@Service
@Builder
@Data
public abstract class AbstractPaymentGet implements PaymentGet {
@Autowired
private PaymentFeignClient paymentFeignClient;
public AbstractPaymentGet() {
}
protected PaymentFeignClient getPaymentFeignClient() {
return paymentFeignClient;
}
@Override
public abstract PaymentRespVo getPaymentRespVO(Order order, ConfirmOrderCreateReqVo reqVo);
}
3)、不同类型的实现
@Slf4j
public class BackendPay extends AbstractPaymentGet {
@Override
public PaymentRespVo getPaymentRespVO(Order order, ConfirmOrderCreateReqVo reqVo) {
BackendPayReqVo backendPayReqVo = BackendPayReqVo.builder()
.orderId(order.getId())
.orderVo(PaymentOrderVoBuilder.createBuilder(order).builder())
.shopOrderPaymentVos(Lists.transform(reqVo.getShopOrderPaymentVos(), new ShopOrderPayment2PShopOrderPaymentVo())).build();
log.info("门店发起后台支付:{}", JSON.toJSONString(backendPayReqVo));
return getPaymentFeignClient().backendPay(backendPayReqVo);
}
}
@Slf4j
public class CiticMicroPay extends AbstractPaymentGet {
@Override
public PaymentRespVo getPaymentRespVO(Order order, ConfirmOrderCreateReqVo reqVo) {
PayAdminVoInDto payAdminVoInDto = PayAdminVoInDtoBuilder.createBuilder(reqVo.getAdminVoInDto()).build();
CiticPayRequest request = CiticPayRequest.builder()
.orderVo(PaymentOrderVoBuilder.createBuilder(order).setShopOrderPaymentVos(Lists.transform(reqVo.getShopOrderPaymentVos(), new ShopOrderPayment2PShopOrderPaymentVo())).builder())
.authCode(reqVo.getAuthCode()).ip(reqVo.getIp()).storeId(reqVo.getStoreId())
.adminVoInDto(payAdminVoInDto).build();
log.info("门店发起全付通支付:{}", JSON.toJSONString(request));
return getPaymentFeignClient().citicMicroPay(request);
}
}
@Slf4j
public class SynthesizePay extends AbstractPaymentGet {
@Override
public PaymentRespVo getPaymentRespVO(Order order, ConfirmOrderCreateReqVo reqVo) {
PayAdminVoInDto payAdminVoInDto = PayAdminVoInDtoBuilder.createBuilder(reqVo.getAdminVoInDto()).build();
SynthesizePayReqVo request = new SynthesizePayReqVo();
request.setStoreId(reqVo.getStoreId());
request.setAuthCode(reqVo.getAuthCode());
request.setIp(reqVo.getIp());
request.setAdminVoInDto(payAdminVoInDto);
request.setOrderVo(PaymentOrderVoBuilder.createBuilder(order).builder());
request.setOrderId(order.getId());
request.setShopOrderPaymentVos(Lists.transform(reqVo.getShopOrderPaymentVos(), new ShopOrderPayment2PShopOrderPaymentVo()));
log.info("门店发起混合支付:{}", JSON.toJSONString(request));
return getPaymentFeignClient().synthesizePay(request);
}
}
4)、最好需要初始化调用的工具地方,即Context(上一篇博客)
public class PaymentStrategyFactory {
private static final MultiKeyMap<PaymentType, String> strategies = new MultiKeyMap<>();
static {
strategies.put(new MultiKey<>(new PaymentType[]{CITIC}),
"BackendPay");
strategies.put(new MultiKey<>(new PaymentType[]{CASH,
RECHARGE_PAY, POS,
BANK_TRANSFER, ALIPAY,
WECHAT, CREDIT_PAY, OTHER_PAY}),
"CiticMicroPay");
strategies.put(new MultiKey<>(new PaymentType[]{SYNTHESIZE}),
"SynthesizePay");
}
public static PaymentGet create(PaymentType paymentType) {
String impl = strategies.get(paymentType);
try {
if (impl == null) {
throw new DepotNextDoorException(500, "无效的支付方式");
}
return (PaymentGet)Class.forName(impl).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
5)、调用方式如上写的:
PaymentRespVo paymentRespVo = PaymentStrategyFactory.create(order.getPaymentType())
.getPaymentRespVO(order, reqVo);
思考:第一种实现方式做完之后,发现为了重构一段代码,并且这种后续应该不怎么会进行扩展的。却引入了好多的类,反而变得更复杂了。这应该不是其他人也想看到了,但是这种应该是比较适合每一个实现类都有很多实现方法的时候应该比较好(类似抽象工厂模式)。所以想到了枚举类的抽象方法,果然值用了两个类就解决了,并且也很清晰的结构。并且调用远程feign的bean也不想再注入,反正都要传两个参数了还不如直接传三个,如下:
3、使用策略加枚举的调用方式
1)、配置策略和触发方法
public class PaymentStrategy {
/**
* 策略配置
*/
private static final MultiKeyMap<PaymentType, PaymentStrategyEnum> strategies = new MultiKeyMap<>();
static {
strategies.put(new MultiKey<>(new PaymentType[]{CITIC}),
PaymentStrategyEnum.CITIC_MICRO_PAY);
strategies.put(new MultiKey<>(new PaymentType[]{CASH,
RECHARGE_PAY, POS,
BANK_TRANSFER, ALIPAY,
WECHAT, CREDIT_PAY, OTHER_PAY}),
PaymentStrategyEnum.BACKEND_PAY);
strategies.put(new MultiKey<>(new PaymentType[]{SYNTHESIZE}),
PaymentStrategyEnum.SYNTHESIZE_PAY);
strategies.put(new MultiKey<>(null),
PaymentStrategyEnum.OTHER_PAY);
}
/**
* 获取远程数据
*
* @param order 订单
* @param reqVo 请求
* @param paymentFeignClient 支付feign
* @return 支付数据
*/
public static PaymentRespVo doGet(Order order, ConfirmOrderCreateReqVo reqVo,
PaymentFeignClient paymentFeignClient) {
return strategies.get(order.getPaymentType()).getPaymentRespVO(order, reqVo, paymentFeignClient);
}
}
2)、实现方法(放到枚举内)
@Slf4j
public enum PaymentStrategyEnum {
BACKEND_PAY {
@Override
public PaymentRespVo getPaymentRespVO(Order order, ConfirmOrderCreateReqVo reqVo, PaymentFeignClient paymentFeignClient) {
BackendPayReqVo backendPayReqVo = BackendPayReqVo.builder()
.orderId(order.getId())
.orderVo(PaymentOrderVoBuilder.createBuilder(order).builder())
.shopOrderPaymentVos(Lists.transform(reqVo.getShopOrderPaymentVos(), new ShopOrderPayment2PShopOrderPaymentVo())).build();
log.info("门店发起后台支付:{}", JSON.toJSONString(backendPayReqVo));
return paymentFeignClient.backendPay(backendPayReqVo);
}
},
CITIC_MICRO_PAY {
@Override
public PaymentRespVo getPaymentRespVO(Order order, ConfirmOrderCreateReqVo reqVo, PaymentFeignClient paymentFeignClient) {
PayAdminVoInDto payAdminVoInDto = PayAdminVoInDtoBuilder.createBuilder(reqVo.getAdminVoInDto()).build();
CiticPayRequest request = CiticPayRequest.builder()
.orderVo(PaymentOrderVoBuilder.createBuilder(order).setShopOrderPaymentVos(Lists.transform(reqVo.getShopOrderPaymentVos(), new ShopOrderPayment2PShopOrderPaymentVo())).builder())
.authCode(reqVo.getAuthCode()).ip(reqVo.getIp()).storeId(reqVo.getStoreId())
.adminVoInDto(payAdminVoInDto).build();
log.info("门店发起全付通支付:{}", JSON.toJSONString(request));
return paymentFeignClient.citicMicroPay(request);
}
},
SYNTHESIZE_PAY {
@Override
public PaymentRespVo getPaymentRespVO(Order order, ConfirmOrderCreateReqVo reqVo, PaymentFeignClient paymentFeignClient) {
PayAdminVoInDto payAdminVoInDto = PayAdminVoInDtoBuilder.createBuilder(reqVo.getAdminVoInDto()).build();
SynthesizePayReqVo request = new SynthesizePayReqVo();
request.setStoreId(reqVo.getStoreId());
request.setAuthCode(reqVo.getAuthCode());
request.setIp(reqVo.getIp());
request.setAdminVoInDto(payAdminVoInDto);
request.setOrderVo(PaymentOrderVoBuilder.createBuilder(order).builder());
request.setOrderId(order.getId());
request.setShopOrderPaymentVos(Lists.transform(reqVo.getShopOrderPaymentVos(), new ShopOrderPayment2PShopOrderPaymentVo()));
log.info("门店发起混合支付:{}", JSON.toJSONString(request));
return paymentFeignClient.synthesizePay(request);
}
},
OTHER_PAY {
@Override
public PaymentRespVo getPaymentRespVO(Order order, ConfirmOrderCreateReqVo reqVo, PaymentFeignClient paymentFeignClient) {
throw new DepotNextDoorException(500, "无效的支付方式");
}
};
public abstract PaymentRespVo getPaymentRespVO(Order order, ConfirmOrderCreateReqVo reqVo, PaymentFeignClient paymentFeignClient);
}
3)、调用方式
PaymentRespVo paymentRespVo = PaymentStrategy.doGet(order, reqVo, paymentFeignClient);
最好感觉清爽多了,应该是比较好的结果了。设计模式,我还是觉得多看看源码怎么写的,还有自己项目中多去用用就理解了,反正这不就是别人理解总结的经验嘛。欢迎大家有好的方式,多讨论。