大型Java進階專題(七) 設計模式之委派模式與策略模式

前言

​ 今天開始我們專題的第七課了。本章節將介紹:你寫的程式碼中是否覺得很臃腫,程式中有大量的if…else,想優化程式碼,精簡程式邏輯,提升程式碼的可讀性,這章節將介紹如何通過委派模式、策略模式讓你程式碼更優雅,消除程式大量冗餘的程式碼。本章節參考資料書籍《Spring 5核心原理》中的第一篇 Spring 內功心法(Spring中常用的設計模式)(沒有電子檔,都是我取其精華並結合自己的理解,一個字一個字手敲出來的)。

委派模式

委派模式的定義及應用場景

​ 委派模式不屬於GOF23種設計模式中。委派模式(Delegate Pattern)的基本作用就是負責任務的調用和分配任務,跟代理模式很像,可以看做是一種特殊情況下的靜態代理的全權代理,但是代理模式注重過程,而委派模式注重結果。委派模式在Spring中應用非常多,大家常用的DispatcherServlet其實就是用到了委派模式。現實生活中也常有委派的場景發生,例如:老闆(Boss)給項目經理(Leader)下達任務,項目經理會根據實際情況給每個員工派發工作任務,待員工把工作任務完成之後,再由項目經理彙報工作進度和結果給老闆。我們用程式碼來模擬下這個業務場景,先來看一下類圖:

創建員工介面

package com.study.demo2;

/**
 * @ClassName IEmployee
 * @Deacription 員工介面
 * @Author wang.zhong.yuan
 * @Date 2020/7/9 16:36
 * @Version 1.0
 **/
public interface IEmployee {

    /**
     * 需要做的工作
     * @param command
     */
    void doingSomeThing(String command);
}

員工A實現類

package com.study.demo2;

/**
 * @ClassName EmployeeA
 * @Deacription 員工A
 * @Author wang.zhong.yuan
 * @Date 2020/7/9 16:38
 * @Version 1.0
 **/
public class EmployeeA implements IEmployee {

    public void doingSomeThing(String command) {
        System.out.println("我是員工A,需要做:"+command);
    }
}

員工B實現類

package com.study.demo2;

/**
 * @ClassName EmployeeB
 * @Deacription 員工B
 * @Author wang.zhong.yuan
 * @Date 2020/7/9 16:48
 * @Version 1.0
 **/
public class EmployeeB implements IEmployee {

    public void doingSomeThing(String command) {
        System.out.println("我是員工B,我要做:"+command);
    }
}

領導類

package com.study.demo2;

import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName Leader
 * @Deacription 領導類
 * @Author wang.zhong.yuan
 * @Date 2020/7/9 16:51
 * @Version 1.0
 **/
public class Leader implements IEmployee {

    private Map<String,IEmployee> underling = new HashMap<String, IEmployee>();

    public Leader() {
        underling.put("註冊功能",new EmployeeA());
        underling.put("登陸功能",new EmployeeB());
    }

    /**
     * 自己不做事,委派給對應的員工去做
     * @param command
     */
    public void doingSomeThing(String command) {
        underling.get(command).doingSomeThing(command);
    }
}

創建BOSS類,安排工作給Leader

package com.study.demo2;

/**
 * @ClassName Boss
 * @Deacription TODO
 * @Author 19054253
 * @Date 2020/7/9 17:03
 * @Version 1.0
 **/
public class Boss {

    public void command(String command, Leader leader){
        leader.doingSomeThing(command);
    }
	//測試功能
    public static void main(String[] args) {
        //客戶請求(Boss)、委派者(Leader)、被被委派者(Target)
        //委派者要持有被委派者的引用
        //代理模式注重的是過程, 委派模式注重的是結果
        //策略模式注重是可擴展(外部擴展),委派模式注重內部的靈活和復用
        //委派的核心:就是分發、調度、派遣
        //委派模式:就是靜態代理和策略模式一種特殊的組合
        Boss boss = new Boss();
        Leader leader = new Leader();
        boss.command("登陸功能",leader);
    }
}

輸出結果

通過上面的程式碼,生動地還原了項目經理分配工作的業務場景,也是委派模式的生動體現。

策略模式

策略模式的定義與應用

