iT邦幫忙

8

六大軟體開發基礎設計原則

  • 分享至 

  • xImage
  •  

這是一些工作閒暇看書看文章整理出來的資料,設計模式之禪是本不錯的好書。

六大軟體開發基礎設計原則算是寫物件導向程式語言時的好幫手,讓工程師撰寫程式時有較良好的習慣,能架構出更穩健的系統,寫出更高效能的程式碼。

目錄
I. 單一職責原則

  1. 簡介
  2. 單一職責原則的好處
  3. 實踐原則
    II. 里氏替換原則
  4. 簡介
  5. 繼承規範的四層含義
  6. 實踐原則
    III. 依賴倒置原則
  7. 簡介
  8. 依賴的三種寫法
  9. 實踐原則
    IV. 介面隔離原則
  10. 簡介
  11. 實踐原則
    V. 迪米特法則
  12. 簡介
  13. 低耦合的四層含義
  14. 實踐原則
    VI. 開閉原則
  15. 簡介
  16. 實踐原則

以上六個原則的首字母聯合起來就是SOLID(solid,穩定的),代表使用以上六個原則可以使設計更穩定、靈活與強健。
I. 單一職責原則

  1. 簡介
    單一職責原則(Single Responsibility Principle,SRP)其定義為:應該且僅有一個原因引起類別的變更。假設我們設計一個業務角色存取控制的模型,傳統上我們將增加用戶、用戶管理等功能都寫入同一個介面中,如圖1-1。這樣的設計方式會一團糟,不易於維護。我們應該把用戶的屬性與行為分開,將用戶資訊抽取成一個業務物件(Business Object,BO),把行為抽取成業務邏輯(Business Logic,Biz),按照這個思路就可以將類別圖修正為圖1-2。利用介面導向就可以依需求全部實做,或是只當其中單一功能,UserInfo要獲得用戶資訊,就當作IUserBO的實做類別,若希望能操作用戶行為,就當作IUserBiz的實做類別及可。


圖 1-1 傳統用戶資訊維護類別圖

圖 1-2 職責劃分後的類別圖

  1. 單一職責原則的好處
    單一職責原則提出了一個程式設計的標準,用“職責”或“變化原因”來衡量介面或類別設計是否優良,但是這些都是不可度量的,會因專案而異,因環境而異。單一職責原則好處如下:
     類別的複雜度降低:實現什麼職責都有清晰明確的定義。
     可讀性提升:複雜度降低可提升可讀性。
     可維護性提升:複雜度降低易於維護。
     變更引起的風險降低:修改介面只會影響相應有實做的類別,降低改錯風險。

  2. 實踐原則
    對於介面,很容易就可以做到單一職責。但是對於類別實做就得多方面考慮,生搬硬套單一職責類別可能會造成類別數量劇增,過度細分則會增加系統維護的複雜性,因此可以靈活運用。單一職責也適用於方法,一個方法盡可能只做一件事情,譬如定義一個修改用戶密碼方法,就別把此方法合併到修改用戶資訊方法中,簡而言之,單一職責方法清晰明確,可以避免別人還要猜測方法的邏輯,維護較為容易。對於單一職責原則,建議為介面一定要做到單一職責原則,類別的設計則盡量做到只有一個原因引起的變化,方法則盡可能只做一件事情。

