設(shè)計(jì)模式-委派/策略模式

1. 委派模式

1.1 委派模式的簡(jiǎn)介

  • 委派模式不屬于 GOF23 種設(shè)計(jì)模式中。
  • 委派模式( Delegate Pattern )的基本作用就是負(fù)責(zé)任務(wù)的調(diào)用和分配任務(wù),跟代理模式很像,可以看做是一種特殊情況下的靜態(tài)代理 的全權(quán)代理,但是代理模式注重過程,而委派模式注重結(jié)果。

1.2 委派模式的使用場(chǎng)景

  • 委派模式在 Spring 中應(yīng)用非常多,大家常用的 DispatcherServlet 其實(shí)就是用到了委派模式。

  • 現(xiàn)實(shí)生活中也常有委 派的場(chǎng)景發(fā)生,例如:老板(Boss)給項(xiàng)目經(jīng)理(Leader)下達(dá)任務(wù),項(xiàng)目經(jīng)理會(huì)根據(jù) 實(shí)際情況給每個(gè)員工派發(fā)工作任務(wù),待員工把工作任務(wù)完成之后,再由項(xiàng)目經(jīng)理匯報(bào)工 作進(jìn)度和結(jié)果給老板。

1.3 場(chǎng)景實(shí)現(xiàn)

  • 上述工作中的場(chǎng)景是大家熟悉的,當(dāng) BossLeader 下發(fā)任務(wù)后, Leader 會(huì)根據(jù)實(shí)際情況來分配給響應(yīng)的組員,我們將這一實(shí)際場(chǎng)景進(jìn)行抽象化處理,用代碼來進(jìn)行實(shí)現(xiàn)

  • 首先我們要明確其中的關(guān)系,客戶請(qǐng)求(Boss)、委派者(Leader)、被委派者(Target) 在這個(gè)構(gòu)建中 委派者與被委派者都服務(wù)與客戶請(qǐng)求,只是真實(shí)的操作時(shí)讓被委派者執(zhí)行的,有點(diǎn)像靜態(tài)代理

  • 總體模型視圖如下:

  • 編寫 LeaderTarget 的共有父接口

public interface IEmployee {

    void doWork(String commd);
}

  • 編寫相應(yīng)的實(shí)現(xiàn)類

編寫普通員工類:

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

    }
}

編寫 Leader 實(shí)現(xiàn):

public class Leader implements IEmployee {

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

在初始化 Leader 時(shí)我們首先將對(duì)應(yīng)的規(guī)則記錄,也就是委派的規(guī)則,那些任務(wù)需要派給 A , 那么任務(wù)需要派給 B ,后期的其他需求也是在這里進(jìn)行擴(kuò)展

編寫 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) {
        //委派分發(fā)
        leader.doWork(cmd);
    }
}

測(cè)試代碼:

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

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

1.4 小結(jié)

  • 我們通過上面代碼可以發(fā)現(xiàn)委派模式就是靜態(tài)代理和策略模式一種特殊的組合

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

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

  • 委派的核心:就是分發(fā)、調(diào)度、派遣


2. 策略模式

2.1 策略模式簡(jiǎn)介

  • 策略模式是一種行為設(shè)計(jì)模式, 它能讓你定義一系列算法, 并將每種算法分別放入獨(dú)立的類中, 以使算法的對(duì)象能夠相互替換。

  • 此模式讓算法的變化不會(huì)影響到使用算法的用戶

2.2 場(chǎng)景適用

  • 1、假如系統(tǒng)中有很多類,而他們的區(qū)別僅僅在于他們的行為不同。

  • 2、一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種。

2.3 場(chǎng)景模擬

2.3.1 場(chǎng)景問題提出

前提:

  • 假設(shè)你為旅游者們?cè)O(shè)計(jì)了一款導(dǎo)游程序。 該程序的核心功能是提供美觀的地圖, 以幫助用戶在任何城市中快速定位。

  • 用戶期待的程序新功能是自動(dòng)路線規(guī)劃: 他們希望輸入地址后就能在地圖上看到前往目的地的最快路線。

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

  • 你以為這樣就夠了?這只是個(gè)開始,沒多久時(shí)間你又要為騎行者規(guī)劃路線。 又過了一段時(shí)間, 你又要為游覽城市中的所有景點(diǎn)規(guī)劃路線。此時(shí)相信面對(duì)不斷臃腫的代碼已經(jīng)苦不堪言了,每次都的改動(dòng)大量的代碼

實(shí)際問題:

  • 每次線路的增加都讓整個(gè)開發(fā)團(tuán)隊(duì)非常頭痛,因?yàn)槊看卧黾有碌木€路規(guī)劃后整個(gè)代碼中的主體類都會(huì)增加一倍,慢慢的整個(gè)團(tuán)都都無法繼續(xù)維護(hù)這大量凌亂的代碼

  • 當(dāng)在使用過程中暴露出缺陷和某些功能的微調(diào)時(shí),那么對(duì)當(dāng)前的修改都會(huì)影響到整個(gè)線路規(guī)劃,同時(shí)增加了程序運(yùn)行中的其他風(fēng)險(xiǎn)

  • 越到后期團(tuán)隊(duì)合作將變得越低效。 尤其在后期招募了新的團(tuán)隊(duì)成員,他們需要大量的時(shí)間來熟悉和適應(yīng)這些內(nèi)容,同時(shí)在各種版本合并中掙扎。在實(shí)現(xiàn)新功能的過程中, 你的團(tuán)隊(duì)需要修改同一個(gè)巨大的類, 這樣他們所編寫的代碼相互之間就可能會(huì)出現(xiàn)沖突。

