理解面向設(shè)計(jì)原則
面向?qū)ο笤O(shè)計(jì)(OOD)核心原則讓我的程序模塊達(dá)到“高內(nèi)聚低耦合”,這是來自于30年前興起的結(jié)構(gòu)化設(shè)計(jì)(structured Design),但是同樣適用于我們的OOD。
1.高內(nèi)聚:
高內(nèi)聚是指某個(gè)特定模塊(程序,類型)都應(yīng)完成一系列相關(guān)功能,描述了不同程序,類型中方法,方法中不同操作描述的邏輯之間的距離相近。高內(nèi)聚意味可維護(hù)性,可重新性,因?yàn)槟K對(duì)外部的依賴少(功能的完備性)。如果兩個(gè)模塊之間的修改,互不影響這說明模塊之間是高內(nèi)聚的。模塊的內(nèi)聚和其擔(dān)當(dāng)?shù)穆氊?zé)成反比,即,模塊的職責(zé)越多,模塊的內(nèi)聚性越低,這也是模塊的單一原則(SRP),SRP提倡每個(gè)類型都最好只承擔(dān)單一的職責(zé),只有單一的改變因素。
2.低耦合:
耦合是描述模塊之間的依賴程度,如果一個(gè)模塊的修改,都有影響另一個(gè)模塊則,兩模塊之間是相互依賴耦合的。(依賴具有傳遞性,耦合的兩個(gè)模塊可能間接依賴),低耦合是我們的設(shè)計(jì)目的,但不是不存在耦合不存依賴,依賴是必須的,因?yàn)槟K之間必須通信交互,不過我的設(shè)計(jì)依賴應(yīng)該依賴于不變或者不易變的接口,無需了解模塊的具實(shí)現(xiàn)(OO封裝性)。
在面向?qū)ο螅何覀兛梢院喪鰹楣δ芡陚洌ǜ邇?nèi)聚)的對(duì)象之間的交互是依賴于不變或不易變的接口契約(低耦合)。
實(shí)現(xiàn)高內(nèi)聚低耦合:行之有效的方式是分了關(guān)注點(diǎn)(SOC),將系統(tǒng)拆分成功能不同沒有重疊功能集。每個(gè)功能只關(guān)注一個(gè)方面(Aspect)保證模塊之間功能沒有或者盡量少的重復(fù)。模塊化內(nèi)部實(shí)現(xiàn)細(xì)節(jié)隱藏,只暴露必須的接口,使得模塊之間依賴于抽象,達(dá)到穩(wěn)定。分離關(guān)注點(diǎn)的思想存在于我們軟件設(shè)計(jì)的各個(gè)領(lǐng)域。如在.net的世界里SOA(面向服務(wù)架構(gòu))服務(wù)就是關(guān)注點(diǎn),只暴露出必要的契約。分層架構(gòu)從邏輯上利用接口抽象信息隱藏,減少依賴。MVC,MVP也是遵循分了關(guān)注點(diǎn)原則,達(dá)到表現(xiàn)層和邏輯的分離。
面向?qū)ο笤O(shè)計(jì)原則:
1.降低耦合度:對(duì)象直接需要交互,這就存在依賴,為了實(shí)現(xiàn)低耦合就必須減少依賴,依賴于穩(wěn)定或不易變抽象。考慮如下訂單日志記錄場(chǎng)景:我們需要在訂單每部操作記錄更改日志。
- public class OrderManager
- {
- public void Create(Order order)
- {
- //訂單處理.
- Logger log = new Logger();
- var history=GetHistory();
- log.log(history);
- }
- }
在這里我們的OrderManager和Logger存在高耦合,Logger類的修改可能導(dǎo)致OrderManager的修改,而且不能隨意切換我們的日志記錄方式,比如文件,控制臺(tái),數(shù)據(jù)庫等日志方式。
面向抽象編程提出抽象(接口,abstract類)是不易變的穩(wěn)定抽象;對(duì)于OrderManager來說我不需要了解日志記錄組件內(nèi)部,只需要明白提供那些接口可用,怎么用。
- public interface ILogger
- {
- void Log(History history);
- }
- public class Logger
- {
- public void Log(History history)
- {
- //內(nèi)部實(shí)現(xiàn)
- };
- }
那么我們可以從設(shè)計(jì)模式工廠模式(工廠模式是負(fù)責(zé)一些列相似對(duì)象的創(chuàng)建)Create 日志組件ILogger。
我們的OrderManager 就可以實(shí)現(xiàn)為:
- ILogger log =LoggerFactory.Create();
- log.Log(history);
這樣我們的OrderManager就依賴于ILogger,而隔離Logger具體實(shí)現(xiàn),將依賴于抽象,把變化縮小到Factory內(nèi)部(同樣也可以用抽象工廠),如果日志實(shí)現(xiàn)變化我們可以重新實(shí)現(xiàn)ILogger ,修改Factory邏輯,如果內(nèi)部利用配置我的需求變更轉(zhuǎn)移到配置。這就是面向?qū)ο蟮谝辉瓌t,依賴于抽象而隱藏實(shí)現(xiàn)。(利用IOC是一種更好的方式)
2.代碼的重用性:盡量保證相同功能代碼只出現(xiàn)一次(Code once run anywhere)。代碼的重用在面對(duì)對(duì)象設(shè)計(jì)中有繼承和組合兩種方式,一般推薦組合優(yōu)先。組合依賴于接口,組合更安全,易于維護(hù),測(cè)試。繼承存在父類訪問權(quán)限,父類的修改導(dǎo)致子類的變化,太多的繼承也有導(dǎo)致派生類的膨脹,維護(hù)管理也是件頭痛的事。
3.開閉原則(OCP):表述擁抱需求變化,盡量做到對(duì)模塊的擴(kuò)展開發(fā),修改關(guān)閉。對(duì)于新增需求我們完美的做法是新增類型而不是修改邏輯,這就意味著我們必須使用組合或者是繼承體系(為了避免上一條重用性,我的繼承應(yīng)該是干凈的繼承體系,派生類應(yīng)該只是新增功能而不是修改來自父類上下文),
4.里氏替換(LSP):表述派生類應(yīng)該可以在任何地方替代父類使用。并不是所有的子類都可以完全替換子類,比如設(shè)計(jì)父類私有上下文信息的訪問,導(dǎo)致子類無法訪問。
5.依賴倒置(DIP):描述組件之間高層組件不應(yīng)該依賴于底層組件。依賴倒置是指實(shí)現(xiàn)和接口倒置,采用自頂向下的方式關(guān)注所需的底層組件接口,而不是其實(shí)現(xiàn)。DI框架實(shí)現(xiàn)IOC(控制反轉(zhuǎn))就是DIP很好的插入底層組件構(gòu)造框架(分構(gòu)造注入,函數(shù)注入,屬性注入)。微軟Unity,Castle windsor,Ninject等框架支持。
最后分離關(guān)注點(diǎn),衍生出聲明式編程,面向方面編程(AOP)實(shí)現(xiàn)縱切關(guān)注點(diǎn),把具體業(yè)務(wù)邏輯和日志安全等框架集公用邏輯分離。 關(guān)于IOC/AOP參見博客我的IOC/AOP隨筆目錄 不在累贅。
原文鏈接:http://www.cnblogs.com/whitewolf/archive/2012/05/08/2489425.html
【編輯推薦】