重識設計模式-策略模式(Strategy Pattern)

本文已同步發表到個人技術微信公衆號,掃一掃文章底部的二維碼或在微信搜索 「程序員驛站」便可關注,不按期更新優質技術文章。同時,也歡迎加入QQ技術羣(羣號:650306310)一塊兒交流學習!java

策略模式在代碼編寫過程當中有常用到,好比,JAVA AWT 中的 LayoutManager,Android屬性動畫源碼中的Interpolator(插值器)等,下面我將介紹策略模式相關知識點,但願對你們可以有幫助。程序員

定義

策略模式定義了一系列的算法,並將每個算法封裝起來,使每種算法之間能夠相互替換。策略模式主要解決在有多種算法類似的狀況下,使用if...else所帶來的複雜和難以維護的問題。一般是在一個系統有許多許多類,而區分它們的只是他們直接的行爲時使用。算法

角色

策略模式會涉及到三個角色,分別爲環境(Context)角色、抽象策略(Strategy)角色、具體策略(ConcreteStrategy)角色。bash

環境(Context)角色: 持有一個Strategy的引用;
抽象策略(Strategy)角色: 這是一個抽象角色,一般由一個接口或抽象類實現。此角色給出全部的具體策略類所需的接口;
具體策略(ConcreteStrategy)角色: 包裝了相關的算法或行爲。微信

案例回放

某院線公司須要開發一套影院售票系統,在該系統中須要爲不一樣類型的用戶提供不一樣的電影票打折方式,具體打折方案以下:
(1) 學生憑學生證可享受票價8折優惠;
(2) 年齡在10週歲及如下的兒童可享受每張票減免10元的優惠( 原始票價需大於等於20元) ;
(3) 影院VIP用戶除享受票價半價優惠外還可進行積分,積分累計到必定額度可換取電影院贈送的獎品。學習

Note:該系統在未來可能還要根據須要引入新的打折方式。測試

爲了實現打折算法的複用,並可以靈活地向系統中增長新的打折方式,這裏咱們使用策略模式做爲電影院打折方案的基礎結構再好不過了,使用策略模式以後基本結構以下:動畫

以上結構圖中MovieTicket充當環境類角色,Discount充當抽象策略角色,StudentDiscount、ChildrenDiscount 和VIPDiscount充當具體策略角色。下面是各個類的完整代碼:ui

MovieTicket.javathis

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 電影票類:環境類
 */
public class MovieTicket {
    private double price;
    /** 維持一個對抽象折扣類的引用 */
    private Discount discount;

    public void setPrice(double price) {
        this.price = price;
    }

    /**
     * 注入一個折扣類對象
     */
    public void setDiscount(Discount discount) {
        this.discount = discount;
    }

    public double getPrice() {
        //調用折扣類的折扣價計算方法
        return discount.calculate(this.price);
    }
}
複製代碼

Discount.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 折扣類:抽象策略類
 */
public interface Discount {
    double calculate(double price);
}
複製代碼

StudentDiscount.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 學生票折扣類:具體策略類
 */
public class StudentDiscount implements Discount {
    public double calculate(double price) {
        System.out.println("學生票:");
        return price * 0.8;
    }
}
複製代碼

ChildrenDiscount.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 兒童票折扣類:具體策略類
 */
public class ChildrenDiscount implements Discount {
    public double calculate(double price) {
        System.out.println("兒童票:");
        return price - 10;
    }
}
複製代碼

VIPDiscount.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * VIP會員票折扣類:具體策略類
 */
public class VIPDiscount implements Discount {
    public double calculate(double price) {
        System.out.println("VIP票:");
        System.out.println("增長積分!");
        return price * 0.5;
    }
}
複製代碼

編寫客戶端測試代碼:

Client.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 客戶端測試代碼
 */
public class Client {
    public static void main(String args[]) {
        double originalPrice = 60.0;
        double currentPrice;

        MovieTicket mt = new MovieTicket();
        mt.setPrice(originalPrice);

        System.out.println("原始價爲:" + originalPrice);
        System.out.println("---------------------------------");

        Discount discount = new StudentDiscount();
//        Discount discount = new ChildrenDiscount();
//        Discount discount = new VIPDiscount();
        
        mt.setDiscount(discount); //注入折扣對象
        currentPrice = mt.getPrice();
        System.out.println("折後價爲:" + currentPrice);
    }
}
複製代碼

