策略模式

1.策略模式介紹

    在軟件開發中經常會遇到這樣的情況:實現某一個功能可以有多種算法或策略,我們根據實際情況選擇不同的算法或策略來完成該功能。例如,排序算法,可以使用插入排序、歸併排序、冒泡排序等。
    針對這種情況啊,一種常規的方法是將多種算法寫在一個類中。例如,需要提供多種排序算法,可以將這些算法寫到一個類中,每一種方法對應一個具體的排序算法;當然,也可以將這些排序封裝在一個統一的方法中,通過if…else…或者case等條件判斷語句來選擇具體的算法。這兩種實現方法我們都可以稱之爲硬編碼。然而,當很多算法集中在一個類中時,這個類就會變得臃腫,這個類的維護成本會變高,在維護時更容易引發錯誤。如果我們需要增加一種新的排序算法,需要修改封裝算法類的源代碼。這就明顯違反了常說的OCP原則和單一職責原則。
    如果將這些算法或者策略抽象出來,提供一個統一的接口,不同的算法或策略有不同的實現類,這樣在程序客戶端就可以通過注入不同的實現對象來實現算法或策略的動態替換,這種模式的可擴展性、可維護性也就更高。

2.策略模式的定義

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

3.策略模式的使用場景

  • 針對同一類型問的多種處理方式,僅僅是具體行爲有差別時;
  • 需要安全的封裝多種同一類型的操作時;
  • 出現同一抽象類有多個子類,而有需要使用if-else或者switch-case來選擇具體子類時;

4.策略模式的UML類圖

在這裏插入圖片描述
角色介紹:

  • Context:用來操作策略的上下文環境;
  • Stragety:策略的抽象類;
  • ConcreteStragetyA、ConcreteStragetyB:具體的策略實現;

5.策略模式的簡單實現

    通常如果一個問題有多個解決方案時,最簡單的方式就是利用if-else或者switch-case 方法根據不同的情景選擇不同的解決方案,但這種簡單的方案問題太多,例如耦合性太高、代碼臃腫、難以維護等。但是,如果解決方案中包括大量的處理邏輯需要封裝,或者處理方式變動較大的時候則就顯得混亂、複雜,當需要增加一種方案時就需要修改類中的代碼。這種情況情況策略模式就能很好地解決這類問題,它將各種方案分離出來,讓程序客戶端根據具體的需求來動態的選擇不同的策略方案。
    下面我們以坐公共交通工具的費用計算來演示一個簡單的示例。
計費規則:

  1. 公交車 2元;
  2. 地鐵 :
    距離(km) 價格(元)
    <=6 3
     >6 &&<=12 4
      >12 &&<=22 5
     >22 &&<=32 6
     >32 7

第一個版本

public class PriceCalculator {
	// 公交車類型
	private static final int BUS = 1;
	// 地鐵類型
	private static final int SUBWAY = 2;

	public static void main(String[] args) {
           PriceCalculator calculator=new PriceCalculator();
           System.out.println("坐16公里的公交車票價爲:"+calculator.calculatePrice(16, BUS));
           System.out.println("坐16公里的地鐵票價爲:"+calculator.calculatePrice(16, SUBWAY));
	}

	/*
	 * 公交車票價按次收費 每次2元
	 */
	private int busPrice(int km) {
		return 2;
	}
	/*
	 * 地鐵6公里內3元 6~12公里4元 12~22公里5元 22~32公里6元
	 */
	private int subwayPrice(int km) {
		if (km <= 6) {
			return 3;
		} else if (km <= 12) {
			return 4;
		} else if (km <= 22) {
			return 5;
		} else if (km <= 32) {
			return 6;
		} else {
			return 7;
		}
	}
	
	private int calculatePrice(int km, int type) {
		if (type == BUS) {
			return busPrice(km);
		} else if (type == SUBWAY) {
			return subwayPrice(km);
		}
		return 0;
	}
}

    PriceCalculator類很明顯的問題就是並不是單一職責(一個類中應該是一組相關性很高的函數、數據的封裝),首先它承擔了計算公交車和地鐵坐價格的職責;另一個問題就是通過if-else的形式來判斷使用哪種計算形式。當我們增加一種出行方式時,如出租車,那麼我們就需要在PriceCalculator中增加一個方法來計算出租車出行的價格,並且在calculatePircate(int km, int type)方法中增加一個判斷,代碼:

