- 想像一下,你正在開發一款冒險遊戲。我們的主角是一位名叫「阿代」的勇者。阿代遇到怪獸時需要攻擊,但他有很多種攻擊方式。
- 第一階段:混亂的菜鳥寫法(沒有使用模式 )
- 阿代: 「如果我手上拿著劍,我就砍;如果我手上拿著弓箭,我就射;如果我手上拿著魔杖,我就丟火球……」
- 在程式碼裡,這就像是一個超級長的
if-else
或switch
語句: - 如果 (武器 == 劍) { 執行砍擊動作(); } 否則 如果 (武器 == 弓箭) { 執行射擊動作(); } 否則 如果 (武器 == 魔杖) { 執行魔法動作(); } … (後面還有幾百種武器)
- 這有什麼問題?
- 阿代太累了:每次你要在這個遊戲裡加一把新武器(比如「平底鍋」),你就得把阿代的身體(程式碼)切開,塞進一個新的
else if
。這很容易把阿代弄壞(產生 Bug)。 - 邏輯臃腫:阿代的程式碼會變得越來越長,最後變成一團沒人看得懂的義大利麵。
- 阿代太累了:每次你要在這個遊戲裡加一把新武器(比如「平底鍋」),你就得把阿代的身體(程式碼)切開,塞進一個新的
- 第二階段:策略模式登場(換個思維)
- 這時候,策略模式就像是一個聰明的裝備大師,它走過來告訴你:
- 「嘿!阿代不需要知道『怎麼揮劍』或『怎麼射箭』。阿代只需要知道他手上有一個攻擊按鈕,至於按下去會發生什麼,由他手上的武器決定。」
- 這就是策略模式的核心:把「做什麼(攻擊)」和「怎麼做(揮劍、射箭)」分開。
- 我們可以把這個模式拆解成三個簡單的角色:
- 1. 介面 (The Interface) —— 也就是「通用插座」
- 我們先定義一個規則,所有的武器都必須遵守這個規則。比如,所有武器都必須有一個功能叫做
attack()
。- 比喻: USB 插孔。不管你插的是滑鼠還是鍵盤,接頭形狀必須是一樣的。
- 我們先定義一個規則,所有的武器都必須遵守這個規則。比如,所有武器都必須有一個功能叫做
- 2. 具體策略 (Concrete Strategy) —— 也就是「各種武器」
- 我們把每一種攻擊方式都做成一個獨立的小零件。
- 寶劍策略:實現
attack()
→效果是「揮砍」。 - 弓箭策略:實現
attack()
→效果是「射擊」。 - 平底鍋策略:實現
attack()
→效果是「敲暈敵人」。 - 比喻: 這些就是你背包裡的各種裝備。
- 寶劍策略:實現
- 我們把每一種攻擊方式都做成一個獨立的小零件。
- 3. 情境 (Context) —— 也就是「勇者阿代」
- 阿代身上有一個「武器插槽」。他不在乎插上去的是什麼,他只管按攻擊鍵。
- 比喻: 阿代是一台遊戲主機,武器是遊戲卡帶。換卡帶就能玩不同遊戲,不用拆主機。
- 阿代身上有一個「武器插槽」。他不在乎插上去的是什麼,他只管按攻擊鍵。
- 來看一點點超級簡單的程式碼(偽代碼)
- 現在的邏輯變成了這樣:
- 第一步:定義插座(介面)
interface 武器 {
void 攻擊(); // 每個武器都要會攻擊
}
第二步:製造武器(具體策略)
class 寶劍 implements 武器 {
void 攻擊() { print("帥氣地揮砍!"); }
}
class 弓箭 implements 武器 {
void 攻擊() { print("遠距離射擊!"); }
}
第三步:勇者阿代(情境)
class 勇者 {
武器 我的武器; // 阿代手上有個武器欄位
void 設定武器(武器 新武器) {
this.我的武器 = 新武器; // 這裡就是「換裝備」的動作
}
void 戰鬥() {
我的武器.攻擊(); // 阿代:我不管手裡是啥,反正給我打就對了!
}
}
- 🎉 結局:發生了什麼改變?
- 有一天,遊戲策劃說:「我們要加一個隱藏神武——鹹魚!」
- 以前(沒有策略模式):你得打開「勇者阿代」的程式碼,在那個幾千行的
if-else
裡面小心翼翼地加一行「如果是鹹魚,就甩尾」。一旦改錯,阿代可能連劍都不會用了。 - 現在(有策略模式):
- 你完全不用碰阿代的程式碼。
- 你只要新建一個檔案
鹹魚.class
。 - 在遊戲裡呼叫
阿代.設定武器(new 鹹魚())
。 - 搞定!
- 以前(沒有策略模式):你得打開「勇者阿代」的程式碼,在那個幾千行的
- 有一天,遊戲策劃說:「我們要加一個隱藏神武——鹹魚!」
- 📚 總結:策略模式的口訣
- 「把變化的東西(策略),從不變的東西(情境)裡拿出來,包裝成獨立的零件,讓它們可以隨時替換。」
- 就像換手機殼、換電池、或是換遊戲裝備一樣自然。這就是策略模式的精髓!
完整程式碼如下
from abc import ABC, abstractmethod
# ---------------------------------------------------------
# 1. The Strategy Interface (The "Universal Socket")
# ---------------------------------------------------------
class WeaponStrategy(ABC):
"""
The interface that all weapons must implement.
This ensures every weapon has an 'attack' method.
"""
@abstractmethod
def attack(self):
# Every concrete weapon must implement this method
pass
# ---------------------------------------------------------
# 2. Concrete Strategies (The "Weapons")
# ---------------------------------------------------------
class Sword(WeaponStrategy):
def attack(self):
# Implementation for sword attack
print(">> Slash! The hero strikes with a sword.")
class Bow(WeaponStrategy):
def attack(self):
# Implementation for bow attack
print(">> Whoosh! The hero shoots an arrow from a distance.")
class FryingPan(WeaponStrategy):
def attack(self):
# Implementation for the funny weapon
print(">> Bonk! The hero hits the enemy with a frying pan.")
# ---------------------------------------------------------
# 3. The Context (The "Hero")
# ---------------------------------------------------------
class Hero:
def __init__(self, name, weapon: WeaponStrategy):
"""
The Hero is initialized with a name and a default weapon.
:param weapon: An object that follows the WeaponStrategy interface
"""
self.name = name
self._weapon = weapon
def set_weapon(self, weapon: WeaponStrategy):
"""
Allows the hero to switch weapons (strategies) at runtime.
"""
print(f"\n[{self.name} is switching weapon...]")
self._weapon = weapon
def fight(self):
"""
The hero delegates the attack logic to the weapon object.
The hero doesn't know HOW to attack, just calls the method.
"""
print(f"{self.name} starts fighting:")
self._weapon.attack()
# ---------------------------------------------------------
# Client Code (The "Game Logic")
# ---------------------------------------------------------
if __name__ == "__main__":
# 1. Create a hero named "Ardai" starting with a Sword
hero = Hero("Ardai", Sword())
# Ardai fights with the sword
hero.fight()
# 2. Strategy Pattern in action: Switch to a Bow dynamically
hero.set_weapon(Bow())
hero.fight()
# 3. Switch to a Frying Pan
hero.set_weapon(FryingPan())
hero.fight()