進行面向?qū)ο蟪绦蛟O(shè)計的時候,我們需要面對很多問題,比如:
- 什么時候需要一個類
- 一個類應(yīng)該包含哪些功能,不包含哪些功能
- 類之間的依賴關(guān)系設(shè)計
還有很多其它的問題,如果不遵照一些原則,而一切按照“本能”來的話,你的程序很快就會成為一坨不可靠,不容易擴展,不容易復(fù)用的“屎山”。面向?qū)ο笤O(shè)計原則就是為了保證你的代碼可靠,易擴展,易復(fù)用而人為規(guī)定的一系列方法,它們是由大量資深的前輩們總結(jié)出來的經(jīng)驗。
這里先介紹一個經(jīng)典的設(shè)計原則:SOLID
| SRP | The Single Responsibility Principle | ''A class should have one, and only one, reason to change.'' |
| OCP | The Open Closed Principle | You should be able to extend a classes behavior, without modifying it. |
| LSP | The Liskov Substitution Principle | Derived classes must be substitutable for their base classes. |
| ISP | The Interface Segregation Principle | Make fine grained interfaces that are client specific. |
| DIP | The Dependency Inversion Principle | Depend on abstractions, not on concretions. |
這里先說一下SRP,參考文檔,假設(shè)我們在實現(xiàn)一個游戲,需要計算矩形的面積,以及在GUI中顯示矩形這兩個功能,那么如果你很直接地實現(xiàn)一個Rectangle類,并同時讓它實現(xiàn)計算面積跟GUI顯示,那么就違反了SRP原則。
首先,如果我們用的是C++(或者java之類,Python不會有這個問題,它是通過解釋器運行的,不用link)那么這個Rectangle類需要link GUI的庫,相對于單純只進行幾何計算,需要額外的link和compile時間以及內(nèi)存開銷(java中需要加入GUI庫的class文件)。其次,如果我們需要修改Rectangle的渲染部分代碼,由于它跟計算部分寫在同一個類里面,這會導(dǎo)致我們需要對計算部分也進行重編譯以及測試等。
所以,一個遵循SRP的設(shè)計,應(yīng)該是把計算邏輯跟渲染邏輯分別做在兩個類里面,也就是“每個類只有一個職責”。那么問題來了,我們該如何去定義一個“職責”,也就是功能應(yīng)該細分到什么粒度?無限細分顯然是錯誤的,我們沒有必要給每一個小功能定義一個類,而有時候看似合理的劃分其實又是包含了多個職責的,原文給出的說明是:“A class should have one, and only one, reason to change.”看似很好理解,其實有時候也很具有迷惑性,這里有一個例子:
如果我們定義一個Modem類,它具有以下函數(shù):
class Modem
{
public void dial(String pno);
public void hangup();
public void send(char c);
public char recv();
}
看上去似乎一切完美,但是它事實上是違背了SRP原則的,dial和hangup屬于連接管理功能,而send和recv則是數(shù)據(jù)管理,也就是對于Modem接口,有超過一個以上的理由去修改。
我們可以把Modem拆分成DataChannel和Connection兩個接口,這樣做似乎有點過于繁瑣,并且這兩個部分經(jīng)常是需要同時使用的,所以我們可以使用一個ModemImplementation類繼承這兩個接口。
class DataChannel
{
public virtual void send(char c) = 0;
public virtual char recv() = 0;
}
class Connection
{
public virtual void dial(String pno) = 0;
public virtual void hangup() = 0;
}
class ModemImplementation : public DataChannel, public Connection
{
public void send(char c);
public char recv();
public void dial(String pno);
public void hangup();
}
也許看上去這很丑陋且冗余,但這經(jīng)常是必要的,細分必然會導(dǎo)致繁瑣不便(很多場合經(jīng)常會需要同時使用多個細分功能),接口上細分,類實現(xiàn)再組合保留了便利性和SRP原則的益處,比如系統(tǒng)中有的模塊如果需要data部分,那么它可以僅引入DataChannel,通過interface去調(diào)用ModemImplementation內(nèi)部的數(shù)據(jù)部分功能,沒有任何模塊需要去依賴ModemImplementation,只有主函數(shù)需要知道ModemImplementation的存在。
總的來說,應(yīng)用SRP原則時需要正確地去區(qū)分所謂的“responsibility”,這個沒那么容易,同時出于方便考慮,在把功能細分以后重組有時也是必要的。