策略模式是一個很簡單的模式,也是一個很常用的模式,可謂短小精悍,類庫有很多使用策略模式的例子,所以本文以模擬類庫爲例子,學習策略模式,也熟悉了java類庫設計中的精華,加深了我們的OO思想。
1 概念
策略模式(Strategy):它定義了一系列的算法,並將每一個算法封裝起來,而且使它們還可以相互替換。策略模式讓算法的變化不會影響到使用算法的客戶。(原文:The Strategy Pattern defines a family of algorithms,encapsulates each one,and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.)
2 模擬java類庫中Comparable和Comparator接口
本文實現一個簡單的排序算法,通過一步步設計,設計Comparable和Comparator接口,理解了策略模式和java類庫設計這2個接口的原因和不同用途。
3 最簡單的排序例子
public class Test {
public static void main(String[] args) {
int[] nums = { 3, 4, 1, 5, 2 };
DataSortor.sort(nums);
DataSortor.p(nums);
}
}
class DataSortor {
public static void sort(int[] nums) {
for (int i = 0; i < nums.length; i += 1) {
for (int j = i + 1; j < nums.length; j += 1) {
if (nums[i] > nums[j]) {
nums[i] = nums[i] + nums[j];
nums[j] = nums[i] - nums[j];
nums[i] = nums[i] - nums[j];
}
}
}
}
public static void p(int[] nums) {
for (int i : nums) {
System.out.print(i + " ");
}
System.out.println();
}
}
一個最簡單使用冒泡的排序一個int數組,優點簡單明瞭,缺點擴展性和代碼重用性不強(不考慮算法本身,比如什麼樣的數據用不用的排序算法),而且支持的類型單一!
4 支持多重類型的排序算法
public class Test {
public static void main(String[] args) {
int[] nums = { 3, 4, 1, 5, 2 };
DataSortor.sort(nums);
DataSortor.p(nums);
double[] doubles = { 1.2, 0.3, 2.5, 8.9, 0};
DataSortor.sort(doubles);
DataSortor.p(doubles);
Dog[] dogs = {new Dog(3,3), new Dog(1,1), new Dog(5,5)};
DataSortor.sort(dogs);
DataSortor.p(dogs);
}
}
class DataSortor {
public static void sort(int[] nums) {
for (int i = 0; i < nums.length; i += 1) {
for (int j = i + 1; j < nums.length; j += 1) {
if (nums[i] > nums[j]) {
nums[i] = nums[i] + nums[j];
nums[j] = nums[i] - nums[j];
nums[i] = nums[i] - nums[j];
}
}
}
}
public static void p(Dog[] dogs) {
for(int i=0; i<dogs.length; i+=1) {
System.out.print(dogs[i] + " ");
}
System.out.println();
}
public static void sort(Dog[] dogs) {
for (int i = 0; i < dogs.length; i += 1) {
for (int j = i + 1; j < dogs.length; j += 1) {
if (dogs[i].getHeight()>dogs[j].getHeight()) {
Dog temp = dogs[i];
dogs[i] = dogs[j];
dogs[j] = temp;
}
}
}
}
public static void p(double[] doubles) {
for (double i : doubles) {
System.out.print(i + " ");
}
System.out.println();
}
public static void sort(double[] doubles) {
for (int i = 0; i < doubles.length; i += 1) {
for (int j = i + 1; j < doubles.length; j += 1) {
if (doubles[i] > doubles[j]) {
doubles[i] = doubles[i] + doubles[j];
doubles[j] = doubles[i] - doubles[j];
doubles[i] = doubles[i] - doubles[j];
}
}
}
}
public static void sort1(int[] nums) {
for (int i = nums.length - 1; i >= 0; i -= 1) {
for (int j = 0; j < i; j += 1) {
if (nums[j] > nums[j+1]) {
nums[j+1] = nums[j+1] + nums[j];
nums[j] = nums[j+1] - nums[j];
nums[j+1] = nums[j+1] - nums[j];
}
}
}
}
public static void p(int[] nums) {
for (int i : nums) {
System.out.print(i + " ");
}
System.out.println();
}
}
class Dog {
private int height;
private int weight;
public Dog(int height, int weight) {
super();
this.height = height;
this.weight = weight;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Dog(" + height + "," + weight +")";
}
}
輸出結果:
1 2 3 4 5
0.0 0.30000000000000004 1.2000000000000002 2.5 8.9
Dog(1,1) Dog(3,3) Dog(5,5)
添加了更多基本類型的排序和基本類的比較,但也擴展性和重用性差還是很差。
如果我們繼續添加新的類,我們必須重新寫比較方法,重新改我們的排序代碼,排序算法不能得到重用!所以我們可以添加比較接口!
5 引入Comparable接口
public class Test {
public static void main(String[] args) {
Dog[] dogs = { new Dog(3, 3), new Dog(1, 1), new Dog(5, 5) };
DataSortor.sort(dogs);
DataSortor.p(dogs);
}
}
class DataSortor {
public static void p(Object[] objs) {
for (int i = 0; i < objs.length; i += 1) {
System.out.print(objs[i] + " ");
}
System.out.println();
}
public static void sort(Comparable[] objs) {
for (int i = 0; i < objs.length; i += 1) {
for (int j = i + 1; j < objs.length; j += 1) {
if (objs[i].compareTo(objs[j])>0) {
Comparable temp = objs[i];
objs[i] = objs[j];
objs[j] = temp;
}
}
}
}
}
interface Comparable {
int compareTo(Object obj);
}
class Dog implements Comparable {
private int height;
private int weight;
public Dog(int height, int weight) {
super();
this.height = height;
this.weight = weight;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Dog(" + height + "," + weight + ")";
}
@Override
public int compareTo(Object obj) {
if (obj instanceof Dog) {
Dog d = (Dog) obj;
if (height > d.getHeight())
return 1;
else
return -1;
}
return 0;
}
}
實現了Comparable接口的類,都可以使用排序算法,而不需要更改算法代碼!提高的可用性,據有很大的擴展性。
但實現Comparable接口實現比較方法不能解決使用其他比較策略比較大小,比如上面使用cat的height作爲比較依據,可能以後使用cat的weight作爲比較依據。所以我們提出了策略模式,引入了Comparator接口,可以讓cat選擇比較策略,來實現不同的比較方法。
6 引入Comparator接口
這樣可以設計不同的Comparator來實現不同的比較方式,大大的提高了程序的擴展性,但值得說的類也要實現Comparable接口,只是在Comparable接口的實現方法中使用我們自己設置的排序策略,當然我們可以指定默認的排序算法,如上,這樣如果有特殊需求時,在設置相應的排序算法,極大的提高了系統的靈活性。
現在,應該對策略模式有點較深的認識了吧,知道了爲什麼java類庫中爲什麼有了Comparable還有Comparator接口了吧。
7 策略模式優缺點
優點:
1、 簡化了單元測試,因爲每個算法都有自己的類,可以通過自己的接口單獨測試。
2、 避免程序中使用多重條件轉移語句,使系統更靈活,並易於擴展。
3、 遵守大部分GRASP原則和常用設計原則,高內聚、低偶合。
缺點:
1、 因爲每個具體策略類都會產生一個新類,所以會增加系統需要維護的類的數量。
2、 在基本的策略模式中,選擇所用具體實現的職責由客戶端對象承擔,並轉給策略模式的Context對象。(這本身沒有解除客戶端需要選擇判斷的壓力,而策略模式與簡單工廠模式結合後,選擇具體實現的職責也可以由Context來承擔,這就最大化的減輕了客戶端的壓力。)
8 簡單工廠模式和策略模式區別(摘錄,出處)
這兩種模式的作用就是擁抱變化,減少耦合。在變化來臨時爭取做最小的改動來適應變化。這就要求我們把些“善變”的功能從客戶端分離出來,形成一個個的功能類,然後根據多態特性,使得功能類變化的同時,客戶端代碼不發生變化。
簡單工廠模式
簡單工廠模式:有一個父類需要做一個運算(其中包含了不同種類的幾種運算),將父類涉及此運算的方法都設成虛方法,然後父類派生一些子類,使得每一種不同的運算都對應一個子類。另外有一個工廠類,這個類一般只有一個方法(工廠的生成方法),這個方法的返回值是一個超類,在方法的內部,根據傳入參數的不同,分別構造各個不同的子類的對象,並返回。客戶端並不認識子類,客戶端只認識超類和工廠類。每次客戶端需要一中運算時,就把相應的參數傳給工廠類,讓工廠類構造出相應的子類,然後在客戶端用父類接收(這裏有一個多態的運用)。客戶端很順理成章地用父類的計算方法(其實這是一個虛方法,並且已經被子類特化過了,其實是調用子類的方法)計算出來結果。如果要增加功能時,你只要再從父類中派生相應功能的子類,然後修改下工廠類就OK了,對於客戶端是透明的。
策略模式
策略模式:策略模式更直接了一點,沒有用工廠類,而是直接把工廠類的生成方法的代碼寫到了客戶端。客戶端自己構造出了具有不同功能的子類(而且是用父類接收的,多態),省掉了工廠類。策略模式定義了算法家族,分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化,不會影響到使用算法的客戶。這裏的算法家族和簡單工廠模式裏的父類是同一個概念。當不同的行爲堆砌在一個類中時,就很難避免使用條件語句來選擇合適的行爲,將這些行爲封裝在一個個獨立的策略子類中,可以在客戶端中消除條件語句。
簡單工廠模式+策略模式:爲了將工廠方法的代碼從客戶端移出來,我們把這些代碼搬到了父類的構造函數中,讓父類在構造的時候,根據參數,自己實現工廠類的作用。這樣做的好處就是,在客戶端不用再認識工廠類了,客戶端只要知道父類一個就OK,進一步隔離了變化,降低了耦合。
在基本的策略模式中,選擇所用具體實現的職責由客戶端對象成端,並轉給客戶端。這本身並沒有減除客戶端需要選擇判斷的壓力,而策略模式與簡單工廠模式結合後,選擇具體實現的職責也可以由父類承擔,這就最大化地減輕了客戶端的職責。
9 總結
策略模式作爲一種軟件設計模式,指對象有某個行爲,但是在不同的場景中,該行爲有不同的實現算法。