設計模式-委派/策略模式

1. 委派模式

1.1 委派模式的簡介

  • 委派模式不屬於 GOF23 種設計模式中。
  • 委派模式( Delegate Pattern )的基本作用就是負責任務的調用和分配任務,跟代理模式很像,可以看做是一種特殊情況下的靜態代理 的全權代理,但是代理模式注重過程,而委派模式注重結果。

1.2 委派模式的使用場景

  • 委派模式在 Spring 中應用非常多,大家常用的 DispatcherServlet 其實就是用到了委派模式。

  • 現實生活中也常有委 派的場景發生,例如:老闆(Boss)給項目經理(Leader)下達任務,項目經理會根據 實際情況給每個員工派發工作任務,待員工把工作任務完成之後,再由項目經理彙報工 作進度和結果給老闆。

1.3 場景實現

  • 上述工作中的場景是大家熟悉的,當 BossLeader 下發任務後, Leader 會根據實際情況來分配給響應的組員,我們將這一實際場景進行抽象化處理,用程式碼來進行實現

  • 首先我們要明確其中的關係,客戶請求(Boss)、委派者(Leader)、被委派者(Target) 在這個構建中 委派者與被委派者都服務與客戶請求,只是真實的操作時讓被委派者執行的,有點像靜態代理

  • 總體模型視圖如下:

  • 編寫 LeaderTarget 的共有父介面

public interface IEmployee {

    void doWork(String commd);
}

  • 編寫相應的實現類

編寫普通員工類:

public class EmployeeA implements IEmployee {
    @Override
    public void doWork(String commd) {
        System.out.println("EmployeeA 正在處理 "+commd +"任務");
    }
}
public class EmployeeB implements IEmployee {
    @Override
    public void doWork(String commd) {
        System.out.println("EmployeeB 正在處理 "+commd +"任務");

    }
}

編寫 Leader 實現:

public class Leader implements IEmployee {

    private static Map<String,IEmployee> handlerMapping = new HashMap<>();
    public Leader(){
        //初始化規則
        handlerMapping.put("Login",new EmployeeA());
        handlerMapping.put("Pay",new EmployeeB());
    }
    @Override
    public void doWork(String commd) {
        handlerMapping.get(commd).doWork(commd);
    }
}

在初始化 Leader 時我們首先將對應的規則記錄,也就是委派的規則,那些任務需要派給 A , 那麼任務需要派給 B ,後期的其他需求也是在這裡進行擴展

編寫 Boss 類:

/**
 * @author: anonystar
 * @time: 2020/5/27 16:48
 */
public class Boss {

    private Leader leader;
    
    public Boss(Leader leader){
        this.leader = leader;
    }

    public void command(String cmd) {
        //委派分發
        leader.doWork(cmd);
    }
}

測試程式碼:

/**
 * @author: anonystar
 * @time: 2020/5/28 9:40
 */
public class SimpleDelegateTest {

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

1.4 小結

  • 我們通過上面程式碼可以發現委派模式就是靜態代理和策略模式一種特殊的組合

  • 代理模式注重的是過程, 委派模式更注重的是結果

  • 委派者要持有被委派者的引用

  • 委派的核心:就是分發、調度、派遣


2. 策略模式

2.1 策略模式簡介

  • 策略模式是一種行為設計模式, 它能讓你定義一系列演算法, 並將每種演算法分別放入獨立的類中, 以使演算法的對象能夠相互替換。

  • 此模式讓演算法的變化不會影響到使用演算法的用戶

2.2 場景適用

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

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

2.3 場景模擬

2.3.1 場景問題提出

前提:

  • 假設你為旅遊者們設計了一款導遊程式。 該程式的核心功能是提供美觀的地圖, 以幫助用戶在任何城市中快速定位。

  • 用戶期待的程式新功能是自動路線規劃: 他們希望輸入地址後就能在地圖上看到前往目的地的最快路線。

  • 程式的首個版本只能規劃公路路線,這滿足了駕車旅行的人們的需求,但是也很明顯的會忽略其他選擇,所以你需要在一次次的迭代中增加新的規劃線路方案,如增加步行線路、公共交通線路等等。

  • 你以為這樣就夠了?這只是個開始,沒多久時間你又要為騎行者規劃路線。 又過了一段時間, 你又要為遊覽城市中的所有景點規劃路線。此時相信面對不斷臃腫的程式碼已經苦不堪言了,每次都的改動大量的程式碼

實際問題:

  • 每次線路的增加都讓整個開發團隊非常頭痛,因為每次增加新的線路規劃後整個程式碼中的主體類都會增加一倍,慢慢的整個團都都無法繼續維護這大量凌亂的程式碼

  • 當在使用過程中暴露出缺陷和某些功能的微調時,那麼對當前的修改都會影響到整個線路規劃,同時增加了程式運行中的其他風險

  • 越到後期團隊合作將變得越低效。 尤其在後期招募了新的團隊成員,他們需要大量的時間來熟悉和適應這些內容,同時在各種版本合併中掙扎。在實現新功能的過程中, 你的團隊需要修改同一個巨大的類, 這樣他們所編寫的程式碼相互之間就可能會出現衝突。

2.3.2 解決方案

  • 策略模式建議找出負責用許多不同方式完成特定任務的類, 然後將其中的演算法抽取到一組被稱為策略的獨立類中。

名為上下文的原始類必須包含一個成員變數來存儲對於每種策略的引用。 上下文並不執行任務, 而是將工作委派給已連接的策略對象。

上下文不負責選擇符合任務需要的演算法——客戶端會將所需策略傳遞給上下文。 實際上, 上下文並不十分了解策略, 它會通過同樣的通用介面與所有策略進行交互, 而該介面只需暴露一個方法來觸發所選策略中封裝的演算法即可。

因此, 上下文可獨立於具體策略。 這樣你就可在不修改上下文程式碼或其他策略的情況下添加新演算法或修改已有演算法了。

2.4 程式碼實現