II. 里氏替換原則

  1. 簡介
    在物件導向語言中,繼承是不可或缺的。繼承的優點如下:
     程式碼共享,減少工作量。
     提升程式碼重用性。
     子類別可形似父類別,但又易於父類別。
     提升程式碼的可獲展性。
     提升產品或專案的開放性。
    所有的事物都是優缺點並存。繼承的缺點如下:
     繼承式侵入性的。
     降低程式碼的靈活性。
     增強耦合性。
    引入里氏替換原則(Liskov Substitution Principle,LSP)可以讓“利”的因素發揮最大作用,同時減少“弊”帶來的麻煩,避免繼承時糾紛不斷或是規則壓制。里氏替換原則的定義:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.(所有參照基礎類別的地方必須能透明的使用其衍生類別的物件)。通俗解釋,只要父類別能出現的地方,子類別就可以出現,並且替換子類別不會產生任何錯誤或異常。但是反過來就不行,子類別能出現的地方,父類別未必能適應。

  2. 繼承規範的四層含義
    里氏替換原則包含了四層含義:
     子類別必須完全實做父類別的方法,如圖2-1,什麼槍都能打人。
     子類別可以有自己的個性:子類別可以有自己的方法與屬性,如圖2-2,AUG繼承Rifle還附帶狙擊鏡。
     覆寫或實做父類別的方法時輸入參數可以被放大:達成父類別能出現的地方,子類別就可以出現,子類別中方法的前置條件要比父類別中被覆寫的方法前置條件相同或更鬆。
     覆寫或實做父類別的方法時輸出結果可以被縮小:覆寫要求子類別中方法的返回值S須小於父類別中被覆寫的方法返回值T相同或是小於。


圖 2-1 槍類別圖

圖 2-2 Rifle子類別圖

  1. 實踐原則
    採用里氏替換原則可以增強程式的強健性,版本升級時也可以保持良好的兼容性。即使增加子類別,原有的子類別也可以繼續運作。在專案中採用里氏替換原則應盡量避免子類別的“個性”,避免子類別與父類別之間無法調和。

III. 依賴倒置原則

  1. 簡介
    在程式中,每個高層模組都是由低層模組組合成的,抽象是指介面或是抽象類別,細節就是實做類別。依賴倒置原則(Dependence Inversion Principle,DIP)包含三層含義:
     高層模組不應依賴低層模組,兩者都應依賴其抽象。
     抽象不應依賴細節。
     細節應依賴抽象。
    因此依賴導致原則其實就是物件導向程式設計(Object-Oriented Design,OOD)的精隨之一,以程式語言描述如下:
     模組間的依賴透過抽象類別或是介面產生。
     介面或抽象類別不依賴於實做類別。
     實做類別依賴介面或抽象類別。
    採用依賴原則可以減少類別之間的耦合性,提升系統的穩定性,降低併行開發引起的風險,並且可以提升程式碼的可讀性與可維護性。圖3-1為不使用依賴倒置原則的類別圖,駕駛只能開賓士,若要開其他種車還需要修改被繼承者,增加修改成本。圖3-2使用依賴倒置原則,抽象不依賴細節,駕駛若要開寶馬只要修改場景及可。


圖 3-1司機駕駛賓士車類別圖

圖 3-2引入倒置原則後的類別圖

  1. 依賴的三種寫法
    物件的依賴關係由三種模式來傳遞:
     建構函式傳遞依賴物件:建構函式注入。
    public interface Icar
    {
    public void run();
    }

public class Driver : IDriver
{
private Icar car;

public Driver(Icar _car) //建構函式注入
{
this.car = _car;
}
public void driver() //駕駛汽車
{
this.car.run();
}
}
 Setter方法傳遞依賴物件:在抽象或介面中設置Setter方法聲明依賴關係,稱為Setter依賴注入。
public interface IDriver
{
public void setCar(Icar car); // Setter依賴注入,車輛型號

public void driver(); //駕駛汽車
}

public class Driver : IDriver
{
private Icar car;
public void setCar(Icar car)
{
this.car = car;
}
public void driver()
{
this.car.run();
}
}

 介面聲明依賴物件:介面注入。
static void Main(string[] args)
{
IDriver Jun = new Driver(); //介面注入
Icar bmw = new BMW();
Jun.drive(bmw);
}

  1. 實踐原則
    依賴倒置原則是透過介面或抽象類別使各類別或模組的實做彼此獨立,不互相影響,以下為幾個使用原則:
     每個類別都盡量有介面或是抽象類別。
     任何類別都不應該從具體類別派生。
     盡量不要覆寫基礎類別方法。
     結合里氏替換原則來應用。

