你好!在今天的課程中,我們將討論策略模式。在前面的課程中,我們已經簡要地熟悉了繼承的概念。如果您忘記了,我會提醒您,該術語指的是針對常見編程任務的標準解決方案。在 CodeGym,我們經常說您幾乎可以通過谷歌搜索任何問題的答案。這是因為你的任務,無論是什麼,可能已經被其他人成功解決了。模式是最常見任務的可靠解決方案,或者是解決問題情況的方法。這些就像您不需要自己重新發明的“輪子”,但您確實需要知道如何以及何時使用它們:) 模式的另一個目的是促進統一架構。閱讀別人的代碼絕非易事!每個人寫的代碼不一樣,因為同一個任務可以通過多種方式解決。但是模式的使用可以幫助不同的程序員理解編程邏輯,而無需深入研究每一行代碼(即使是第一次看到它!)今天我們來看一種最常見的設計模式,稱為“策略”。 想像一下,我們正在編寫一個程序,該程序將主動使用 Conveyance 對象。我們的程序到底做什麼並不重要。我們創建了一個包含一個Conveyance父類和三個子類的類層次結構:Sedan、Truck和F1Car。
public class Conveyance {
public void go() {
System.out.println("Moving forward");
}
public void stop() {
System.out.println("Braking!");
}
}
public class Sedan extends Conveyance {
}
public class Truck extends Conveyance {
}
public class F1Car extends Conveyance {
}
所有三個子類都從父類繼承了兩個標準方法:go()和stop()。我們的程序很簡單:我們的車只能向前行駛,然後踩剎車。繼續我們的工作,我們決定給汽車一個新方法:fill()(意思是“加滿油箱”)。我們將它添加到Conveyance父類中:
public class Conveyance {
public void go() {
System.out.println("Moving forward");
}
public void stop() {
System.out.println("Braking!");
}
public void fill() {
System.out.println("Refueling!");
}
}
這麼簡單的情況真的會出問題嗎?事實上,他們已經...
public class Stroller extends Conveyance {
public void fill() {
// Hmm... This is a stroller for children. It doesn't need to be refueled :/
}
}
我們的程序現在有一個交通工具(嬰兒車),不太符合一般概念。它可以有踏板或無線電控制,但有一件事是肯定的——它沒有任何地方可以加油。我們的類層次結構導致常用方法被不需要它們的類繼承。遇到這種情況怎麼辦?好吧,我們可以覆蓋Stroller類中的fill()方法,這樣當您嘗試給嬰兒車加油時什麼也不會發生:
public class Stroller extends Conveyance {
@Override
public void fill() {
System.out.println("A stroller cannot be refueled!");
}
}
但是,如果除了重複代碼之外沒有其他原因,這很難稱為成功的解決方案。例如,大多數類會使用父類的方法,但其餘類將被迫重寫它。如果我們有 15 個類並且我們必須重寫其中 5-6 個的行為,代碼重複將變得相當廣泛。也許接口可以幫助我們?例如,像這樣:
public interface Fillable {
public void fill();
}
我們將使用一個fill()方法創建一個Fillable接口。那麼,那些需要加油的交通工具就會實現這個接口,而其他交通工具(比如我們的嬰兒車)則不會。但是這個選項不適合我們。將來,我們的類層次結構可能會變得非常大(想像一下世界上有多少種不同類型的交通工具)。我們放棄了之前涉及繼承的版本,因為我們不想覆蓋 fill ()方法很多很多次。現在我們必須在每個班級實施它!如果我們有 50 個呢?如果要在我們的程序中進行頻繁更改(對於實際程序幾乎總是如此!),我們將不得不匆忙完成所有 50 個類並手動更改每個類的行為。那麼,在這種情況下,我們到底應該怎麼做呢?為了解決我們的問題,我們將選擇不同的方式。也就是說,我們會將類的行為與類本身分開。這意味著什麼?如您所知,每個對像都有狀態(一組數據)和行為(一組方法)。我們的運輸類的行為包括三個方法:go()、stop()和fill()。前兩種方法就可以了。但是我們將把第三種方法移出傳送類。這會將行為與類分離(更準確地說,它只會分離部分行為,因為前兩個方法將保留在原處)。那麼我們應該把我們的fill()方法放在哪裡呢?什麼都沒有想到:/它似乎正是它應該在的地方。我們將把它移到一個單獨的界面:FillStrategy!
public interface FillStrategy {
public void fill();
}
為什麼我們需要這樣的接口?一切都很簡單。現在我們可以創建幾個實現這個接口的類:
public class HybridFillStrategy implements FillStrategy {
@Override
public void fill() {
System.out.println("Refuel with gas or electricity — your choice!");
}
}
public class F1PitstopStrategy implements FillStrategy {
@Override
public void fill() {
System.out.println("Refuel with gas only after all other pit stop procedures are complete!");
}
}
public class StandardFillStrategy implements FillStrategy {
@Override
public void fill() {
System.out.println("Just refuel with gas!");
}
}
我們創建了三種行為策略:一種用於普通汽車,一種用於混合動力車,一種用於一級方程式賽車。每種策略都實現不同的加油算法。在我們的例子中,我們只是在控制台上顯示一個字符串,但每個方法都可能包含一些複雜的邏輯。我們接下來做什麼?
public class Conveyance {
FillStrategy fillStrategy;
public void fill() {
fillStrategy.fill();
}
public void go() {
System.out.println("Moving forward");
}
public void stop() {
System.out.println("Braking!");
}
}
我們將FillStrategy接口用作Conveyance父類中的一個字段。請注意,我們並沒有指出具體的實現——我們使用的是接口。汽車類將需要FillStrategy接口的特定實現:
public class F1Car extends Conveyance {
public F1Car() {
this.fillStrategy = new F1PitstopStrategy();
}
}
public class HybridCar extends Conveyance {
public HybridCar() {
this.fillStrategy = new HybridFillStrategy();
}
}
public class Sedan extends Conveyance {
public Sedan() {
this.fillStrategy = new StandardFillStrategy();
}
}
讓我們看看我們得到了什麼!
public class Main {
public static void main(String[] args) {
Conveyance sedan = new Sedan();
Conveyance hybrid = new HybridCar();
Conveyance f1car = new F1Car();
sedan.fill();
hybrid.fill();
f1car.fill();
}
}
控制台輸出:
Just refuel with gas!
Refuel with gas or electricity — your choice!
Refuel with gas only after all other pit stop procedures are complete!
偉大的!加油過程正常進行!順便說一句,沒有什麼能阻止我們在構造函數中使用策略作為參數!例如,像這樣:
public class Conveyance {
private FillStrategy fillStrategy;
public Conveyance(FillStrategy fillStrategy) {
this.fillStrategy = fillStrategy;
}
public void fill() {
this.fillStrategy.fill();
}
public void go() {
System.out.println("Moving forward");
}
public void stop() {
System.out.println("Braking!");
}
}
public class Sedan extends Conveyance {
public Sedan() {
super(new StandardFillStrategy());
}
}
public class HybridCar extends Conveyance {
public HybridCar() {
super(new HybridFillStrategy());
}
}
public class F1Car extends Conveyance {
public F1Car() {
super(new F1PitstopStrategy());
}
}
讓我們運行我們的main()方法(保持不變)。我們得到相同的結果!控制台輸出:
Just refuel with gas!
Refuel with gas or electricity — your choice!
Refuel with gas only after all other pit stop procedures are complete!
策略設計模式定義了一系列算法,封裝了它們中的每一個,並確保它們可以互換。它允許您修改算法,而不管客戶端如何使用它們(這個定義取自“Head First Design Patterns”一書,對我來說似乎很棒)。 我們已經在具有不同實現的單獨接口中指定了我們感興趣的算法系列(給汽車加油的方法)。我們將它們與汽車本身分開。現在,如果我們需要對特定的加油算法進行任何更改,它不會以任何方式影響我們的汽車類別。為了實現互換性,我們只需要向我們的Conveyance類添加一個 setter 方法:
public class Conveyance {
FillStrategy fillStrategy;
public void fill() {
fillStrategy.fill();
}
public void go() {
System.out.println("Moving forward");
}
public void stop() {
System.out.println("Braking!");
}
public void setFillStrategy(FillStrategy fillStrategy) {
this.fillStrategy = fillStrategy;
}
}
現在我們可以即時更改策略:
public class Main {
public static void main(String[] args) {
Stroller stroller= new Stroller();
stroller.setFillStrategy(new StandardFillStrategy());
stroller.fill();
}
}
如果嬰兒車突然開始使用汽油運行,我們的程序將準備好處理這種情況:) 僅此而已!您又學到了一種設計模式,這在處理實際項目時無疑是必不可少的並且很有幫助 :) 下次見!
GO TO FULL VERSION