【程式設計師1.0到2.0系列】(2) 很難搞懂的繼承與多型
主題: |
【程式設計師1.0到2.0系列】(2) 很難搞懂的繼承與多型 |
文章簡介: |
物件導向常提到的繼承與多型,觀念都懂~但在實務上應用時,往往都不知道要怎麼設計和規劃,導致理論會說,卻無法實際應用,這篇我們就來進行實務上的開發吧。 |
範例語言: |
C# |
作者: |
Reck Lee |
版本/產出日期: |
2016/12/22 |
【程式設計師1.0到2.0系列】(2) 很難搞懂的繼承與多型
作者:Reck Lee
前言:
上一篇提到了如何將傳統程式邏輯轉化成物件導向的邏輯,也提到了小小兵如何建立並封裝處理邏輯
這篇開始來說明關於物件導向最難懂的「繼承」與「多型」這兩個觀念要如何進行實作,而不會卡在開發的路上。
【聲明:程式設計師1.0和2.0是指針對開發方式擁有不同觀點的差異,並非其他涵意之解釋】
內容:
跟許多1.0的工程師聊過物件導向,發現最難理解的就是「繼承」和「多型」要如何開發。
最常聽到的做法,就是學校或訓練課程裡教的做法是,首先先看需求,然後定義一個"父類別",接著該封裝的屬性或方法就封裝,該抽象(abstract)的就設定抽象,該提供給繼承類別改寫(override)的方法就先設計好,最後就寫一個"子類別"去繼承設計好的"父類別"以進行開發。
像是要先設計好「車」(父類別)後,再開發「小客車」(繼承於「車」的子類別),聽起來很耳熟,是吧~
乍聽之下似乎很合理,但許多人就卡在這個地方,就是如何設計一個適當的「車」去繼承?許多1.0的工程師在思考這個問題後,發現很難設計出良好的「車」來提供給子類別繼承,於是通常在時程壓力下,就馬上決定放棄使用物件導向設計,先開發再說,或是乾脆複製貼上類似的類別,造成相同的程式碼位於多個類別中(違反DRY原則[*註1]),持續留下龐大的技術債給後續維護的工程師們。
好的~~~
我們換一個思維來談,對2.0的工程師來說,繼承和多型並不是困難的事情,原因並不是2.0工程師有多少經驗去設計出「車」,而是會嘗試利用不同的思維(每個人不盡相同)去開發繼承和多型的類別,以下是我個人的見解和思維,分享給1.0的工程師參考。
1. 父類別請使用"群組類型的事物"的概念看待,而非父子”單一繼承”的觀念去思考
• 父和子的存在很容易侷限去思考物件導向的多型特性,因為"父"通常會有單一存在的想法(人類的父親通常只有一位),較不容易直覺的去考量彈性的設計,若換成可以多個存在的「現實認知的,屬於群體類型的事物」,會比較容易思考,例如「車組」、「鳥群」都可以,我個人使用的事物則是「部門」
2. "部門"的存在,是因為先有"部員",才後有"部門"
• 如果只有一個"部員"(類別),就不需要刻意的去設計"部門"(父類別)去給部員繼承,避免過度設計。
• 不需要為了寫繼承和多型而刻意設計,而是需要才去設計
• 當有兩個以上性質相近的"部員"出現,為了方便管理,才需要成立一個"部門",例如:A部員是負責採購罐裝飲料,B部員採購葷食便當,兩個人都是負責"採購"這件事,就可以考慮成立一個"採購部"來管理A和B部員
原本的A部員和B部員如下(各自負責不同的工作):
成立採購部,來管理A和B部員(為了統一管理採購的方法,統一方法的名字叫BuyGood):
3. "部員"的特性是決定到哪個"部門"的關鍵(繼承條件)
• C部員是負責採購大瓶飲料,D部員是負責採購素食便當,分析發現A和C部員都是負責採購飲料,B和D部門則是負責採購便當,那麼就把A和C部員移到新的"飲料採購部",而B和D則移到新的"便當採購部"
因應C部員加入,改成立”飲料採購部”,將A和C納入相同特性的部門做管理(統一採購的方法叫BuyDrink)
因應D部員加入,改成立”便當採購部”,將B和D納入相同特性的部門做管理(統一採購的方法叫BuyConvenient)
4. "部門"若有相同的功能且評估有需要再抽一層時,「重構」是一定要做的事
• "飲料採購部"和"便當採購部"都是具有相同功能(採購)的部門,這時最好是使用「重構」的思維,調整"採購部"的結構,抽出兩個部門相同的「採購」功能來做此類別的主要功能。
• 各個部門各司其職,採購部(父類別)提供「採購」功能,飲料採購部(父類別、子類別)提供「採購飲料」功能,便當採購部(父類別、子類別)提供「採購便當」功能
重構之後,採購部就只要負責形象的傳達(像是抽象化這樣),我將概念轉換一下,形成一個具存在感的父類別如下:
飲料採購部則整理如下:
便當採購部則整理如下:
看似變複雜(好多個類別)了,但請仔細看看每個類別裡提供的方法,都只描述了一個作業目的,亦即符合SRP單一職責原則[*註2]
單一職責原則的好處有:
I. 類別的複雜度降低 => 每個類別負責什麼,一目瞭然
II. 可讀性提升 => 每個方法只描述一項作業,容易理解
III. 可維護性提升 => 只有一個作業目的,維護就輕鬆許多
IV. 變更引起的風險降低 => 降低因增加條件判斷而造成的修改意外機會
5. 多型的觀點是在運用"部門",而非運用"部員"
• 要搞懂物件導向的程式,最主要的是著眼在"部門"的運用而非"部員",才能讓程式具有彈性
• 在程式的操作上,只要向"部門"提出需求就可以了,利用多型的特性就可以直接完成需求的操作;若需要更具彈性的做法,則可考慮參考使用"設計模式"(Design Pattern)來輔助設計,這些更具彈性的做法,之後再另闢文章再談。
即便重構完成,也搞懂了繼承的設計方式,還是有許多工程師不太理解要如何運用
我們來舉個實例:
這段程式碼可以用「飲料採購部(DrinkPurchaseDepartment)派出A部員(EmployeeA)來處理需求,由飲料採購部負責買飲料(BuyDrink)」的方式就可以理解。
就可以知道A部員會代表飲料採購部去處理事情,得到輸出結果如下:
若是有人需要飲料採購部去買大瓶的飲料,我們知道是C部員負責的,只要修改負責人如下:
則輸出結果就由C部員去處理事情,得到輸出結果如下:
6. 彈性來自於對結構的理解並擴充,而非修改原本的(開閉原則OCP[*註3])
• 物件應透過擴展來實作變化,而不是一直去修改原本的類別來滿足變化
E部員被告知負責採購現搖飲料,觀察它的特性屬於飲料類,與A和C部員作業相同,所以將E部員歸在飲料採購部,實作如下:
若有人需要現搖飲料,就派出E部員去代表飲料採購部處理,調整負責人如下:
輸出結果:
這篇談到許多跟繼承和多型有關的思維,希冀能促使傳統開發人員能開始嘗試接觸到物件導向的開發,讓程式開發能更有趣又具有活性。
今天就到此結束,謝謝。
參考資料
1、 不要自我重複(DRY - Don't repeat yourself)
https : //read01.com/yk7eQg.html
2、 單一職責原則(SRP - Single Responsibility Principle)
http : //teddy-chen-tw.blogspot.tw/2011/12/3.html
3、 開閉原則(OCP - Open-Closed Principle)
http : //teddy-chen-tw.blogspot.tw/2011/12/2.html
4、 物件導向程式設計
https : //msdn.microsoft.com/zh-tw/library/dd460654.aspx
5、 程式碼格式化工具
http : //www.andre-simon.de/zip/download.php