IV. 介面隔離原則

  1. 簡介
    介面隔離原則是針對介面進行規範約束,包含以下四層含義:
     介面要盡量小:介面隔離原則的核心定義,不出現臃腫的介面,但也不能過度拆分而違反單一職責原則。
     介面要高內聚:提升介面、類別與模組的處理能力,減少對外的交互作用。
     訂製服務:系統不同模組間必然有耦合,有耦合就要有互相存取的介面,訂製服務就是指提供存取需要的方法,避免臃腫的介面。
     介面設計是有限度的:介面設計越細系統越靈活,但是同時也會造成架構的複雜化,因此介面設計須適度。

  2. 實踐原則
    介面和類別盡量使用原子(不可分割的)介面或類別來組裝,如何劃分可以依據底下幾個原則衡量:
     一個介面只服務於一個子模組或是業務邏輯。
     減少介面中的方法,只留下重複使用率高的。
     舊系統若介面已經被汙染,盡量修改,若風險太高可用適配器模式轉化處理。
     了解環境,拒絕盲從。

V. 迪米特法則

  1. 簡介
    迪米特法則(Law of Demeter,LoD)也稱為最少知識原則(Least Knowledge Principle,LKP)。含義為一個物件應對其他物件有最少的了解。通俗來說就是我只要知道你提供這麼多public方法,其他我都一概不關心。

  2. 低耦合的四層含義
    迪米特法則對類別的低耦合提出明確的要求,包含以下四層含義:
     只和朋友交流:兩個物件之間的耦合就是朋友關係,類別之間應符合迪米特法則,一個類別只和朋友類別交流不要出現getA().getB().getC()這種情況。
     朋友間也有距離:盡量不要對外公布太多public方法與非靜態public變數,應盡量內斂。
     是你的就是你的:如果方法放在本類別中不會增加類別間的關係,也不會產生負面影響,那就放置在本類別中。
     謹慎使用序列化:使用序列化(Serializable)時,若一端的類別或介面已經更新了,另一端卻沒有同步更新,傳輸時存取權限不同會產生序列化失敗。

  3. 實踐原則
    迪米特法則核心理念就是類別之間解耦合、弱耦合。只有弱耦合以後,類別的重複利用率才可以提升,附帶的影響就是會產生大量的中轉或跳轉類別,使系統複雜性上升,因此使用時須反覆衡量。

VI. 開閉原則

  1. 簡介
    開閉原則的定義:一個軟體實體如類別、模組及函式應該對擴展開放,對修改關閉。含意是說一個軟體應該透過擴展來實做變化,而不是修改已有的程式碼來實做變化。以書局銷售書籍為例,類別圖如圖6-1,如果某天我們需要開始打折銷售,對以上線的專案來說,以下三種方法可以解決這個問題:
     修改介面:在IBook上新增方法專門進行打折處理,但如此一來BookStore與NovelBook都要修改,介面應該是穩定可靠的,不應經常發生變化。
     修改實作類別:修改NovelBook類別中的方法,此方法經常使用,在專案有明確章程或是優流架構設計時是個非常優秀的方法,但此方法也是有缺陷的,例如書及採購人員也是看到打折後的價格,可能會因為訊息不對襯造成決策失誤,因此此方法也不是最佳。
     透過擴展實做變化:增加一個子類別OffNovelBook覆寫,高層次的模組透過OffNovelBook類別產生新的物件,針對業務變化對系統進行最小化的開發工作,修改的少風險也較小,修改後的類別圖如圖6-2。


圖 6-1書局銷售書籍類別圖

圖 6-2擴展後的書局銷售書籍類別圖

關閉原則的重要性如下:
 對測試的影響:以擴展取代修改,要測試增加新的測試方法及可,不必修改原有的測試功能使其更複雜。
 可提升重利用性:獨立實做邏輯可使程式碼容易重複利用。
 提升可維護性:上線後維護人員已擴展取代修改可以避免受到原有程式碼海洋的摧殘與折磨。
 物件導向開發的要求:把所有事情都抽象化成物件,可以靈活快速應對變化。