編譯並運行程序,輸出結果以下:

原始價爲:60.0
---------------------------------
學生票:
折後價爲:48.0
複製代碼

若是須要更換具體策略類,例如將學生票改成兒童票,只需將具體策略類StudentDiscount改成ChildrenDiscount便可:

Client.java

/**
 * Created by chendx on 2019/3/15
 * @since 1.0
 * 客戶端測試代碼
 */
public class Client {
    public static void main(String args[]) {
        double originalPrice = 60.0;
        double currentPrice;

        MovieTicket mt = new MovieTicket();
        mt.setPrice(originalPrice);

        System.out.println("原始價爲:" + originalPrice);
        System.out.println("---------------------------------");

//        Discount discount = new StudentDiscount();
        Discount discount = new ChildrenDiscount();
//        Discount discount = new VIPDiscount();
        
        mt.setDiscount(discount); //注入折扣對象
        currentPrice = mt.getPrice();
        System.out.println("折後價爲:" + currentPrice);
    }
}
複製代碼

從新運行客戶端程序,輸出結果以下:

原始價爲:60.0
---------------------------------
兒童票:
折後價爲:50.0
複製代碼

若是須要增長新的打折方式,原有代碼均無須修改,只要增長一個新的折扣類做爲抽象折扣類的子類,實如今抽象折扣類中聲明的打折方法,將原有具體折扣類類名改成新增折扣類類名便可,徹底符合「開閉原則」,

典型應用

咱們常用到屬性動畫插值器就是策略模式的典型應用之一,這裏爲了節省篇幅,就不在分析屬性動畫插值器在源碼中的整個調用流程,屬性動畫插值器類圖以下:

優勢

1.策略模式提供了管理相關的算法族的辦法。策略類的等級結構定義了一個算法或行爲族。恰當使用繼承能夠把公共的代碼移到父類裏面,從而避免代碼重複;

2.使用策略模式能夠避免使用多重條件(if-else)語句,多重條件語句不易維護,它把採起哪種算法或採起哪種行爲的邏輯與算法或行爲的邏輯混合在一塊兒,通通列在一個多重條件語句裏面,比使用繼承的辦法還要原始和落後。

缺點

1.客戶端必須知道全部的策略類,並自行決定使用哪個策略類。這就意味着客戶端必須理解這些算法的區別,以便適時選擇恰當的算法類。換言之,策略模式只適用於客戶端知道算法或行爲的狀況。

2.因爲策略模式把每一個具體的策略實現都單獨封裝成爲類,若是備選的策略不少的話,那麼對象的數目就會很可觀。

重點

策略模式的重心不是如何實現算法,而是如何組織、調用這些算法,從而讓程序結構更靈活,具備更好的維護性和擴展性。

特色

運行時策略的惟一性: 運行期間,策略模式在每個時刻只能使用一個具體的策略實現對象,雖然能夠動態地在不一樣的策略實現中切換,可是同時只能使用一個。

平等性: 策略模式一個很大的特色就是各個策略算法的平等性。對於一系列具體的策略算法,你們的地位是徹底同樣的,正由於這個平等性,才能實現算法之間能夠相互替換。全部的策略算法在實現上也是相互獨立的,相互之間是沒有依賴的。

使用場景

一、若是在一個系統裏面有許多類,它們之間的區別僅在於它們的行爲,那麼使用策略模式能夠動態地讓一個對象在許多行爲中選擇一種行爲。

二、一個系統須要動態地在幾種算法中選擇一種。

三、若是一個對象有不少的行爲,若是不用恰當的模式,這些行爲就只好使用多重的條件選擇語句來實現。

關注個人技術公衆號"程序員驛站",天天都有優質技術文章推送,微信掃一掃下方二維碼便可關注:

image
相關文章
相關標籤/搜索