設計模式—–策略模式

  • 2019 年 11 月 2 日
  • 筆記

策略模式

定義

所謂策略模式就是定義了演算法族,分別封裝起來,讓他們之前可以互相轉換,此模式讓該演算法的變化獨立於使用演算法的客戶

理解

**策略這個詞應該怎麼理解,打個比方說,我們出門的時候會選擇不同的出行方式,比如騎自行車、坐公交、坐火車、坐飛機、坐火箭等等,這些出行方式,每一種都是一個策略 **

**再比如我們去逛商場,商場現在正在搞活動,有打折的、有滿減的、有返利的等等,其實不管商場如何進行促銷,說到底都是一些演算法,這些演算法本身只是一種策略,並且這些演算法是隨時都可能互相替換的,比如針對同一件商品,今天打八折、明天滿100減30,這些策略間是可以互換的 **

UML類圖

**其中,Context是上下文,用一個ConcreteStrategy來配置,維護一個對Strategy對象的引用;Strategy是策略類,用於定義所有支援演算法的公共介面;ConcreteStrategy是具體策略類,封裝了具體的演算法或行為,繼承於Strategy **

解釋

1.Context 上下文

Context上下文角色,也叫Context封裝角色,起承上啟下的作用,屏蔽高層模組對策略、演算法的直接訪問,封裝可能存在的變化

public class Context {        Strategy strategy;        public Context(Strategy strategy){          this.strategy = strategy;      }        /**       * 上下文介面       */      public void contextInterface(){          strategy.algorithmInterface();      }  }

2.Strategy 策略角色

抽象策略角色,是對策略、演算法家族的抽象,通常為介面,定義每個策略或演算法必須具有的方法和屬性。algorithm是「運演算法則」的意思

public interface Strategy {      /**       * 演算法方法       */      public void algorithmInterface();  }

3.具體策略角色

用於實現抽象策略中的操作,即實現具體的演算法,下方用print代替。測試類共3個ConcreteStrategy,其它兩個類與ConcreteStrategyA同理,就不再贅述了

class ConcreteStrategyA implements Strategy{        @Override      public void algorithmInterface() {          System.out.println("策略A的具體演算法實現");      }  }

4.Client 客戶端

下面依次更換策略,測試一下策略模式

public class StrategyClient {      public static void main(String[] args) {          Context context;            context = new Context(new ConcreteStrategyA());          context.contextInterface();            context = new Context(new ConcreteStrategyB());          context.contextInterface();            context = new Context(new ConcreteStrategyC());          context.contextInterface();      }  }

運行結果如下:

實例一:

​ 我們都知道排序演算法有很多種,但是什麼時候選擇冒泡排序,什麼時候選擇選擇排序,什麼時候選擇插入排序,所以這裡用排序演算法來演示策略模式的實現

