[設計模式] - No.12 Strategy模式
Strategy模式
Strategy
模式又稱策略模式,在策略模式中,我們將特定的演算法封裝起來,從而實現整體地替換演算法的功能,以便我們能夠按照不同的演算法去解決同一個問題。《圖解設計模式》這本書中的,策略模式這一章提供的程式碼示例有些冗長,所以我參考了這篇文章,編寫了一個簡單的例子,用較少的程式碼解釋什麼是策略模式。
假設我們有一個場景,在商場中針對不同的客戶的身份,提供不同的優惠方案,最簡單的一種實現如下所示:
public class MarketPrice {
private final String OLD_CUSTOME = "老客戶";
private final String NEW_CUSTOME = "新客戶";
private final String VIP_CUSTOME = "VIP客戶";
public double getDiscount(String custome){
double discount = 1.0;
if(OLD_CUSTOME.equals(custome)){
System.out.println("老客戶有9折優惠!");
discount = 0.9;
}
else if(NEW_CUSTOME. equals(custome)){
System.out.println("新客戶沒有優惠!");
}
else if (VIP_CUSTOME.equals(custome)){
System.out.println("VIP客戶有8優惠!");
discount = 0.8;
}
return discount;
}
}
這段程式碼看起來沒有什麼問題,並且在現有的工程中,很多時候我們也是這樣做的。但是,一旦我們希望對現有程式碼進行擴充套件,例如,增加一個針對SVIP
MarketPrice
進行擴充套件,在if-else
中增加新的語句,這又違反了開閉原則。
在設計模式的學習中,我們發現SOLID
原則中,最重要的一個就是開閉原則,其他的原則基本上是為了開閉原則服務的。由於我們希望遵循開閉原則,對修改關閉,對擴充套件開放,所以我們才希望將抽象和實現分離開,才需要使用里氏替換原則,才需要依賴反轉,最終達到解耦的目的。
重新回到程式碼,我們將MarketPrice
中的演算法抽離出來,封裝成獨立的演算法,當我們需要不同的優惠策略的時候,只需要替換演算法即可。
public interface Strategy {
public double getDiscount();
}
public class OldCustomerStrategy implements Strategy{
@Override
public double getDiscount() {
System.out.println("老客戶有9折優惠!");
return 0.9;
}
}
public class VIPCustomerStrategy implements Strategy {
@Override
public double getDiscount() {
System.out.println("VIP客戶有8優惠!");
return 0.8;
}
}
public class DiscountManager {
private Strategy strategy;
public DiscountManager(Strategy strategy) {
this.strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public double getDiscountByCustomer(){
return strategy.getDiscount();
}
}
在主程式中,我們這樣呼叫:
public class Main {
public static void main(String args []){
double product = 2000;
DiscountManager manager = new DiscountManager(new VIPCustomerStrategy());
double discount4 = manager.getDiscountByCustomer();
System.out.println(String.format("VIP客戶實際購買價 $%.2f", product*discount4));
manager.setStrategy(new OldCustomerStrategy());
double discount5 = manager.getDiscountByCustomer();
System.out.println(String.format("老客戶實際購買價 $%.2f", product*discount5));
}
}
如果我們希望增加一個針對SVIP
的優惠策略,只需要在編寫一個新的類,繼承Strategy
並複寫其getDiscount
函式即可。
在上面的程式碼中我們可以看到,雖然外部呼叫的是DiscountManager
的getDiscountByCustomer
介面,但是其實這個函式並沒有自己計算具體的折扣,而是委託其內部聚合的Startegy
物件來計算折扣。正式因為我們在DiscountManager
中使用的是委託的方式,委託是一種弱關聯的方式,這使得我們可以很方便的整體替換折扣演算法。
對比之前的Bridge
模式,我們會發現該兩者的實現方式很相似,都是通過委託的方式來實現功能,兩種模式的具體UML
類圖如下:
策略模式:
Bridge模式:
可以看到,兩個UML類圖的結構幾乎一樣,不同的地方就是,在Bridge模式中,在類的功能層次層次可能會增加新的子類。這與兩個模式的用途有關:
策略模式的目的在於能夠方便的、動態替換策略演算法。而Bridge
模式主要的特點是,將類的功能層次和實現層次分離開,使得二者可以獨立的擴充套件。言下之意,在策略模式中,我們呼叫策略的Context
一般不會頻繁的更改,我們聚焦的是Strategy
類的替換,而在Bridge模式是為了Abstraction
和Implementor
都可以很方便地擴充套件。