Java設計模式-策略模式分析

挫敗de小馬哥發表於2020-10-05

提示:文章寫完後,目錄可以自動生成,如何生成可參考右邊的幫助文件


描述


博文簡介

通過對策略模式的學習,消除程式中大量的冗餘程式碼和多重條件轉移語句。

策略模式的定義及應用場景

策略模式的定義
策略模式(Strategy Pattern)是指定義了演算法家族、分別封裝起來,讓它們之間可以互 相替換,此模式讓演算法的變化不會影響到使用演算法的使用者。

策略模式的應用場景
1、假如系統中有很多類,而他們的區別僅僅在於他們的行為不同。
2、一個系統需要動態地在幾種演算法中選擇一種。

策略模式實現

大家都知道,網路上的各種培訓課程經常會有優惠活動,優惠策略會有很多種可能如:領取優惠券抵扣、返現促銷、拼團優惠。下面我們用程式碼來模擬這種場景。
首先我們建立一 個促銷策略的抽象PromotionStrategy:

/*** 促銷策略抽象 */ 
public interface PromotionStrategy { 
	void doPromotion(); 
}

然後分別建立優惠券抵扣策略 CouponStrategy 類、返現促銷策略 CashbackStrategy 類、拼團優惠策略 GroupbuyStrategy 類和無優惠策略 EmptyStrategy 類。
CouponStrategy 類:

/*** 優惠券 **/ 
public class CouponStrategy implements PromotionStrategy { 
	public void doPromotion() { 
		System.out.println("領取優惠券,課程的價格直接減優惠券面值抵扣"); 
	} 
}

CashbackStrategy 類:

/*** 返現活動 */ 
public class CashbackStrategy implements PromotionStrategy { 
	public void doPromotion() { 
		System.out.println("返現促銷,返回的金額轉到支付寶賬號"); 
	} 
}

GroupbuyStrategy 類:

/*** 拼團優惠 */ 
public class GroupbuyStrategy implements PromotionStrategy{ 
	public void doPromotion() { 
		System.out.println("拼團,滿 20 人成團,全團享受團購價"); 
	} 
}

EmptyStrategy 類:

/*** 無優惠 **/ 
public class EmptyStrategy implements PromotionStrategy { 
	public void doPromotion() { 
		System.out.println("無促銷活動"); 
	} 
}

然後建立促銷活動方案 PromotionActivity 類:

/*** 優惠活動 **/ 
public class PromotionActivity { 
	private PromotionStrategy promotionStrategy;
	public PromotionActivity(PromotionStrategy promotionStrategy) { 
		this.promotionStrategy = promotionStrategy; 
	}
	public void execute(){ 
		promotionStrategy.doPromotion(); 
	} 
}

編寫客戶端測試類:

public static void main(String[] args) { 
	PromotionActivity activity1 = new PromotionActivity(new CouponStrategy());
	PromotionActivity activity2 = new PromotionActivity(new CashbackStrategy()); 
	activity1.execute(); 
	activity2.execute(); 
}

此時,同學們會發現,如果把上面這段測試程式碼放到實際的業務場景其實會被罵。 因為我們做活動時候往往是要根據不同的需求對促銷策略進行動態選擇的,並不會一次性執行多種優惠。所以,我們的程式碼通常會這樣寫:

public static void main(String[] args) { 
	PromotionActivity promotionActivity = null; 
	String promotionKey = "COUPON"; 
	if(StringUtils.equals(promotionKey,"COUPON")){ 
		promotionActivity = new PromotionActivity(new CouponStrategy()); 
	}else if(StringUtils.equals(promotionKey,"CASHBACK")){ 
		promotionActivity = new PromotionActivity(new CashbackStrategy()); 
	}
	//此處偷懶...... 
	promotionActivity.execute(); 
}