2.3.2 解決方案

  • 策略模式建議找出負(fù)責(zé)用許多不同方式完成特定任務(wù)的類, 然后將其中的算法抽取到一組被稱為策略的獨(dú)立類中。

名為上下文的原始類必須包含一個(gè)成員變量來存儲(chǔ)對(duì)于每種策略的引用。 上下文并不執(zhí)行任務(wù), 而是將工作委派給已連接的策略對(duì)象。

上下文不負(fù)責(zé)選擇符合任務(wù)需要的算法——客戶端會(huì)將所需策略傳遞給上下文。 實(shí)際上, 上下文并不十分了解策略, 它會(huì)通過同樣的通用接口與所有策略進(jìn)行交互, 而該接口只需暴露一個(gè)方法來觸發(fā)所選策略中封裝的算法即可。

因此, 上下文可獨(dú)立于具體策略。 這樣你就可在不修改上下文代碼或其他策略的情況下添加新算法或修改已有算法了。

2.4 代碼實(shí)現(xiàn)

  • 構(gòu)建路線頂級(jí)接口
/**
 * 路線接口
 * @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();
}
  • 實(shí)現(xiàn)具體線路方式 如步行線路、駕車線路、騎行線路等,均實(shí)現(xiàn) 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 =========");

    }
}
  • 構(gòu)建路線的上下文,作為對(duì)外使用的唯一入口,調(diào)用所有的策略均從這里使用
package org.strategy.travel;

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

    // 上下文會(huì)維護(hù)指向某個(gè)策略對(duì)象的引用。上下文不知曉策略的具體類。
    // 上下文必須通過策略接口來與所有策略進(jìn)行交互。
    private Route route;

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

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

  • 測(cè)試代碼
    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();
    }

上面代碼我們會(huì)發(fā)現(xiàn)如果有很多策略時(shí),那么會(huì)造成大量的if語句,這里我們可以使用工廠模式來進(jìn)行簡(jiǎn)化,可以看我們之前的文章在i-code.online

  • 我們構(gòu)建一個(gè)工廠來簡(jiǎn)化創(chuàng)建
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);
    }
}

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

2.5 使用場(chǎng)景

  • 當(dāng)你想使用對(duì)象中各種不同的算法變體, 并希望能在運(yùn)行時(shí)切換算法時(shí), 可使用策略模式。

策略模式讓你能夠?qū)?duì)象關(guān)聯(lián)至可以不同方式執(zhí)行特定子任務(wù)的不同子對(duì)象, 從而以間接方式在運(yùn)行時(shí)更改對(duì)象行為。

  • 當(dāng)你有許多僅在執(zhí)行某些行為時(shí)略有不同的相似類時(shí), 可使用策略模式。

策略模式讓你能將不同行為抽取到一個(gè)獨(dú)立類層次結(jié)構(gòu)中, 并將原始類組合成同一個(gè), 從而減少重復(fù)代碼。

  • 如果算法在上下文的邏輯中不是特別重要, 使用該模式能將類的業(yè)務(wù)邏輯與其算法實(shí)現(xiàn)細(xì)節(jié)隔離開來。

策略模式讓你能將各種算法的代碼、 內(nèi)部數(shù)據(jù)和依賴關(guān)系與其他代碼隔離開來。 不同客戶端可通過一個(gè)簡(jiǎn)單接口執(zhí)行算法, 并能在運(yùn)行時(shí)進(jìn)行切換。

  • 當(dāng)類中使用了復(fù)雜條件運(yùn)算符以在同一算法的不同變體中切換時(shí), 可使用該模式。

策略模式將所有繼承自同樣接口的算法抽取到獨(dú)立類中, 因此不再需要條件語句。 原始對(duì)象并不實(shí)現(xiàn)所有算法的變體, 而是將執(zhí)行工作委派給其中的一個(gè)獨(dú)立算法對(duì)象。

2.6 策略模式的優(yōu)缺點(diǎn)

2.6.1 優(yōu)點(diǎn):

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

2.6.2 缺點(diǎn):

  • 1、客戶端必須知道所有的策略,并且自行決定使用哪一個(gè)策略類。
  • 2、代碼中會(huì)產(chǎn)生非常多策略類,增加維護(hù)難度

本文由AnonyStar 發(fā)布,可轉(zhuǎn)載但需聲明原文出處。
仰慕「優(yōu)雅編碼的藝術(shù)」 堅(jiān)信熟能生巧,努力改變?nèi)松?br> 歡迎關(guān)注微信公賬號(hào) :云棲簡(jiǎn)碼 獲取更多優(yōu)質(zhì)文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容