當我們想要整理和分類物品的時候,會有許多判斷和想法,通常會將物品按照特定的步驟分類
- 依照類型: 把不須冷藏食物放櫃子、需冷層或冷凍食物放在冰箱中
- 依照大小: 確定空間是否足夠大來容納一個物品
- 依照用途: 蔬菜放下層、冷凍食品放上層
- 依照期限: 期限長短由內至外排序
在程式裡面的體現就是 if-else 條件語句,根據不同的條件執行不同的程式碼塊,舉個例子來看看
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
| 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) { } }
|
在上面的例子中,我們就會發現判斷的條件越來越多,隨著角色的擴充邏輯就會漸漸失控。
延續剛剛的例子,如果今天收到老闆新的需求希望成年的使用者可以多使用額外的隱藏功能,這樣一來是不是又要再一次的複雜化我們的程式呢?
1 2 3 4
| function isPlayerAdult(player) { return player.age >= 18; }
|
小編在過去幾年的經驗當中有體驗過底下四種邏輯控制的方法,各有各的好處可以依照情境使用
- Guard Clause
- Railway Programming
- Strategy Pattern
- Inversion of Control
Guard Clause
Guard Clause 是 JavaScript 程式碼中的一種常見技巧,目標是為了取代複雜的 if-else 判斷語句。
守衛的目的主要提前檢查並處理錯誤,只要提早發現錯誤就提早處理錯誤並提早跳出。
以剛剛的年齡判斷來說 Guard Clause 的範例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function checkAge(age) { if (age < 0) { console.log("年齡不能為負數"); return; }
if (age < 18) { console.log("你尚未成年"); return; }
console.log("你是一個成年人"); }
checkAge(25); checkAge(12); checkAge(-5);
|
在這個例子中,我們使用了兩個 Guard Clause 來檢查不合格的情況,即年齡是否小於 0 和是否小於 18,如果發現不符合成年人條件,函式會提前結束,並輸出相應的訊息。
Guard clauses 在 JavaScript 中的使用場景通常包括檢查函數的參數,處理邊界情況,確保數據有效性,並提前處理錯誤。
這有助於減少錯誤和不正確的操作,同時提高了程式碼的可靠性和可讀性。
Railway Programming
Railway Programming 通常是指使用 Functional Programming 技巧來處理錯誤流,在這種模型中,資料流就像在鐵路軌道上運行的火車一樣,可以選擇不同的方向 左
或右
軌道,具體取決於處理成功 (right) 或失敗 (left)。
Functional Programming 中的 Either 就是一個包著正確值或是錯誤的盒子,以下是一個簡單的 Railway Programming 使用 Either 來表示成功和失敗的情況,檢查年齡是否為成年,然後執行一個操作。
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
| const Either = { left: (value) => ({ isLeft: true, value, }),
right: (value) => ({ isLeft: false, value, }), };
const isInvalidAge = (age) => isNaN(age) || age < 0; const isChild = (age) => age < 18;
function checkAge(age) { if (isInvalidAge(age)) { return Either.left("年齡不能為負數"); }
if (isChild(age)) { return Either.left("未成年"); }
return Either.right("成年"); }
function processAge(age) { return checkAge(age).isLeft ? `錯誤:${checkAge(age).value}` : `結果:${checkAge(age).value}`; }
console.log(processAge(25)); console.log(processAge(15)); console.log(processAge("abc"));
|
Strategy Pattern
策略模式用於在元件間共享行為和邏輯,目的是將可互換的行為封裝成獨立的策略物件,在運行時動態選擇適當的策略來執行特定的任務。
可以幫助實現元件的
在 React 中,策略模式可以應用於元件的行為和邏輯。
- 策略元件: StrategyA 和 StrategyB,這兩個元件分別實現了不同的行為
- 策略是 ‘A’ -> 渲染 StrategyA
- 策略是 ‘B’ -> 渲染 StrategyB
- ContextComponent 是上下文元件,根據傳遞的策略選擇渲染相應的策略元件
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
| const StrategyA = ({ data }) => { return <div>Strategy A: {data}</div>; };
const StrategyB = ({ data }) => { return <div>Strategy B: {data}</div>; };
const ContextComponent = ({ strategy, data }) => { if (strategy === "A") { return <StrategyA data={data} />; } else if (strategy === "B") { return <StrategyB data={data} />; } };
const App = () => { const strategy = "A"; const data = "Hello, Strategy Pattern!";
return <ContextComponent strategy={strategy} data={data} />; };
|
以剛剛年齡的例子來說
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
const AgeVerificationStrategies = { adult: (age) => age >= 18, positive: (age) => age >= 0, };
const ContextComponent = ({ strategy, age }) => { const verifyAge = AgeVerificationStrategies[strategy];
if (verifyAge(age)) { return <div>年齡驗證通過:{age} 歲</div>; } else { return <div>年齡驗證失敗:{age} 歲</div>; } };
const App = () => { const strategy = "adult"; const age = 25;
return <ContextComponent strategy={strategy} age={age} />; };
|
Inversion of Control
控制反轉是一個常見改變程式結構和元件相互互動方式的一種方式,提倡將控制權交給框架或容器,將控制權交給使用的人。
面板加上輸出端子會變成螢幕,加了上網功能搭配遙控器
後就成為上網電視
遙控器就是控制反轉的一個概念,我們不直接操作上網電視而是透過遙控器,不管是實體遙控器或著是手機 APP 都可以達到一樣的目的,電視的操作流程不被預先定義,而是當使用者使用時才用遙控器去進行各種不同的操作流程。
底下舉兩個簡單的例子,透過把 normalize
的控制權放到 props 就能夠動態的去改動 input 文字大小寫。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const Input = ({ normalize }) => { const [value, setValue] = useState("");
return ( <input value={text} onChange={({ target }) => setValue(normalize(target.value))} /> ); };
<Input normalize={(text) => text.toUpperCase()} />; <Input normalize={(text) => text.toLowerCase()} />;
|
把剛剛的例子加上年齡限制
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
| const Input = ({ onInputChange, isAgeValid }) => { const [value, setValue] = useState(""); const [isValid, setIsValid] = useState(true);
const handleInputChange = ({ target }) => { const inputValue = target.value; const normalizedValue = onInputChange(inputValue); setValue(normalizedValue);
const age = parseInt(normalizedValue, 10); const isValidAge = isAgeValid(age); setIsValid(isValidAge); };
return ( <div> <input value={value} onChange={handleInputChange} style={{ borderColor: isValid ? "green" : "red" }} /> {!isValid && <p>年齡必須在 18 到 100 歲之間</p>} </div> ); };
const App = () => { const toUpperCaseNormalize = (text) => text.toUpperCase(); const isAgeValid = (age) => age >= 18 && age <= 100;
return ( <div> <Input onInputChange={toUpperCaseNormalize} isAgeValid={isAgeValid} /> </div> ); };
|
Dependency Injection
實現 IoC 有多種方式,通常與依賴注入(DI)密切相關。
元件或物件都是從外部提供其依賴的功能或服務而不是在內部建立它們。
以剛剛那個例子來說
1 2 3
| const isAgeValid = (age) => age >= 18 && age <= 100;
<Input onInputChange={toUpperCaseNormalize} isAgeValid={isAgeValid} />;
|
isAgeValid
就被注入到 Input
元件中,這樣即使驗證年齡的法規變了也可以輕易的改動,而不影響程式的核心的輸入邏輯。
好萊塢法則
不要給我們打電話,我們會給你打電話 (don’t call us, we’ll call you)
好萊塢法則
好萊塢法則的一個主要概念是提供了時間和使用上的彈性,不先預設定義好所有情境,而是當需要用到的時候,由使用的人再定義並給予規則或回應方式。
喜歡這篇文章,請幫忙拍拍手喔 🤣