1. 程式人生 > 其它 >Spring5設計模式 - 策略模式

Spring5設計模式 - 策略模式

技術標籤:設計模式Spring

設計模式 - 策略模式

策略模式:它定義了演算法的家族,分別封裝起來,讓他們之前可以相互替換,此模式讓演算法的改變,不會影響到演算法的客戶。

適用場景:

  1. 假如系統中有很多類,而他們的區別僅僅在於他們的行為不同.

  2. 一個系統需要動態地在幾種演算法中選擇一種.

一、用策略模式實現選擇支付方式的業務場景

在商城系統中,常常存在多種策略的優惠活動,比如拼團、返現促銷、優惠券抵扣。下面是一個模擬這個場景的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();
    }
}

優惠策略UML

從測試類來看,一次開啟了多項優惠活動,但我們通常並不會一次性執行多種優惠,所以進行改造:

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;
        }
    }
}