設計模式—策略模式
簡述
預先定義有着不同執行過程但結果相同的算法族,運行時指定所需算法。
算法族
此處為一組有共同主題的有相同結果的不同算法的集合。
話不多說,看個優化案例。
優化案例
最初版v0
不使用策略模式的案例。四種不同的計算策略。客戶端的代碼如下。
// 客戶端
public class Client {
public static void main(String[] args) {
String target = "公園";
Scanner sc = new Scanner(System.in);
String input = sc.next();
if ("foot".equals(input)) {
System.out.println("徒步到目的地:" + target);
} else if ("bike".equals(input)) {
System.out.println("騎單車到目的地:" + target);
} else if ("car".equals(input)) {
System.out.println("開車到目的地:" + target);
}
sc.close();
}
}
具體的條件分支都寫在客戶端,日後增加新的條件分支時也需要修改客戶端。修改客戶端這件事往往是不太願意接受的。原因是我們希望客戶端儘可能少的改變,以便減少客戶使用系統的學習成本。
傳統的方法就只能增加if條件判斷了,如下。
修改版v1
只需修改客戶端,其他代碼不變。
// 客戶端
public class Client {
public static void main(String[] args) {
String target = "公園";
Scanner sc = new Scanner(System.in);
String input = sc.next();
if ("foot".equals(input)) {
Foot foot = new Foot();
foot.toTarget(target);
} else if ("bike".equals(input)) {
Bike bike = new Bike();
bike.toTarget(target);
} else if ("car".equals(input)) {
Car car = new Car();
car.toTarget(target);
}
sc.close();
}
}
可以看出客戶端依舊與各個具體的類耦合(從類的創建到方法的調用都是如此)。
可以使用策略模式優化,使得方法調用不需要if條件判斷,傳入什麼樣的對象就使用什麼對象的行為。
修改版v2(策略模式)
public interface Trans {
void toTarget(String target);
}
// 徒步去目的地
public class Foot implements Trans {
@Override
public void toTarget(String target) {
System.out.println("徒步到目的地:" + target);
}
}
// 騎單車去目的地
public class Bike implements Trans {
@Override
public void toTarget(String target) {
System.out.println("騎單車到目的地:" + target);
}
}
// 開車去目的地
public class Car implements Trans {
@Override
public void toTarget(String target) {
System.out.println("開車到目的地:" + target);
}
}
// 上下文類,根據客戶端業務的需求持有不同的計算對象
public class Context {
private Trans trans;
public Context(Trans trans) {
this.trans = trans;
}
// 更改持有的計算對象
public change(Trans trans) {
this.trans = trans;
}
// 實際調用持有的trans實現計算
public int toTarget(String target) {
return trans.toTarget(target);
}
}
修改後,客戶端代碼調用。
// 客戶端
public class Client {
public static void main(String[] args) {
String target = "公園";
Scanner sc = new Scanner(System.in);
String input = sc.next();
Context context = null;
if ("foot".equals(input)) {
context = new Context(new Foot());
} else if ("bike".equals(input)) {
context = new Context(new Bike());
} else if ("car".equals(input)) {
context = new Context(new Car());
}
System.out.println(context.toTarget(target));
sc.close();
}
}
代碼量確實有一定的減少,但是客戶端代碼從只與各個具體Trans類的實現類耦合到多耦合一個上下文類,這樣想與我們的需求背道而馳啊。實際上,單純的策略模式就是如此,只負責減少方法調用的if語句,而不設計對象創建的封裝與優化。
說到對象創建的優化,就得說到工廠模式了,事實上在使用策略模式時,為了創建對象也變得方便,通常也會使用到工廠模式進行優化。詳情看以下優化案例。
修改版v3(策略+簡單工廠)
現有代碼都不需要改變,只需要使用簡單工廠封裝上下文對象的創建即可。
// 工廠類,創建持有不同Trans對象的上下文對象
public class Factory {
public static Context create(String input) {
if ("foot".equals(input)) {
return new Context(new Foot());
} else if ("bike".equals(input)) {
return new Context(new Bike());
} else if ("car".equals(input)) {
return new Context(new Car());
}
return null;
}
}
修改後,客戶端代碼調用。
// 客戶端
public class Client {
public static void main(String[] args) {
String target = "公園";
Scanner sc = new Scanner(System.in);
Context context = Factory.create(sc.next());
System.out.println(context.toTarget(target));
sc.close();
}
}
客戶端代碼大幅減少,並且客戶端中僅僅與Context類存在耦合。創建與使用的核心邏輯都從客戶端剝離,且具體調用的方法也只有在運行時才知曉(核心目的)。這樣就能少些很多if語句了。
總結
優點
- 可以大幅減少if語句的書寫。
- 增加新的實現方法也不需要修改客戶端代碼,只需要增加實現類。
缺點
-
單純的策略模式需要客戶端對於各個實現類有足夠的了解,提升了開發時對系統的理解難度。
-
策略過多時,存在策略膨脹的問題。鑒於策略膨脹問題,應該慎用策略模式。這是使用混合模式或許可以解決這個問題。
混合模式
即在策略模式的實現類的方法中使用if語句分割各個情況。
適用場景
- 想要優化系統中過多的
if語句時。