從諸葛亮的三個錦囊妙計談策略模式

話說當年東吳孫權爲劉備借走了荊州不還而耿耿於懷,卻不料甘夫人去世,周郎頓時計上心來,讓孫權將其妹嫁與劉備,騙劉備來東吳完婚。劉備又不是傻子,當然知道其中的貓膩,當即表態:打死也不去。諸葛亮卻說無妨,當下給了趙雲三個錦囊妙計,讓他陪劉備去東吳完婚……最後的結果大家都知道,趙雲在恰當的時候一一打開三個錦囊妙計,從而將危機一一化解。周瑜只落了個“周郎妙計安天下,賠了夫人又折兵”的笑柄。
這個故事,我們感興趣的是三個錦囊妙計。諸葛亮一定是運用策略模式的高手,我們想啊:諸葛亮爲什麼要這麼麻煩,做三個錦囊,他完全可以只做一個錦囊,將這三個妙計都寫在它上面。可他沒有這麼做,而是正確的運用了策略模式做了三個錦囊。這樣做的好處十分明顯:諸葛亮一個錦囊寫一個妙計,他的思路十分清晰,不會三個計策相互混亂。而趙雲看妙計的時候也十分方便,什麼時候看哪個妙計,使用十分方便,如果三個妙計混在一起,他就沒這麼方便了。
現在看來,這個故事正包含了策略模式的解決問題的思路:解決某一問題有多種方法或算法,如諸葛亮給了三個妙計,類的提供者要將這多種方法或算法獨立開來,如諸葛亮將三個妙計分三個錦囊裝,類的使用者,如趙雲,在相應的條件,如故事中的恰當時間,使用相應的方法或算法。
前面我們說過命令模式,命令模式是對行爲的封裝;策略模式其實跟它的解決思路一樣,是對算法的封裝。當然,它們都一樣的擁有同一個接口。
下面我們以一些例子來詳細的說一說策略模式。
首先的一個例子是我們對數組的排序,對數組的排序有很多算法,我們想做一個由用戶指定一個整形數組和對它排序的算法,我們給出結果這樣一個方法,我們可以這樣做:

public int[] sort(int[] inArray,String type)

{
       if(type.equals(“a”))
        {
               int k = 0;
               for(int i=0;i<inArray.length;i++)
               {
                      for(int j=i+1;j<inArray.length;j++)
                      {

                             if(inArray[i]> inArray[j])

                             {

                                    k = inArray[i];

                                    inArray[i] = inArray[j];

                                    inArray[j] = k;

                             }
                      }
               }
}
else if(type.equals(“b”))
{
       int k = 0;
       for(int i=0;i< inArray.length;i++)
       {

              for(int j=0;j< inArray.length-i-1;j++)

              {
                     if(inArray [j]> inArray [j+1])
                     {
                            k = inArray [j];
                            inArray [j] = inArray [j+1];
                            inArray [j+1] = k;
                     }
              }
       }
}
}
一看上面的代碼,大家就覺得問題很多:第一,所有的算法都攪和在一起,不滿足單一職責原則。第二,依賴細節,不滿足依賴顛倒原則。第三,擴展性不好,如果要增加一個新的算法,需要對該方法進行改動。
下面我們來看看策略模式的解決思路。
首先是對所有的算法抽象一個接口,這是爲了滿足擴展性的基本條件,幾乎所有的模式都要以接口爲基礎:
public interface SortAlgorithm
{

        public void sort(int[] inArr);

}
然後是各個算法的具體實現,它們實現了SortAlgorithm接口,完成了對關注點的分離,滿足單一職責原則:

public class SortOne implements SortAlgorithm

{

        public void sort(int[] inArr)

        {
               int k = 0;
               for(int i=0;i<inArr.length;i++)
               {
                      for(int j=i+1;j<inArr.length;j++)
                      {

                             if(inArr[i]> inArr[j])

                             {

                                    k = inArr[i];

                                    inArr[i] = inArr[j];

                                    inArr[j] = k;

                             }
                      }
               }
}
}

