關(guān)鍵字:
- 如何寫好業(yè)務(wù)代碼
- 業(yè)務(wù)架構(gòu)
- 設(shè)計模式
- 模版方法
- 策略模式
- 工廠模式
本文概要:
對于做web開發(fā)的java程序員來說许帐,如何寫出更好看的業(yè)務(wù)代碼。本文會展示利用設(shè)計模式中模版方法
主经,策略
荣暮,工廠
3種模式來優(yōu)化平鋪直敘的代碼。
業(yè)務(wù)簡介:
開始之前需要先了解一下業(yè)務(wù)罩驻。
- 業(yè)務(wù)是通過調(diào)用支付寶接口來做支付訂單穗酥。
- 業(yè)務(wù)中有10種訂單類型。
- 通過接口參數(shù)里的payType參數(shù)確定是哪種訂單惠遏,然后執(zhí)行對應(yīng)的訂單分支邏輯迷扇,調(diào)用阿里支付,返回交易編號
優(yōu)化前的部分代碼如下:
-
http接口接收訂單類型參數(shù)爽哎,然后調(diào)用service的getPayInfo方法時傳入這個類型蜓席。
-
getPayInfo方法根據(jù)參數(shù)payType做switch case,這本身沒有太大問題课锌,但是清注意厨内,每一個case里,代碼都分為2個步驟渺贤,步驟1準(zhǔn)備參數(shù)雏胃,步驟2調(diào)用與case對應(yīng)的方法,并且每個方法的參數(shù)個數(shù)一樣志鞍,類型除了最后一個之外瞭亮,也都是一樣的。
-
上面case語句里調(diào)用的方法如下圖固棚,可以分為2個或3個步驟统翩,步驟1組裝參數(shù)對象,步驟2調(diào)用alipayService.getTradeNo方法此洲,步驟3根據(jù)步驟2的返回內(nèi)容做后續(xù)處理厂汗,最后返回阿里支付的交易編號。
優(yōu)化前代碼的一些問題
- 對于上面圖002中的getPayInfo方法呜师,如果業(yè)務(wù)要在增加一種訂單類型娶桦,那么就需要再增加一個case的同時,增加case中的步驟1汁汗,步驟2代碼衷畦。這樣的話就會導(dǎo)致每次增加一種新訂單類型都需要維護很大的一大坨代碼。而且如果某個現(xiàn)有訂單類型的步驟1知牌,步驟2邏輯需要改動祈争,也需要在這個switch case里修改。如果用高大上的語言來描述送爸,那就是沒有符合開閉原則铛嘱,沒有做到單一職責(zé)原則,沒有做到高內(nèi)聚低耦合等等袭厂。
怎么來優(yōu)化呢
- 上面的代碼圖002中service里的getPayInfo方法中墨吓, case里寫了大量(省略了7個case分支)的重復(fù)代碼,各個case中的步驟1纹磺,步驟2都是可以抽象出來的帖烘。
- 圖002中case里調(diào)用各自的方法獲取tradeNo。這些方法在圖003中可以看出來也有共通性橄杨,也可以抽象出來秘症。
- 從整體的代碼邏輯上來看,圖002中的每個case分支式矫,及其后續(xù)操作乡摹,似乎都有著同樣的共性,只有少部分的邏輯有各自的特性采转,那么很容易想到用模版方法模式來做優(yōu)化聪廉。因為有很多case分支,也很容易聯(lián)想到策略模式了故慈。
- 優(yōu)化的思路如下:
- 把不同的訂單類型都當(dāng)作不同的策略板熊,那么也就是每一個case中的邏輯都是一個不同的策略;
- 因為每一個訂單類型的代碼流程大致都相同察绷,只有少數(shù)不同的步驟干签,那么就定義一個模版類,將相同的步驟統(tǒng)一寫在模版類里拆撼,不同的步驟容劳,讓策略類去繼承模版類后,自己去實現(xiàn)闸度;
- 把switch case轉(zhuǎn)移到一個工廠類里鸭蛙,通過工廠類生成不同的策略對象;
- 外層代碼調(diào)用工廠類返回對象的模版方法筋岛,即可完成訂單支付流程娶视。
優(yōu)化之后的代碼
- 首先創(chuàng)建一個模版類的接口
IOrderAlipayStrategy
,模版類實現(xiàn)這個接口睁宰,該接口只有一個方法payThroughAlipay
肪获,具體使用請看 2.
/**
* @Author: yesiming
* @Platform: Mac
* @Date: 5:14 下午 2020/9/25
*
* 代碼優(yōu)化:支付寶小程序支付優(yōu)化成通過工廠,策略柒傻,模版 3種模式實現(xiàn)
* 此接口為:訂單支付的策略接口
*/
public interface IOrderAlipayStrategy {
/**
* @param alipayConfig
* @param payType
* @param orderId
* @param alipayUserId
* @return
*
* 通過alipay支付孝赫,OrderInfo,包含阿里支付返回的TradeNo
*/
OrderInfo payThroughAlipay(AlipayConfig alipayConfig, PayType payType, String orderId, String alipayUserId) throws Exception;
}
OrderInfo
用戶存放模版方法返回的內(nèi)容,代碼如下红符。
public class OrderInfo {
private String payOrderId;
private BigDecimal totalAmount;
private String subject;
private String tradeNo;
public OrderInfo(BigDecimal totalAmount, String subject, PayType payType, String orderNo) {
this.payOrderId = String.join("-", payType.getCode(), orderNo);
this.totalAmount = totalAmount;
this.subject = subject;
}
// 省略其他getter青柄,setter
}
- 創(chuàng)建抽象模版類伐债,實現(xiàn)
IOrderAlipayStrategy
接口,并且實現(xiàn)payThroughAlipay
方法致开,可以從代碼中看到峰锁,payThroughAlipay
里的1,2双戳,3虹蒋,4,5飒货,這5個步驟中魄衅,只有1,5塘辅,是與業(yè)務(wù)相關(guān)的晃虫,2,3扣墩,4是與業(yè)務(wù)無關(guān)的傲茄。那么該方法中年的2,3沮榜,4步驟的代碼就可以在該模版類中實現(xiàn)盘榨,并且由該方法調(diào)用。1蟆融,5步驟因為是與業(yè)務(wù)相關(guān)草巡,也就是說對于不同的訂單類型,實現(xiàn)不同型酥,那么1山憨,5步驟的方法在模版類中就寫成抽象方法,具體實現(xiàn)推遲到繼承模版方法的各個策略類中弥喉。當(dāng)然某些場景下1郁竟,5步驟也可以寫成默認實現(xiàn),具體策略類對默認實現(xiàn)不滿意的話由境,可以覆蓋默認實現(xiàn)棚亩。
/**
* @Author: yesiming
* @Platform: Mac
* @Date: 5:28 下午 2020/9/25
*/
public abstract class AbstractOrderAlipayStrategy implements IOrderAlipayStrategy {
@Value("${url.domain.name}")
public String DOMAIN_NAME;
@Autowired
private AlipayService alipayService;
@Override
public OrderInfo payThroughAlipay(AlipayConfig alipayConfig, PayType payType, String orderId, String alipayUserId) throws Exception {
// 1. 準(zhǔn)備參數(shù),業(yè)務(wù)相關(guān)
OrderInfo orderInfo = prepareArgs(orderId, payType);
// 2. 組裝DTO對象
AlipayTradeCreateBizContentDTO bizContentInputDTO =
assembleAlipayTradeCreateBizContentDTO(alipayUserId, orderInfo);
// 3. 調(diào)用阿里支付
String tradeNo = realPayThroughAlipay(alipayConfig,
bizContentInputDTO,
DOMAIN_NAME + AlipayConstants.NOTIFY_MINIPROGRAM_PAYCALLBACK);
// 4. 附加對TradeNo的處理
attachTradeNo(orderInfo, tradeNo);
// 5. 后續(xù)處理虏杰,業(yè)務(wù)相關(guān)
followUp(tradeNo);
return orderInfo;
}
/**
* 業(yè)務(wù)相關(guān)讥蟆,返回不同Order類型
* @param orderId
* @return
*/
public abstract OrderInfo prepareArgs(String orderId, PayType payType);
/**
* 組裝DTO
* 業(yè)務(wù)無關(guān),模版實現(xiàn)
*/
private AlipayTradeCreateBizContentDTO assembleAlipayTradeCreateBizContentDTO(String alipayUserId,
OrderInfo orderInfo) {
AlipayTradeCreateBizContentDTO bizContentInputDTO = new AlipayTradeCreateBizContentDTO();
bizContentInputDTO.setBuyerId(alipayUserId);
bizContentInputDTO.setOutTradeNo(orderInfo.getPayOrderId());
bizContentInputDTO.setTotalAmount(orderInfo.getTotalAmount());
bizContentInputDTO.setSubject(orderInfo.getSubject());
return bizContentInputDTO;
}
/**
* 調(diào)用Alipay支付
* 業(yè)務(wù)無關(guān)纺阔,模版實現(xiàn)
*/
private String realPayThroughAlipay(AlipayConfig alipayConfig,
AlipayTradeCreateBizContentDTO bizContentInputDTO,
String notifyUrl) throws Exception {
String tradeNo = alipayService.getTradeNo(alipayConfig, bizContentInputDTO,
DOMAIN_NAME + AlipayConstants.NOTIFY_MINIPROGRAM_PAYCALLBACK);
return tradeNo;
}
/**
* 處理tradeNo返回值
* 業(yè)務(wù)無關(guān)瘸彤,模版實現(xiàn)
*/
private void attachTradeNo(OrderInfo orderInfo, String tradeNo) {
orderInfo.setTradeNo(tradeNo);
}
/**
* 業(yè)務(wù)相關(guān),自定義后續(xù)處理笛钝,
* 如果需要后續(xù)處理可以重寫此方法
*
* @param tradeNo
*/
public void followUp(String tradeNo) {}
}
- 到此為止质况,已經(jīng)把優(yōu)化之前的每個case分支里的流程框架寫完了愕宋,接下來只需要完成每個case分支的策略類即可。
- 策略類需要繼承模版類结榄,并且重寫模版類中的業(yè)務(wù)相關(guān)方法中贝。
4.1 策略類:員工訂單服務(wù)費
/**
* @Author: yesiming
* @Platform: Mac
* @Date: 5:13 下午 2020/9/25
*
* 員工訂單服務(wù)費
*/
@Component
public class OrgOrderServiceStrategy extends AbstractOrderAlipayStrategy {
@Autowired
private OrgOrderMapper orgOrderMapper;
/**
* 重寫了模版類中的業(yè)務(wù)相關(guān)方法,實現(xiàn)業(yè)務(wù)的獨立性
* @param orderId
* @param payType
* @return
*/
@Override
public OrderInfo prepareArgs(String orderId, PayType payType) {
OrgOrder orgOrderService = orgOrderMapper.getById(Long.parseLong(orderId));
BigDecimal payAmount = orgOrderService.getTotalServiceCostAmount();
OrderInfo orderInfo = new OrderInfo(payAmount,orgOrderService.getTypeName(), payType, orgOrderService.getOrderNo());
return orderInfo;
}
}
4.2 策略類:員工訂單商品服務(wù)費
@Component
public class OrgOrderObjStrategy extends AbstractOrderAlipayStrategy {
@Autowired
private OrgOrderMapper orgOrderMapper;
@Override
public OrderInfo prepareArgs(String orderId, PayType payType) {
OrgOrder orgOrderService = orgOrderMapper.getById(Long.parseLong(orderId));
BigDecimal payAmount = orgOrderService.getObjCostAmount();
OrderInfo orderInfo = new OrderInfo(payAmount,orgOrderService.getTypeName(), payType, orgOrderService.getOrderNo());
return orderInfo;
}
}
4.3. 策略類:活動訂單
@Component
public class ActivityOrderStrategy extends AbstractOrderAlipayStrategy {
@Autowired
private ConfigurableActivityService configurableActivityService;
@Autowired
private ActivityOwnerMapper activityOwnerMapper;
/**
* 簡化方法之間的數(shù)據(jù)傳遞
*/
private ThreadLocal<Long> activityOwnerIdTL = new ThreadLocal<>();
@Override
public OrderInfo prepareArgs(String orderId, PayType payType) {
ActivityOwner activityOwner = configurableActivityService.getActivityOwnerById(Long.parseLong(orderId));
activityOwnerIdTL.set(activityOwner.getId());
BigDecimal payAmount = activityOwner.getApplyMoney();
OrderInfo orderInfo = new OrderInfo(
payAmount, // 支付金額
WechatConstants.orderBody, //
payType, // 支付類型
activityOwner.getId().toString() + "-" + System.currentTimeMillis());
return orderInfo;
}
/**
* 后續(xù)處理潭陪,
* 需要prepareArgs()方法執(zhí)行過程中的數(shù)據(jù)雄妥,還不想通過返回值來傳遞
* 可以通過ThreadLocal來完成
*
* @param tradeNo
*/
@Override
public void followUp(String tradeNo) {
if (tradeNo != null) {
ActivityOwner activityOwner = new ActivityOwner();
activityOwner.setId(activityOwnerIdTL.get());
activityOwner.setSource("mini_alipay");
activityOwnerMapper.updateSource(activityOwner);
}
}
}
4.4 還有很多策略類不寫了最蕾,從上面給出的3個策略類的代碼看依溯,前2個策略類,只實現(xiàn)了prepareArgs
方法瘟则,沒有實現(xiàn)followUp
方法黎炉,那么對于這2個策略類來說,followUp
的處理將使用模版類里提供的默認處理方式醋拧,也就是什么都不做慷嗜,第三個策略類重寫了followUp
方法,那么它將使用自己的重寫邏輯丹壕。
- 策略類寫完了庆械,下面就可以在service里調(diào)用了【担可是這么多策略類如何調(diào)用呢缭乘,怎么知道調(diào)用哪一個呢?可以將switch case放到一個工廠類里面琉用。需要注意的是堕绩,使用spring框架時,這里一定要注入需要用到的策略類邑时,不能在case里new奴紧,new的話會導(dǎo)致策略類里的屬性不被填充。
---------- 這里有個小小的坑晶丘,請看后文【后記】部分 ----------
/**
* @Author: yesiming
* @Platform: Mac
* @Date: 10:16 下午 2020/9/25
*/
@Component
public class OrderStrategyFactory {
@Autowired
private ActivityOrderStrategy activityOrderStrategy;
@Autowired
private GroupbuyOrderStrategy groupbuyOrderStrategy;
@Autowired
private OrgOrderObjStrategy orgOrderObjStrategy;
@Autowired
private OrgOrderRewardStrategy orgOrderRewardStrategy;
@Autowired
private OrgOrderServiceStrategy orgOrderServiceStrategy;
@Autowired
private ShopOrderStrategy shopOrderStrategy;
@Autowired
private ShopVoucherStrategy shopVoucherStrategy;
@Autowired
private YxOrderStrategy yxOrderStrategy;
public IOrderAlipayStrategy createOrderInstannce(PayType payType) {
IOrderAlipayStrategy orderAlipayStrategy = null;
switch (payType) { // TODO: 這里需要增加一個策略工廠類
case ORG_ORDER_SERVICE: // DONE
orderAlipayStrategy = orgOrderServiceStrategy;
break;
case ORG_ORDER_OBJ: // DONE
orderAlipayStrategy = orgOrderObjStrategy;
break;
case ORG_ORDER_REWAED: // DONE
orderAlipayStrategy = orgOrderRewardStrategy;
break;
case GROUPBUY_ORDER: // DONE
orderAlipayStrategy = groupbuyOrderStrategy;
break;
case SHOP_ORDER: // DONE
orderAlipayStrategy = shopOrderStrategy;
break;
case SHOP_VOUCHER: // DONE
orderAlipayStrategy = shopVoucherStrategy;
break;
case YX_ORDER: // DONE
orderAlipayStrategy = yxOrderStrategy;
break;
case PROPERTY_FEE:
// PropertyCostGd propertyCostGd = propertyCostsGdMapper.selPropertyCostGdByApplyId(orderId);
// OwnerAccountPropertyGd ownerAccountPropertyGd = getPropertyFee(alipayConfig, domainName, payOrderId, alipayUserId, propertyCostGd);
// payOrderId = String.join("-", PayType.PROPERTY_FEE.getCode(), ownerAccountPropertyGd.getNumber());
// tradeNo = ownerAccountPropertyGd.getPaymentPlatformBillCode();
// break;
case ACTIVITY_ORDER: // DONE
orderAlipayStrategy = activityOrderStrategy;
break;
default:
}
return orderAlipayStrategy;
}
}
- 有了工廠類黍氮,那么現(xiàn)在就能在service里調(diào)用了。
為了看出來區(qū)別浅浮,優(yōu)化前的那一大段switch case我保留下來了滤钱。
@Override
public String getPayInfo(AlipayConfig alipayConfig, String domainName, PayType payType, String orderId, String alipayUserId) throws Exception {
// switch (payType) { // TODO: 這里需要增加一個策略工廠類
// case ORG_ORDER_SERVICE: // DONE
// OrgOrder orgOrderService = orgOrderMapper.getById(Long.parseLong(orderId));
// payAmount = orgOrderService.getTotalServiceCostAmount();
// payOrderId = String.join("-", payType.getCode(), orgOrderService.getOrderNo());
// tradeNo = getOrgOrderServicePrepayId(alipayConfig, domainName, payOrderId, alipayUserId, orgOrderService);
// break;
// case ORG_ORDER_OBJ: // DONE
// OrgOrder orgOrderObj = orgOrderMapper.getById(Long.parseLong(orderId));
// payAmount = orgOrderObj.getObjCostAmount();
// payOrderId = String.join("-", payType.getCode(), orgOrderObj.getOrderNo());
// tradeNo = getOrgOrderObjPrepayId(alipayConfig, domainName, payOrderId, alipayUserId, orgOrderObj);
// break;
// case ORG_ORDER_REWAED: // DONE
// OrgOrder orgOrderReward = orgOrderMapper.getById(Long.parseLong(orderId));
// payAmount = orgOrderReward.getRewardAmount();
// payOrderId = String.join("-", payType.getCode(), orgOrderReward.getOrderNo(), Long.toString(System.currentTimeMillis()));
// tradeNo = getOrgOrderRewardPrepayId(alipayConfig, domainName, payOrderId, alipayUserId, orgOrderReward);
// break;
// case GROUPBUY_ORDER: // DONE
// PrivilegeGroupbuyingOrder privilegeGroupbuyingOrder = privilegeGroupbuyingOrderMapper.getById(Long.parseLong(orderId));
// payAmount = privilegeGroupbuyingOrder.getTotalAmount();
// payOrderId = String.join("-", payType.getCode(), privilegeGroupbuyingOrder.getNumber());
// tradeNo = getGroupbuyAlipayPrepayId(alipayConfig, domainName, payOrderId, alipayUserId, privilegeGroupbuyingOrder);
// break;
// case SHOP_ORDER: // DONE
// PrivilegeShopsOrder privilegeShopsOrder = privilegeShopsOrderMapper.getById(Long.parseLong(orderId));
// payAmount = privilegeShopsOrder.getTotalAmount();
// payOrderId = String.join("-", payType.getCode(), privilegeShopsOrder.getNumber());
// tradeNo = getShopOrderAlipayPrepayId(alipayConfig, domainName, payOrderId, alipayUserId, privilegeShopsOrder);
// break;
// case SHOP_VOUCHER: // DONE
// PrivilegeShopsVoucher privilegeShopsVoucher = privilegeShopsVoucherMapper.getById(Long.parseLong(orderId));
// payAmount = privilegeShopsVoucher.getTotalAmount();
// payOrderId = String.join("-", payType.getCode(), privilegeShopsVoucher.getNumber());
// tradeNo = getShopVoucherAlipayPrepayId(alipayConfig, domainName, payOrderId, alipayUserId, privilegeShopsVoucher);
// break;
// case YX_ORDER: // DONE
// YxOrder yxOrder = yxOrderMapper.getYxOrder(orderId);
// payAmount = yxOrder.getRealPrice();
// payOrderId = String.join("-", payType.getCode(), yxOrder.getOrderId());
// tradeNo = getYxOrderAlipayPrepayId(alipayConfig, domainName, payOrderId, alipayUserId, yxOrder);
// break;
// case PROPERTY_FEE:
//// PropertyCostGd propertyCostGd = propertyCostsGdMapper.selPropertyCostGdByApplyId(orderId);
//// OwnerAccountPropertyGd ownerAccountPropertyGd = getPropertyFee(alipayConfig, domainName, payOrderId, alipayUserId, propertyCostGd);
//// payOrderId = String.join("-", PayType.PROPERTY_FEE.getCode(), ownerAccountPropertyGd.getNumber());
//// tradeNo = ownerAccountPropertyGd.getPaymentPlatformBillCode();
//// break;
// case ACTIVITY_ORDER: // DONE
// ActivityOwner activityOwner = configurableActivityService.getActivityOwnerById(Long.parseLong(orderId));
// payAmount = activityOwner.getApplyMoney();
// payOrderId = String.join("-", PayType.ACTIVITY_ORDER.getCode(), activityOwner.getId().toString(), System.currentTimeMillis()+"");
// tradeNo = getActivitePrepayId(alipayConfig, domainName, payOrderId, alipayUserId, activityOwner);
// break;
// default:
// }
// 工廠根據(jù)payType創(chuàng)建并返回對應(yīng)的策略類。
IOrderAlipayStrategy orderAlipayStrategy = orderStrategyFactory.createOrderInstannce(payType);
// 策略類去執(zhí)行模版方法
OrderInfo orderInfo = orderAlipayStrategy.payThroughAlipay(alipayConfig, payType, orderId, alipayUserId);
savePayLog(payType, orderId, orderInfo.getPayOrderId(), PayChannel.ALI, orderInfo.getTradeNo(), alipayUserId, orderInfo.getTotalAmount());
return orderInfo.getTradeNo();
}
-
優(yōu)化完成脑题,看一下增加的類與接口
總結(jié)
對于大部分業(yè)務(wù)代碼件缸,遇到這種業(yè)務(wù)場景應(yīng)該也不少,而模版方法與策略模式的結(jié)合正是用來解決這種場景的利器叔遂,當(dāng)然他炊,當(dāng)策略特別多的時候也會導(dǎo)致其他問題争剿,比如類爆炸,不過那是另一個話題了痊末。
后記
以上的代碼蚕苇,基本上實現(xiàn)了目標(biāo),提取出通用代碼凿叠,各個策略類各司其職只專注于自身特性的業(yè)務(wù)代碼涩笤,通過工廠類產(chǎn)生需要的業(yè)務(wù)類對象。
但是盒件,有一個缺陷:工廠類蹬碧,工廠類通過switch case語句來產(chǎn)生策略類對象,如果訂單類型增加了炒刁,也就需要策略類恩沽,工廠也就需要增加對新策略類的支持。
這時就需要修改工廠類翔始,修改類容如下:
1. 增加新的策略類屬性罗心;
2. 增加case語句。
好的城瞎,問題來了:開閉原則被打破了渤闷!
如何繼續(xù)優(yōu)化這部分呢?請看《寫出優(yōu)雅的業(yè)務(wù)代碼(2):優(yōu)化掉工廠模式中的 switch case》