今天好疲累一定是因為星期三,暫時先發概念的物件導向,明天開始再來看TypeScript。
可以了解物件導向的需求以及設計原則,讓就算沒有行遍萬里路的新手上路程式設計師,也能知道哪些狀況是需要避免的,以及能以更宏觀的角度在設計建構程式,內容擔心篇幅過大,未包含程式碼,另外可能不是那麼全面甚至有錯誤,可留言討論。
耦合可以用積木來舉例,如果小時候玩的積木都是用黏著的,當想重組或是想把我蓋好的城堡屋頂換到別的城堡時,會相當困難,必須努力拆開,然後再黏上新的城堡,但如果城堡頂端大小不一,美觀上就是一個問題,可能又要拆更多部分,才能在裝在新城堡上。 這也是高耦合的表現。
那該怎麼解決的呢?
樂高就設計了同樣的凹凸街口,讓我們可以快速拆裝不同的積木,也讓耦合降低可以拆裝自如,這就是解耦合的方式,增加統一介面,也是設計模式跟原則可以避免及改善的。
內聚也可以用積木舉例,假設我今天想組裝一個車子1:1的模型,於是我特別打電話給積木廠商,訂了一堆大型客製化積木,包含一體的斜面積木,等等..沒錯我組完了一台車,但我這時候想蓋城堡,於是我拆掉發現,一體的斜面以及大型積木根本沒有辦法拆成蓋城堡的大小,於是我又要重新購買積木,這就是低內聚。
每一個積木職責應該可以切割更小,每一單位職責更細更單一。大型積木可以由小塊的積木構成,所以如果用小塊積木,未來彈性可以更大,也不會需要再重新訂製積木。可以更有彈性,不用再耗費重製成本,
所以高內聚、低耦合是目標也是理想!
A class should have only one reason to change.
並不是功能單一化,而是職責單一化。
例子
可使用車子前進、後退、開窗、加速、查看油量、洗車、加油。
洗車以及加油並不是車子的職責,洗車內的方法也是呼叫洗車員來洗車,當今天洗車廠倒閉了,也得修改汽車內的洗車方法,加油站意同。
解決方法
抽離洗車、加油方法給洗車員工以及加油員工,降低耦合且降低修改率。
擴充開放、修改封閉
當增加新功能時,儘量不要修改原程式碼的狀況下進行擴充。通常違反OCP發生在高耦合的設計、透過if/else判斷,等等..新需求產生時,需要大規模修改或是變更相關類別。
避免方法有:繼承、抽象介面、裝飾模式(Decorator Pattern)
子類別需兌現對父類別的承諾,遵照父類別設計開發。
只要是父類別或是Interface出現的地方,都可以用子類別或是該 Interface實作來取代,而不會破壞程式原有的行為。
有趣的例子
鋼鐵人有Mark系列鋼鐵裝,都有按摩功能,但從初代研發到42代有點懶了。今天剛好要研發自毀功能,目的是用在鋼鐵人3電影中的一幕,但真的有點懶,就直接把自毀程式寫在Mark43的按摩功能,結果某天鋼鐵人有點累了,不小心使用按摩功能..
還有很多範例像是,矩形範例、機器鴨範例、模型範例,都是指實作了介面,卻不依照父類別或是介面意義實作,導致不可預期的結果。
所謂的介面分離原則就是:『用戶不應該被迫相依於他們用不到的函式』
即用戶不應該有他們不需要的介面功能,如果有用不到的功能表示該介面已經被污染。
如果介面太大,不符合需求就得切割介面,僅和該類別最相關即可。
例子
假設有一馬戲團,Interface鳥類都有,走、飛、吃、叫。於是馴獸師可以知道只要是鳥類表演,就用這四個指令就可以了。這時候加入一隻企鵝,馴獸師喊了飛...但企鵝不會飛。馴獸師發現有時候會遇到這種問題,得花更多心思看是什麼動物,不能再耍廢邊玩手機邊主持馬戲團節目了。感覺聽起來有點像LSP,但其實也就是如果全部都使用某一大範圍介面,會使介面意義不明且混亂。
高階模組不應該依賴於低階模組,兩者都該依賴抽象
高階模組透過依賴抽象介面,而不是直接依賴低階模組。
例子
人有吃的方法,直接依賴餅乾。人吃餅乾,就是直接使用餅乾被吃的方法。今天餅乾突然給他過度包裝了,裝了一層超精美的包裝,叫拆包裝再吃的方法,於是當餅乾上市之後,人不能直接使用餅乾被吃的方法,得修改成拆包裝再吃。這也就是高階的人得受到低階餅乾的限制。
解決方式
制定食物的介面,人吃食物且食物一定要有被吃的方法,餅乾實作食物的介面,也就是餅乾一定要實作被吃,餅乾也一定會在被吃的方法被吃掉。且可以隨時抽換其他實作食物介面的食物。
這篇真的講得很幽默,大推依賴倒置原則
DI(Dependency Injection, 依賴注入)則為其實作的方法。
物件導向程式設計基本原則 - SOLID
Wikipedia SOLID
我曾經是個冒險者