深入淺出設計模式(Design Pattern)-策略模式(1-Strategy Pattern)

Ben
生活的各種可能性
8 min readJan 23, 2024
photo by JESHOOTS.COM

嗨我們又見面了,很開心還能看到你,第一篇要來介紹策略模式(Strategy Pattern)。

那事不宜遲就直接開始吧

策略模式(Strategy Pattern)

首先我們先來看看書中對他的定義:定義和封裝一系列的演算法,並且讓他們是可以替換的,這個模式可以讓你在不影響用戶端的情況下獨立改變演算法。

這是什麼意思呢?我們來看看以下範例

情境:有一個超夯的鴨子模擬遊戲,當初設計這個遊戲的時候使用標準的物件導向技術,並建立一個超類別Duck(擁有quack、swim、display、fly等等方法),他可以讓其他鴨子品種來繼承,但在之後有新的需求的時候發現了一些問題,例如:橡皮鴨子會飛來飛去、木製的誘餌鴨既不會飛也不會叫。

這邊使用了繼承,雖然可以重複使用程式碼,但在這個情境下有些鴨子類別會繼承到他不需要的功能,造成維護上的問題,也許可以用覆寫的方式覆寫一些功能,但公司表示六個月就會更新一次,於是你就必須在每一次更新視情況去覆寫他們,如果有一百隻鴨子你就要做一百次,這簡直是一個惡夢。

軟體開發不變的真理:改變。

無論你的應用程式設計得多好,一段時間後,他一定需要成長並改變,否則他就會死亡。

設計原則:找出應用程式中會變的部分,把他們和不會變的部分隔開。

把會變的部分封裝起來,讓它不會影響其餘部分。這樣做可以減少修改程式造成的意外後果,也可以讓系統更靈活。

接下來就來看看一個簡單的範例是怎麼做的

// 鴨子的抽象類別
abstract class Duck {
// 宣告兩個行為介面型態的參考,所有的鴨子類別都會繼承他們
flyBehavior: FlyBehavior;
quackBehavior: QuackBehavior;

// 在建構子中初始化兩種行為的介面內容
constructor(flyBehavior: FlyBehavior, quackBehavior: QuackBehavior) {
this.flyBehavior = flyBehavior;
this.quackBehavior = quackBehavior;
}

// 必須實作的抽象方法
public abstract display(): void;

public performFly(): void {
this.flyBehavior.fly();
}

public performQuack(): void {
this.quackBehavior.quack();
}

public setFlyBehavior(fb: FlyBehavior): void {
this.flyBehavior = fb;
}

public setQuackBehavior(qb: QuackBehavior): void {
this.quackBehavior = qb;
}

public swim(): void {
console.log("All ducks float, even decoys!!");
}
}

// 所有飛行行為的介面
interface FlyBehavior {
fly: () => void;
}

// 實作會飛的飛行行為(實作介面 - FlyBehavior)
class FlyWithWings implements FlyBehavior {
public fly(): void {
console.log("I'm flying!!");
}
}

// 實作不會飛的飛行行為(實作介面 - FlyBehavior)
class FlyNoWay implements FlyBehavior {
public fly(): void {
console.log("I can't fly");
}
}

// 所有鳴叫行為的介面
interface QuackBehavior {
quack: () => void;
}

// 實作會叫的鳴叫行為
class Quack implements QuackBehavior {
public quack(): void {
console.log("Quack");
}
}

// 實作不會叫的鳴叫行為
class MuteQuack implements QuackBehavior {
public quack(): void {
console.log("Silence");
}
}

// 實作吱吱叫的鳴叫行為
class Squeak implements QuackBehavior {
public quack(): void {
console.log("Squeak");
}
}

class SuperDuck extends Duck {
public display(): void {
console.log("I'm super duck");
}
}

const flyWithWings = new FlyWithWings();
const flyNoWay = new FlyNoWay();
const squeak = new Squeak();
const muteQuack = new MuteQuack();

const superDuck = new SuperDuck(flyWithWings, squeak);

superDuck.display(); // I'm super duck
superDuck.performFly(); // I'm flying
superDuck.performQuack(); // Squeak
superDuck.setFlyBehavior(flyNoWay);
superDuck.setQuackBehavior(muteQuack);
superDuck.performFly(); // I can't fly
superDuck.performQuack(); // muteQuack

我們會有抽象的鴨子類別(abstract class Duck)、飛行行為的介面(interface FlyBehavior)、鳴叫行為的介面(interface QuackBehavior)。

abstract class Duck : 鴨子的介面中將飛行與鳴叫的行為定義好,並且在建構子中接受兩種行為,也就是我們把會變的部分獨立拉取出去做封裝。鴨子裡實際的鳴叫和飛行行為是委託給建構子中接受的參數去做的(flyBehavior、quackBehavior),以及我們也提供兩個method可以去更改鴨子的飛行和鳴叫的行為。

interface(FlyBehavior、QuackBehavior) : 介面用來被實作,也就是我們將會變的行為抽象成介面,並且去實作它,接著再將作為Duck的建構式的參數傳入。

我們針對介面去實作我們要的行為,這樣我們就能因應各種的鴨子品種去實作各式各樣不同的行為內容,而不是在Duck類別裡直接實作行為讓我們動彈不得。

設計原則:針對介面寫程式,而不是針對實作。

而鴨子的行為也不是因為繼承而來,而是透過和行為物件組合起來獲得的。

在以上用組合建立的範例中提供很大的彈性,它不僅可以將一系列的演算法封裝成各自的類別,也可以讓你在執行期改變行為,只要你所組合的物件實作了正確的行為介面。

設計原則:多用組合,少用繼承。

重點提示

接下來我們來看看這個章節中重要的重點提示:

  • 知道物件導向的基本概念不會讓你成為優秀的物件導向設計者。
  • 好的物件導向設計是可重複使用的、可擴展的、容易維護的。
  • 模式可以告訴你如何做出優質的物件導向設計。
  • 模式是行之有效的物件導向設計經驗。
  • 模式不提供程式碼,而是提供通用的解決方案,幫你處理設計問題。
  • 模式不是被發明出來的,而是被發現的。
  • 大部分的模式與原則都是為了處理軟體的變動。
  • 大部分的模式都可以讓系統的某個部分獨立於其他部分進行改變。
  • 我們通常會將系統中會變的部分封裝起來。
  • 模式提供共同的語言,可以讓開發者之間的溝通產生最大的價值。

結語

之前自己對設計模式一直是處於聽過,但不知道他實際上內容是什麼。自己在看完這本書之後,其實解了蠻多之前聽過但不懂的概念,而那些概念都是圍繞著如何寫出可以重複使用、可擴展、容易維護的程式碼,同時也讓我擁有用更高的層面來看如何設計程式架構這件事情的能力,以及對物件導向設計有更深一層的認識。

下一篇要介紹:觀察者模式(Observer Pattern)

這篇就到這邊瞜,如果有任何問題歡迎留言給我,喜歡的話也別忘了clap起來唷。

--

--