public class SortTwo implements SortAlgorithm

{

        public void sort(int[] inArr)

        {
               int k = 0;
       for(int j=0;j< inArr.length-i-1;j++)
       {

              if(inArr[j]> inArr[j+1])

              {
                     k = inArr[j];
                     inArr[j] = inArr[j+1];
                     inArr[j+1] = k;
              }
               }
}
}
然後我們來看客戶端的調用:

public int[] sort(int[] inArray,String type)

{
       SortAlgorithm sa;
       if(type.equals(“a”))
        {

               sa = new SortOne();

        }

        else if(type.equals(“b”))

        {

               sa = new SrotTwo();

}
else
{
       ……
}
sa.sort(inArray);
}
上面我們就完成了策略模式對該問題的解決,滿足了單一職責原則;有了一定的擴展性,如果新增一個算法的話,去實現SortAlgorithm接口就可以了。
當然,我們在客戶端代碼可以看到,策略模式依然沒有完全去掉客戶端對具體實現類的依賴,這使得我們增加一個新的算法以後,仍然需要對客戶端進行修改。所以策略模式和命令模式一樣,只是初步增加了一定的擴展性。如果要完全去掉客戶端對具體類的依賴,可以結合工廠模式來對該問題作進一步優化,具體做法大家可以自己做,這裏不再講述。
所謂策略模式,簡單說來就是對算法的封裝。在實際的編碼過程中,我們會遇到各種各種的選擇算法的問題,這時候就可以使用策略模式了,下面以一個例子來作爲本文的結束。
假設我們有一個POJO類:Employee,裏面存儲了很多用戶的信息,如下:
public class Employee
{

        private int id;

        private String name;

        private double servedYears;

        ……

        public void setId(int id)

        {
               this.id = id;
}
public int getId()
{
       return this.id;
}

public void setName(String name)

{
       this.name = name;
}
public String getName()
{
       return this.name;
}

public void setServedYears(double servedYears)

{
       this.servedYears = servedYears;
}

public double getServedYears()

{
       return this.servedYears;
}
……
}
現在我們有一個關於Employee的數組,需要對數組進行排序,我們知道該使用Arrays.sort()對該數組進行排序,但是在使用該方法的時候,我們首先要實現Comparator接口,如下:

Arrays.sort(emps,new Comparator(){

        Public int compare(Object o1,Object o2)

        {

               return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears();

}
});
上面實現了一個對POJO對象的排序過程,現在的問題是我們分別需要對Employee類的id、name和servedYears爲索引進行排序。我們可以看出,這明顯是多個算法的問題,可以使用策略模式來解決問題:
以id爲索引的比較器:

public class IdComparator implements Comparator

{

        Public int compare(Object o1,Object o2)

        {

               return ((Employee)o1).getId()-((Employee)o2).getId();

}
}
以name爲索引的比較器:

public class NameComparator implements Comparator

{

        Public int compare(Object o1,Object o2)

        {

               return ((Employee)o1).getName()-((Employee)o2).getName();

}
}
以servedYears爲索引的比較器:

public class ServedYearsComparator implements Comparator

{

        Public int compare(Object o1,Object o2)

        {

               return ((Employee)o1).getservedYears()-((Employee)o2).getServedYears();

}
}
然後,我們做一個工廠來獲取具體的比較器:
public class Factory
{

        public static Comparator getInstance(String type)

        {
               if(type.equals(“id”))
               {
                      return new IdComparator();
}
else if(type.equals(“name”))
{
   return new NameComparator();
}
else
{
   return ServedYearsComparator();
}
}
}
那麼我們就可以對客戶端進行編碼了:

public void sort(Employee[] emps,String type)

{
       Arrays.sort(emps,Factory.getInstance(type));
}
我們可以看到,結合了工廠模式的策略模式的確使多算法的問題得到了很好的解決:抽取了各個算法來單獨關注,滿足了單一職責原則;滿足了依賴顛倒原則,有了很好的擴展性。等等。
還等什麼呢?趕快到實踐中去靈活運用這些模式吧!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章