這樣要來介紹的是開關原則,其實以字面上來說會有點讓人誤解其中的意思,個人認為有點像是積木或是拼圖疊加的概念,但是概念上也是很簡單,只要熟悉觀念即可變成自己的設計心法,讓我們來看看吧。
開關原則
在OOP的設計中,開關原則需要保持開放程式的擴充性質,並且關閉程式的修改。
聽起來好像很奇怪?那麼我們來舉例吧!
如果今天你是設計汽車的工程師,當有人拿車子要給你修理輪胎的時候,你一定只會把輪胎更換而已,並不會動到其他東西,例如:引擎, 座椅……。只需要把指定的元件更換,而不需要動到過多的東西。
換句話說
以OOP的設計原則來說,開關原則可以讓程式保持DRY(Don’t Repeat Yourself)
實例展示
讓我們來看以下的例子,我有一個訂單,裡面包含了飲品:
1 2 3 4 5 6 7 8 9 10 11
| App = () => { const drink = { name: 'milk', size: 1000, } return ( <div className="App"> <Order drink={drink}/> </div> ); }
|
訂單:
1 2 3 4 5 6 7 8 9
| Order = ({ drink }) => { const { name, size } = drink; return ( <div> <h2>{name}</h2> <p>limit: {size}</p> </div> ); }
|
問題來了
然而現在問題來了,如果今天突然出現其他需求,需要飲品的規格不太一樣:
1 2 3 4 5 6 7 8 9 10 11
| const drink2 = { name: 'coke', size: 350, isScalable: false, };
const drink3 = { name: 'water', size: 600, isPure: true, };
|
怎麼做
這個時候,一般的開發者會怎麼做呢?沒錯,就是你想的那樣
1 2 3 4 5 6 7
| return ( <div className="App"> <Order drink={drink}/> <Order drink={drink2}/> <Order drink={drink3}/> </div> );
|
當然,因為規格不一樣,因此我們必須把我們的Component改成這樣:
1 2 3 4 5 6 7 8 9 10 11 12
| Order = ({ drink }) => { const { name, size, isScalable, isPure } = drink; const [hasUpgraded, setHasUpgraded] = React.useState(false); return ( <div> <h2>{name}</h2> <p>limit: {size}</p> { isPure ? <p>Clean</p> : <p>Not clean</p> } { isScalable && <button onClick={() => setHasUpgraded(!hasUpgraded)}>Upgrade</button> } </div> ); }
|
你會發現程式碼開始變得越來越髒了,如果isPure和isScalable裡面所做的事情是大量的JSX代碼,可想而知,別人拿到這個程式碼會需要耗費較大的時間閱讀你的程式碼。那…該怎麼辦呢?那就使用HOC來改善吧!
結合HOC改善
有學過OOP相關的語言的人,應該知道上面的例子應用在物件導向上,是可以設計成繼承關係的。不過React團隊鼓勵大家使用function組件大於繼承,而High Order Function就是一個例子,他的設計原則也可以保持程式的DRY。
我們可以透過HOC把water功能從drink中抽離出來:
1 2 3 4 5 6 7 8 9 10
| const concatWaterOrder = DrinkComponent => props => { const { drink } = props; const { isPure } = drink; return ( <DrinkComponent drink={drink}> { isPure ? <p>Clean</p> : <p>Not clean</p> } { props.children } </DrinkComponent> ); };
|
最後,把共用擁有的功能變成drink component,後面接著放置其他component產生出來的DOM:
1 2 3 4 5 6 7 8 9 10
| const drinkOrder = props => { const { name, limit } = props.drink; return ( <div> <h2>{name}</h2> <p>limit: {size}</p> { props.children } </div> ); };
|
如何使用
其實這樣就客製化好我們的開關component了,使用方式:
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
| App = () => { const drink = { name: 'milk', size: 1000, }; const drink2 = { name: 'coke', size: 350, isScalable: false, };
const drink3 = { name: 'water', size: 600, isPure: true, };
const waterOrder = concatWaterOrder(drinkOrder);
return ( <div className="App"> <Order drink={drink}/> <waterOrder drink={drink3}/> </div> ); }
|
同理製作
同理,繼續做其他component
1 2 3 4 5 6 7 8 9 10
| const concatSodaOrder = DrinkComponent => props => { const { drink } = props; const { isScalable } = drink; return ( <DrinkComponent drink={drink}> { isScalable && <button onClick={() => setHasUpgraded(!hasUpgraded)}>Upgrade</button> } { props.children } </DrinkComponent> ); };
|
結果:
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
| App = () => { const drink = { name: 'milk', size: 1000, }; const drink2 = { name: 'coke', size: 350, isScalable: false, };
const drink3 = { name: 'water', size: 600, isPure: true, };
const waterOrder = concatWaterOrder(drinkOrder); const sodaOrder = concatSodaOrder(sodaOrder);
return ( <div className="App"> <Order drink={drink}/> <waterOrder drink={drink3}/> <sodaOrder drink={drink2}/> </div> ); }
|
彈性的使用方式
而且HOC是可以很彈性的使用的,你也可以這樣做:
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
| App = () => { const drink = { name: 'milk', size: 1000, }; const drink2 = { name: 'coke', size: 350, isScalable: false, };
const drink3 = { name: 'water', size: 600, isPure: true, };
const drink4 = { name: 'sparklingWater', size: 580, isPure: true, isScalable: false, };
const waterOrder = concatWaterOrder(drinkOrder); const sodaOrder = concatSodaOrder(sodaOrder); const sparklingWaterOrder = concatWaterOrder(concatSodaOrder(drinkOrder));
return ( <div className="App"> <Order drink={drink}/> <waterOrder drink={drink3}/> <sodaOrder drink={drink2}/> <sparklingWaterOrder drink={drink4}/> </div> ); }
|
如此一來,重複的特性就不需要再另外設計一個component了,很符合開關原則的重複利用與擴充性