public void selectSort(String type){          if("type1".equals(type)){              //選擇快速排序          }          else if("type2".equals(type)){              //選擇插入排序          }          else if("type3".equals(type)){              //選擇冒泡排序          }          else if("type4".equals(type)){              //選擇選擇排序          }          ......      }

對於這樣的程式碼實現,除了程式碼中充斥著大量的if…else if…else,導致程式可維護性很差,而且系統的可擴展性不好,如果某個排序模組進行更改了,有可能需要修改源程式碼。所以在對於這樣的情景是非常合適使用策略模式的。

那麼如何使用策略模式呢?首先我們需要定義一個介面,該介面提供排序演算法,然後定義想要的排序演算法,實現給介面即可。如下:

首先是Sort介面,該介面定義了排序演算法,所有的排序演算法都應該實現該介面

public interface Sort{      public abstract int[] sort(int arr[]);  }

然後是三個具體的排序演算法,他們實現Sort介面。

**冒泡排序:BubbleSort.java **

public class BubbleSort implements Sort{      public int[] sort(int arr[]){         int len=arr.length;         for(int i=0;i<len;i++){             for(int j=i+1;j<len;j++){                int temp;                if(arr[i]>arr[j]){                    temp=arr[j];                    arr[j]=arr[i];                    arr[i]=temp;                }             }          }          System.out.println("冒泡排序");          return arr;      }  }

**插入排序:InsertionSort.java **

public class InsertionSort implements Sort {      public int[] sort(int arr[]) {          int len = arr.length;          for (int i = 1; i < len; i++) {              int j;              int temp = arr[i];              for (j = i; j > 0; j--) {                  if (arr[j - 1] > temp) {                      arr[j] = arr[j - 1];                    } else                      break;              }              arr[j] = temp;          }          System.out.println("插入排序");          return arr;      }  }

**選擇排序:SelectSort.java **

public class SelectionSort implements Sort {      public int[] sort(int arr[]) {          int len = arr.length;          int temp;          for (int i = 0; i < len; i++) {              temp = arr[i];              int j;              int samllestLocation = i;              for (j = i + 1; j < len; j++) {                  if (arr[j] < temp) {                      temp = arr[j];                      samllestLocation = j;                  }              }              arr[samllestLocation] = arr[i];              arr[i] = temp;          }          System.out.println("選擇排序");          return arr;      }  }

**最後就是測試類客戶端了Client.java **

public class ArrayHandler  {      private Sort sortObj;        public int[] sort(int arr[])      {          sortObj.sort(arr);          return arr;      }        public void setSortObj(Sort sortObj) {          this.sortObj = sortObj;      }  }
public class Client  {      public static void main(String args[])      {         int arr[]={1,4,6,2,5,3,7,10,9};         int result[];         ArrayHandler ah=new ArrayHandler();           Sort sort = new SelectionSort();    //使用選擇排序           ah.setSortObj(sort); //設置具體策略         result=ah.sort(arr);           for(int i=0;i<result.length;i++)         {                 System.out.print(result[i] + ",");         }      }  }

運行結果:

選擇排序

1,2,3,4,5,6,7,9,10,

實例二:

**以商場促銷為例使用策略模式實現商場促銷演算法 **

1. 上下文類

首先聲明一個CashSuper對象,通過構造方法,傳入具體的收費策略,getResult()方法的功能為根據收費策略的不同獲得計算結果。

public class CashContext {        private CashSuper cashSuper;        public CashContext(CashSuper cashSuper) {          this.cashSuper = cashSuper;      }        public double getResult(double money) {          return cashSuper.acceptCash(money);      }    }

2. 現金收費抽象類

策略類,為抽象類,抽象出收費的方法供子類實現。

public abstract class CashSuper {        public abstract double acceptCash(double money);    }

3. 正常收費子類

沒有任何活動的情況,正常收費,返回原價。

public class CashNormal extends CashSuper {        @Override      public double acceptCash(double money) {          return money;      }  }

4. 打折收費子類

打折活動,根據折扣返回打折後的價格。

public class CashRebate extends CashSuper {        private double moneyRebate = 1;    //折扣        public CashRebate(double moneyRebate) {          this.moneyRebate = moneyRebate;      }        @Override      public double acceptCash(double money) {          return money * moneyRebate;      }  }

5. 返利收費子類

返利活動,輸入返利條件和返利值,比如滿300返100,moneyCoditation為300,moneyReturn為100。

result = money - Math.floor(money / moneyConditation) * moneyReturn; 的意思為,如果當前金額大於等於返利條件,則使用當前金額減去返利值

public class CashReturn extends CashSuper {        private double moneyConditation = 0.0;    //返利條件      private double moneyReturn = 0.0d;    //返利值        public CashReturn(double moneyConditation, double moneyReturn) {          this.moneyConditation = moneyConditation;          this.moneyReturn = moneyReturn;      }        @Override      public double acceptCash(double money) {          double result = money;            if (money >= moneyConditation) {              result = money - Math.floor(money / moneyConditation) * moneyReturn;          }            return result;      }  }

6. Client客戶端

下面寫一個簡單的程式測試一下上方編寫的程式碼。

 public class Client {        public static void main(String[] args) {          CashContext cashContext = null;            Scanner scanner = new Scanner(System.in);          System.out.print("請輸入打折方式(1/2/3):");          int in = scanner.nextInt();          String type = "";            switch (in) {              case 1:                  cashContext = new CashContext(new CashNormal());                  type += "正常收費";                  break;                case 2:                  cashContext = new CashContext(new CashReturn(300, 100));                  type += "滿300返100";                  break;                case 3:                  cashContext = new CashContext(new CashRebate(0.8));                  type += "打8折";                  break;                default:                  System.out.println("請輸入1/2/3");                  break;          }            double totalPrices = 0;            System.out.print("請輸入單價:");          double price = scanner.nextDouble();          System.out.print("請輸入數量:");          double num = scanner.nextDouble();          totalPrices = cashContext.getResult(price * num);            System.out.println("單價:" + price + ",數量:" + num + ",類型:" + type + ",合計:" + totalPrices);            scanner.close();      }    }

正常收費結果如下:

應用

1.何時使用

  • 一個系統有許多類,而區分它們的只是他們直接的行為時

2.方法

  • 將這些演算法封裝成一個一個的類,任意的替換

3.優點

  • 演算法可以自由切換
  • 避免使用多重條件判斷(如果不用策略模式我們可能會使用多重條件語句,不利於維護)
  • 擴展性良好,增加一個策略只需實現介面即可

4.缺點

  • 策略類數量會增多,每個策略都是一個類,復用的可能性很小
  • 所有的策略類都需要對外暴露

5.使用場景

  • 多個類只有演算法或行為上稍有不同的場景
  • 演算法需要自由切換的場景
  • 需要屏蔽演算法規則的場景

6.應用實例

  • 出行方式,自行車、汽車等,每一種出行方式都是一個策略
  • 商場促銷方式,打折、滿減等
  • Java AWT中的LayoutManager,即布局管理器

7.注意事項

  • 如果一個系統的策略多於四個,就需要考慮使用混合模式來解決策略類膨脹的問題

Tags: