java 設計模式:策略模式

概念:

策略模式定義了一系列的算法,並將每一個算法封裝起來,而且使它們還可以相互替換,策略模式讓算法獨立於使用它的客戶而獨立變化。

策略模式使這些算法在客戶端調用它們的時候能夠互不影響地變化。

使用場景:

一個類定義了多種行爲,並且這個行爲在這個類的方法中以多個條件語句形式出現,那麼可以使用策略模式避免在類中使用大量的條件語句。

UML:

代碼展示:

爲了更清晰的展示出策略模式的優點,我在此寫一套不用策略模式實現的代碼。如下:

/**
 * 有如下幾個超市價格規則 1:五月一日 價格統統8折 2:十月一日 價格統統7折 3:十一月一日 價格統統半價
 * Created by on 2019/4/30.
 */
public class Price {

       public double jisuanPrice(double price,String s){
        double p = price;
        System.out.println(s);
        switch (s){
            case "五月一日":
                p = price*0.8;
                break;
            case "十月一日":
                p = price*0.7;
                break;
            case "十一月一日":
                p = price*0.5;
                break;
        }
        return p;
    }

}
//運行
Price p = new Price();
System.out.println(p.jisuanPrice(10),"五月一日");

你會發現,也很清晰表示了判斷不同的日期來計算不同的價格,不同日期用switch來判斷,但是呢,如果日期很多,而且規則又有了對特定商品的價格規則,那麼這個類的負擔是否有些複雜了呢?這個類不是單一職責。還根據swith來計算規則。

於是我寫下了如下的策略方法:

/**
 * 半價計算策略
 */
public class Banjia implements IPrice {
    @Override
    public double jisuanPrice(double price) {
        return price*0.5;
    }
}
/**
 * 8折計算策略
 */
public class BaZhe  implements IPrice {
    @Override
    public double jisuanPrice(double price) {
        return price*0.8;
    }
}
/**
 * 正常計算策略
 */
public class NorPrice implements IPrice {
    @Override
    public double jisuanPrice(double price) {
        return price;
    }
}

/**
 * 7折計算
 */
public class Qizhe implements IPrice {
    @Override
    public double jisuanPrice(double price) {
        return price*0.7;
    }
}
//作爲價格管理器一定要持有IPrice的引用
public class Price{

    private IPrice iPrice;

    public void setiPrice(IPrice iPrice) {
        this.iPrice = iPrice;
    }

    public double jisuanPrice(double price) {
        return iPrice.jisuanPrice(price);
    }
}
//運行
//1.創建具體測策略實現
IPrice iprice = new BaZhe();
//2.在創建策略上下文的同時,將具體的策略實現對象注入到策略上下文當中
Price p = new Price();
p.setiPrice(iprice);
 //3.調用上下文對象的方法來完成對具體策略實現的回調
System.out.println(p.jisuanPrice(10));
        

這個能夠明顯是對價格做了分類,假如說十月一日要進行半價打折,那你是不是很容易就改變了策略呢?不需要動其他代碼。

策略模式優點

1、上下文Context 和具體策略器(oncreteStrategy)是鬆耦合關係。

2、滿足開-閉原則。增加新的具體策略不需要修改context。

開閉原則:

  1. 對於擴展是開放的(Open for extension)。這意味着模塊的行爲是可以擴展的。當應用的需求改變時,我們可以對模塊進行擴展,使其具有滿足那些改變的新行爲。也就是說,我們可以改變模塊的功能。
  2. 對於修改是關閉的(Closed for modification)。對模塊行爲進行擴展時,不必改動模塊的源代碼或者二進制代碼。

策略和上下文的關係:

  • 在策略模式中,一般情況下都是上下文持有策略的引用,以進行對具體策略的調用。但具體的策略對象也可以從上下文中獲取所需數據,可以將上下文當做參數傳入到具體策略中,具體策略通過回調上下文中的方法來獲取其所需要的數據。

如下例子來自 https://www.cnblogs.com/lewis0077/p/5133812.html 非常好的一篇文章

**下面我們演示這種情況:**

  在跨國公司中,一般都會在各個國家和地區設置分支機構,聘用當地人爲員工,這樣就有這樣一個需要:每月發工資的時候,中國國籍的員工要發人民幣,美國國籍的員工要發美元,英國國籍的要發英鎊。
public interface PayStrategy {
    //在支付策略接口的支付方法中含有支付上下文作爲參數,以便在具體的支付策略中回調上下文中的方法獲取數據
     public void pay(PayContext ctx);
 }

//人民幣支付策略
public class RMBPay implements PayStrategy {
    @Override
    public void pay(PayContext ctx) {
        System.out.println("現在給:"+ctx.getUsername()+" 人民幣支付 "+ctx.getMoney()+"元!");
    }
}

//美金支付策略
public class DollarPay implements PayStrategy {
    @Override
    public void pay(PayContext ctx) {
        System.out.println("現在給:"+ctx.getUsername()+" 美金支付 "+ctx.getMoney()+"dollar !");
    }
}

//支付上下文,含有多個算法的公有數據
public class PayContext {
    //員工姓名
    private String username;
    //員工的工資
    private double money;
    //支付策略
    private PayStrategy payStrategy;

    public void pay(){
        //調用具體的支付策略來進行支付
        payStrategy.pay(this);
    }

    public PayContext(String username, double money, PayStrategy payStrategy) {
        this.username = username;
        this.money = money;
        this.payStrategy = payStrategy;
    }

    public String getUsername() {
        return username;
    }

    public double getMoney() {
        return money;
    }
}