如何使用開閉原則:
 抽象約束:扣過介面化抽象類別可以約束一組可能變化的行為,並且能夠實做對擴展開放,其中包含三層含義:
 透過介面或抽象類別約束擴展。
 參數類型、引用物件盡量使用介面或者抽象類別,而不是實做類別。
 抽象層盡量保持穩定,不允許修改。
 中介資料控制模組行為:中介資料為用來描述環境和資料的數據,也就是配置參數,簡而言之就是利用配置參數控制程式行為減少重複開發。
 制定專案章程:對專案來說,約定優於配置,彼此約定好溝通方式比透過介面或抽象類別約束效率更高。
 封裝變化:找出預計有變化或是不穩定的點,為這些變化點創建穩定的介面,也就是封裝可能發生的變化。
2. 實踐原則
開閉原則是其中最基礎的原則,也是其他五大原則的精神領袖。使用開閉原則時,專案章程非常重要,並且要預知可能的變化。開閉原則是一個終極目標,朝此方向努力可以非常顯著的改善一個系統的架構,真正做到擁抱變化的境界。


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
bizpro
iT邦大師 1 級 ‧ 2012-07-20 14:45:07

leonk0614提到:
我們應該把用戶的屬性與行為分開,將用戶資訊抽取成一個業務物件(Business Object,BO),把行為抽取成業務邏輯(Business Logic,Biz),按照這個思路就可以將類別圖修正為圖1-2。

圖1-2的做法似乎對了一半, Value Object的介面化的道理滿牽強的, 實務上很少如此, 應該是將之類別化. 至於Business Logic, 肯定是要介面化的, 因為面對的未知應用太多了.

leonk0614提到:
迪米特法則(Law of Demeter,LoD)也稱為最少知識原則(Least Knowledge Principle,LKP)

這是王道, 低耦合是整個軟體發展最核心的要件. 尤其應自掃門前雪, 休管他人瓦上霜.

leonk0614提到:
含意是說一個軟體應該透過擴展來實做變化,而不是修改已有的程式碼來實做變化。

理論上是如此, 實際上常發生太多的的擴展, 使得維護更加困難. 這裡是實力的展現. 以品管的角度來看, 應該是在減少可能擴展的機會, 要花更多的時間在分析與抽離現況, 盡可能的將抽離的資訊用低耦合的做法來符合未來的需求, 盡量減少未來擴展的機會.

leonk0614 iT邦新手 5 級 ‧ 2012-07-20 16:58:52 檢舉

^^謝謝您的指導, 我只算是初入社會1年, 剛觸碰設計模式的菜鳥, 請多多指教囉!

bizpro iT邦大師 1 級 ‧ 2012-07-20 21:06:23 檢舉

別在abstract(抽離, 抽象)上打轉, abstract是結果,不是方法, 方法是要以低耦合為核心, 今天這個物件模型, 我應該要怎麼做, 才能擁有"最少"但"最足夠"的知識, 產生一個抽象的"結果". 要精思考, 數學要好. 如果數學不好, 要趁年輕, 趕緊K, 這不是說, 寫軟體要用數學, 而是沒有數學的精思考訓練, 寫出的, 很容易落漆的

0

直接開書單不就好了?

  1. 大話設計模式
    作者:程杰
    出版:悅知文化
    EAN:978-986-676-1799
    庫存:未知

  2. 物件導向設計模式
    作者:四人幫 (Erich Gamma, Richard Helm, Ralph Johnson, JohnVissides)
    譯者:葉秉哲
    出版:天瓏
    EAN:978-957-205-4116

  3. 深入淺出設計模式 (Head First Design Pattern)
    作者:Elisabeth Freeman, Eric Freeman, Bert Bates, Kathy Sierra
    譯者:蔡學鏞
    出版:歐萊禮
    EAN:978-986-779-4529

  4. 深入淺出物件導向分析與設計 (Head First Object-Oriented Analysis and Design)
    作者:Brett D. McLaughlin, Gray Pollice, Dave West
    譯者:楊仁和
    出版:歐萊禮
    EAN:978-986-779-4994

我要留言

立即登入留言