1 背景
某天早上,Skr郎正在一邊悠哉的喫着加了三個雞蛋的手抓餅,一邊悠閒地逛着論壇,看着沙雕網友的帖子,Skr郎會心一笑,正欲給沙雕帖子點贊,郵件忽的彈出,Skr郎慢悠悠的打開郵件
任務: 對接阿里支付接口
時限: 一週
要求如下:
1. 單筆限額不超過1W
2. 可以成功提現
3. 發起提現後,可以主動查詢提現結果
Skr郎心中暗喜,看我兩天搞定,剩下的時間就可以嘿嘿嘿了
/**
* 阿里業務處理
*
* @author gp6
* @date 2020/3/13
*/
public class AlibabaHandler {
void singleQuota() {
System.out.println("阿里校驗單筆限額");
}
void pay() {
System.out.println("阿里支付邏輯");
}
void getResult() {
System.out.println("阿里主動查詢支付結果");
}
}
/**
* 支付服務類
*
* @author gp6
* @date 2020/3/13
*/
public class PayService {
public static void main(String[] args) {
// 支付接口(第一個方法)
AlibabaHandler alibabaHandler = new AlibabaHandler();
alibabaHandler.singleQuota();
alibabaHandler.pay();
// 查詢接口(第二個方法)
AlibabaHandler alibabaHandler1 = new AlibabaHandler();
alibabaHandler1.getResult();
}
}
Skr郎看着完成的代碼,心中評價道:這段代碼,邏輯嚴謹,註釋清晰,優雅中透露着灑脫,灑脫中透露着不羈,完美!
正洋洋自得間,項目經理喊道:Skr郎,你過來一下!Skr郎趕緊屁顛屁顛的跑過去.
項目經理:臨時加個需求,把微信支付也接進來,根據前臺傳的渠道,使用不同的支付方式,時間不加.
Skr郎:好的,沒問題(MMP)
/**
* 微信業務處理
*
* @author gp6
* @date 2020/3/13
*/
public class WechatHandler {
void singleQuota() {
System.out.println("微信校驗單筆限額");
}
void pay() {
System.out.println("微信支付邏輯");
}
void getResult() {
System.out.println("微信主動查詢支付結果");
}
}
/**
* 支付服務類
*
* @author gp6
* @date 2020/3/13
*/
public class PayService {
public static void main(String[] args) {
// 前臺傳的渠道 1:阿里 2:微信
String channel = "1";
// 支付邏輯(第一個方法)
if ("1".equals(channel)) {
// 阿里支付接口
AlibabaHandler alibabaHandler = new AlibabaHandler();
alibabaHandler.singleQuota();
alibabaHandler.pay();
} else if ("2".equals(channel)) {
// 微信支付接口
WechatHandler wechatHandler = new WechatHandler();
wechatHandler.singleQuota();
wechatHandler.pay();
}
// 查詢接口(第二個方法)
if ("1".equals(channel)) {
// 阿里查詢
AlibabaHandler alibabaHandler = new AlibabaHandler();
alibabaHandler.getResult();
} else if ("2".equals(channel)) {
// 微信查詢
WechatHandler wechatHandler = new WechatHandler();
wechatHandler.getResult();
}
}
}
Skr郎看着完成的代碼,心中評價道:這段代碼,邏輯嚴謹,註釋清晰,優雅中透露着灑脫,灑脫中透露着不羈,雖然沒有上一版優雅,但還是比較不錯的!
正洋洋自得間,又聽項目經理喊道:Skr郎,你過來一下!Skr郎心裏想着如果這次加需求不加時間,老子堅決不做!
項目經理:臨時在加個需求,把通聯和Ping++支付也接進來,根據前臺傳的渠道,使用不同的支付方式,時間不加.
Skr郎:好的,沒問題(心裏問候一下他的族中長輩)
/**
* Ping++業務處理
*
* @author gp6
* @date 2020/3/13
*/
public class PingHandler {
void singleQuota() {
System.out.println("Ping++校驗單筆限額");
}
void pay() {
System.out.println("Ping++支付邏輯");
}
void getResult() {
System.out.println("Ping++主動查詢支付結果");
}
}
/**
* 通聯業務處理
*
* @author gp6
* @date 2020/3/13
*/
public class AllinpayHandler {
void singleQuota() {
System.out.println("通聯校驗單筆限額");
}
void pay() {
System.out.println("通聯支付邏輯");
}
void getResult() {
System.out.println("通聯主動查詢支付結果");
}
}
/**
* 支付服務類
*
* @author gp6
* @date 2020/3/13
*/
public class PayService {
public static void main(String[] args) {
// 前臺傳的渠道 1:阿里 2:微信 3:Ping++ 4:通聯
String channel = "1";
// 支付邏輯(第一個方法)
if ("1".equals(channel)) {
// 阿里支付接口
AlibabaHandler alibabaHandler = new AlibabaHandler();
alibabaHandler.singleQuota();
alibabaHandler.pay();
} else if ("2".equals(channel)) {
// 微信支付接口
WechatHandler wechatHandler = new WechatHandler();
wechatHandler.singleQuota();
wechatHandler.pay();
}else if ("3".equals(channel)) {
// Ping++支付接口
PingHandler pingHandler = new PingHandler();
pingHandler.singleQuota();
pingHandler.pay();
}else if ("4".equals(channel)) {
// 通聯支付接口
AllinpayHandler allinpayHandler = new AllinpayHandler();
allinpayHandler.singleQuota();
allinpayHandler.pay();
}
// 查詢接口(第二個方法)
if ("1".equals(channel)) {
// 阿里查詢
AlibabaHandler alibabaHandler = new AlibabaHandler();
alibabaHandler.getResult();
} else if ("2".equals(channel)) {
// 微信查詢
WechatHandler wechatHandler = new WechatHandler();
wechatHandler.getResult();
}else if ("3".equals(channel)) {
// Ping++查詢
PingHandler pingHandler = new PingHandler();
pingHandler.getResult();
}else if ("4".equals(channel)) {
// 通聯查詢
AllinpayHandler allinpayHandler = new AllinpayHandler();
allinpayHandler.getResult();
}
}
}
Skr郎看着完成的代碼,心中評價道:這段代碼,邏輯嚴謹,註釋清晰…我呸,這寫的什麼鬼東西!看着長長的if else,Skr郎陷入沉思…
如果後面再加新的渠道,是不是每次都要改動PayService中的調用邏輯,而且如果有別的方法要用此段邏輯,if else 又要複製一遍
腦中過濾着以往所學的設計模式…突然,靈光一閃,策略模式
2 策略模式
定義:
定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的變化不會影響使用算法的客戶
看了策略模式的定義,是不是很符合這種場景
定義了一系列算法(支付邏輯),並將每個算法(支付邏輯)封裝起來,使它們可以相互替換(根據不同channel切換不同的支付邏輯),且算法的變化不會影響使用算法的客戶(PayService)
簡直完美,Skr郎沾沾自喜
/**
* 支付的抽象策略類
* 抽象策略(Strategy)類:定義了一個公共接口,各種不同的算法以不同的方式實現這個接口,環境角色使用這個接口調用不同的算法,一般使用接口或抽象類實現
*
* @author gp6
* @date 2020/3/13
*/
public interface PayHandler {
/**
* 單筆限額
*/
void singleQuota();
/**
* 支付
*/
void pay();
/**
* 提現結果查詢
*/
void getResult();
}
/**
* 微信業務處理
* 具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現
*
* @author gp6
* @date 2020/3/13
*/
public class WechatHandler implements PayHandler {
public void singleQuota() {
System.out.println("微信校驗單筆限額");
}
public void pay() {
System.out.println("微信支付邏輯");
}
public void getResult() {
System.out.println("微信主動查詢支付結果");
}
}
/**
* 阿里業務處理
* 具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現
*
* @author gp6
* @date 2020/3/13
*/
public class AlibabaHandler implements PayHandler {
public void singleQuota() {
System.out.println("阿里校驗單筆限額");
}
public void pay() {
System.out.println("阿里支付邏輯");
}
public void getResult() {
System.out.println("阿里主動查詢支付結果");
}
}
通聯與Ping++代碼類似
/**
* 環境(Context)類:持有一個策略類的引用,最終給客戶端調用
*
* @author gp6
* @date 2020/3/13
*/
public class Context {
private PayHandler payHandler;
public Context(PayHandler payHandler){
this.payHandler = payHandler;
}
/**
* 單筆限額
*/
void singleQuota() {
payHandler.singleQuota();
}
/**
* 支付
*/
void pay() {
payHandler.pay();
}
/**
* 提現結果查詢
*/
void getResult() {
payHandler.getResult();
}
}
/**
* 支付服務類
*
* @author gp6
* @date 2020/3/13
*/
public class PayService {
public static void main(String[] args) {
// 前臺傳的渠道 1:阿里 2:微信 3:Ping++ 4:通聯
Integer channel = 1;
// 支付邏輯(第一個方法)
Context context;
switch (channel) {
case 1:
context = new Context(new AlibabaHandler());
break;
case 2:
context = new Context(new WechatHandler());
break;
case 3:
context = new Context(new PingHandler());
break;
case 4:
context = new Context(new AllinpayHandler());
break;
default:
throw new RuntimeException("渠道不支持!");
}
context.singleQuota();
context.pay();
// 查詢接口(第二個方法)
Context context2;
switch (channel) {
case 1:
context2 = new Context(new AlibabaHandler());
break;
case 2:
context2 = new Context(new WechatHandler());
break;
case 3:
context2 = new Context(new PingHandler());
break;
case 4:
context2 = new Context(new AllinpayHandler());
break;
default:
throw new RuntimeException("渠道不支持!");
}
context2.getResult();
}
}
使用了策略模式,但是調用者(PayService)中,還是有重複的大量的if else
根據不用的渠道創建不同的具體策略類(new AlibabaHandler()),這不是典型的工廠模式嗎…
3 策略模式結合工廠模式
將工廠模式放入策略環境中
/**
* 環境(Context)類:持有一個策略類的引用,最終給客戶端調用
*
* @author gp6
* @date 2020/3/13
*/
public class Context {
private PayHandler payHandler;
public Context(Integer channel){
switch (channel) {
case 1:
payHandler = new AlibabaHandler();
break;
case 2:
payHandler = new WechatHandler();
break;
case 3:
payHandler = new PingHandler();
break;
case 4:
payHandler = new AllinpayHandler();
break;
default:
throw new RuntimeException("渠道不支持!");
}
}
/**
* 單筆限額
*/
void singleQuota() {
payHandler.singleQuota();
}
/**
* 支付
*/
void pay() {
payHandler.pay();
}
/**
* 提現結果查詢
*/
void getResult() {
payHandler.getResult();
}
}
/**
* 支付服務類
*
* @author gp6
* @date 2020/3/13
*/
public class PayService {
public static void main(String[] args) {
// 前臺傳的渠道 1:阿里 2:微信 3:Ping++ 4:通聯
Integer channel = 1;
// 支付邏輯(第一個方法)
Context context = new Context(channel);
context.singleQuota();
context.pay();
// 查詢接口(第二個方法)
Context context2 = new Context(channel);
context2.getResult();
}
}
此時如果再添加渠道,只需在Context的構造方法的判斷邏輯中添加新的渠道,然後在相應的支付邏輯類中寫入相應邏輯,只在策略環境中存在if else,調用者無需寫重複的if else
switch (channel) {
case 1:
payHandler = new AlibabaHandler();
break;
case 2:
payHandler = new WechatHandler();
break;
case 3:
payHandler = new PingHandler();
break;
case 4:
payHandler = new AllinpayHandler();
break;
// 新的渠道
case 5:
payHandler = new XXXHandler();
break;
default:
throw new RuntimeException("渠道不支持!");
}
/**
* 新渠道的業務邏輯處理
* 具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現
*
* @author gp6
* @date 2020/3/13
*/
public class XXXHandler implements PayHandler{
.....
}
寫到此處Skr郎欣喜的點點頭,暗自得意,正打算將代碼提交,忽然眉頭一皺,每次添加新的渠道,都要修改判斷邏輯,不是違背了 開閉原則 嗎
Skr再次沉思
4 使用反射遵循開閉原則
/**
* 環境(Context)類:持有一個策略類的引用,最終給客戶端調用
*
* @author gp6
* @date 2020/3/13
*/
public class Context {
private PayHandler payHandler;
public Context(PayEnum payEnum){
try {
payHandler= (PayHandler) Class.forName(payEnum.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException("渠道錯誤!");
}
}
/**
* 單筆限額
*/
void singleQuota() {
payHandler.singleQuota();
}
/**
* 支付
*/
void pay() {
payHandler.pay();
}
/**
* 提現結果查詢
*/
void getResult() {
payHandler.getResult();
}
}
/**
* 策略枚舉
*
* @author gp6
* @date 2020/3/13
*/
public enum PayEnum {
/**
* 渠道對應類全路徑
*/
OPERATION_ADD("com.gp6.pay.AlibabaHandler", 1),
OPERATION_SUB("com.gp6.pay.WechatHandler", 2),
OPERATION_MUL("com.gp6.pay.PingHandler", 3);
PayEnum(String className, Integer channel) {
this.className = className;
this.channel = channel;
}
/**
* 類的全路徑
*/
private String className;
/**
* 渠道
*/
private Integer channel;
/**
* 匹配渠道
*
* @param channel 渠道
* @return 相關枚舉
*/
public static PayEnum matchChannel(Integer channel) {
for (PayEnum payEnum : PayEnum.values()) {
if (payEnum.getChannel().equals(channel)) {
return payEnum;
}
}
throw new RuntimeException("渠道不支持!");
}
public String getClassName() {
return className;
}
public Integer getChannel() {
return channel;
}
}
/**
* 支付服務類
*
* @author gp6
* @date 2020/3/13
*/
public class PayService {
public static void main(String[] args) {
// 前臺傳的渠道 1:阿里 2:微信 3:Ping++ 4:通聯
Integer channel = 1;
// 支付邏輯(第一個方法)
Context context = new Context(PayEnum.matchChannel(channel));
context.singleQuota();
context.pay();
// 查詢接口(第二個方法)
Context context2 = new Context(PayEnum.matchChannel(channel));
context2.getResult();
}
}
此時,如果想添加通聯的渠道只需在PayEnum中添加
OPERATION_MUL("com.gp6.pay.AllinpayHandler", 4)
然後在com.gp6.pay.AllinpayHandler中撰寫相應邏輯即可
Skr郎寫到此處,心滿意足的點點頭,心中評價道:這段代碼,邏輯嚴謹,註釋清晰,優雅中透露着灑脫,灑脫中透露着不羈,真是"此碼只應天上有,人間難得幾回尋!"
呀! 想不到我作詩的功夫也如此深厚,哎,如此完美的男人,別人只能羨慕嫉妒恨!
5 策略模式類圖
StrategyContext相當於文章中的Context
StrategyOperation相當於文章中的PayHandle
StrategyOperationAdd與StrategyOperationMul相當於文章中的AlibabaHandler/AllinpayHandler/PingHandler/WechatHandler