  • 構建路線頂級介面
/**
 * 路線介面
 * @author: anonystar
 * @url: i-code.online
 * @time: 2020/6/8 16:51
 */
public interface Route {

     String ROUTE_WALK = "walk";
     String ROUTE_CAR = "car";
     String ROUTE_CYCLING = "cycling";

    public void doRoute();
}
  • 實現具體線路方式 如步行線路、駕車線路、騎行線路等,均實現 Route 介面
/**
 * 駕車線路
 * @author: anonystar
 * @url: i-code.online
 * @time: 2020/6/8 16:58
 */
public class CarRoute implements Route {
    @Override
    public void doRoute() {
        System.out.println("======== 駕車線路 start =========");
    }
}
/**
 * 騎行線路
 * @author: anonystar
 * @url: i-code.online
 * @time: 2020/6/8 17:01
 */
public class CyclingRoute implements Route {
    @Override
    public void doRoute() {
        System.out.println("======== 騎行線路 start =========");
    }
}
/**
 * 步行線路
 * @author: anonystar
 * @url: i-code.online
 * @time: 2020/6/8 17:02
 */
public class WalkRoute implements Route {
    @Override
    public void doRoute() {
        System.out.println("======== 步行線路 start =========");

    }
}
  • 構建路線的上下文,作為對外使用的唯一入口,調用所有的策略均從這裡使用
package org.strategy.travel;

/**
 * 構建路線 上下文
 * @author: anonystar
 * @url: i-code.online
 * @time: 2020/6/9 14:45
 */
public class RouteContext {

    // 上下文會維護指向某個策略對象的引用。上下文不知曉策略的具體類。
    // 上下文必須通過策略介面來與所有策略進行交互。
    private Route route;

    // 上下文通常會通過構造函數來接收策略對象,
    // 同時還提供設置器以便在運行時切換策略。
    public RouteContext(Route route){
        this.route = route;
    }
    public void setRoute(Route route) {
        this.route = route;
    }

    // 上下文會將一些工作委派給策略對象,而不是自行實現不同版本的演算法。
    public void execute(){
        route.doRoute();
    }
}

  • 測試程式碼
    public void travle3(){
        String cmd = "walk";

        RouteContext route = null;
        if (cmd.equals(Route.ROUTE_WALK)){
            route = new RouteContext(new WalkRoute());
        }else if (cmd.equals(Route.ROUTE_CAR)){
            route = new RouteContext( new CarRoute());
        }
        route.execute();
    }

上面程式碼我們會發現如果有很多策略時,那麼會造成大量的if語句,這裡我們可以使用工廠模式來進行簡化,可以看我們之前的文章在i-code.online

  • 我們構建一個工廠來簡化創建
package org.strategy.travel;

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

/**
 * 獲取上下文工廠
 * @author: anonystar
 * @url: i-code.online
 * @time: 2020/6/8 17:22
 */
public class RouteContextFactory {

    private static Map<String,Route> routeMap = new HashMap<>();

    private RouteContextFactory(){

    }

    static {
        routeMap.put(Route.ROUTE_CAR,new CarRoute());
        routeMap.put(Route.ROUTE_WALK,new WalkRoute());
        routeMap.put(Route.ROUTE_CYCLING,new CyclingRoute());
    }

    public static RouteContext getRoute(String cmd){
        Route route = routeMap.get(cmd);
        if ( null == route){
            route = routeMap.get(Route.ROUTE_CAR);
        }
        return new RouteContext(route);
    }
}

  • 測試程式碼
 /**
     * 通過工廠方法來簡化
     */
    public static void travle4(){
        String cmd = "car";
        RouteContext route = RouteContextFactory.getRoute(cmd);
        route.execute();
    }

2.5 使用場景

  • 當你想使用對象中各種不同的演算法變體, 並希望能在運行時切換演算法時, 可使用策略模式。

策略模式讓你能夠將對象關聯至可以不同方式執行特定子任務的不同子對象, 從而以間接方式在運行時更改對象行為。

  • 當你有許多僅在執行某些行為時略有不同的相似類時, 可使用策略模式。

策略模式讓你能將不同行為抽取到一個獨立類層次結構中, 並將原始類組合成同一個, 從而減少重複程式碼。

  • 如果演算法在上下文的邏輯中不是特別重要, 使用該模式能將類的業務邏輯與其演算法實現細節隔離開來。

策略模式讓你能將各種演算法的程式碼、 內部數據和依賴關係與其他程式碼隔離開來。 不同客戶端可通過一個簡單介面執行演算法, 並能在運行時進行切換。

  • 當類中使用了複雜條件運算符以在同一演算法的不同變體中切換時, 可使用該模式。

策略模式將所有繼承自同樣介面的演算法抽取到獨立類中, 因此不再需要條件語句。 原始對象並不實現所有演算法的變體, 而是將執行工作委派給其中的一個獨立演算法對象。

2.6 策略模式的優缺點

2.6.1 優點:

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

2.6.2 缺點:

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

本文由AnonyStar 發布,可轉載但需聲明原文出處。
仰慕「優雅編碼的藝術」 堅信熟能生巧,努力改變人生
歡迎關注微信公帳號 :雲棲簡碼 獲取更多優質文章
更多文章關注筆者部落格 :雲棲簡碼

Tags: