C# 物件導向 OOA OOD

【程式設計師1.0到2.0系列】(2) 很難搞懂的繼承與多型

李宗翰 2016/12/23 13:14:50
1727

主題:

【程式設計師1.0到2.0系列】(2) 很難搞懂的繼承與多型

文章簡介:

物件導向常提到的繼承與多型,觀念都懂~但在實務上應用時,往往都不知道要怎麼設計和規劃,導致理論會說,卻無法實際應用,這篇我們就來進行實務上的開發吧。

範例語言:

C#

作者:

Reck Lee

版本/產出日期:

2016/12/22


【程式設計師1.02.0系列(2) 很難搞懂的繼承與多型

作者:Reck Lee

前言:

上一篇提到了如何將傳統程式邏輯轉化成物件導向的邏輯,也提到了小小兵如何建立並封裝處理邏輯

這篇開始來說明關於物件導向最難懂的「繼承」與「多型」這兩觀念要如何進行實作,而不會卡在開發的路上。

聲明:程式設計師1.02.0是指針對開發方式擁有不同觀點的差異,並非其他涵意之解釋

內容:

跟許多1.0工程師聊過物件導向,發現最難理解的就是「繼承」和「多型」要如何開發。

最常聽到的做法,就是學校或訓練課程裡教的做法是,首先看需求,然後定義一個父類別接著該封裝的屬性或方法就封裝,該抽象abstract)的就設定抽象,該提供給繼承類別改寫(override的方法就先設計好,最後就寫一個子類別去繼承設計好的父類別以進行開發。

像是要先設計好「車」(父類別)後,再開發「小客車」(繼承於「車」的子類別),聽起來很耳熟,是吧~

乍聽之下似乎很合理,但許多人就卡在這個地方,就是如何設計一個適當的「車」去繼承?許多1.0的工程師思考這個問題後,發現很難設計出良好的「車」來提供給子類別繼承,於是通常在時程壓力下,就馬上決定放棄使用物件導向設計,先開發再說,或是乾脆複製貼上類似的類別,造成相同的程式碼位於多個類別中(違反DRY原則[*1],持續留下龐大的技術債給後續維護的工程師們。

好的~~

我們換一個思維來談,對2.0的工程師來說,繼承和多型並不是困難的事情,原因並不是2.0工程師有多少經驗去設計出「車」,而是會嘗試利用不同的思維(每個人不盡相同)去開發繼承和多型的類別,以下是我個人的見解和思維,分享給1.0的工程師參考。


1. 父類別請使群組類型的事物的概念看待,而非父子單一繼承的觀念去思考

父和子的存在很容易侷限去思考物件導向的多型特性,因為通常會有單一存在的想法(人類的父親通常只有一位),較不容易直覺的去考量彈性的設計換成可以多個存在的「現實認知的,屬於群體類型的事物」,會比較容易思考,例如「車」、「鳥」都可以,我個人使用的事物則是「部門


2. "部門的存在,是因為部員有"部門

如果只有一個部員(類別),就不需要刻意的去設計部門(父類別)給部員繼承,避免過度設計

不需要為了寫繼承和多型而刻意設計,而是需要才去設計

當有兩個以上性質相近的部員出現,為了方便管理,才需要成立一個部門,例如:A部員是負責採購罐裝飲料,B部員採購葷食便當,兩個人都是負責採購這件事,就可以考慮成立一個採購部來管理部員

原本的A部員和B部員如下(各自負責不同的工作)

成立採購部,來管理AB部員為了統一管理採購的方法,統一方法的名字叫BuyGood):


3. "部員的特性是決定到哪個部門的關鍵(繼承條件)

C部員是負責採購大瓶飲料D部員是負責採購素食便當,分析發現C部員都是負責採購飲料,部門則是負責採購便當,那麼就把C部員移到新的飲料採購部,而則移到新的便當採購部

因應C部員加入,改成立飲料採購部,將納入相同特性的部門做管理(統一採購的方法叫BuyDrink)

因應D部員加入,改成立便當採購部,將納入相同特性的部門做管理(統一採購的方法叫BuyConvenient)


4. "部門若有相同的功能且評估有需要再抽一層時,重構是一定要做的事

"飲料採購部便當採購部是具有相同功能(採購)的部門,這時最好是使用「重構」的思維,調整採購部的結構,抽出兩個部門相同的「採購」功能來做此類別的主要功能

各個部門各司其職,採購部(父類別)提供「採購」功能,飲料採購部(父類別、子類別)提供「採購飲料」功能,便當採購部(父類別、子類別)提供「採購便當」功能

重構之後,採購部就只要負責形象的傳達(像是抽象化這樣),我將概念轉換一下,形成一個具存在感的父類別如下:

飲料採購部整理如下:

便當採購部則整理如下:

看似變複雜(好多個類別)了,但請仔細看看每類別裡提供的方法,都只描述了一個作業目的,亦即符合SRP單一職責原則[*2]

單一職責原則的好處有:

I. 類別的複雜度降低 => 類別負責什麼,目瞭然

II. 可讀性提升 => 方法只描述一項作業,容易理解

III. 可維護性提升 => 只有一個作業目的,維護就輕鬆許多

IV. 變更引起的風險降低 => 降低因增加條件判斷而造成的修改意外機會


5. 多型的觀點是在運用部門,而非運用部員

搞懂物件導向的程式,最主要的是著眼在部門的運用而非部員,才能讓程式具有彈性

在程式的操作上,只要向部門提出需求就可以了,利用多型的特性就可以直接完成需求的操作;若需要更具彈性的做法,則可考慮參考使用設計模式Design Pattern)來輔助設計,這些更具彈性的做法,之後再另文章再談。

即便重構完成,也搞懂了繼承的設計方式,還是有許多工程師不太理解要如何運用

我們來舉個實例:

這段程式碼可以用「飲料採購部(DrinkPurchaseDepartment)派出A部員(EmployeeA)來處理需求,由飲料採購部負責買飲料(BuyDrink)」的方式就可以理解。

就可以知道A部員會代表飲料採購部去處理事情,得到輸出結果如下:

若是有人需要飲料採購部去買大瓶的飲料,我們知道是C部員負責的,只要修改負責人如下:

則輸出結果就由C部員去處理事情,得到輸出結果如下:


6. 彈性來自於對結構的理解並擴充,而非修改原本的(開閉原則OCP[*3])

物件應透過擴展來實作變化,而不是一直去修改原本的類別來滿足變化

E部員被告知負責採購現搖飲料,觀察它的特性屬於飲料類,與AC部員作業相同,所以將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

李宗翰