策略模式(Strategy Pattern)是指定義了演算法家族、分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化不會影響到使用演算法的用戶。假如系統中有很多類,而他們的區別僅僅在於他們的行為不同。一個系統需要動態地在幾種演算法中選擇一種,都可以用策略模式實現。

程式碼實現

一個常見的應用場景就是大家在下單支付時會提示選擇支付方式,如果用戶未選,系統也會默認好推薦的支付方式進行結算。來看一下類圖,下面我們用策略模式來模擬此業務場景:

創建支付狀態類:

package com.study.demo3;

/**
 * @ClassName PayState
 * @Deacription 支付狀態
 * @Author wang
 * @Date 2020/7/9 17:48
 * @Version 1.0
 **/
public class PayState {
    private int code;
    private Object data;
    private String message;

    public PayState(int code, Object data, String message) {
        this.code = code;
        this.data = data;
        this.message = message;
    }

    @Override
    public String toString() {
        return "PayState{" +
                "code=" + code +
                ", data=" + data +
                ", message='" + message + '\'' +
                '}';
    }
}

創建支付介面

package com.study.demo3;

/**
 * @ClassName Payment
 * @Deacription 支付渠道
 * @Author wang
 * @Date 2020/7/9 19:12
 * @Version 1.0
 **/
public interface IPayment {

    /**
     * 獲取支付類型
     * @return
     */
    String getName();

    /**
     * 查詢餘額
     * @param uid
     * @return
     */
    double queryBalance(String uid);

    /**
     * 支付
     * @param uid
     * @param amount
     * @return
     */
    PayState pay(String uid,double amount);

}

創建支付抽象類,完成一些通用的程式碼:

package com.study.demo3;

/**
 * @ClassName AbstractPayment
 * @Deacription 支付抽象類
 * @Author wang
 * @Date 2020/7/9 19:16
 * @Version 1.0
 **/
public abstract class AbstractPayment implements IPayment{

    public PayState pay(String uid, double amount) {
        if (queryBalance(uid) < amount){
            return new PayState(-1,"支付失敗","餘額不足");
        }
        return new PayState(200,"支付成功","共計支付:"+amount);
    }
}

創建阿里支付渠道

package com.study.demo3;

/**
 * @ClassName AliPay
 * @Deacription 支付寶支付
 * @Author wang
 * @Date 2020/7/9 19:20
 * @Version 1.0
 **/
public class AliPay extends AbstractPayment {

    public String getName() {
        return "支付寶支付";
    }

    public double queryBalance(String uid) {
        return 500;
    }
}

創建京東支付渠道

package com.study.demo3;

/**
 * @ClassName JDPay
 * @Deacription 京東支付
 * @Author wang
 * @Date 2020/7/9 19:22
 * @Version 1.0
 **/
public class JDPay extends AbstractPayment {
    public String getName() {
        return "京東支付";
    }

    public double queryBalance(String uid) {
        return 900;
    }
}

創建微信支付渠道

package com.study.demo3;

/**
 * @ClassName WXPay
 * @Deacription 微信支付
 * @Author wang
 * @Date 2020/7/9 19:23
 * @Version 1.0
 **/
public class WXPay extends AbstractPayment {
    public String getName() {
        return "微信支付";
    }

    public double queryBalance(String uid) {
        return 256;
    }
}
package com.study.demo3;

/**
 * @ClassName Order
 * @Deacription 訂單類
 * @Author wang
 * @Date 2020/7/9 19:35
 * @Version 1.0
 **/
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){
        IPayment payment = PayStrategy.get(payKey);
        System.out.println("歡迎使用" + payment.getName());
        System.out.println("本次交易金額為:" + amount + ",開始扣款...");
        return payment.pay(uid,amount);
    }

    //測試程式碼
    public static void main(String[] args) {
        Order order = new Order("123", "AB123", 400);
        //這個值是在支付的時候才決定用哪個值 用戶自己決定
        order.pay("AliPay");
    }
}

輸出結果:

希望通過大家耳熟能詳的業務場景來舉例,讓小夥伴們更深刻地理解策略模式。

小結

策略模式的優缺點

優點:
1、策略模式符合開閉原則。
2、避免使用多重條件轉移語句,如if…else…語句、switch語句
3、使用策略模式可以提高演算法的保密性和安全性。

缺點:
1、客戶端必須知道所有的策略,並且自行決定使用哪一個策略類。
2、程式碼中會產生非常多策略類,增加維護難度。