重用為什么那么難?
程序員都是聰明人,沒有誰愿意干重復(fù)勞動這樣的傻事,因此,程序中出現(xiàn)重復(fù)代碼是程序員的恥辱。就算不能消除重復(fù)代碼,至少也可以對于相同的功能,用不同的代碼來實現(xiàn)所以發(fā)明新輪子的程序員才會那么多。
面向?qū)ο笞鳛橐环N橫空出世的新技術(shù),首先承諾的就是“更好的重用性”,而“重用性”這樣一個閃閃發(fā)光的詞,也的確能夠吸引程序員的實現(xiàn),那么多新的理論、新的技術(shù)、新的方法、新的框架、新的思想,用來說服別人接受的一個最大的理由,就是“更好的重用性”。然而,OO以及一直以來不斷發(fā)展的
OO相關(guān)技術(shù),對于重用性的提高,作出了多大的貢獻呢?
JavaEye的age0有一段話特別讓我佩服:“我還是得反復(fù)強調(diào),OO設(shè)計的價值并不在于所謂的“代碼重用”或者“接口重用”,任何一種設(shè)計方法都能夠獲得“重用”的能力,就算是寫匯編一樣可以“重用”。”一個同志能夠如此決絕的對于重用不屑一顧,真是了不起。我們還是來面向大多數(shù)希望更好的重用的程序員,分析一下在OO出現(xiàn)之后程序員是如何追求重用這一目標(biāo)的吧。
在面向過程的時代,重用很簡單,就是代碼的重用,函數(shù)、函數(shù)庫、模板、模板庫。如此而已。在ADT出現(xiàn)之后,世界分裂了。代碼重用的需求,現(xiàn)在分裂為三個部分:數(shù)據(jù)類型的重用、ADT內(nèi)部代碼的重用、操作ADT的代碼的重用。
這句話特別關(guān)鍵,讓我再仔細(xì)分析給大家看看:ADT=抽象數(shù)據(jù)類型。就是封裝了操作和數(shù)據(jù)的一種用戶自定義數(shù)據(jù)類型。
1、如果僅僅是ADT,那么相近的用戶自定義數(shù)據(jù)類型就無法重用,因此出現(xiàn)了一個數(shù)據(jù)類型的重用需求;
2、因為ADT封裝了對于數(shù)據(jù)的操作,對于A類數(shù)據(jù)的操作,就無法被B類數(shù)據(jù)重用,因此出現(xiàn)了一個ADT內(nèi)部代碼的重用需求;
3、因為對于ADT的操作是借助ADT的外部界面執(zhí)行的,也就是說,對于接近但是不同的類型的操作,必須寫出不同的代碼,因此出現(xiàn)了對于操作ADT的代碼的重用需求。
這樣的分裂的三個需求,在隨后的OO的發(fā)展歷史中,分別被兩種方法來初步滿足。第一、第二種需求,被繼承這樣的技術(shù)來試圖滿足;第三種技術(shù)被泛型類試圖滿足。這兩個技術(shù)滿足了三種重用的需求了嗎?沒有!不但沒有滿足,而且還帶來的諸多麻煩的問題,更在分別滿足三種需求的時候,起了沖突。
由于封裝與重用性之間,存在著本質(zhì)性的沖突,因此,OO的分析、設(shè)計、編程方法就始終處于一個難學(xué)、難用、難懂的狀態(tài)。我們說給OO下定義非常困難,但是大家都應(yīng)該承認(rèn),ADT是OO的根。數(shù)據(jù)與操作的封裝是一切OO思想的基礎(chǔ),也是所有OO信奉者從來沒有懷疑的“前提”!
在繼承與泛型不能解決重用難題之后,OO大師們提出了OO設(shè)計原則,提出了OO設(shè)計模式,這是我接下來的文章里將要細(xì)細(xì)批駁的兩大“貢獻”。但是OO的原則、模式,依然沒有解決重用難題。在此之后,又有人提出了AOP、IoC這樣的概念,還有人真正開始和我一樣懷疑封裝的意義,而開發(fā)了
CGLib,Mixin這樣的動態(tài)改變對象行為與結(jié)構(gòu)的技術(shù),這也是我將要批判的“最新進展”。到了這個時候,真正理解OO本質(zhì)的人,應(yīng)該已經(jīng)看出來了, OO時代即將結(jié)束,因OO而帶來的混亂也該結(jié)束了。現(xiàn)在唯一的問題是:“什么樣的技術(shù),才是可行的、替代的方案呢?”
OO設(shè)計原則批判
OO設(shè)計原則,這是很多開發(fā)資源網(wǎng)站必備的一個欄目、專題、至少也要轉(zhuǎn)載一篇放在自己的網(wǎng)站上的東西。所有的程序員,如果你不開發(fā)面向?qū)ο蟮某绦蛞簿土T了——
反正你已經(jīng)落伍很久了,如果你要想開發(fā)OO程序,而竟然沒有把那些OO設(shè)計原則熟讀背誦,搞得滾瓜爛熟。那么你就完了,一個公司面試你的時候,問你:“你對SRP的理解是怎么樣的?”,而你居然不知道SRP是什么,那么這家公司你也就別想進去了。作為OO程序員的《舊約圣經(jīng)》(設(shè)計模式自然是《新約圣經(jīng)》)他怎么就會那么神圣呢?
介紹OO設(shè)計原則的文章很多,我在google上搜索了一下:“約有58,200項符合OO設(shè)計原則的查詢結(jié)果”。真正能夠介紹得透徹的,還真是沒幾個。正好我手邊有一本Bob大叔的《UML
for JAVA Programmers》那上面的介紹,在我看來,是最好的OO設(shè)計原則介紹之一了。另外一本不在手邊的《敏捷軟件開發(fā)
原則、模式與實踐》也是Bob大叔的,還要詳盡一些。如果要批判,自然要找這樣的靶子來練!
1、單一職責(zé)原則(SRP)
一個類只能因為一個原因而改變。
“A class should have one, and only one, reason to change.”
這個原則何等的簡單,又是何等的模糊呢?什么叫做一個原因呢?我們來看下面這個類:
java代碼:
class User{
private String name;
private int age;
public void setName(String name){
this.name=name;
}
public void setAge(int age){
this.age=age;
}
}
請問,這個類是不是違反了SRP原則呢?設(shè)置用戶的名字與設(shè)置用戶的年齡,是一個原因,還是兩個原因呢?Bob大叔在自己的書里舉了一個例子,說明了違反SRP原則的情況,一個Employee類,包含了計算工資和扣稅金額、在磁盤上讀寫自己、進行XML格式的相互轉(zhuǎn)換、并且能夠打印自己到各種報表。我說拜托啊大叔!一個類里的方法多到如此驚人的程度,自然是違反了SRP原則,但是我們要為它瘦身,該瘦到什么程度呢?按照大叔繼續(xù)給出的自己的答案,它把計算工資和扣稅金額的兩個功能留給了Employee,其他的分離了出去。這個答案正確嗎?員工的工資和稅收是自己算的?還是有一個“財務(wù)部”對象來計算的呢?且不說那么掃興的事情,就看看那個類圖里分離出來的那幾個類:
EmployeeXMLConverter、EmployeeDatabase、TaxReport、EmployeeReport、 PayrollReport。這些類還需要有自己的內(nèi)部數(shù)據(jù)嗎?請注意,他們事實上都是通過接受Employee對象的內(nèi)部數(shù)據(jù)而工作的,換句話說,這些所謂的類,根本就不是什么類,只不過是一個個用Class關(guān)鍵字包裹起來的函數(shù)庫!當(dāng)我們看到一個臃腫的Employee類,被拆成6個各不相同的類之后,內(nèi)心自然升起了“房子打掃干凈之后的喜悅”。但是,且慢!灰塵到哪里去了呢?當(dāng)我們把一個類拆成6個類之后,那個原本的類自然已經(jīng)遵守了SRP原則,然后新誕生的5個類,是不是也該遵守SRP原則呢?如果我們不能將一個原則應(yīng)用于整個系統(tǒng)設(shè)計中的所有的對象,僅僅像小孩打掃衛(wèi)生一樣,把灰塵掃到隔壁房間,這剩下的事情,誰來處理呢?
好吧,我們不要這么嚴(yán)厲,畢竟這只是一個原則,追問太深似乎并不合適。我只想再搞清楚幾個問題:按照SRP原則,C++中是不是一律不應(yīng)該出現(xiàn)多重繼承呢?按照SPR原則,Java中的一個類是不是一律不應(yīng)該既繼承一個類,又實現(xiàn)一個對象呢?一個簡單的POJO,被動態(tài)增強之類的辦法,添加出來的新的持久化能力,是不是也是違反SRP原則的呢?歸根結(jié)蒂,我的問題是:按照SPR原則,我那些剩下的,但是又必須要找地方寫的代碼,究竟應(yīng)該寫在哪里呢?
2、開放-封閉原則(OCP)
軟件實體(類、模塊、方法等)應(yīng)該允許擴展,不允許修改。
“Software entities (classes, modules, functions, etc.) should be open for
extension, but closed for modification.”
這個原則倒是非常的清楚明白,你不能改已經(jīng)寫好的代碼,而應(yīng)該擴展已有的代碼!如何做到這一點呢?Bob大叔舉了一個經(jīng)典的例子:個人認(rèn)為這個例子說明的是一個使用接口,隔離相互耦合的類的通常做法。而且這個做法不應(yīng)叫做OCP,而應(yīng)該叫做DIP。查了一下c2.com里的OCP的解釋:
In other words, (in an ideal world...) you should never need to change
existing code or classes: All new functionality can be added by adding new
subclasses and overriding methods, or by reusing existing code through
delegation.
但是在Bob大叔的OCP解釋中,這個原則的具體實現(xiàn)被偷換了概念,從“鼓勵多使用繼承”,變成了“鼓勵面向接口編程”。為什么?因為繼承式OCP實踐已經(jīng)被證明會帶來相當(dāng)多的副作用,而面向接口編程又如何呢?我們在討論DIP的時候再詳細(xì)討論吧。
有一個在JavaEye的討論的連接可以參考:對于OCP原則的困惑
3、里斯科夫替換原則(LSP)
子類型必須能夠替代他們的基本類型。
“Subtype must be substitutable for their base types.”
對于這個問題,我都不用多說什么,只引用Bob大叔在c2上的一句話,以作為我的支持。
“I believe that LSP is falsely believed by some few to be a principle of OO
design, when really it isn't.”
4、依賴關(guān)系倒置原則(DIP)
A.上層模塊應(yīng)該不依賴于下層模塊,它們都依賴于抽象。
B.抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。
“A. High level modules should not depend upon low level modules. Both should
depend upon abstractions. ”
“B. Abstractions should not depend upon details. Details should depend upon
abstractions.”
Bob大叔還補充說明了一下,這個原則的意思就又變了:更確切的說,不要依賴易變的具體類。也就是說,不容易變的具體類,還是可以依賴的。那么,當(dāng)我們開始一次系統(tǒng)開放的時候,那些類是易變的具體類呢?那些類是不易變的具體類呢?怎么才算是易變、怎么才算是不易變呢?我們來看看代碼吧:
java代碼:
class A{
public void doA(){
}
}
class B{
A a=new A();
a.doA();
}
按照DIP原則,Class B依賴于一個具體實現(xiàn)類Class A,因此是可恥的!最起碼你應(yīng)該寫成這樣:
java代碼:
interface A{
public void doA(){
}
}
class AImpl implements A{
public void doA(){
}
}
class B{
A a=new AImpl();
a.doA();
}
這樣,AImpl和B都依賴于Interface A,很漂亮吧。還不夠好,A a=new AImpl();還是很丑陋的,應(yīng)該進一步隔離!A a=AFactory.createA();在AFactory里,再寫那些見不得人的new
AImpl(); 代碼。然后呢?這還沒完,更加Perfect的辦法,是采用XML配置+動態(tài)IOC裝配來得到一個A,這樣,B就能夠保證只知道這個世界上有一個
Interface A,而不知道這個Interface
A是從哪里來的了。這么做的理由是什么呢?有一個很嚇人的理由:“如果A被B直接使用,那么對于A的任何改動,就會影響到B了。這就叫依賴,而這樣的依賴會很危險!”
我們來看看這頗有說服力的話,漏洞何在?A的變化有兩種情況,一種是只修改A中的方法的內(nèi)部實現(xiàn),無論是直接使用A還是使用Interface
A的某一個實現(xiàn),這時候B的代碼都不用改。另一種是修改了方法的調(diào)用接口,如果是直接使用A的Class
B,就需要修改相關(guān)的調(diào)用代碼,而如果是使用接口的呢?就需要同時修改Class B和Interface
A。這樣看來,采用接口方式,反而需要修改更多的代碼!這使用接口的好處何在?
5、接口隔離原則(ISP)
客戶端不應(yīng)該依賴于自己不用的方法。
“The dependency of one class to another one should depend on the smallest
possible interface.”
這個我就不說了!因為這個原則和SPR原則是矛盾的!就像合成復(fù)用原則(CRP)與LSP原則矛盾一樣。
關(guān)于這個批判,我昨天晚上只寫了一半,今天算是虎頭蛇尾的寫完了。最后錄一段Bob大叔的話,作為結(jié)尾:
什么時候應(yīng)該運用這些原則呢?首先要給您一個痛苦的告誡,讓所有系統(tǒng)在任何時候都完全遵循所有原則是不明智的。
運用這些原則的最好方式是有的放矢,而不是主動出擊。在您第一次發(fā)現(xiàn)代碼中有結(jié)構(gòu)性的問題。或者第一次意識到某個模塊受到另一個模塊的改變的影響時,才應(yīng)該來看看這些原則中是否有一條或者多條可以用來解決問題。
......
找到得分點的最佳辦法是大量寫單元測試。如果能夠先寫測試,再寫要測試的代碼,效果會更好。
讓我來翻譯一下上面的告誡。原則不是你可以用來預(yù)防問題的,而是當(dāng)你已經(jīng)陷入麻煩的時候,你可以停下來悔恨一下。至于解決之道,依然不是很清楚,因此,你需要寫大量的單元測試。而且,大量的單元測試并不是幫你檢查你的設(shè)計漏洞,而是幫你更真切的感受自己的設(shè)計是否正確。至于他究竟是不是正確,這就看個人自己的感覺了。更為驚人的是,在測試驅(qū)動開發(fā)的建議中,如何驅(qū)動開發(fā)的準(zhǔn)則,竟然是循環(huán)的來自于OO設(shè)計原則的。
這樣的OO設(shè)計原則,就像老爸老媽給我們的人生教誨:“你要做好人啊”,別的什么都沒說。而且我們還遇到了話都說不清的糊涂爹娘,怎么才算好人,不清楚,怎么才算壞人呢?被警察抓了,肯定就是壞人了。至于如何才能做得更好?自己體會吧。
設(shè)計模式批判
為什么要批判設(shè)計模式?設(shè)計模式不是OO開發(fā)的救星嗎?作為“可復(fù)用的面向?qū)ο?#8221;的基礎(chǔ)設(shè)施,設(shè)計模式大大的超越了OO設(shè)計原則給予我們的承諾,還記得我們前面的分析嗎?OO設(shè)計原則并不擔(dān)保你在設(shè)計之前就能避免錯誤,相反的,你往往需要在屢屢受傷之后,才會明白設(shè)計原則的真諦。而設(shè)計模式是如此的偉大,他甚至可以幫你提前避免問題,只要你可能遇到的問題,符合設(shè)計模式手冊中,所描述的那種場景,基本上你就可以直接采用相應(yīng)的設(shè)計方案了。如果找不到正好合適的,你也可以改造自己面對的問題,使得他看起來究就像設(shè)計模式手冊中描述的那樣,然后你就可以放心使用相應(yīng)的設(shè)計方案了。如果你無法在那23個模式中找到合適的答案——你可真是太挑剔了——那么你只能自己想法組合一下那23個中的2~3模式,總之,一切盡在其中。
好吧,事實其實沒有那么夸張,“GoF”從來沒有宣稱“設(shè)計模式”能夠包治百病,更沒有說過使用“設(shè)計模式”可以預(yù)防疾病,他們也的確謙虛的承認(rèn),設(shè)計模式肯定不止23個。但是,GoF也必須承認(rèn)的一點就是:“Design
Patterns原本是用來指導(dǎo)設(shè)計的。大多數(shù)時候,都是在實際開發(fā)之前完成的。”而且,按照設(shè)計模式原本的思維模式,應(yīng)該把一個系統(tǒng)中的各個類,按照他們所說的一堆模式組織起來,其根本目的,就是不讓后來的改動,再去修改以前的代碼,所謂OCP原則在設(shè)計模式中的實際體現(xiàn),就是對擴展開放、對修改封閉。
In other words, (in an ideal world...) you should never need to change
existing code or classes: All new functionality can be added by adding new
subclasses and overriding methods, or by reusing existing code through
delegation.
再強調(diào)一遍:“設(shè)計模式認(rèn)為,靈活性、可復(fù)用性是設(shè)計出來的”,而在此之后的發(fā)展我們可以看到,新的大師們又偷換了概念,將“設(shè)計——實施”的兩階段過程,變成了一個“TDD+重構(gòu)”的持續(xù)改進過程,他們不但提倡改以有的代碼,而且要大改特改,持續(xù)不斷的改,唯一還帶著的帽子是:“重構(gòu)的目標(biāo)是得到設(shè)計模式。”重構(gòu)真的能以設(shè)計模式為目標(biāo)嗎?我們下一篇再討論。
請允許我先借力打力,利用重構(gòu)這一新生事物,攻擊一下設(shè)計模式這個老東西。為什么靈活性、可復(fù)用性不能是設(shè)計出來的?
軟件開發(fā),一個很重要的工作,就是對付“需求變更”,軟件工程的辦法是盡可能的抵擋、或者有效控制變更的發(fā)生。技術(shù)人員的目標(biāo),則是努力開發(fā)出更加靈活的技術(shù),以適應(yīng)可能的變化。值得一提的是,現(xiàn)在軟件開發(fā)的管理者,也開始相信,擁抱變化比限制變更,是更為可取的手段了。
更加靈活的技術(shù),更加容易理解,方便描述事實的語言,設(shè)計模式等等等等,都是用來適應(yīng)可能的變化的。對于技術(shù)人員來說,如果能夠預(yù)測可能的變化,并在系統(tǒng)設(shè)計期就將其考慮進去,那么變化就成為計劃的一部分,注意這句話,因為實際的情況往往是:“計劃趕不上變化”。越是自信的技術(shù)人員,越是自以為能夠預(yù)測可能的變化,將變化提前設(shè)計進入自己的系統(tǒng)。所以,濫用設(shè)計模式的人,往往是那些自以為水平很高的半桶水。而重構(gòu)這一思路的出現(xiàn),就是對于設(shè)計模式這種“企圖預(yù)測變化”的否定。事實上,即使是重構(gòu),也是危險的,因為從原始狀態(tài),到第一個變化發(fā)生時,我們能夠得到的只有兩個狀態(tài)點,這兩個點聯(lián)成直線所指向的一個方向,并不一定就是變化的方向,因此,重構(gòu)是一個好辦法,而將得到設(shè)計模式作為重構(gòu)的目標(biāo),卻相當(dāng)危險。
設(shè)計模式背后的思路非常清楚,就是將可能變化納入設(shè)計模式的考慮之中,所以我們看到設(shè)計模式的目標(biāo)“可復(fù)用”,其實是一個轉(zhuǎn)了一個彎以后的目標(biāo),“在盡可能重用已有代碼的前提下,適應(yīng)變化”。我的觀點是:“首先需要滿足的不是復(fù)用,而是快速修改”,這個問題太大以后有機會再討論吧。
這篇關(guān)于設(shè)計模式的批判,我寫了好幾天,始終感覺難以下手。今天和徐昊討論,他的話我認(rèn)為非常有道理:“設(shè)計模式的成功,正好證明了OO的失敗”。這個思路相當(dāng)有用,我決定就按這個調(diào)子來寫。當(dāng)然,設(shè)計模式并不是只有一個,而是有很多很多個,作為一種“專家經(jīng)驗交流的規(guī)范描述格式”,設(shè)計模式已經(jīng)非常多了。我們今天也不批判更多的模式,僅僅對GoF的23個模式下手吧。
GoF的23個設(shè)計模式,主要分為三類:創(chuàng)建型模式、結(jié)構(gòu)型模式、行為型模式。我們就分別批判這三種模式吧。
創(chuàng)建型模式之所以需要,其實正好證明了OO的失敗。因為在OO的思想中,創(chuàng)建對象這種事情,天然就應(yīng)該是對象自己處理的。一個類在定義時,可以定義一個以上的構(gòu)造函數(shù),來決定如何得到自己的實例,那為什么還需要把創(chuàng)建對象這個事情,交到別人手里?按照SRP原則,無論出于什么原因,創(chuàng)建自己總是自己的職責(zé)吧!所以按照SRP原則,所有的創(chuàng)建型模式都不應(yīng)該出現(xiàn),出現(xiàn)了也是錯誤的。但是為什么我們需要創(chuàng)建型模式呢?先看看GoF的解釋:“隨著系統(tǒng)演化得越來越依賴于對象復(fù)合而不是類繼承,創(chuàng)建型模式變得更為重要。當(dāng)這種情況發(fā)生時,重心從對一組固定行為的硬編碼,轉(zhuǎn)移為定義一個較小的基本行為集,這些行為可以被組合成任意數(shù)目的復(fù)雜的行為,這樣創(chuàng)建有特定行為的對象要求的不僅僅是實例化一個類。”
這樣的解釋,有一定的道理,但是卻局限于“用組合取代繼承”這樣一個“當(dāng)年的熱門話題”。在我看來,根本的原因在于:“在OO的經(jīng)典思想中,對象之外的環(huán)境是不存在的,因此,所有的對象,都要考慮如何產(chǎn)生自己,如何銷毀自己,如何保存自己,總之,自己的事情自己做。”Java的一個巨大進步就在于,銷毀自己這件事情,不再強求對象自己去考慮了,因為這種事情過于瑣碎,而且復(fù)雜易錯。但是創(chuàng)建自己這件事情,java依然沒有考慮到也不該交給對象自己考慮的。于是設(shè)計模式中的種種創(chuàng)建模式被發(fā)明出來,用以滿足復(fù)雜多變的創(chuàng)建需求。這個根本原因同時也解釋了為什么單例模式會被發(fā)明,按照GoF的解釋,是無法說明為什么我們會需要單例模式地。而當(dāng)我們有了對象環(huán)境的概念之后,各種創(chuàng)建自然有“容器環(huán)境”來完成,“單例”模式也只需要在環(huán)境中配置,有了OO容器之后,所有的創(chuàng)建模式都被一個概念所代替了。在沒有這樣的概念之前,我們需要用各種模式技巧,來實現(xiàn)“支離破碎”的環(huán)境。而在真正的容器環(huán)境出現(xiàn)之后,我們就不再需要這些設(shè)計模式了。
讓我再說一遍:“如果你能夠理解為什么現(xiàn)在會出現(xiàn)那么多容器,就能理解設(shè)計模式中的創(chuàng)建模式,只不過是用來解決OO概念欠缺的一種不完善的手段了。”
再來看結(jié)構(gòu)型模式,個人認(rèn)為將“Adapter、Bridge、Composite、Decorator、Facade、 Flyweight、Proxy”統(tǒng)統(tǒng)歸入結(jié)構(gòu)型模式,是不恰當(dāng)?shù)摹3薈omposite模式算是結(jié)構(gòu)模式,F(xiàn)lyweight算是一種“節(jié)約內(nèi)存的技術(shù)手段”之外,其他的模式,都屬于打破OO靜態(tài)封裝的技巧。我們知道,按照OO的設(shè)定,一個類,就是一種類型,它在代碼寫下的時候,就已經(jīng)決定了自己的內(nèi)部數(shù)據(jù)與外部行為。怎么還能在運行的時候再改變呢?事實證明,這樣需求不但存在,而且重要,設(shè)計模式之所以被大家欣賞,一個重要的原因,就是他能夠幫助程序員部分?jǐn)[脫“靜態(tài)封裝屬性與行為”的限制。
OO能從關(guān)系型數(shù)據(jù)庫借鑒些什么?
今天這篇是關(guān)于OO VS. RDB的,OO作為一種編程范型,主要是集中于處理“操作”,而RDBMS作為一種數(shù)據(jù)管理工具,主要是集中于“數(shù)據(jù)”。但是,在一個需要數(shù)據(jù)庫的系統(tǒng)中,必然的情況是:操作的對象自然是各種各樣的數(shù)據(jù),而數(shù)據(jù)的管理,自然要通過操作。因此,OO與RDB,從最初淺的角度來理解,雖然分別位于“業(yè)務(wù)邏輯層”與“數(shù)據(jù)層”,但是相互之間卻又有著非常緊密的聯(lián)系。在OO與RDB之間存在著的緊張關(guān)系,其根源在于:“OO世界中的數(shù)據(jù)是被封裝的孤立數(shù)據(jù);RDB世界中的操作是針對行集的。”
因此,一個對象實例內(nèi)部的數(shù)據(jù),應(yīng)該保存到數(shù)據(jù)庫中的哪些表、哪些行、哪些列,是一個非常復(fù)雜的問題。反過來說,當(dāng)我們要在內(nèi)存中恢復(fù)一個對象實例時,要從多少表、多少行、多少列中采集數(shù)據(jù),并正確轉(zhuǎn)化為對象實例的內(nèi)部數(shù)據(jù),也是相當(dāng)?shù)膹?fù)雜。O/R
Mapping,需要考慮的問題還不止于此,在RDB中自然存在的“關(guān)系”這一概念,在OO當(dāng)中,卻沒有明確的對應(yīng)概念。在OO的世界里,對象之間存在各種各樣的關(guān)系,卻非常難以進行規(guī)范化的描述。再加上:“添加、修改、刪除、查詢”等等情況的O/R映射,以及與“關(guān)系”相關(guān)的“級聯(lián)操作”——“級聯(lián)添加、級聯(lián)修改、級聯(lián)刪除、級聯(lián)查詢”,一個好的O/R
Mapping工具,要做出來真是千難萬難。
很多人都意識到了這一點!是的,問題的確存在。但是,為什么呢?該怎么辦呢?幾乎沒有人會反思OO的不是,而是想當(dāng)然的認(rèn)為:“關(guān)系數(shù)據(jù)庫技術(shù)太老了,跟不上OO技術(shù)的進步,因此,我們需要OODB這樣的能夠直接支持OO的數(shù)據(jù)庫。”
“以其昏昏,使人昭昭”的事情,從來沒有發(fā)生過。依著我前面的分析,在OO這樣的基礎(chǔ)薄弱的理論上,怎么可能搞出有實用價值的數(shù)據(jù)庫呢?
在看到了徐昊的《關(guān)于面向?qū)ο笤O(shè)計與數(shù)據(jù)模型的精彩論述》之后,我相信自己找到了知音。他說:“OO在數(shù)據(jù)模型設(shè)計上不具有思維簡潔性。”并且提出了一個重要的詞匯:“邊語義”!這使我相信,和我有類似想法的同志,是存在的。后來又現(xiàn)場聽到了曹曉鋼同志的《ORM時代的數(shù)據(jù)訪問技術(shù)》的演講,并且在他的筆記本里看到了他做的一些代碼,居然與我的很多想法不謀而合!再加上后來與徐昊的幾次MSN交流,終于使我敢于開始寫這樣一篇“OO喪鐘”的文章,因為,我相信自己并不是孤獨的反對者。
OO可以從關(guān)系型數(shù)據(jù)庫那里借鑒些什么呢?
1、關(guān)系:也就是徐昊所說的邊語義。在
OO中,對象與對象之間是否存在關(guān)系,在對象之外是不知道的。當(dāng)一個對象被封裝起來以后,他內(nèi)部是否使用、關(guān)聯(lián)、組合了其他的對象,是不可知的。因此,我們看到的通常的OO圖,只能說是Object被剖開了以后的對象圖。事實上,關(guān)系是被隱藏起來的。而在RDB中,關(guān)系非常明確的被定義與標(biāo)識出來,一目了然。這將帶來巨大的描述效果。相比起UML
Class圖,E-R要容易理解得多。
2、Primary Key:這是RDB
特有的概念,在OO中沒有對應(yīng)概念。因此,我們要判斷兩個對象是否相等,就相當(dāng)困難。如果每個對象都有一個“一次設(shè)置,終身不變的Primary
Key”,那么對象之間的比較語義,就能夠被清楚的區(qū)分為:IS和LIKE。IS就是Primary
Key相同的兩個對象,他們應(yīng)該完全一致,甚至在內(nèi)存中,也只應(yīng)該保存一份。LIKE,就是成員數(shù)據(jù)相同的兩個對象,他們不是一個東西,僅僅是像而已。
3、SQL:這也是RDB特有的語言,而在OO的世界里,查找一個對象的工作,從來沒有被規(guī)范過。