設計模式--策略模式
策略模式
- 定義
策略模式是定義了演算法族,分別封裝起來,讓它們之間可以相互替換,此模式讓演算法的變化可以獨立於使用演算法的客戶。
先不用著急理解定義,先看下面的例子
- 栗子
假設我們有一個Car類(代表所有的汽車),我們知道汽車都可以發動、加速、剎車等,而現在的汽車種類有非常非常多,不同的品牌,使用不同的燃料等等,於是,為了提高程式碼的複用性,我們可以將所有汽車都具有的共性封裝到Car類中,不同型別的車有各自的類,他們的個性就在各自的類中實現即可。程式碼如下:
Car類
public class Car{ /** * 發動 */ public void start(){ System.out.println("發動"); } /** * 加速 */ public void accele(){ System.out.println("加速"); } /** * 剎車 */ public void brake(){ System.out.println("剎車"); } }
寶馬汽車類
/**
* 賓士 繼承Car類
*/
public class BWMCar extends Car{
/**
* 檢視汽車品牌
*/
public void getBrand(){
System.out.println("寶馬");
}
/**
* 補充燃料
*/
public void refuel(){
System.out.println("加油");
}
}
特斯拉汽車類
/** * 特斯拉 繼承Car */ public class TeslaCar extends Car{ /** * 檢視汽車品牌 */ public void getBrand(){ System.out.println("特斯拉"); } /** * 補充燃料 */ public void refuel(){ System.out.println("充電"); } }
測試執行類
public class Test { public static void main(String[] args) { BWMCar bwm = new BWMCar(); TeslaCar tesla = new TeslaCar(); System.out.println("=======寶馬======="); bwm.start(); bwm.accele(); bwm.brake(); bwm.getBrand(); bwm.refuel(); System.out.println("========特斯拉========"); tesla.start(); tesla.accele(); tesla.brake(); tesla.getBrand(); tesla.refuel(); } }
執行結果:
=======寶馬=======
發動
加速
剎車
寶馬
加油
========特斯拉========
發動
加速
剎車
特斯拉
充電
類圖
對於現在來講這樣做已經可以了,但是,開發過程中從來不缺新需求的提出。假設此時比亞迪的電動車也要新增上,如果我們繼續按照上賣弄繼承的方式,可以得到下面比亞迪電動車類。
public class BYDCar extends Car{
/**
* 檢視汽車品牌
*/
public void getBrand(){
System.out.println("比亞迪");
}
/**
* 補充燃料
*/
public void refuel(){
System.out.println("充電");
}
}
我們可以看到,BYDCar 類和TeslaCar類除了getBrand() 的方法不一樣外,refuel() 方法是一模一樣的,那麼我們是不是可以將這個共有的方法提取出來呢?
提取到Car中顯然是不可以的,因為BWMCar 同時也繼承的Car類,如果將refuel()【充電】方法提取到Car中,那麼所有補充燃料方式不是充電的Car的子類都需要重寫refuel()方法,當子類特別多的時候,將會是非常麻煩的事情,後期如果有變動,修改起來也會令人瘋狂!
這時候你可能會有這樣的想法,我們可以再設計出兩個子類,一個作為汽油車的父類,一個作為電動車的父類,由這兩個類繼承Car,得到的類圖如下所示
看似這樣是可以的,但是我們知道比亞迪也有傳統的汽油車,那用繼承該如何實現呢?每個品牌下都有很多型號的汽車,用繼承全部實現可以嗎?
不論是如何實現,我們從上面兩次實現中都發現了繼承的缺陷,顯然,繼承很難滿足快速變化的需求,或者說繼承不是最佳的實現方式!
分析上面類圖可以發現,子類很多的方法都是相同的,只是具體的實現不同。是不是非常熟悉的感覺,沒錯,就是介面!我們接下來的解決方法就是使用介面來實現。
首先先抽離獲取品牌的方法,我們知道,汽車的品牌有很多,同時每個品牌又有非常多的型號的汽車,所以品牌非常有必要抽離!
我們抽離出一個介面Brand,所有的汽車品牌都需要實現這個介面,而具體的實現都是由具體的品牌自己確定。
同理,可以抽離出燃料補充介面:
同時我們再Car類中新增這兩種行為的屬性,同時抽離這兩種行為,並將其委託給具體的行為介面實現類去執行
完整的類圖如下:
再這裡使用的是組合來整合所有的行為,而不是繼承,下面用程式碼來實現吧
Brand介面
/**
* 品牌介面
*/
public interface Brand {
void getBrand();
}
品牌實現類
/**
* 寶馬品牌實現類 繼承品牌介面
*/
public class BWM implements Brand{
public void getBrand(){
System.out.println("寶馬");
}
}
public class Tesla implements Brand{
public void getBrand(){
System.out.println("特斯拉");
}
}
public class BYD implements Brand{
public void getBrand(){
System.out.println("比亞迪");
}
}
補充燃料介面
/**
* 補充燃料介面
*/
public interface RefuelWay {
void refuel();
}
補充燃料實現介面
/**
* 加油補充燃料實現類
*/
public class Oil implements RefuelWay{
public void refuel(){
System.out.println("加油");
}
}
public class Electricity implements RefuelWay{
public void refuel(){
System.out.println("充電");
}
}
Car實現類
public class Car{
/**
* 展示品牌行為
*/
private Brand brand;
/**
* 補充燃料方式行為
*/
private RefuelWay refuelWay;
/**
* 構造方法中設定兩種行為
*/
public Car(Brand brand,RefuelWay refuelWay){
this.brand = brand;
this.refuelWay = refuelWay;
}
/**
* 展示品牌 委託給品牌具體實現類
*/
public void getBrand(){
brand.getBrand();
}
/**
* 補充燃料 委託給補充燃料的具體實現類
*/
public void refuel(){
refuelWay.refuel();
}
/**
* 發動
*/
public void start(){
System.out.println("發動");
}
/**
* 加速
*/
public void accele(){
System.out.println("加速");
}
/**
* 剎車
*/
public void brake(){
System.out.println("剎車");
}
/**
* 展示方法 方便我們測試
*/
public void display(){
this.start();
this.accele();
this.brake();
this.getBrand();
this.refuel();
}
}
測試類
public class Test {
public static void main(String[] args) {
// 建立不同的品牌
Brand bwm = new BWM();
Brand tesla = new Tesla();
Brand byd = new BYD();
// 建立不同的燃料補充方式
RefuelWay oil = new Oil();
RefuelWay electricity = new Electricity();
// 構造 寶馬的汽油車
System.out.println("------------構造 寶馬的汽油車--------------");
Car car1 = new Car(bwm,oil);
car1.display();
// 構造 寶馬電動車
System.out.println("------------構造 寶馬電動車--------------");
Car car2 = new Car(bwm, electricity);
car2.display();
// 構造 特斯拉電動車
System.out.println("------------構造 特斯拉電動車--------------");
Car car3 = new Car(tesla,electricity);
car3.display();
// 構造 比亞迪電動車
System.out.println("------------構造 比亞迪電動車--------------");
Car car4 = new Car(byd,electricity);
car4.display();
// 構造 比亞迪汽油車
System.out.println("------------構造 比亞迪汽油車--------------");
Car car5 = new Car(byd,oil);
car5.display();
}
}
輸出:
------------構造 寶馬的汽油車--------------
發動
加速
剎車
寶馬
加油
------------構造 寶馬電動車--------------
發動
加速
剎車
寶馬
充電
------------構造 特斯拉電動車--------------
發動
加速
剎車
特斯拉
充電
------------構造 比亞迪電動車--------------
發動
加速
剎車
比亞迪
充電
------------構造 比亞迪汽油車--------------
發動
加速
剎車
比亞
對於一開始使用的繼承,如果父類後期發生變化,那麼對於子類的影響是非常大的,同時繼承也非常的不靈活。而使用組合時,如果後期行為發生了變化,我們只需要切換這個行為的實現類即可,而Car中使用的時實現這個行為的介面,對其子類不會產生影響,如果需要新增新的行為,只需要新增一個新的行為介面,並將其委託給具體的實現類即可。
由此我們可以得出:實際開發中,我們應當多用組合,少用繼承。
這裡對展示品牌和補充燃料方式行為的封裝就是使用的策略模式,再次閱讀策略模式的定義,應當容易理解了。