//外部客戶端
public class Client {
    public static void main(String[] args) {
        //創建具體的支付策略
        PayStrategy rmbStrategy = new RMBPay();
        PayStrategy dollarStrategy = new DollarPay();
        //準備小王的支付上下文
        PayContext ctx = new PayContext("小王",30000,rmbStrategy);
        //向小王支付工資
        ctx.pay();

        //準備Jack的支付上下文
        ctx = new PayContext("jack",10000,dollarStrategy);
        //向Jack支付工資
        ctx.pay();
    }
}
控制檯輸出:

現在給:小王 人民幣支付 30000.0元!
現在給:jack 美金支付 10000.0dollar !

那現在我們要新增一個銀行賬戶的支付策略,該怎麼辦呢?

  顯然我們應該新增一個支付找銀行賬戶的策略實現,由於需要從上下文中獲取數據,爲了不修改已有的上下文,我們可以通過繼承已有的上下文來擴展一個新的帶有銀行賬戶的上下文,然後再客戶端中使用新的策略實現和帶有銀行賬戶的上下文,這樣之前已有的實現完全不需要改動,遵守了開閉原則。

//銀行賬戶支付
public class AccountPay implements PayStrategy {
    @Override
    public void pay(PayContext ctx) {
        PayContextWithAccount ctxAccount = (PayContextWithAccount) ctx;
        System.out.println("現在給:"+ctxAccount.getUsername()+"的賬戶:"+ctxAccount.getAccount()+" 支付工資:"+ctxAccount.getMoney()+" 元!");
    }
}

//帶銀行賬戶的支付上下文
public class PayContextWithAccount extends PayContext {
    //銀行賬戶
    private String account;
    public PayContextWithAccount(String username, double money, PayStrategy payStrategy,String account) {
        super(username, money, payStrategy);
        this.account = account;
    }

    public String getAccount() {
        return account;
    }
}

//外部客戶端
public class Client {
    public static void main(String[] args) {
        //創建具體的支付策略
        PayStrategy rmbStrategy = new RMBPay();
        PayStrategy dollarStrategy = new DollarPay();
        //準備小王的支付上下文
        PayContext ctx = new PayContext("小王",30000,rmbStrategy);
        //向小王支付工資
        ctx.pay();
        //準備Jack的支付上下文
        ctx = new PayContext("jack",10000,dollarStrategy);
        //向Jack支付工資
        ctx.pay();
        //創建支付到銀行賬戶的支付策略
        PayStrategy accountStrategy = new AccountPay();
        //準備帶有銀行賬戶的上下文
        ctx = new PayContextWithAccount("小張",40000,accountStrategy,"1234567890");
        //向小張的賬戶支付
        ctx.pay();
    }
}

控制檯輸出:

現在給:小王 人民幣支付 30000.0元!
現在給:jack 美金支付 10000.0dollar !
現在給:小張的賬戶:1234567890 支付工資:40000.0 元!

除了上面的方法,還有其他的實現方式嗎?

  當然有了,上面的實現方式是策略實現所需要的數據都是從上下文中獲取,因此擴展了上下文;現在我們可以不擴展上下文,直接從策略實現內部來獲取數據,看下面的實現:

//支付到銀行賬戶的策略
public class AccountPay2 implements PayStrategy {
    //銀行賬戶
    private String account;
    public AccountPay2(String account) {
        this.account = account;
    }
    @Override
    public void pay(PayContext ctx) {
        System.out.println("現在給:"+ctx.getUsername()+"的賬戶:"+getAccount()+" 支付工資:"+ctx.getMoney()+" 元!");
    }
    public String getAccount() {
        return account;
    }
    public void setAccount(String account) {
        this.account = account;
    }
}

//外部客戶端
public class Client {
    public static void main(String[] args) {
        //創建具體的支付策略
        PayStrategy rmbStrategy = new RMBPay();
        PayStrategy dollarStrategy = new DollarPay();
        //準備小王的支付上下文
        PayContext ctx = new PayContext("小王",30000,rmbStrategy);
        //向小王支付工資
        ctx.pay();
        //準備Jack的支付上下文
        ctx = new PayContext("jack",10000,dollarStrategy);
        //向Jack支付工資
        ctx.pay();
        //創建支付到銀行賬戶的支付策略
        PayStrategy accountStrategy = new AccountPay2("1234567890");
        //準備上下文
        ctx = new PayContext("小張",40000,accountStrategy);
        //向小張的賬戶支付
        ctx.pay();
    }
}

控制檯輸出:

現在給:小王 人民幣支付 30000.0元!
現在給:jack 美金支付 10000.0dollar !
現在給:小張的賬戶:1234567890 支付工資:40000.0 元!

那我們來比較一下上面兩種實現方式:

  擴展上下文的實現:

    優點:具體的策略實現風格很是統一,策略實現所需要的數據都是從上下文中獲取的,在上下文中添加的數據,可以視爲公共的數據,其他的策略實現也可以使用。

    缺點:很明顯如果某些數據只是特定的策略實現需要,大部分的策略實現不需要,那這些數據有“浪費”之嫌,另外如果每次添加算法數據都擴展上下文,很容易導致上下文的層級很是複雜。

  在具體的策略實現上添加所需要的數據的實現:

    優點:容易想到,實現簡單

    缺點:與其他的策略實現風格不一致,其他的策略實現所需數據都是來自上下文,而這個策略實現一部分數據來自於自身,一部分數據來自於上下文;外部在使用這個策略實現的時候也和其他的策略實現不一致了,難以以一個統一的方式動態的切換策略實現。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章