本文不是為了論證面向對象方法論
那需要深厚的理論知識和豐富的實踐經驗
本人兩方面都差得很遠
這裡只是試圖給出一個對面象接口的深入淺出的簡單原則
就象數學很難
數論很難
但是九九表不難
各位數字之和被
整除推出這個整數能被
整除也不難
(但是
兩者都很有用)
其實
總感覺oo被多數人都誤解了
Fp世界的人一說oo
必然就拿出oo的類呀
繼承啊
來和fp比較一番
多態被他們理解為在繼承中的一種定制(也就是override了)
而oo世界中的人呢
有的也是抱著類
繼承不放
增量設計是他們的聖經
有的則是捧著一本本經典的面向對象著作念經
什麼design pattern拉
refactoring啦
OO software construction啦
孜孜不倦地一個一個原則
一個一個定義
一個一個模式地反復辨析
所謂讀書破萬卷
下筆如有神啊
書上很多微言大義被反復引用
到處套用
但是有時候卻總是看上去不是那麼回事
同一句話可以被兩個人引用卻得到不同的結論
就象都是讀新約的Christian
卻搞出了天主教
新教等等互指為異端的教派
到底是書錯了?還是讀書的人錯了?
個人最討厭故弄玄虛
把簡單的事情搞復雜
這裡
讓我試試能不能簡單地解釋一下面向接口這個oo原則
任何軟件都是由各種不同的模塊組成的(沒錯
最小的軟件
如一個hello world
也是)
從自頂向下的觀點看
一個大模塊由若干個小模塊組成
一個小模塊又由若干個更小的模塊組成
就象大樓由磚造成
磚由分子組成
分子由原子組成一樣
這些模塊之間不可能是互相獨立的
相互之間肯定要有各種關系
這些關系可以被總結為簡單的兩種
需求和服務
需求
就是我要求別人給我提供什麼樣功能的服務
服務
就是我提供一個什麼樣功能的服務
所有的關系
都是這樣兩個原子關系的組合
當設計任何一個模塊的時候
你所看見的就只應該是這個模塊對外界的需求和要提供的服務
你不應該看見隔著十萬八千裡的模塊乙
也不應該看到容器或者配置文件是如何把模塊們(包括你現在設計的模塊)組裝起來的
那些
都屬於另外一個維度
另外一個和你不相交的宇宙空間的事
一些c++同志喜歡二分法
軟件在他們那裡變成一個簡單的庫
用戶這樣的結構
在他們看來
庫可以任意復雜
只要給用戶提供一個簡單的接口就夠了
他們沒有看到
所謂的
庫
用戶
的劃分是相對的而不是絕對的
一個模塊提供一定的功能
那麼它相對於使用它的功能的模塊就是一個
庫
而這個模塊可能還要別人提供一些功能
那麼象對於提供這些服務的模塊
它又是
用戶
兩個模塊
很又可能互相都是用戶
也都是庫
(只不過相對於不同的服務層面
不同的維度而已)
這樣的服務/需求的關系遍布於軟件的各個地方
而所謂oo
面向接口
就是用來管理這些依賴關系的
就象你整理自己的計算機網絡布線或者電視機後面的各種顏色的線一樣
oo也就是一套行之有效的整理這些關系
讓它們不要變成一團亂麻的經驗之談
任何一個理論系統
要想優美
就要遵循下面的准則
完整
自恰
簡單
比如幾何學
用了幾條最簡單的
互相不相關的(所謂
正交
是也)的公理
組建出了一個宏偉的大廈
面向對象的設計原則也應該如此
我試著給出下面兩個公理
讓我們看看能不能
.完整地描述面向對象方法
.不自相矛盾
.簡單
原則A
需求者只要求自己需要的
ask no more
ask no less!
原則B
服務者只提供最小的能夠提供足夠功能的界面
promise no more
promise no less!
從這兩個原則
我們試著推演一下其它的許多oo的准則來
.Ioc原則
或者dip原則
所謂具體依賴抽象
抽象不依賴具體
這是關於需求者的一個設計方法
遇到一個需要的功能
這個功能的實現實際上和我自己模塊的實現不相關
正交
所以我定義一個接口
從外界注射進來一個實現
那麼
用原則A是怎麼得到這個准則的呢?
首先
ask no less
所以如果功能不是和我正交的
那麼僅僅定義一個接口從外界注射進來對我就不夠
不符合no less
比如
我的實現碰巧讓我需要一個InputStreamReader
而不能是StringReader
那麼
如果僅僅從外界注射進來一個Reader
對我的實現來說
它達不到我的要求
所以
根據no less
ioc進來的需要是和當前模塊實現正交的
然後
no more
如果我不用ioc
直接自己new一個FileReader如何?本來只需要InputStreamReader
你卻要求它的子類型FileReader? 明顯違反了no more的要求
再舉個例子
ioc要求不要new
而是從外界注射
那麼是不是說我們就永遠不能new呢?永遠都不能X
instance()呢?
當然不是
注意
我們的前提是正交
是no less
假如
我有一個抽象工廠
java代碼:
interface PersistenceFactory{
}
Persistence create();
}
那麼
當實現這個工廠的jdbc實現的時候
很可能是這樣
java代碼:
class JdbcPersistenceFactory{
}
Persistence create(){
}
Return JdbcPersistence
instance();
}
}
這裡
你用了一個靜態工廠
直接依賴於JdbcPersistence實現類了
是不是違反了ioc規則呢?
當然不是
請注意
我們的模塊本身就是實現JdbcPersistence的
那麼
從外界再ioc一個PersistenceFactory或者Persistence就不符合正交
no less的要求了
而且
其實從常識就可以看出來
你JdbcPersistenceFactory的任務就是生成一個關於jdbc的PersistenceFactory
你如果自己不做
再ioc進來
這層層推诿
真正的工作誰做呢?
.Lsp
所謂任何地方如果你期待的是一個父類型Base
那麼把它替換成任何的子類型Derived
Derived
程序都能正常工作
還是關於需求者的
如果你做到了ask no more
比如說你只需要Base提供的功能
就不要在接口上要求Derived
Derived
如此
我們自然可以任意替換實際的實現
如果你做到了ask no less
需要InputStreamReader就直接要求InputStreamReader而不是Reader
你就不會需要在代碼中做downcast到InputStreamReader的動作
也就不會出現把Reader替換成StringReader之後出現的運行時錯誤
.單一職責原則
一個模塊只應該做一件事
仍然是需求者的設計方法
這裡的
事
的概念應該是一個正交於其它
事
的功能
兩個互相緊密耦合的
事
其實是一件事
根據ask no more
如果一個模塊做了兩件正交的事
也就是把兩個正交的模塊耦合在一起
就意味著在我這個濫模塊的某個地方有從一個模塊到另一個模塊的不正當的需求
你要求了你不應該要求的
Ocp
開閉原則
軟件
模塊應該是可以不用改動代碼而被擴展的
其實
ocp與其說是一個原則
不如說是一個理想
它並沒有指出具體的可操作方法
而只是給了一個目標
一些人認為這就意味著類可以繼承
這個看法太狹隘了
擴展一個模塊固然可以用繼承和override
但是
用接口組合一樣可以做到
關鍵是
如果你的模塊依賴抽象的接口而不是具體的類
那麼別人就可以很容易通過接口組合
adapter
decorator什麼的通過給你傳遞不同的接口實現而達到擴展的目的
這裡面
仍然是一個簡單的ask no more在起作用
總而言之
所謂面向接口
對需求者來說
就是
用接口定義好自己需要的功能
no more
no less
而所謂
多態
就是用來實現接口用的工具而已
完了
以上都是關於需求者的
那也是面向接口的主要方面
那麼如何約束服務者呢?
封裝啊
封裝實際上完全是給服務提供者的工具
你可以用它來隱藏自己的實現細節
通過最小化對客戶的服務承諾來得到最大的設計彈性
你要寫一個BankAccount
是否要公開所有的內部成員呢?一般可能都不是吧?
對於這些服務的提供者
如果公開了數據成員
那麼對用戶的account
balance =
;這種動作
你沒有任何彈性
只能老老實實地做field update
相比於setBalance()
後者可以自由地在內部做trace啦
或者把職責轉交給內部類啦
等等等等
靈活得多
那麼
為什麼後者靈活呢?因為用方法封裝了field之後
我們promise的東西少了
我不對客戶承諾
我肯定修改我的balance成員變量
而是簡單地說
我肯定會修改那個邏輯上的balance
你再getBalance()就可以得到這個新的值
至於我是不是物理上內部有一個balance變量
是不是setBalance()就直接去修改這個變量
對不起
無可奉告
我可能沒有
也可能有
可能今天沒有明天有
也可能今天有
明天一重構就沒有了
兩者其實都達到了用戶的需求
但是後者明顯沒有承諾不必要承諾的實現細節
所以根據promise no more的原則
封裝後比封裝前好
下面再唠叨一遍靜態工廠
對於類
java代碼:
class X implements I{
}
public X(){…}
public static I instance(){return new X();}
}
下面兩個方法都各自對服務做了什麼承諾呢?
jav
From:http://tw.wingwit.com/Article/program/Java/hx/201311/25924.html