面向?qū)ο蟪绦蛟O(shè)計原則(Principles of OOD)——SOLID

進行面向?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原則的,dialhangup屬于連接管理功能,而sendrecv則是數(shù)據(jù)管理,也就是對于Modem接口,有超過一個以上的理由去修改。

我們可以把Modem拆分成DataChannelConnection兩個接口,這樣做似乎有點過于繁瑣,并且這兩個部分經(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”,這個沒那么容易,同時出于方便考慮,在把功能細分以后重組有時也是必要的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容