在之前的文章中有提出了當重複的東西一再出現的問題,當時並沒有特別的去談一些理論和解決方法,但在後面幾天小編開始慢慢的置入 SOLID 中的
今天想來開箱另外一個設計程式時的介面隔離原則,談談透過介面的隔離來確保高內聚性 (Cohesion) 和低耦合性 (Coupling),使程式碼易於理解、擴充和維護,一起來看看有沒有機會解決上次的問題吧。
Interface Segregation Principle
讓我們複習一下之前提到的情境描述,假設我們正在開發一個角色扮演遊戲 (RPG) 的程式,其中有不同類型的玩家,包括基本玩家和進階玩家,每個玩家類型都有自己的檢查條件,重構的目標是希望確保程式碼易於擴充,以應對未來可能新增的玩家類型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
const player1 = { name: "玩家1", level: 1, health: 100, damage: 10, };
const player2 = { name: "玩家2", level: 5, health: 150, damage: 15, agility: 20, inventory: ["劍", "盾"], };
function processPlayerData( name, level, health, damage, agility, inventory, statusCode ) { if ( health > 10 && (name === "foo" || damage < 5) && (name !== "bar" || agility > 20) ) { } if (statusCode === 20100) { }
if (statusCode === 20101) { } }
|
定義介面
首先定義了 Player 的介面,包含了所有玩家類型都需要具備的通用屬性和方法。
但由於角色的不同,改善的方向應該是把角色分開,這樣每個角色都只會用到自己需要的屬性和方法。
- 基本玩家: 透過
createPlayer
函數實現了 Player 介面,確保了基本玩家包含了通用的屬性遵循了 Player 介面 - 進階玩家: 透過
createAdvancedPlayer
函數擴充 Player 介面,確保了進階玩家不僅遵循了 Player 介面也包含了進階玩家特有的屬性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| function createPlayer(playerData) { return { ...playerData, }; }
const player1 = createPlayer({ name: "玩家1", level: 1, health: 100, damage: 10, });
function createAdvancedPlayer(advancedPlayerData) { return { ...createPlayer(advancedPlayerData), }; }
const player2 = createAdvancedPlayer({ name: "玩家2", level: 5, health: 150, damage: 15, agility: 20, inventory: ["劍", "盾"], });
function processPlayerData(player) { const { name, health, damage, agility } = player; if ( health > 10 && (name === "foo" || damage < 5) && (name !== "bar" || agility > 20) ) { } if (statusCode === 20100) { }
if (statusCode === 20101) { } }
|
通用處理函數
處理通用處理函數的部分,這裡建立了 processPlayerData
函數,可以接受任何類型的玩家物件作為參數,目標是不直接依賴於特定的玩家類型讓函數更具通用性,方便未來新增更多的角色。
判斷的部份選擇把不同功能的判斷用 function 拆開,將這些條件拆分成獨立的函數可以提高程式碼的可讀性和維護性,這樣做讓每個檢查條件都有自己的名稱,更容易理解和測試。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| function isHealthy(player) { return player.health > 10; }
function hasDesiredNameOrLowDamage(player) { return player.name === "foo" || player.damage < 5; }
function hasDesiredNameOrHighAgility(player) { return player.name !== "bar" || player.agility > 20; }
function processPlayerData(player) { if ( isHealthy(player) && hasDesiredNameOrLowDamage(player) && hasDesiredNameOrHighAgility(player) ) { } if (statusCode === 20100) { }
if (statusCode === 20101) { } }
|
重構完之後發現,只有 AdvancedPlayer 才需要有 hasDesiredNameOrHighAgility,所以再把腳色拆分進行重構,讓每個玩家類型都分別具有自己的檢查邏輯,基本玩家和進階玩家分別知道如何檢查自己的條件,這就是介面隔離。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| function isHealthy(player) { return player.health > 10; }
function hasDesiredNameOrLowDamage(player) { return player.name === "foo" || player.damage < 5; }
function hasDesiredNameOrHighAgility(player) { return player.name !== "bar" || player.agility > 20; }
function createPlayer(playerData) { const { name, health, damage } = playerData; return { name, health, damage,
checkPlayerCondition() { return isHealthy(player) && hasDesiredNameOrLowDamage(player); }, }; }
function createAdvancedPlayer(advancedPlayerData) { const { name, health, damage, agility } = advancedPlayerData; const player = createPlayer({ name, health, damage }); return { ...player, agility,
checkAdvancedPlayerCondition() { return hasDesiredNameOrHighAgility(player); }, }; }
function processPlayerData(player) { if (player.checkPlayerCondition()) { console.log(`${player.name} 符合基本條件`); }
if ( player?.checkAdvancedPlayerCondition && player?.checkAdvancedPlayerCondition() ) { console.log(`${player.name} 符合進階條件`); }
if (statusCode === 20100) { }
if (statusCode === 20101) { } }
const player1 = createPlayer({ name: "玩家1", health: 100, damage: 10, }); const player2 = createAdvancedPlayer({ name: "玩家2", health: 150, damage: 15, agility: 20, });
processPlayerData(player1);
processPlayerData(player2);
|
processPlayerData
函數最後不需要知道你是哪種特定的玩家類型,只關心玩家是否符合 Player 介面,如果未來新增其他類型的玩家,我們只需透過 Player 介面建立一個新的玩家類別,而不需要修改現有的程式碼。
透過這樣的設計降低了不同角色之間的耦合性,這就是介面隔離原則 (Interface Segregation Principle),介面隔離原則有助於確保程式碼的結構清晰,並使不同部分之間的依賴關係簡化,同時支持未來的擴充和修改。
喜歡這篇文章,請幫忙拍拍手喔 🤣