這樣改造之後,滿足了業務需求,客戶可根據自己的需求選擇不同的優惠策略了。但是, 經過一段時間的業務積累,我們的促銷活動會越來越多。於是,我們的程式猿天天加班凌晨都很吃力了,每次上活動之前都要通宵改程式碼,而且要做重複測試,判斷邏輯可能也變得越來越複雜,梳理起來也變得非常棘手。這時候,我們就需要思考程式碼應該重構了,具體怎麼操作呢,最終結合經驗我們發現可以結合單例模式和工廠模式。
建立 PromotionStrategyFactory 類:

/*** 促銷策略工廠 **/ 
public class PromotionStrategyFactory { 
	private static Map<String,PromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<String, PromotionStrategy>(); 
	static { 
		PROMOTION_STRATEGY_MAP.put(PromotionKey.COUPON,new CouponStrategy()); 
		PROMOTION_STRATEGY_MAP.put(PromotionKey.CASHBACK,new CashbackStrategy()); 
		PROMOTION_STRATEGY_MAP.put(PromotionKey.GROUPBUY,new GroupbuyStrategy()); 
	}
	private static final PromotionStrategy NON_PROMOTION = new EmptyStrategy(); 
	private PromotionStrategyFactory(){} 
	public static PromotionStrategy getPromotionStrategy(String promotionKey){ 
		PromotionStrategy promotionStrategy = PROMOTION_STRATEGY_MAP.get(promotionKey); 
		return promotionStrategy == null ? NON_PROMOTION : promotionStrategy; 
	}
	private interface PromotionKey{ 
		String COUPON = "COUPON"; 
		String CASHBACK = "CASHBACK"; 
		String GROUPBUY = "GROUPBUY"; 
	} 
}

這時候我們客戶端程式碼就應該這樣寫:

public static void main(String[] args) { 
	String promotionKey = "GROUPBUY"; 
	PromotionActivity promotionActivity = new PromotionActivity(PromotionStrategyFactory.getPromotionStrategy(promotionKey)); 
	promotionActivity.execute(); 
}

程式碼優化之後,是不是我們作為加班猿的概率就又降低了一點,每次上新活動,不影響原來的程式碼邏輯。效率大大的提高了。

選擇支付方式的業務場景

為了加深對策略模式的理解,我們再來舉一個案例。相信小夥伴們都 用過支付寶、微信支付、銀聯支付以及京東白條。一個常見的應用場景就是大家在下單 支付時會提示選擇支付方式,如果使用者未選,系統也會預設好推薦的支付方式進行結算。 下面我們用策略模式來模擬此業務場景:
建立 Payment 抽象類,定義支付規範和支付邏輯,程式碼如下:

/*** 支付渠道 **/ 
public abstract class Payment { 
	//支付型別 
	public abstract String getName(); 
	//查詢餘額 
	protected abstract double queryBalance(String uid); 
	//扣款支付 
	public PayState pay(String uid,double amount) { 
		if(queryBalance(uid) < amount){ 
			return new PayState(500,"支付失敗","餘額不足"); 
		}
		return new PayState(200,"支付成功","支付金額:" + amount); 
	} 
}

分別建立具體的支付方式
京東白條 JDPay 類:

public class JDPay extends Payment { 
	public String getName() { 
		return "京東白條"; 
	}
	protected double queryBalance(String uid) { 
		return 600; 
	} 
}

支付寶 AliPay 類:

public class AliPay extends Payment { 
	public String getName() { 
		return "支付寶"; 
	}
	protected double queryBalance(String uid) { 
		return 1000; 
	} 
}

微信支付 WechatPay 類:

public class WechatPay extends Payment { 
	public String getName() { 
		return "微信支付"; 
	}
	protected double queryBalance(String uid) { 
		return 300; 
	} 
}

銀聯支付 UnionPay 類:

public class UnionPay extends Payment { 
	public String getName() { 
		return "銀聯支付"; 
	}
	protected double queryBalance(String uid) { 
		return 150; 
	} 
}

建立支付狀態的包裝類 PayState:

/*** 支付完成以後的狀態 **/ 
public class PayState { 
	private int code; 
	private Object data; 
	private String msg; 
	
