Spring5設計模式 - 策略模式
設計模式 - 策略模式
策略模式:它定義了演算法的家族,分別封裝起來,讓他們之前可以相互替換,此模式讓演算法的改變,不會影響到演算法的客戶。
適用場景:
-
假如系統中有很多類,而他們的區別僅僅在於他們的行為不同.
-
一個系統需要動態地在幾種演算法中選擇一種.
一、用策略模式實現選擇支付方式的業務場景
在商城系統中,常常存在多種策略的優惠活動,比如拼團、返現促銷、優惠券抵扣。下面是一個模擬這個場景的demo:
促銷策略的介面 PromotionStrategy:
public interface PromotionStrategy {
void doPromotion ();
}
優惠券抵扣策略 CouponStrategy:
public class CouponStrategy implements PromotionStrategy {
@Override
public void doPromotion() {
System.out.println("領取優惠券,課程價格直接抵扣優惠券面值");
}
}
返現促銷策略 CashbackStrategy:
public class CashbackStrategy implements PromotionStrategy {
@Override
public void doPromotion() {
System.out.println("返現促銷,返回現金到支付寶賬戶");
}
}
拼團策略 GroupBuyStrategy:
public class GroupBuyStrategy implements PromotionStrategy {
@Override
public void doPromotion() {
System.out.println("拼團,滿20人,全團享受團價");
}
}
當然還有一個無優惠活動策略 EmptyStrategy:
public class EmptyStrategy implements PromotionStrategy {
@Override
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 class PromotionTest {
public static void main(String[] args) {
PromotionActivity activity618 = new PromotionActivity(new CashbackStrategy());
PromotionActivity activity1111 = new PromotionActivity(new CouponStrategy());
activity618.execute();
activity1111.execute();
}
}
從測試類來看,一次開啟了多項優惠活動,但我們通常並不會一次性執行多種優惠,所以進行改造:
public class PromotionTest {
public static void main(String[] args) {
PromotionActivity activity = null;
String promotionKey = "COUPON";
if ("COUPON".equals(promotionKey)) {
activity = new PromotionActivity(new CouponStrategy());
} else if ("CASHBACK".equals(promotionKey)) {
activity = new PromotionActivity(new CashbackStrategy());
} else {
activity = new PromotionActivity(new EmptyStrategy());
}
activity.execute();
}
}
這樣就滿足了業務需求,但是隨著優惠活動的增多,判斷邏輯越來越複雜,編寫好活動程式碼後需要重複測試,會大大增加工作量。那麼我們可以結合工廠模式和單例模式來對此進行優化:
新增活動策略工廠類 PromotionStrategyFactory :
public class PromotionStrategyFactory {
private static final Map<String, PromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<>();
private static final PromotionStrategy NON_PROMOTION = new EmptyStrategy();
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 interface PromotionKey {
String COUPON = "COUPON";
String CASHBACK = "CASHBACK";
String GROUPBUY = "GROUPBUY";
}
public PromotionStrategyFactory() {
}
public static PromotionStrategy getPromotionStrategy(String promotionKey) {
PromotionStrategy promotionStrategy = PROMOTION_STRATEGY_MAP.get(promotionKey);
return promotionStrategy == null ? NON_PROMOTION : promotionStrategy;
}
}
測試類
public class PromotionTest {
public static void main(String[] args) {
String promotionKey = "COUPON";
PromotionStrategy strategy = PromotionStrategyFactory.getPromotionStrategy(promotionKey);
PromotionActivity activity = new PromotionActivity(strategy);
activity.execute();
}
}
如此一優化,直接刪除了邏輯判斷,每次新增活動時都會輕鬆很多。再舉一個支付的案例,如果對接過銀聯、百度收銀臺、京東、QQ、支付寶、微信等支付的朋友看了下面這個案例,絕對深有體會。
二、支付案例
所有的支付方式都有 名稱、支付、查詢餘額等行為,抽取成一個支付方式的抽象類 Payment:
public abstract class Payment {
public abstract String getName();
//通用邏輯放到抽象類裡面實現
public R pay(String uid, double amount) {
//餘額是否足夠
if (queryBalance(uid) < amount) {
return new R(500, "支付失敗", "餘額不足");
}
return new R(200, "支付成功", "支付金額" + amount);
}
protected abstract double queryBalance(String uid);
}
R 是一個統一返回類
public class R {
private int code;
private Object data;
private String msg;
public R(int code, String msg, Object data) {
this.code = code;
this.data = data;
this.msg = msg;
}
@Override
public String toString() {
return "{" +
"code=" + code +
", data=" + data +
", msg='" + msg + '\'' +
'}';
}
}
AliPay
public class AliPay extends Payment {
public String getName() {
return "支付寶";
}
protected double queryBalance(String uid) {
return 900;
}
}
JDPay
public class JDPay extends Payment {
public String getName() {
return "京東白條";
}
protected double queryBalance(String uid) {
return 500;
}
}
UnionPay
public class UnionPay extends Payment {
@Override
public String getName() {
return "銀聯支付";
}
@Override
protected double queryBalance(String uid) {
return 120;
}
}
WeChatPay
public class WeChatPay extends Payment {
@Override
public String getName() {
return "微信支付";
}
@Override
protected double queryBalance(String uid) {
return 263;
}
}
訂單類 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;
}
public R pay() {
return pay(PayStrategy.DEFAULT_PAY);
}
// 完美解決 if...else 或者 switch
public R pay(String payKey) {
Payment payment = PayStrategy.get(payKey);
System.out.println("歡迎使用" + payment.getName());
System.out.println("本次交易金額為" + amount + ",開始扣款");
return payment.pay(uid, amount);
}
}
測試類 Test
public class PayTest {
public static void main(String[] args) {
Order order = new Order("1", "123456789123", 325);
R result = order.pay(PayStrategy.ALI_PAY);
System.out.println(result);
}
}
三、優缺點
優點:
1、策略模式符合開閉原則。
2、避免使用多重條件轉移語句,如if…else…語句、switch 語句
3、使用策略模式可以提高演算法的保密性和安全性。
缺點:
1、客戶端必須知道所有的策略,並且自行決定使用哪一個策略類。
2、程式碼中會產生非常多策略類,增加維護難度。
四、簡單實現Spring中的 DispatcherServlet
我們常用的兩種兩種架構風格:REST和RPC,REST API 的每個URI對應的都是某一份資源,在我們通過Spring的Controller的URI進行訪問資源時,會經過DispatchServlet分發給對應的Servlet。在J2EE的標準中,每一個URL對應一個Servlet。
會員控制器
public class MemberController {
public Object getMemberById(int id) {
return null;
}
}
訂單控制器:
public class OrderController {
public Object getOrderById(int id) {
return null;
}
}
DispatcherServlet
/**
* SpringMVC的委派模式就是通過DispatcherServlet將請求的URI轉為不同的java程式碼
*/
public class DispatcherServlet extends HttpServlet {
private List<Handler> handlerMapping = new LinkedList<>();
@Override
public void init() throws ServletException {
try {
handlerMapping.add(new Handler().setUri("/web/getMemberById").setController(MemberController.class.newInstance()).setMethod(MemberController.class.getMethod("getMemberById", int.class)));
handlerMapping.add(new Handler().setUri("/web/getOrderById").setController(OrderController.class.newInstance()).setMethod(OrderController.class.getMethod("getOrderById", int.class)));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatcher(req, resp);
}
/**
* 使用策略模式去掉多個if-else判斷
*/
private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
String requestURI = req.getRequestURI();
Handler h = null;
for (Handler handler : handlerMapping) {
if (Objects.equals(handler.getUri(), requestURI)) {
h = handler;
break;
}
}
try {
h.getMethod().invoke(h.getController(), Integer.parseInt(req.getParameter("mid")));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
class Handler {
String uri;
Object controller;
Method method;
public String getUri() {
return uri;
}
// 返回this是函數語言程式設計中的鏈式
public Handler setUri(String uri) {
this.uri = uri;
return this;
}
public Object getController() {
return controller;
}
public Handler setController(Object controller) {
this.controller = controller;
return this;
}
public Method getMethod() {
return method;
}
public Handler setMethod(Method method) {
this.method = method;
return this;
}
}
}