你還在使用 if else 寫代碼?試試 “策略模式” 吧!

Java技術棧

www.javastack.cn

關注閱讀更多優質文章

我們使用的app大多都有分享的功能,我們可以選擇分享到不同的地方,比如微博、微信、QQ等等,雖然是同一個內容,但是分享到不同的平臺就會有不同的處理方式,比如要跳轉到不同的app或者直接複製鏈接等等。如果讓你來實現這個功能,你會如何實現呢?

如果你對設計模式不熟悉,那麼第一反應就是有if...else或者switch語句來進行條件判斷,根據用戶的不同選擇而使用不同的處理方法。我們用代碼簡化地處理一下:

public void Share{
public void shareOptions(String option){
       if(option.equals("微博")){
           //function1();
           //...
      }else if(option.equals("微信")){
           //function2();
           //...
      }else if(option.equals("朋友圈")){
           //function3();
           //...
      }else if(option.equals("QQ")){
           //function4();
           //...
      }
       //...
}

如果只是寫一個這麼簡單的功能,那麼這樣的代碼也未嘗不可,因爲這樣的代碼量不多,後續也不需要怎麼拓展和修改,維護起來也不算麻煩。但是,我們工作中遇到的都是一些比較複雜的項目,要保證項目的可讀性、可維護性和可拓展性,就必須在代碼上下功夫了。

這裏我們就要提到一個概念,那就是設計模式了。設計模式是指針對軟件開發過程中重複發生的問題的解決辦法。其中以被稱爲Gang of Four(GoF)的4人整理出的23種設計模式最爲有名。

當然,我不可能在這裏把23種設計模式全部介紹完,就算全部介紹一遍,而且你還能全部記住,也不一定奏效。首先,你沒有必要全部記下來,因爲這23種設計模式並非都是經常使用到的設計模式。其次,死記硬背下這些設計模式沒有任何意義,重要的是在自己腦海中理解設計模式是怎樣解決問題的。

今天我們就談談可以優化以上問題的設計模式,策略模式(Strategy  Design Pattern)。

策略模式(Strategy  Design Pattern)

策略指的是計策、謀略,而模式一般指人爲整理而成的,在某種場景下重複發生的問題的解決辦法。在編程中,我們可以把策略看做是“算法”,而策略模式,按照GoF的定義,就是我們設計一些算法,把它們封裝起來,讓它們可以相互替換,這樣就可以輕鬆地切換不同的算法來解決同一個問題。

我們看一下策略模式中有哪些角色。

  • Strategy(策略)Strategy角色負責決定實現策略所必需的接口(API)。在示例程序中,由strategy接口扮演此角色。

  • ConcreteStrategy(具體的策略)ConcreteStrategy角色負責實現Strategy角色的接口(API),即負責實現具體的策略(戰略、方向、方法和算法)。

  • Context(上下文)負責使用Strategy角色。Context角色保存了ConcreteStrategy角色的實例,並使用ConcreteStrategy角色去實現需求(總之,還是要調用Strategy角色的接口(API))。

再看看策略模式的類圖:

介紹到這裏,相信你對策略模式有了初步的認識,那我們就用策略模式來重構前面的代碼,讓你加深對策略模式的理解。

//定義策略接口
public interface DealStrategy{
   void dealMythod(String option);
}

//定義具體的策略1
public class DealSina implements DealStrategy{
   @override
   public void dealMythod(String option){
       //...
  }
}

//定義具體的策略2
public class DealWeChat implements DealStrategy{
   @override
   public void dealMythod(String option){
       //...
  }
}

//定義上下文,負責使用DealStrategy角色
public static class DealContext{
   private String type;
   private DealStrategy deal;
   public  DealContext(String type,DealStrategy deal){
       this.type = type;
       this.deal = deal;
   }
   public DealStrategy getDeal(){
       return deal;
   }
   public boolean options(String type){
       return this.type.equals(type);
   }
}
public void Share{
   private static List<DealContext> algs = new ArrayList();
   //靜態代碼塊,先加載所有的策略
   static {
       algs.add(new DealContext("Sina",new DealSina()));
       algs.add(new DealContext("WeChat",new DealWeChat()));
  }
public void shareOptions(String type){
       DealStrategy dealStrategy = null;
       for (DealContext deal : algs) {
           if (deal.options(type)) {
               dealStrategy = deal.getDeal();
               break;
          }  
      }
       dealStrategy.dealMythod(type);
  }
}

再回憶一下策略模式中的各個角色,代碼中的DealStrategy接口就是策略,DealSina和DealWeChat是具體的策略,DealContext就是使用策略的上下文。

所以這樣的代碼已經符合策略模式的代碼結構了。我們通過策略模式將策略的定義、創建、使用解耦,讓每一部分都不至於太複雜,也去除了if...else這樣的條件判斷語句,代碼的可維護性和可拓展性都提高了。

我們把可變的部分放到 了Share 類中的靜態代碼段中。如果有新的需求,要添加一個分享的方式時,只需要定義好具體的策略,然後修改 Share 類中的靜態代碼段,其他代碼都不需要修改。

策略模式還是比較常用的一種設計模式,比如java中給我定義好的Comparator 接口就是策略模式的一個實踐。

如果需要對某個類的排序,而該類沒有實現Comparable接口,那麼可以建立一個實現Comparator接口的比較器即可。通過實現Comparator類來新建一個比較器,然後通過該比較器來對類進行排序。

//策略接口
public interface Comparator<T> {
   int compare(T o1, T o2);
}

//需要我們來定義具體的策略
public class sorter implements Comparator{
  public int compare(String s1, String s2) {
      return Integer.compare(s1.length(), s2.length());
 }
}
//一般我們會用更簡潔的Lambda表達式來實現

如果你使用的是Java ,想更符合開閉原則,並對反射有一定了解,那還可以通過反射來避免對類的修改。

你可以通過一個配置文件或者定義一個註解來標註定義的策略類;通過讀取配置文件或者搜索被標註的策略類,通過反射動態地加載這些策略類、創建策略對象;當我們新添加一個策略的時候,只需要將這個新添加的策略類添加到配置文件或者用定義的註解標註即可。

Strategy模式特意將算法與其他部分分離開來,只是定義了與算法相關的接口(APl),然後在程序中以委託的方式來使用算法。

這樣看起來程序好像變複雜了,其實不然。例如,當我們想要通過改善算法來提高算法的處理速度時,如果使用了Strategy模式,就不必修改Strategy角色的接口(API)了,僅僅修改ConcreteStrategy角色即可。

而且,使用委託這種弱關聯關係可以很方便地整體替換算法。例如,使用Strategy模式編寫棋類程序時,可以方便地根據棋手的選擇切換AI電腦的水平。

至此,我們可以小結出策略模式的使用場景:

  • 一個項目中有許多類,它們之間的區別僅在於它們的行爲,希望動態地讓一個對象在許多行爲中選擇一種行爲時;

  • 一個項目需要動態地在幾種算法中選擇一種時;

  • 一個對象有很多的行爲,不想使用多重的條件選擇語句來選擇使用哪個行爲時。

策略模式不僅僅可以優化if else代碼,其主要的作用還是解耦策略的定義、創建和使用,控制代碼的複雜度,讓每個部分都不至於過於複雜、代碼量過多。除此之外,對於複雜代碼來說,策略模式還能讓其滿足開閉原則,添加新策略的時候,最小化、集中化代碼改動,減少引入 bug 的風險。

可能有人會問:狀態模式也可以優化if else,那麼策略模式和狀態模式又有什麼不同呢?

讓我們來簡單回顧一下狀態模式的類圖:

使用策略模式和狀態模式都可以替換被委託對象,而且它們的類之間的關係也很相似。但是兩種模式的目的不同。

在策略模式中,ConcreteStrategy角色是表示算法的類。在Strategy模式中,可以替換被委託對象的類。當然如果沒有必要,也可以不替換。

而在狀態模式中,ConcreteState角色是表示“狀態”的類。在State模式中,每次狀態變化時,被委託對象的類都必定會被替換。

好了,關於策略模式我們就是介紹到這裏。在做項目時用過策略模式嗎?是在什麼場景中使用呢?歡迎留言說說。

最近熱文:

1、Spring Boot 2.3.1 發佈, 10 個新特性!

2、一週面試了 30 人,面到我心態爆炸…

3、求求你們別再寫滿屏的 try catch 了!

4、寫了個全局變量的bug,被同事們打臉!

5、Java 14 祭出神器,Lombok 被幹掉了?

6、爲什麼 Redis 單線程能達到百萬+QPS?

7、Spring Boot 2.3 優雅關閉新姿勢,真香!

8、Redis 到底是單線程還是多線程?

9、我天!xx.equals(null) 是什麼騷操作??

10、Struts2 爲什麼被淘汰?自己作死!

掃碼關注Java技術棧公衆號閱讀更多幹貨。

點擊「閱讀原文」獲取面試題大全~

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