單一職責原則是物件導向裡極為重要的概念,它主張一個類別或一個模組應該只能有一個修改的理由。假設,我們寫了一個叫做 TriangleShape 的類別:
public class TriangleShape { public float m_fBase{ get; set; } public float m_fHeight{ get; set; } public float Area(){......} public void Draw(){......} }這時,如果有人問起這個類別做了什麼事,而你的回答是「這個類別除了可以拿到三角形的底邊、高、面積之外,還可以有畫出三角形的功能呢!」當你發現描述類別的時候,出現了「還」這種字眼,其實就代表著你的類別已經超出一個以上的職責了。上面的例子還算可以接受,但如果你有一個類別超過三四十個方法,而且處裡的事情很多元,那可能會是一場災難的開始。
類別裡的實體變數應該要少少的,而且類別裡的方法應該要充分的利用它。當你開出的方法用到越多實體變數,代表這個方法更凝聚於該類別。也就是說,每個變數如果都被使用在每個方法中,你就會有一個超大凝聚力的類別。我們應該要盡可能設計高凝聚性的類別,讓他們結合成一個邏輯上的整體。
我們舉一個考慮高凝聚性時該注意的一個例子:當我們要為一個類別新增一個的方法(FunctionB)時,如果我們為此而新增很多實體變數(VariableD、VariableE),通常會造成類別的凝聚性變低。你應該要試著把它獨立出來(新增 ClassB)。
系統的改變是持續性的,每一次的改變都讓我們承受一些風險。以下類別為資料庫串接的模組設計。記住,這個類別我很確定他還會在繼續擴增
public class Sql { public Sql(string table); public string Create(); public string Insert(Object[] fields); public string Find(string key); }如果未來的某一天,我們需要為上面這個類別新增一個「Update」的方法,你會怎麼做?應該很直覺的,直接在類別最下方新增 Update Function 吧。但你能保證新增的方法不會影響到其他功能嗎?或者是,新增的這項功能還要去修改到別地方呢?這是都我們不樂見的,畢竟修改一個類別,你就必須承擔它可能會被改壞的風險。為了避免這樣的問題,我們可以把程式改寫成以下模式:
public class Sql { public Sql(string table); public abstract string generate(); } public class Create : Sql { public Create(string table); public override string generate(); } public class Insert : Sql { public Insert(string table, Object[] fields); public override string generate(); } public class Find : Sql { public Find(string table, string key); public override string generate(); }這樣的設計會變得很好擴充,如果我要新增 Update 功能,沒有任何一個已存在的類別需要被修改。我們只要把 Update 的程式邏輯寫在一個 Sql 的新子類別中就可以了。這就是物件導向設計的關鍵原則 – 開放閉合原則:我們要對類別的擴充具有開放性,但是對修改要有封閉性。
假設你今天要建立一個車子的類別,這個類別裡會用到「輪子」這個物件,你會怎麼寫?你應該會很直覺地寫出下面這段程式吧
public class Wheel{...} public class Car { private Wheel[] m_wheel; }我們在學物件導向的時候,會說 Car 這個類別有(Has a) Wheel 這個類別型態。也就是說,Car 是依賴 Wheel 這個類別。如果我們以模組的階層來看,Car 這個模組就是所謂的高層次模組,而 Wheel 就是在低層次的模組(就想像成長官和部屬的關係,高層長官可以命令低層員工做事。就像車子可以控制輪子一樣)。但這樣的設計並不完美。你可以設想一下,如果今天我們的輪子可以換成大輪子(LargeWheel)和小輪子(SmallWheel)的話,會發生什麼事?你會發現,又要為這兩種輪子各寫一個類別,而且這幾個類別其實根本大同小異,想必會有很多重複的程式在裡面。但這還不是最糟的,有沒有想過,你可能在組裝大輪子的時候,修改了一些車子本體的框架,結果導致本來可以裝一般輪子的車體框架,變得不能裝了,而且小輪子看似也裝不進去。正當你以為只要把大輪子塞好塞滿時,老闆竟跑來跟你說:「恩......我覺得這台車子應該三種輪子都要用上」
為了避免上述的這種窘境,設計模式就提出了一個叫做依賴反轉原則。他的定義如下
- 高層次的模組不應該依賴於低層次的模組,兩者都應該依賴於抽象介面。
- 抽象介面不應該依賴於具體實現。而具體實現則應該依賴於抽象介面。
以上面我們提到的例子來看,我們應該要建立一個 IWheel 的抽象類別,讓 Car 去依賴這個抽象類別。而且 IWheel 這個抽象類別不應該實作任何東西,讓繼承他的 LargeWheel 和 SmallWheel 去實作它。
public interface IWheel{...} public class NormalWheel : IWheel{...} public class LargeWheel : IWheel{...} public class SmallWheel : IWheel{...} public class Car { private IWheel[] m_wheel = new IWheel[3]; m_wheel[0] = new NormalWheel(); m_wheel[1] = new LargeWheel(); m_wheel[2] = new SmallWheel(); }我們在設計模組時,應該要先把抽象介面定義好,它該包含什麼變數和方法,先列出來。當我們要處裡高層次模組時,就要用已經定義好的抽象介面來實作,這時如果我們發現有少什麼功能,再加到抽象介面都還來的及。等到一切都連結好後,我們再來各別為低層次的模組來設計,當然,這時設計的低層次模組都要照著抽象介面的規則來,所以你不可能設計出裝不進車體的輪子了。這麼做不但增加了擴充的可能性,也隔離了我們可以修改的部分。不恰好也遵守了開放-閉合原則嗎?
沒有留言:
張貼留言