public class PriceCalculator {
	// 公交車類型
	private static final int BUS = 1;
	// 地鐵類型
	private static final int SUBWAY = 2;
	// 出租車類型
	private static final int TAXI = 3;
	/*
	 * 2公里內11元 超過部分每公里2.4元
	 */
	private double taxiPrice(double km) {
		if (km <= 2) {
			return 11;
		} else {
			return 2.4 * (km - 2) + 11;
		}
	}
	private double calculatePrice(double km, int type) {
		if (type == BUS) {
			return busPrice(km);
		} else if (type == SUBWAY) {
			return subwayPrice(km);
		} else if (type == TAXI) {
			return taxiPrice(km);
		}
		return 0;
	}

    此時的代碼已經比較混亂了,各種if-else 語句纏繞其中。當價格的計算方法變化時,需要直接修改這個類中的代碼,那麼很可能有一段代碼是其它幾個計算方法所共用的,這就容易引入錯誤。另外,在增加出行方式時,我們有需要在calculatePrice中添加if-else,此時很可能就是複製上一個if-else,然後手動進行修改,手動複製代碼也是很容易引入錯誤的做法之一。這類代碼必然是很難以應對變化的,它會使得代碼變得越來越臃腫,難以維護,下面使用策略模式修改上面代碼。
升級版本
    首先我們需要定義一個抽象的價格計算接口,這裏命名爲CalculateStrategy:

/*
 * 計算接口
 */
public interface CalculateStragegy {
	/*
	 * 按距離計算價格
	 */
	int calculatePrice(int km);
}

    對於每一種出行方式我們都有一個獨立的計算策略類,這些策略類都實現了CalculateStrategy接口,例如下面是公交車和地鐵的計算策略類:

//公交計算策略
public class BusStrategy implements CalculateStragegy{

	/*
	 * 公交車每次2元
	 */
	@Override
	public int calculatePrice(int km) {
		return 2;
	}
}

//地鐵計算策略
public class SubwayStratety implements CalculateStragegy {
	/*
	 * 6公里內3元 6~12公里4元 12~22公里5元 22~32公里6元
	 */
	@Override
	public int calculatePrice(int km) {
		if (km <= 6) {
			return 3;
		} else if (km <= 12) {
			return 4;
		} else if (km <= 22) {
			return 5;
		} else if (km <= 32) {
			return 6;
		} else {
			return 7;
		}
	}
}

    我們再創建一個扮演Context角色的類,這裏命名爲TranficCalculator,具體代碼如下:

public class TranficCalculator {
	public static void main(String[] args) {
		TranficCalculator calculator = new TranficCalculator();
        //設置計算策略
		calculator.SetStragegy(new BusStrategy());
		//計算價格
		System.out.println("公交車乘16公里的價格:"+calculator.calculatePrice(16));
	}

	CalculateStragegy mStragegy;

	public void SetStragegy(CalculateStragegy mStragegy) {
		this.mStragegy = mStragegy;
	}

	public double calculatePrice(int km) {
		return mStragegy.calculatePrice(km);
	}
}

    經過上述的重構之後,去掉了各種各樣的if-else語句,結構變得也清晰,其結構如下:
在這裏插入圖片描述
    這種方案在隱藏實現的同時,可擴展性變得更強,例如,當我們需要添加出租車的計算策略時,只需要添加一個出租車計算策略類,然後將該策略設置給TranficCalculator,最好直接通過TranficCalculator對象的計算方法即可。

總結

    策略模式主要用來分離算法,在相同的行爲抽象下有不同的具體實現策略。這個模式很好地演示了開閉原則,也就是定義抽象,注入不同的實現,從而達到很好的可擴展性。
優點:

  • 結構清晰明瞭、使用簡單直觀
  • 耦合度相對而言較低,擴展方便
  • 操作封裝也更加徹底,數據更爲安全

缺點:

  • 隨着策略的增加,子類也會變得繁多
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章