	public PayState(int code, String msg,Object data) { 
		this.code = code; 
		this.data = data; 
		this.msg = msg; 
	} 
	
	public String toString(){ 
		return ("支付狀態:[" + code + "]," + msg + ",交易詳情:" + data); 
	} 
}

建立支付策略管理類PayStrategy:

/*** 支付策略管理 **/ 
public class PayStrategy { 
	public static final String ALI_PAY = "AliPay"; 
	public static final String JD_PAY = "JdPay"; 
	public static final String UNION_PAY = "UnionPay"; 
	public static final String WECHAT_PAY = "WechatPay"; 
	public static final String DEFAULT_PAY = ALI_PAY; 
	private static Map<String,Payment> payStrategy = new HashMap<String,Payment>(); 
	
	static { 
		payStrategy.put(ALI_PAY,new AliPay()); 
		payStrategy.put(WECHAT_PAY,new WechatPay()); 
		payStrategy.put(UNION_PAY,new UnionPay()); 
		payStrategy.put(JD_PAY,new JDPay()); 
	}
	
	public static Payment get(String payKey){ 
		if(!payStrategy.containsKey(payKey)){ 
			return payStrategy.get(DEFAULT_PAY); 
		}
		return payStrategy.get(payKey); 
	} 
}

建立訂單 Order 類:

public class Order { 
	private String uid; 
	private String orderId; 
	private double amount; 
	public Order(String uid,String orderId,double amount){ 
		this.uid = uid; 
		this.orderId = orderId; 
		this.amount = amount; 
	}
	//完美地解決了 switch 的過程,不需要在程式碼邏輯中寫 switch 了 
	//更不需要寫 if else if 
	public PayState pay(){ 
		return pay(PayStrategy.DEFAULT_PAY); 
	}
	public PayState pay(String payKey){ 
		Payment payment = PayStrategy.get(payKey); 
		System.out.println("歡迎使用" + payment.getName()); 
		System.out.println("本次交易金額為:" + amount + ",開始扣款..."); 
		return payment.pay(uid,amount); 
	} 
}

編寫測試程式碼:

public class PayStrategyTest { 
	public static void main(String[] args) { 
	//省略把商品新增到購物車,再從購物車下單 
	//直接從點單開始 
	Order order = new Order("1","20201005001000009",300.99); 
	//開始支付,選擇微信支付、支付寶、銀聯卡、京東白條、財付通 
	//每個渠道它支付的具體演算法是不一樣的
	//基本演算法固定的 
	//這個值是在支付的時候才決定用哪個值 
	System.out.println(order.pay(PayStrategy.ALI_PAY)); 
	} 
}

這裡通過大家比較熟悉的業務場景來舉例,讓同學們更深刻地理解策略模式。希望大家在以後的工作體現出自己的優勢。

策略模式在 JDK 原始碼中的體現

首先來看一個比較常用的比較器 Comparator 介面,我們看到的一個大家常用的 compare()方法,就是一個策略抽象實現:

public interface Comparator<T> { 
	int compare(T o1, T o2); 
	
	... 

}

Comparator抽象下面有非常多的實現類,我們經常會把Comparator作為引數傳入作為排序策略,例如 Arrays類的parallelSort方法等:

public class Arrays { 

	... 
	
	public static <T> void parallelSort(T[] a, int fromIndex, int toIndex, Comparator<? super T> cmp) { 
	
	... 
	
	}
	
	... 

}

還有TreeMap的構造方法:

public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable { 

	...
	
	public TreeMap(Comparator<? super K> comparator) { 
		this.comparator = comparator; 
	}
	
	... 

}

策略模式的優缺點

優點
1、策略模式符合開閉原則。
2、避免使用多重條件轉移語句,如 if…else…語句、switch 語句
3、使用策略模式可以提高演算法的保密性和安全性。
缺點
1、客戶端必須知道所有的策略,並且自行決定使用哪一個策略類。
2、程式碼中會產生非常多策略類,增加維護難度。

相關文章