關(guān)于面向?qū)ο蟮恼軐W(xué)體系及科學(xué)體系的探討

推薦深度好文:原文鏈接,作者:張洋

序言 Perface

“佛曰:苦海無涯,回頭是岸?!鸾逃谜Z”

面向?qū)ο螅∣bject-Oriented),這是一條令無數(shù)開發(fā)人員魂?duì)繅衾@的短語。幾乎每個(gè)軟件分析師、設(shè)計(jì)師和程序員都時(shí)刻將它銘記于心,對它頂禮膜拜。然而,對大多數(shù)人來說,它又像是天邊的霞光,可望而不可及,無數(shù)次伸出雙手,總是抓不住這虛無縹緲的圣物。于是,我們依然每天將面向?qū)ο蟾吒吖┢?,卻始終無法悟得其道,更不要談嫻熟運(yùn)用其道法了。

面向?qū)ο笙褚粸┛嗪?,無數(shù)人游弋其中,卻久久不得其要領(lǐng);類、對象、繼承、多態(tài)、接口、UML、設(shè)計(jì)模式……無數(shù)概念看得我們眼花繚亂,卻也悟不透其真諦。佛教有云:苦海無涯,回頭是岸。如果置身苦海中無法脫離,那么,我們是否應(yīng)該提高一個(gè)層面去看這片苦海:從哲學(xué)及科學(xué)的角度,去審視面向?qū)ο蟆?/p>

曾有人說:藝術(shù)的極致是科學(xué),科學(xué)的極致是哲學(xué)。此話不無道理,牛頓、愛因斯坦等科學(xué)界泰斗,在其后期都不約而同地轉(zhuǎn)向哲學(xué)研究。當(dāng)然,這里本人無意更不敢將自己與上面兩位大師相提并論,而且本人也不奢求此文能成為一篇頗有思想的佳作。只不過,本人在平時(shí)的實(shí)踐和思考中,略有小得,于是,在這里拿出,和大家一起分享討論。雖然膚淺,但希望本文能成為一絲波紋,為各位脫離苦海提供一點(diǎn)點(diǎn)的推動(dòng)作用。

真經(jīng)第一章——世界 Weltanschauung

“世界觀(德文:Weltanschauung)意為‘著眼世界之上’,是人們對世界的總的根本的看法。任何哲學(xué)問題的探討,歸其出發(fā)點(diǎn)和本源,都是世界觀的問題。什么樣的世界觀決定了什么樣的哲學(xué)觀點(diǎn)?!R克思”

1.1、看世界

我們知道,哲學(xué)領(lǐng)域中,最根本的對立是唯物主義和唯心主義的對立,而附屬其下,又有許多對立,如形而上學(xué)和辯證法的對立、可知論和不可知論的對立等等。這些對立形成了哲學(xué)的基本體系、派別和出發(fā)點(diǎn)。實(shí)際上,這些對立,都是世界觀的對立。世界觀,簡而言之即如何看待這個(gè)世界。世界觀是一切哲學(xué)問題的本源和出發(fā)點(diǎn)。

同樣,在程序世界里,也有著不同的世界觀。而這其中最根本的對立便是過程論和對象論的對立,這個(gè)對立,衍生出了面向過程和面向?qū)ο髢煞N方法論。于是,要真正理解面向過程和面相對象,我們就不得不先深究一下程序世界中這兩種世界觀。

首先要提到的是,不論是過程論還是對象論,都承認(rèn)一點(diǎn),那就是程序世界本質(zhì)上只有兩種東西——數(shù)據(jù)和邏輯。數(shù)據(jù)天性喜靜,構(gòu)成了程序世界的本體和狀態(tài);邏輯天性好動(dòng),作用于數(shù)據(jù),推動(dòng)程序世界的演進(jìn)和發(fā)展。盡管上述觀點(diǎn)是統(tǒng)一的,但是在數(shù)據(jù)和邏輯的存在形式和演進(jìn)形式上,過程論和對象論的觀點(diǎn)截然不同。

過程論認(rèn)為:數(shù)據(jù)和邏輯是分離的、獨(dú)立的,各自形成程序世界的一個(gè)方面(Aspect)。所謂世界的演變,是在邏輯作用下,數(shù)據(jù)做改變的一個(gè)過程。這種過程有明確的開始、結(jié)束、輸入、輸出,每個(gè)步驟有著嚴(yán)格的因果關(guān)系。過程是相對穩(wěn)定的、明確的和預(yù)定義的,小過程組合成大過程,大過程還可以組合成更大的過程。所以,程序世界本質(zhì)是過程,數(shù)據(jù)作為過程處理對象,邏輯作為過程的形式定義,世界就是各個(gè)過程不斷進(jìn)行的總體。

對象論認(rèn)為:數(shù)據(jù)和邏輯不是分離的,而是相互依存的。相關(guān)的數(shù)據(jù)和邏輯形成個(gè)體,這些個(gè)體叫做對象(Object),世界就是由一個(gè)個(gè)對象組成的。對象具有相對獨(dú)立性,對外提供一定的服務(wù)。所謂世界的演進(jìn),是在某個(gè)“初始作用力”作用下,對象間通過相互調(diào)用而完成的交互;在沒有初始作用力下,對象保持靜止。這些交互并不是完全預(yù)定義的,不一定有嚴(yán)格的因果關(guān)系,對象間交互是“偶然的”,對象間聯(lián)系是“暫時(shí)的”。世界就是由各色對象組成,然后在初始作用力下,對象間的交互完成了世界的演進(jìn)。

1.2、一道智力題引發(fā)的思考

上面的描述也許有些不夠直觀,那么,下面我們通過一個(gè)實(shí)際的例子,直觀感受一下在兩種世界觀下,對同一件事物是怎么看的。

大家都聽過這么個(gè)智力題吧:

說有甲、乙、丙三人住店,一間房30。于是每人10元,共計(jì)給店老板30元住進(jìn)一間房。后來店老板發(fā)現(xiàn)弄錯(cuò)了,房價(jià)應(yīng)該是25元,于是給小二5元讓小二退給房客。小二黑心,貪污了2元,退給甲乙丙每人1元。這樣房客每人付了10-1=9元,三九27,加上小二貪污的2元,共29元,問那1元哪里去了?

不知各位聰明的看官是否已經(jīng)參透其中玄機(jī)。不過參不透也沒有關(guān)系,這不是重點(diǎn),重點(diǎn)是,我們現(xiàn)在來分別用過程論和對象論分析一下這件事。

首先,我們來看看過程論是怎么看這件事情的。

圖1.1、過程論看世界

如圖1.1所示,這就是過程論下看這件事的樣子。左邊是過程的各個(gè)步驟,而右邊紅字表示在每個(gè)過程步驟的數(shù)據(jù)情況,這種數(shù)據(jù)情況反映了世界當(dāng)前的狀態(tài)。為簡單起見,我們只考慮在這個(gè)過程中參與分配的數(shù)據(jù)。

初始時(shí)甲乙丙各10元,老板和小二沒有錢,這可以認(rèn)為是這個(gè)過程的初始狀態(tài),這些數(shù)據(jù)是輸入。隨著各個(gè)步驟的進(jìn)行,數(shù)據(jù)不斷更新,而在每個(gè)步驟,數(shù)據(jù)如何更新、更新多少,都是由步驟嚴(yán)格確定的。經(jīng)歷五個(gè)步驟后,數(shù)據(jù)變?yōu)榧滓冶?元,老板25元,小二2元,這就是終止?fàn)顟B(tài),也是這個(gè)過程的輸出。

下面,再來看看對象論下如何看這件事。

圖1.2、對象論看世界

對象論眼中,世界是由各種對象組成的,每個(gè)對象有自己的數(shù)據(jù)和邏輯,如圖1.2所示。在這件事里,有五個(gè)基本對象:甲、乙、丙、小二和老板(注意,這里我們還沒有提到類和抽象等概念,所以不要讓固有思維跳出來,在這里要只認(rèn)識對象,不認(rèn)識類等概念?,F(xiàn)在我們只討論世界觀的基本問題:程序世界的本質(zhì),至于更具體的問題,留待后面討論)。每個(gè)對象有自己的一系列數(shù)據(jù)和邏輯,這里只列出了我們關(guān)心的部分。

然后呢?沒有然后了。沒錯(cuò),在對象論眼里,這就是這件事的本質(zhì)模樣,這件事所涉及的東西就是這么幾個(gè)對象,本來它們各自獨(dú)立,老死不相往來。只不過在“住店”這個(gè)外部驅(qū)動(dòng)力下,幾個(gè)對象“偶然”、“暫時(shí)”互相聯(lián)系,利用其他對象提供的公開服務(wù),完成了一些交互。在交互中,各自的數(shù)據(jù)可能會發(fā)生一些變化,但對象的本質(zhì)沒有變。這里也要注意,這種交互雖然在一定程度上由既定邏輯預(yù)定義,但不像過程論認(rèn)為“萬事萬物都已注定”,在對象論下,對象間的交互是“偶然的”、“暫時(shí)的”,這次五個(gè)人因?yàn)樽〉赀@個(gè)外部驅(qū)動(dòng)力交互了一次。但下次如果魏國和蜀國交戰(zhàn)變?yōu)轵?qū)動(dòng)力,他們間的交互就不是拿錢給錢了,而是刀兵相見。所以,對象論不認(rèn)為“一切都已注定”。

1.3、總結(jié)

通過上面一個(gè)例子,不知各位是否已經(jīng)明白程序世界中兩種世界觀看事物的不同。下面,有一些問題還要明確一下。

I. 過程論和對象論是兩種看世界的觀點(diǎn),沒有孰對孰錯(cuò)、孰好孰壞之分。

II. 過程論和對象論不是一種你死我活的絕對對立,而是一種辯證統(tǒng)一的對立,兩者相互滲透、在一定情況下可以相互轉(zhuǎn)化,是一種“你中有我、我中有你”的對立。如果將對象論中的所有交互提取出來而撇開對象,就變成了過程論,而如果對過程論中的數(shù)據(jù)和邏輯分類封裝并建立交互關(guān)系,就變成了對象論。

III. 過程論相對確定,有利于明晰演進(jìn)的方向,但當(dāng)事物過于龐大繁雜,將很難理清思路。因?yàn)檫^程繁多、過程中又有子過程,容易將整個(gè)世界看成一個(gè)紛繁交錯(cuò)的過程網(wǎng),讓人無法看清。

IV. 對象論相對不確定,但是因?yàn)橐詫ο鬄榛驹兀词购荦嫶蟮氖挛?,也可以很好地分離關(guān)注,在研究一個(gè)對象的交互時(shí),只需要關(guān)系與其相關(guān)的少數(shù)幾個(gè)對象,不用總是關(guān)注整個(gè)流程和世界。但是,對象論也有困難。例如,如何劃分對象才合理?對于同一個(gè)驅(qū)動(dòng)力,為什么不同情況下參與對象和交互流程不一樣?如何確定?其實(shí),這些困難也正是面向?qū)ο蠹夹g(shù)中的困難。

綜上,我們知道在程序世界中,存在著過程論和對象論兩種對立的世界觀,并且其各有千秋,無法定奪孰好孰壞。但是,對象論似乎更有助于分析規(guī)模較大的事物。本文是探討面向?qū)ο蟮?,所以,在下文中,都會選擇對象論作為世界觀。這種以對象為本的世界觀,也是本文后續(xù)一切的基礎(chǔ)和出發(fā)點(diǎn)。

真經(jīng)第二章——抽象 Abstraction

“金、木、水、火、土元素,構(gòu)成宇宙萬物,并作為各種自然現(xiàn)象變化之基礎(chǔ)——五行說”

2.1、導(dǎo)言

上文探討了世界觀問題。我們知道,要想真正理解面向?qū)ο?,首先要用對象論去審視世界。而在對象論中,萬事萬物的本源是對象,對象是組成世界的基本元素。但是,要真正看透一個(gè)世界,只有基本元素是不行的。

中國古代的樸素唯物主義哲學(xué)中,比較有代表性的是五行說。五行說認(rèn)為,世界的基本元素是“金、木、水、火、土”,但若說世界只有“金、木、水、火、土”,也是不成的,所以后續(xù)有云:五行相生相克,相互交織結(jié)合,組成了大千世界。雖然從現(xiàn)代科學(xué)角度看,五行說并不完全準(zhǔn)確,但其有一點(diǎn)事非常正確的,那就是世界首先有基本元素,然后基本元素還要衍生出各種其它東西。

在第一章中,我們說了在對象論中,對象是組成世界的基本元素,但這還不能構(gòu)成真正的世界。下面,我們來看看對象是如何構(gòu)成和衍生出其它事物的。

2.2、類是怎么來的

和真實(shí)世界中構(gòu)成和衍生方式不同,程序世界中,最重要的衍生方式是抽象。例如,眾所周知的類(Class),就是從對象上首先抽象出來的概念。下面我們看一看類是怎么來的。

從哲學(xué)角度說,先有對象,然后才有類,類和對象是“一般和特殊”這一哲學(xué)原理在程序世界中的具體體現(xiàn)。這可能和很多人的直覺不同,因?yàn)樵诰唧w寫程序時(shí),是先定義類,然后才能實(shí)例化對象。在這里,我們是從哲學(xué)層面進(jìn)行探討,所以,對象是本源,類的概念是衍生。為什么?因?yàn)閺恼J(rèn)識論來說,首先有具體認(rèn)知能力,才能有抽象認(rèn)知能力,抽象認(rèn)知能力是一種高層的,人類特有的認(rèn)知能力,它使我們可以從大量具體認(rèn)知中,舍棄個(gè)別的、非本質(zhì)的屬性,提取出共同的、本質(zhì)的屬性,是形成概念的必要手段。

還是以住店的故事為例吧。在我們的世界觀中,那個(gè)故事涉及了五個(gè)對象,剛開始我們沒有抽象的概念,而只是從具體認(rèn)知角度對這五個(gè)對象進(jìn)行認(rèn)知:首先是甲,他有頭、有身子、有胳膊有腿,頭上有眼睛鼻子耳朵,他還有個(gè)名字叫劉備,有個(gè)身份是顧客……除了這些數(shù)據(jù),這個(gè)對象還可以做一些事情,可以吃飯、呼吸、喝水,還能給錢和拿錢……好的,一通認(rèn)知后,我們對甲這個(gè)對象有具體認(rèn)知了;然后,我們對乙進(jìn)行認(rèn)知:他有頭、有身子、有胳膊有腿,頭上有眼睛鼻子耳朵,他還有個(gè)名字叫關(guān)羽,有個(gè)身份是顧客……除了這些數(shù)據(jù),這個(gè)對象還可以做一些事情,可以吃飯、呼吸、喝水,還能給錢和拿錢……認(rèn)知完了,接著是丙、小二和老板……當(dāng)具體認(rèn)知足夠多后,我們發(fā)現(xiàn)一件事情:這幾個(gè)對象很相似啊,有相似的數(shù)據(jù)(但具體值可能不同),有相同的邏輯,于是,我們的抽象認(rèn)知能力告訴我們,這五個(gè)對象很相似,可以看做一類東西,于是,我們給出一個(gè)類,叫“人”,并且認(rèn)為這五個(gè)對象都是“人”這個(gè)類的具體例子,我們叫其為實(shí)例。以后遇到類似的對象,我們都可以知道,這個(gè)對象屬于“人”類。

圖2.1、“人”類的由來

所以,類其實(shí)是抽象認(rèn)知能力作用于程序世界的基本元素——對象后所衍生出來的抽象概念,是抽象思維在程序世界中物化后的產(chǎn)物。當(dāng)然,現(xiàn)實(shí)世界中每個(gè)對象都有無數(shù)的數(shù)據(jù)和邏輯,但在具體到程序世界時(shí),我們往往只關(guān)心具體場景中相關(guān)的數(shù)據(jù)和邏輯。例如,在住店場景中我們關(guān)心現(xiàn)金這則數(shù)據(jù),至于這個(gè)人力氣大不大無所謂;而如果上戰(zhàn)場打仗,我們就關(guān)心攻擊力和力量,現(xiàn)金就不重要了。

2.3、為什么要有類

知道了類是怎么來的,那么類的作用是什么,我們?yōu)槭裁葱枰惸兀?/p>

類可以幫助我們方便地認(rèn)識和定義世界中的對象。這個(gè)作用是顯而易見的。例如當(dāng)今世界有60幾億人,如果不會抽象思維,我們每遇到一個(gè)人,都要認(rèn)知一遍:??!這個(gè)對象有眼睛,有耳朵,有鼻子有嘴,有胳膊有腿……要是真這樣,世界也太瘋狂了。有了類的概念,我們就可以只記類的數(shù)據(jù)和邏輯,而對于具體對象,只要知道它屬于什么“類”,一切就都知道了,所需要區(qū)分的只是不同對象的數(shù)據(jù)具有不同值而已。

其實(shí),這不僅僅是類的作用,我們進(jìn)行抽象思維,就是為了這個(gè)目的。

2.4、總結(jié)

這一章敘述了類的哲學(xué)本質(zhì)、衍生過程和作用。要記住,抽象是形成和衍生概念的基本方法,不只是類,后面的很多概念,都是通過抽象形成的。所以,我們可以說:上天只給了這個(gè)世界各種對象,但我們用抽象去更好地認(rèn)識世界。

真經(jīng)第三章——層次 Arrangement

“道生一,一生二,二生三,三生萬物——老子”

3.1、導(dǎo)言

上文提到,在對象論中,抽象是衍生概念的基本方法。但是你有沒有一個(gè)疑問?所謂抽象,是對許多對象撇開個(gè)性,抽出共性,這樣,抽象過程就不是確定的、唯一的。例如,我們在看過很多對象后,發(fā)現(xiàn)有一類對象有四個(gè)輪子、有發(fā)動(dòng)機(jī)、可以駕駛、是可以被意識反映的客觀實(shí)在。我們抽象出一個(gè)叫“汽車”的類。這次抽象中,我們將有四個(gè)輪子看做了共性,但是,如果撇開這條性質(zhì),僅看后三條,摩托車、輪船、飛機(jī)都符合,于是,我們又可以抽象出“機(jī)動(dòng)交通工具”類。再把有發(fā)動(dòng)機(jī)撇掉,自行車、腳踏三輪車,甚至馬都符合,所以,又得出個(gè)“代步工具”類,最后,把可以駕駛也撇掉,只剩下“是可以被意識反映的客觀實(shí)在”,如果這樣,所有物質(zhì)都符合,這樣,就得出一個(gè)“物質(zhì)”類。

這下子困難就來了,你說我家的奔馳應(yīng)該歸到哪一類呢?我家的奔馳和一只是不是一類東西呢?如果從前三類看,當(dāng)然不是,但是從最后一個(gè)“物質(zhì)”類看,又確實(shí)是一類東西。那到底哪一個(gè)對?事情究竟是怎樣的?其實(shí)答案很簡單:歸到哪一類都正確。至于后一個(gè)問題,無法回答,因?yàn)檫@個(gè)問題單獨(dú)問根本沒有意義。為什么?

關(guān)鍵在于:抽象是有層次的。

3.2、世界是一棵樹

上文說到,對象是基本,我們從對象上抽象出類。但是,世界可并不是一層對象一層類那么簡單,對象抽象出類,在類的基礎(chǔ)上可以再進(jìn)行抽象,抽象出更高層次的類。所以經(jīng)過抽象的對象論世界,形成了一個(gè)樹狀結(jié)構(gòu)。

圖3.1、抽象層次樹示例

圖3.1展示了一棵抽象層次樹的示例。不要懷疑,在對象論中,經(jīng)過初步抽象思維加工后的世界就是這樣樣子。本來,世界只有各個(gè)具體對象(最底下紫色文字表示的層次),這是第0層,是一切抽象的本源和起始,然后,抽象思維作用其上,抽象出初步的類,然后在既有類和對象的基礎(chǔ)上可以再進(jìn)行抽象……如此歸納下去,最終整個(gè)世界歸結(jié)于樹的根節(jié)點(diǎn):本體。所謂本體,即萬物之源、萬物之本,是哲學(xué)層面上最高層次的抽象。在這里,我們將其看成是一個(gè)特殊的類,作為抽象層次樹的根。

千萬不要小看了這棵抽象層次樹,如果能參透其中的奧秘,就能明白很多面向?qū)ο笾械男C(jī),而且很多問題就都迎刃而解了。這種抽象層次樹理論也是后續(xù)諸多內(nèi)容的理論基礎(chǔ)。例如,OO中重要的概念——繼承(Inheritance)和多態(tài)(Polymiorphism),如若探究其哲學(xué)本源,就是從這里來的。

下面,對這棵樹做一些必要的說明。

I. 這是一棵單根樹,最頂層“本體”為唯一的根,最下層葉子節(jié)點(diǎn)為基本對象。一切中間節(jié)點(diǎn)都為類。

II. 越往上的類抽象層次越高,具體度越低,其內(nèi)涵越小,外延越大;越往下的類抽象層次越低,具體度越高,其內(nèi)涵越大,外延越小。說明一下,所謂類的內(nèi)涵,是指類對屬于自己的對象的說明力度,而外延是指類能包含的具體對象的總和。例如,家用電器這個(gè)類,其內(nèi)涵是使用電作為能源并完成特定功能的家用器具,各個(gè)電冰箱、洗衣機(jī)、電磁爐、游戲機(jī)、DVD機(jī)等都在其外延之內(nèi);而娛樂家用電器這個(gè)類,作為比家用電器更低層次的類,其內(nèi)涵除了“使用電作為能源并完成特定功能的家用器具”外,還要是具有娛樂功能,其內(nèi)涵明顯大了,但外延卻縮小了,只包括了各個(gè)游戲機(jī)、DVD機(jī)等對象。

III. 抽象層次樹不是從根部向下長的,而是從葉子節(jié)點(diǎn)向上歸納生成的。

IV. 某一個(gè)葉子節(jié)點(diǎn)所代表的對象可以歸入所有其祖先結(jié)點(diǎn)所代表的類

V. 直接問兩個(gè)葉子節(jié)點(diǎn)屬不屬于一個(gè)類沒有意義,而要指定抽象層次才有意義。例如在較低層,一輛寶馬屬于汽車,而一只蒼蠅屬于昆蟲,不是一類。但如果指定在較高層比較,兩個(gè)都屬于具體物質(zhì),屬于一個(gè)類。

VI. 我們定義,如果一個(gè)節(jié)點(diǎn)CNode非葉子節(jié)點(diǎn)也非根節(jié)點(diǎn),那么在哲學(xué)意義上,這個(gè)節(jié)點(diǎn)繼承于其父節(jié)點(diǎn)PNode,并且說PNode是CNode的泛化。

VII. 我們定義,如果一個(gè)節(jié)點(diǎn)CNode非葉子節(jié)點(diǎn)也非根節(jié)點(diǎn),如果強(qiáng)行將它看成其任何一個(gè)祖先節(jié)點(diǎn)ANode,并當(dāng)做ANode使用,那么在哲學(xué)意義上,叫做多態(tài)性。

3.3、總結(jié)

先說明這么多了,隨著后續(xù)內(nèi)容的深入,還會有更多豐富的內(nèi)容進(jìn)來。例如,后面會看到,所謂的“里氏代換原則(LSP)”,在哲學(xué)本質(zhì)上不過是在這棵樹上所加的一條限制規(guī)則,而“面向接口編程”、“低耦合、高內(nèi)聚”、“依賴倒置”等一系列耳熟能詳?shù)亩陶Z,歸結(jié)到哲學(xué)上也只是這棵樹的一些精化。

另外,看了上面的理論,我想本章開頭留下的疑問也已經(jīng)煙消云散了吧。

再提示一遍,這棵樹非常重要,得其精髓,就能理解諸多OO中概念、原則和方法的本質(zhì)。后續(xù)討論中,抽象層次樹理論將作為重要的理論基礎(chǔ)。

真經(jīng)第四章——繼承 Inheritance

“子類型必須能夠替代掉其父類型——Barbara Liskov”

4.1、原來是先有兒子才有父親

這一章我們討論繼承(Inheritance)。

我們先看一看繼承在哲學(xué)意義上時(shí)怎么來的。對象論的世界觀認(rèn)為,世界的基本元素是對象,我們將抽象思維作用于對象,形成了類的概念,而抽象的層次性形成了抽象層次樹的概念。接著,我們就可以定義:在抽象層次樹上,除根節(jié)點(diǎn)和葉子節(jié)點(diǎn)外,任一節(jié)點(diǎn)CNode非嚴(yán)格繼承其所有祖先節(jié)點(diǎn)所組成的集合中的任一元素,而CNode嚴(yán)格繼承其父節(jié)點(diǎn)PNode。

繼承概念,看似簡單,若深入思考,卻隱藏眾多玄機(jī)。首先,繼承描述的實(shí)際是抽象層次樹上祖先節(jié)點(diǎn)與子孫節(jié)點(diǎn)的關(guān)系,但我個(gè)人一直不贊成使用繼承(Inheritance)一詞來描述這種關(guān)系,而推薦使用泛化(Generalization)一詞。為什么呢?因?yàn)槲覀円呀?jīng)知道,從哲學(xué)和認(rèn)識論角度來說,是先有對象,然后有類;先有子類,然后有父類,是一種自底向上形成的體系。而繼承一詞,明顯帶有自頂向下的暗示,因?yàn)橥窍扔袪敔?、有父親繼承爺爺、然后才能有兒子繼承父親。這樣,就容易讓人誤解成是先有父類才有子類。所以,為了更好的體現(xiàn)繼承的哲學(xué)本質(zhì),我更傾向于使用“泛化”代替“繼承”。當(dāng)然,由于繼承一詞已經(jīng)被普遍使用和接受,接下來我還是會沿用繼承一詞,只不過希望各位時(shí)刻牢記,其實(shí)是先有了子類,才從子類泛化出父類。

當(dāng)然,當(dāng)父類被抽象出來后,可能還會有新的子類加進(jìn)來。但是,當(dāng)初父類一定是從某些子類中泛化出來的,而不會是憑空突然出現(xiàn)的。

4.2、繼承的作用

探討了繼承的本質(zhì),然后我們來探討繼承存在的意義。一切存在的東西都是有意義的,否則就不可能存在。注意,這里的“意義”是中性詞,指事物存在的原由,不要理解成褒義。

我們需要繼承這個(gè)概念,本質(zhì)上是因?yàn)閷ο笳撝惺澜绲倪\(yùn)作往往是在某一抽象層次上進(jìn)行的,而不是在最低的基本對象層次上。舉個(gè)例子,某人發(fā)燒了,對其他人說:我生病了,要去醫(yī)院看醫(yī)生。這句簡短的話中有一個(gè)代詞“我”和三個(gè)名詞“病”、“醫(yī)院”、“醫(yī)生”。這四個(gè)具有名詞性的詞語中,除了“我”是運(yùn)作在世界的最底層——基本對象層外,其他三個(gè)都運(yùn)作在抽象層次,在這個(gè)語境中,“病”、“醫(yī)院”、“醫(yī)生”都是抽象的,他并沒有在醫(yī)院里拉著某個(gè)醫(yī)生對別人說:我生了這個(gè),需要去這里看這個(gè)。但是,本質(zhì)上他確實(shí)是生了一個(gè)具體的病,要去一個(gè)具體的醫(yī)院看一個(gè)具體的醫(yī)生,那么在哲學(xué)上要如何映射這種抽象和具體呢?就是靠繼承, 拿醫(yī)生來說吧,所有繼承自“醫(yī)生”類的類所指的所有具體對象都可以替換掉這里具體的醫(yī)生,這都不影響這句話語義的正確性。

所以,繼承的哲學(xué)作用就是:規(guī)定了抽象與具體之間的可映射性。形式化一點(diǎn)說:設(shè)G(c1,c2)意為c1非嚴(yán)格泛化自c2,I(c,o)意為對象o屬于c的外延,其中c1,c2,c均為類,o為對象。那么,c可在哲學(xué)語義上映射成o,當(dāng)且僅當(dāng)o∈{o|I(c,o)}∪{o|I(c’,o) 且 G(c,c’)}

4.3、開放-關(guān)閉

如果你討厭看形式化的東西,那么上面藍(lán)色文字不看也罷,但是,有一條原則你一定很感興趣,那就是著名的開放-關(guān)閉原則(OCP)。

開放-關(guān)閉原則(OCP):軟件實(shí)體應(yīng)該可以擴(kuò)展,但不可以修改。

為什么忽然扯到OCP呢?因?yàn)?,OCP正是上文討論的哲學(xué)原理在程序世界的具體表述。我們來對比看一下,到底OCP是個(gè)什么意思。

還是上面看病那個(gè)例子,什么叫可以擴(kuò)展?就是說,因?yàn)樵谀硞€(gè)抽象層次是進(jìn)行表述,就不能把話說死了,不能全是這個(gè)、那個(gè)的把每個(gè)對象都指派明白。如,那句話改成“我的右腳扭到了,要去北京航空航天大學(xué)醫(yī)院去看胡青牛醫(yī)生”,這句話就沒有擴(kuò)展性可言了,所有話都說死了,你如果去的是北醫(yī)三院或臨沂市人民醫(yī)院,那么語義就不對了,而如果找的不是胡青牛而是華佗或扁鵲,語義也不對了。為什么無法擴(kuò)展?因?yàn)樗悬c(diǎn)都指定了具體的對象。

而原話“我生病了,要去醫(yī)院看醫(yī)生”則擴(kuò)展性很大,因?yàn)橹灰贿`反可映射性定義,映射到任何符合條件的對象都正確。擴(kuò)展性和靈活性大大提高了。所以,“可以擴(kuò)展”四字從哲學(xué)上其實(shí)是要我們在設(shè)計(jì)和開發(fā)軟件時(shí)提高抽象層次,不要總在具體對象層面上進(jìn)行處理。這下,你明白為什么說OCP可以提高軟件的可擴(kuò)展性和靈活性了吧。

再來說說“不可以修改”,因?yàn)槿绻S便亂改,那就天下大亂了。還是醫(yī)院那個(gè)例子,“醫(yī)院”這個(gè)類所映射到的對象,一定是治病的地方。如果這東西隨便改,例如明天“醫(yī)院”和“食堂”的概念對換了,那麻煩了,我們所有人都要改,要把兩個(gè)概念從腦子中對換過來,全世界的書、報(bào)紙、Internet……凡是依賴這兩者進(jìn)行表述的地方都要改,那不是天下大亂么?軟件世界中也會發(fā)生這種牽一發(fā)而動(dòng)全身的問題。所以我們提倡設(shè)計(jì)好的類一定要“對修改關(guān)閉”。

以上,就是OCP的哲學(xué)意義。

4.4、兒子,你要能完全替代老爹才行

不過,要想世界正常運(yùn)作,只有OCP似乎還有點(diǎn)問題。到目前為止,我們都是在抽象層次樹已經(jīng)存在,并且假定它完全正確的前提下討論的,可是,我們并沒有任何規(guī)則限制抽象層次樹的正確性,例如,如果我把食堂掛到醫(yī)院下,讓食堂成為醫(yī)院的子類,在理論上時(shí)沒有錯(cuò)的,但如果這樣隨便亂規(guī)定繼承關(guān)系,那么一切依賴?yán)^承正確性的原則、概念都沒有意義了。所以,只有OCP是不夠的,需要對繼承進(jìn)行一個(gè)限制。

Barbara Liskov在1987年的OOPSLA大會上發(fā)表了一篇文章——《Data Abstraction and Hierarchy》,其中提出了一個(gè)非常重要的原則,叫里氏代換原則(LSP)。

里氏代換原則(LSP):子類型應(yīng)該能代替掉其父類型,且代替后程序運(yùn)行情況不會錯(cuò)亂。

我們還是用例子去理解LSP。

現(xiàn)代辦公幾乎都要用到個(gè)人計(jì)算機(jī),個(gè)人計(jì)算機(jī)本身是一個(gè)抽象概念,臺式PC是其中一個(gè)子類。后來,發(fā)明了筆記本電腦,我們想把筆記本電腦歸為個(gè)人計(jì)算機(jī)的子類,是否合理呢?根據(jù)LSP,我們將臺式PC都替換成筆記本電腦,世界應(yīng)該是照常運(yùn)行的(當(dāng)然,實(shí)際情況可能復(fù)雜些,有些地方不能用筆記本電腦替換,但這里我們忽略這種差別)。我們辦公時(shí)依賴的類是“個(gè)人計(jì)算機(jī)”,而筆記本電腦完全可以替代這個(gè)類型而使得世界運(yùn)行正常,所以,我們說將筆記本電腦歸于個(gè)人計(jì)算機(jī)的子類是符合LSP的。

后來,又發(fā)明了轉(zhuǎn)基因黃瓜,我們也想將它歸到個(gè)人計(jì)算機(jī)的子類中去,行不行呢?好的,現(xiàn)在我們再運(yùn)用LSP,將世界上每個(gè)依賴個(gè)人計(jì)算機(jī)的地方都替換成一根轉(zhuǎn)基因黃瓜。好的,世界人民都瘋了!明顯這種替換會令世界運(yùn)行錯(cuò)亂。所以,我們不能讓轉(zhuǎn)基因黃瓜繼承個(gè)人計(jì)算機(jī)。

上面的例子是顯而易見的,但有些卻不那么明顯。例如,現(xiàn)在問,獸醫(yī)是醫(yī)生的子類嗎?這個(gè)問題,一下子還真不是很好回答,但我們可以LSP一下,現(xiàn)在,我們把醫(yī)院里的醫(yī)生都替換為獸醫(yī),你還敢去醫(yī)院看病嗎?嗯,這下子不用我多說了吧。

最后一定要說明的是,LSP應(yīng)用于程序世界和現(xiàn)實(shí)世界時(shí)有很大差別的,現(xiàn)實(shí)世界繁雜、不確定性因素多,而程序世界簡單、確定。總之,LSP就是讓你記住一條,凡是系統(tǒng)中有繼承關(guān)系的地方,子類型一定能代替父類型,而且替換后程序運(yùn)行要正常。換言之,繼承是一種嚴(yán)格的“IS-A”關(guān)系,也是“一般和特殊”的哲學(xué)原理在程序世界中的體現(xiàn)。

4.5、總結(jié)

繼承的話題就討論到這里了。很多朋友在運(yùn)用繼承時(shí)有疑惑,或不能很好的確定繼承關(guān)系,歸其根本是沒有真正理解繼承的意義。只要能理解繼承的本質(zhì)意義,加上OCP和LSP的運(yùn)用,是可以寫出正確的繼承體系。

真經(jīng)第五章——耦合 Couple

“一只蝴蝶在巴西輕拍翅膀,可以導(dǎo)致一個(gè)月后德克薩斯州的一場龍卷風(fēng)——蝴蝶效應(yīng)”

5.1、為耦合平反

做程序的人,往往感覺“耦合(Couple)”不是什么好東西。經(jīng)常有人、有書、有文章對我們諄諄教導(dǎo):要降低耦合,要降低耦合……久而久之,好像耦合在程序界成了貶義詞,弄得我們恨不得把耦合從程序里全部拿掉。

這誤解可委屈耦合了。要是哪天沒了耦合,這世界還真玩不轉(zhuǎn)。其實(shí)耦合還有另一個(gè)名字,叫“聯(lián)系”,試問要是世界上所有對象間的聯(lián)系都沒了,世界還能運(yùn)作么?耦合的存在是世界演進(jìn)的途徑,如果沒有耦合,世界就變成了“死世界”,無法演進(jìn)和發(fā)展。所以,耦合可是好東西,我們要感謝它!但是任何東西都有兩面性,過度的耦合確實(shí)會令世界的運(yùn)作產(chǎn)生困難,所以我們提倡降低耦合,這些是后話。

5.2、形形色色的耦合

下面,我們探討各種耦合式怎么出現(xiàn)的。

上一章講述了繼承,其實(shí),繼承的概念出現(xiàn)后,有父子、祖孫關(guān)系的類就有了一種聯(lián)系,這種聯(lián)系叫做“泛化耦合”。這就是我們認(rèn)識的第一種耦合。

泛化耦合(Generalization Couple):由于泛化(繼承)關(guān)系的存在,在兩個(gè)有祖孫、父子關(guān)系的類間形成的一種邏輯關(guān)聯(lián)。

然后,我們討論另一種耦合。

在文章開始,我們說對象論將對象看做基本元素,而對象中有數(shù)據(jù)和方法。在現(xiàn)實(shí)世界中,數(shù)據(jù)并不總是簡單數(shù)據(jù)。客觀存在一些對象,它們的數(shù)據(jù)是另一個(gè)或另一些對象。例如,一個(gè)具體的羊群,有一項(xiàng)數(shù)據(jù)是很多具體的羊。其中羊也是對象。當(dāng)抽象成抽象的“羊群”和“羊”類的時(shí)候,這種包含關(guān)系也隨之被抽象到了類中,由此在兩個(gè)類之間就形成了耦合。

這種耦合出現(xiàn)的哲學(xué)基礎(chǔ)是,對象本身固有的包含關(guān)系,在進(jìn)行事物抽象時(shí)被同時(shí)抽象到了類中。所以,我個(gè)人將其稱為包含耦合。

包含耦合又分為兩種情況,一種是被包含對象單純聚合在包含對象中,但沒有形成哲學(xué)意義上“整體與部分”的關(guān)系,這是一種相對較弱的聯(lián)系,叫做聚合。例如,上例中羊群和羊就是聚合關(guān)系,如果拿掉一兩只羊,羊群還是羊群。

聚合(Aggregation):一種弱的擁有關(guān)系,體現(xiàn)A對象可以包含B對象,但B對象不是A對象的一部分。

另一種情況是,被包含對象和包含對象形成了哲學(xué)意義上“整體與部分”的關(guān)系,如汽車和輪子,把輪子拿掉,汽車就不再是完整意義上的汽車了。這種關(guān)系叫做組合。

組合(Composition):一種強(qiáng)的擁有關(guān)系,體現(xiàn)了嚴(yán)格的部分和整體的關(guān)系,部分和整體具有一樣的生命周期。

通過上面的探討,我們認(rèn)識了泛化耦合、聚合和組合三種耦合形式,最后,還有一種耦合叫依賴。什么是依賴呢?我們知道,在對象論中,將世界的演進(jìn)看成是在初始作用力下,對象之間相互調(diào)用、相互協(xié)作完成的。如果兩個(gè)類在需求范圍內(nèi),既定邏輯上存在協(xié)作的可能,那么這兩個(gè)類就存在依賴關(guān)系(或叫關(guān)聯(lián)關(guān)系)。其實(shí),我們常說的“低耦合,高內(nèi)聚”、“降低耦合”等建議,主要是針對依賴說的。

依賴(Dependency):由于邏輯上相互協(xié)作可能,而形成的一種關(guān)系。

好的,到目前為止,我們已經(jīng)認(rèn)識了四種基本耦合。下面用一副圖,直觀感受一下世界的各種耦合。

圖5.1、耦合示例

圖5.1展示了幾種耦合的示例。其中汽車和交通工具屬于泛化耦合,輪子和方向盤組合于汽車,汽車聚合成車隊(duì),而汽車和司機(jī)具有依賴關(guān)系。這幅圖只是耦合的一個(gè)小片段,實(shí)際上,世界上各種對象形成了一張復(fù)雜的耦合網(wǎng),正因?yàn)橛旭詈系拇嬖?,世界才能演進(jìn)。正如馬克思主義哲學(xué)所說:聯(lián)系是普遍的、客觀的。所以,耦合的存在,有其深刻的哲學(xué)意義。

5.3、總結(jié)

不知你是否會有這樣的疑問:文章開始,不是說對象論將對象看做相互獨(dú)立的嗎?怎么又耦合起來了。這是矛盾的嗎?實(shí)則不矛盾。因?yàn)槲覀兯幍木辰缫呀?jīng)不同。剛開始,我們拋開一切,忘記一切,從本質(zhì)的角度用對象論去看世界,我們看到的對象是相對孤立的。而后來,我們的抽象思維作用于這個(gè)世界,所衍生出來的一系列概念,是我們的抽象能力給這個(gè)世界抹上的色彩。就如我們用唯物主義看世界時(shí),剛開始要拋開一切,認(rèn)為世界只有“可被意識所反映的客觀實(shí)在”,而后,這個(gè)物質(zhì)為本的世界在我們的抽象思維中衍生出各種概念。為了讓我們更好的、系統(tǒng)的認(rèn)識對象論,剛開始,我們拋開一切直取本質(zhì),而后來,我們要層層衍生,將拋卻的東西再找回來,在這個(gè)“找”的過程中,我們才能領(lǐng)會OO中的各種概念、事物其在哲學(xué)意義上是怎么來的。

6.1、導(dǎo)言

在前五章中,我們從世界觀的這話題開始,逐步引出了抽象、層次、繼承和耦合。這些內(nèi)容,形成了對象論中關(guān)于世界的結(jié)構(gòu)體系。

然而,要想真正描述一個(gè)世界,僅有結(jié)構(gòu)式不行的。開始我們說過,世界觀主要關(guān)注兩個(gè)方面:一是世界是什么樣子的(結(jié)構(gòu)),另一個(gè)就是世界時(shí)如何演進(jìn)的(運(yùn)作)。現(xiàn)在,我們來討論對象論中關(guān)于世界運(yùn)作的理論。

這里首先要指出一點(diǎn),“對象論”是關(guān)于程序世界(即將一個(gè)軟件系統(tǒng)看成一個(gè)世界)的世界觀,而非關(guān)于現(xiàn)實(shí)世界的,所以,將對象論應(yīng)用于現(xiàn)實(shí)世界時(shí),往往會有所偏頗。其實(shí)前面的某些地方已經(jīng)體現(xiàn)出這一點(diǎn),而在運(yùn)作理論這里,會體現(xiàn)的尤其明顯。但是為了直觀起見,我依然會將對象論應(yīng)用于現(xiàn)實(shí)世界去舉例子,當(dāng)然我會非常謹(jǐn)慎和小心,并且會明確指出對象論應(yīng)用于現(xiàn)實(shí)世界的偏頗在哪里。

6.2、世界本沒有類

對象論認(rèn)為:世界的演進(jìn),是而且只是各種對象通過互相調(diào)用其他對象的公開服務(wù)而完成交互。

注意,是對象交互,而不是類交互!沒錯(cuò),類之間是永遠(yuǎn)不可能交互的。因?yàn)椴徽撌乾F(xiàn)實(shí)世界還是程序世界,從來不存在具體的類。類只是抽象思維作用于對象的產(chǎn)物,它幫助我們理解、記憶、分析和設(shè)計(jì)。類是抽象的概念,它“客觀”存在,但不是“具體”的存在。

例如,現(xiàn)實(shí)世界中,我們可以找出很多個(gè)“具體的蘋果”對象,但是你能找出一個(gè)東西,說它是“蘋果”這個(gè)類嗎?你這一輩子吃的每一個(gè)蘋果,都是一個(gè)具體的蘋果對象,從來沒有具體的“蘋果類”和你交互過。再上升一點(diǎn),你一生交互過的所有東西,都是對象,而沒有一個(gè)具體的類?!邦悺辈贿^是你的抽象思維作用于對象形成的幫助你理解認(rèn)識世界的抽象概念罷了?!邦悅儭睆牟辉湍阏嬲换ァ?/p>

程序世界中也是一樣,程序運(yùn)行起來,從來都是具體對象之間的交互,類只是幫助你分析設(shè)計(jì)的概念工具罷了。

認(rèn)識到上面幾點(diǎn)對于理解對象論的世界運(yùn)行理論非常重要,時(shí)刻銘記,參與真正世界運(yùn)行的,只有對象,沒有類!對象在世界中,類在我們心中!

這一小節(jié)的標(biāo)題是“世界本沒有類”,代表兩個(gè)意思:一是世界“本來”沒有類,二是世界“本質(zhì)”沒有類。

你可能會問,在第五章“耦合”中,不是說依賴關(guān)系是“兩個(gè)類因?yàn)榭赡芙换ザa(chǎn)生的關(guān)系”嗎?其實(shí),確切點(diǎn)說,應(yīng)該是“兩個(gè)類所能映射到的對象因?yàn)榭赡芙换ザa(chǎn)生的關(guān)系”,本質(zhì)上,依賴本來是對象間的依賴,只不過在抽象時(shí)被同時(shí)抽象到類里面了。

6.3、程序世界——大同的和諧世界

雖然在對象論里,現(xiàn)實(shí)世界和抽象世界的基本運(yùn)作機(jī)理是一樣的,但程序世界和現(xiàn)實(shí)世界在具體運(yùn)作上有很大差別。首先,我要告訴你,程序世界時(shí)多么的大同和和諧!

程序世界與現(xiàn)實(shí)世界第一點(diǎn)區(qū)別:現(xiàn)實(shí)世界的依賴以對象為單位,程序世界的依賴以類為單位。

沒明白這意味著什么?

舉個(gè)例子,在現(xiàn)實(shí)世界中,是不是關(guān)系很重要啊。為什么?因?yàn)槟阏J(rèn)識的人多,可依賴的人就多。例如你生病了,如果你有個(gè)醫(yī)生朋友,看病就方便很多;如果你要打官司,而你又恰巧認(rèn)識律師朋友,是不是很爽呢;如果你想上清華大學(xué),剛好清華大學(xué)校長是你親戚,那一切就好辦多了是吧。

為什么會這樣?究其本質(zhì),是因?yàn)?b>現(xiàn)實(shí)世界中對象間的依賴是以對象為單位的,這種依賴關(guān)系不會隨著泛化過程而被泛化到類里面去。例如,有一個(gè)人現(xiàn)在在北京航空航天大學(xué)上學(xué),從這“一個(gè)人可”以泛化出“人”這個(gè)類,而北航可以泛化出“大學(xué)”這個(gè)類,但這個(gè)具體的人和北航的這種關(guān)系可沒有被泛化到兩個(gè)類中,也就是說,并不是每一個(gè)“人對象”都可以去任何一個(gè)“大學(xué)對象”去上學(xué)的。

不過,如果是程序世界里,上面的推理是可行的,因?yàn)?b>程序世界中對象間的依賴是以類為單位的,這種依賴關(guān)系會隨著泛化過程而被泛化到類里面去。并且,只要兩個(gè)類建立了依賴,那么兩個(gè)類之間的所有對象都兩兩依賴了。換句話說,在程序世界里,只要有一個(gè)“人”和一個(gè)“大學(xué)”發(fā)生了聯(lián)系,那么這種聯(lián)系就被泛化到類中了,隨后,所有的“人”都可以上“任何”的大學(xué)。

圖6.1、兩個(gè)世界中依賴的區(qū)別

看圖6.1,假設(shè)世界上只有三個(gè)人和三所大學(xué)。在現(xiàn)實(shí)世界中,小龍女考上了清華,不過這和其他人其他大學(xué)一點(diǎn)關(guān)系也沒有,這種關(guān)系并沒有體現(xiàn)在類上,看,兩個(gè)類沒有任何聯(lián)系。但在程序世界中,小龍女考上了清華,一下子人和大學(xué)兩個(gè)類就關(guān)聯(lián)起來了,接著,張無忌和郭靖這兩個(gè)不好好學(xué)習(xí)的學(xué)生也沾了光,和三所大學(xué)都聯(lián)系起來了。(提示:其實(shí)這里和第四章講到的OCP和LSP聯(lián)系非常緊密,讀者可以聯(lián)系OCP和LSP兩個(gè)原則自己思考一下為什么程序世界會這樣。)

你知道了吧,在程序世界里,全世界的醫(yī)生隨你看,律師隨你用,大學(xué)隨你上,美食隨你吃!多么和諧大同的美好世界!

看了上面對程序世界的描述,你是不是已經(jīng)垂涎三尺了?恨不得自己變成一段代碼,跑到程序世界里。不過別著急,事情也許沒有你想象的那么美好。下面我們來看另一個(gè)程序世界與現(xiàn)實(shí)世界的區(qū)別。

6.4、程序世界——封建的專制世界

上文描述了程序世界是多么多么美好,不過如果有一天,你真的跑到里面去了,你可就慘了。不信看下面。話說你一進(jìn)程序世界,就迫不及待想在程序世界里找個(gè)漂亮的女朋友,可以嗎?對不起,不成!你想吃法國大餐,對不起,不成!你想上最好的大學(xué),對不起,不成!……搞什么!不是說程序世界什么都可以得到嗎。沒錯(cuò),除了選擇權(quán)!

程序世界里的對象沒有選擇權(quán)。

為什么會這樣?因?yàn)槿绻麑ο笥羞x擇權(quán),就沒法貫徹OCP了!你要是活在程序世界里,不但給你包辦婚姻,連吃飯、上學(xué)……一切的一切,你都得服從包辦,對象一點(diǎn)點(diǎn)選擇權(quán)也沒有。至于誰給你包辦的,那是后話。

看了這些,你還敢去程序世界嗎?不過這還不是最恐怖的,告訴你更恐怖的一點(diǎn):

程序世界里的對象不認(rèn)識對象。

沒錯(cuò),良好的面向?qū)ο筇岢珜ο蟛徽J(rèn)識對象!很不可思議?其實(shí),這就是所謂的“低耦合”,我們喊了那么多年的“低耦合”,到底什么是低耦合?所謂低耦合,就是先剝奪對象的選擇權(quán),再剝奪對象的感覺。對象間誰也不認(rèn)識誰,只知道對象能提供什么服務(wù)。

我們現(xiàn)在了解了程序世界是什么樣子了,下面,我們討論程序世界為什么要這樣。

6.5、有奶就是娘

中國有句俗語,叫“有奶就是娘”,往往用來諷刺那種六親不認(rèn),兩面三刀,誰給好處就跟誰的無恥小人。不過,面向?qū)ο罂墒欠浅L岢坝心叹褪悄铩钡男袨?。如果我們的程序都能做到“有奶就是娘”的地步,那就真是?shí)現(xiàn)了“低耦合”這一教義了,套用梁朝偉的話,在程序世界里,有奶就是娘的行為“是美德”。

要理解上述道理,我們要先拋卻我們腦中的道德、廉恥等概念,從本質(zhì)上看看“有奶就是娘”體現(xiàn)了什么哲學(xué)道理。

“有奶就是娘”,純從字面解釋,是說任何一個(gè)人,只要能給奶喝,就當(dāng)做自己親娘。上升到哲學(xué)層面,是說這么一個(gè)意思:不以其他對象實(shí)體本身為交互準(zhǔn)則,而以其他對象的行為作為交互準(zhǔn)則,與一個(gè)對象是否進(jìn)行交互純粹是從其行為判斷,而不對對象本體有任何概念。

這種處事哲學(xué),在現(xiàn)實(shí)生活中是最被人鄙夷的,但在程序世界里確是最提倡的。如果一個(gè)程序世界里,所有對象都能以“有奶就是娘”的哲學(xué)去處事,那么,這就是一個(gè)最美好運(yùn)作方式。

6.6、接口橫空出世

上文說到,程序世界中提倡的運(yùn)作方式是“有奶就是娘”的方式,但要真正實(shí)現(xiàn)這種方式,似乎還少點(diǎn)東西。我們回顧一下,世界本來只有對象,我們從對象中抽象出了類,這就是目前我們眼中的世界。這樣,我們的交互,要么以對象為準(zhǔn)則,要么以類為準(zhǔn)則。

以對象為準(zhǔn)則,顯然是不行的,因?yàn)槲覀冋f了,對象間根本互不認(rèn)識。以類為準(zhǔn)則,理論上可行,但這樣有問題,就是類本身是對象“實(shí)體的抽象”,是為了更好記憶、描述和認(rèn)識世界而創(chuàng)建的對象,歸根到底,還是“實(shí)體”范疇的概念,所以在哲學(xué)上還是和“以行為作為交互準(zhǔn)則”向左。

認(rèn)識到以上困難,就能認(rèn)識到,目前我們的世界還無法實(shí)現(xiàn)以行為為交互準(zhǔn)則,于是,我們需要為世界再衍生一些內(nèi)容。第二章說過,世界本身只有對象,而衍生其他概念的基本方法是抽象。所以,這里我們當(dāng)然要用抽象衍生一些概念出來。進(jìn)一步,類是對象“實(shí)體”的抽象,而我們需要的是以行為為交互準(zhǔn)則,很自然的,我們完全可以創(chuàng)建一種新概念,這種概念是行為的抽象,這種新概念,就是接口(Interface)。

接口(Interface):對象行為的抽象。

這里要說明,接口和類雖然都是從對象上通過抽象衍生出的概念,但兩者本質(zhì)不同,是從對象的兩個(gè)不同的哲學(xué)角度和動(dòng)機(jī),抽象出的不同概念,并形成世界兩個(gè)完全不同的方面(Aspect)。至于兩者具體有什么區(qū)別,下一小節(jié)詳細(xì)討論。

6.7、接口 vs 抽象類

經(jīng)常有朋友迷惑一件事情,抽象類和接口有什么區(qū)別?何時(shí)使用抽象類,何時(shí)使用接口?但從功能來講,抽象類完全可以代替接口,那為什么還要有接口呢?這一小節(jié)來分析這些問題。

這里附帶說一個(gè)問題,產(chǎn)生這種疑惑的原因,大多是因?yàn)榕笥褌円呀?jīng)習(xí)慣了學(xué)習(xí)一個(gè)東西時(shí),只看其什么樣子?怎么用?而不習(xí)慣于弄清楚一個(gè)東西起源于哪?出現(xiàn)的動(dòng)機(jī)是什么?其實(shí),要想學(xué)好、用好任何一個(gè)東西,后兩個(gè)問題更關(guān)鍵一些。

舉個(gè)例子,有人發(fā)明了吹風(fēng)機(jī),我們?nèi)绻桓闱宄涫鞘裁礃幼印坝袀€(gè)把手,有個(gè)吹風(fēng)筒”,以及怎么用——“打開按鈕能吹出熱風(fēng),關(guān)閉按鈕就停止了”。如果我們只搞清楚這些,那么我們八成用不對這個(gè)東西,為什么?因?yàn)槲覀兏静恢肋@東西是怎么來的,它為什么要被發(fā)明出來。也許我們天天拿他吹臉取暖或吹衣服,還一派洋洋得意以為用的很好的樣子。殊不知這東西其實(shí)是用來吹頭發(fā)幫助頭發(fā)快點(diǎn)干起來的。

不要笑,這種事經(jīng)常發(fā)生在我們身上。因?yàn)樵谲浖_發(fā)中,有太多的東西,我們只顧著學(xué)習(xí)其是什么樣子,怎么個(gè)用法,也許就像吹風(fēng)機(jī)一樣,這些并不復(fù)雜,然后我們就把它用到不該用的地方,還以為自己用得很好。

用不用得好吹風(fēng)機(jī),不在于是否熟練掌握開開關(guān)關(guān),而在于是不是用它吹頭發(fā)。同理,任何東西用得好不好,不在于是不是熟練掌握用法,而在于是不是用對了地方。而要想用對地方,就要弄清楚這個(gè)東西的“怎么出來的”和“出來是做什么用的”。

說了挺多,我們回到接口和抽象類的話題上來。

首先要說明一點(diǎn),“抽象類(Abstract Class)”和“類(Class)”在哲學(xué)意義上沒什么區(qū)別,其區(qū)別僅僅是實(shí)現(xiàn)層面上的,即抽象類只不過是一種特殊的類,編程環(huán)境強(qiáng)制不準(zhǔn)這種類生成實(shí)例,哲學(xué)意義上兩者沒有任何區(qū)別。所以,從哲學(xué)層面討論“抽象類與接口對比”和討論“類與接口對比”是等價(jià)的。

類與接口的不同點(diǎn)有以下幾點(diǎn):

I. 抽象范疇不同。類是對象“體征”的抽象,接口是對象行為的抽象。

II. 抽象動(dòng)機(jī)不同。抽象出類是為了幫助記憶、認(rèn)識世界,抽象出接口是為了實(shí)現(xiàn)低耦合交互。

III. 關(guān)注不同。類關(guān)注共同的體征,接口關(guān)注用來交互的行為。

IV. 存在范疇不同。類存在于抽象層次樹上,接口存在于接口網(wǎng)。

V. 應(yīng)用范疇不同。類應(yīng)用于結(jié)構(gòu)范疇,是靜態(tài)概念,接口應(yīng)用于運(yùn)作范疇,是動(dòng)態(tài)概念。

上面的條目有點(diǎn)學(xué)術(shù)了,通俗說來,類是從對象實(shí)體的的體征范疇上抽象出來的,用來幫助我們記憶、分析世界不同的對象,主要表明對象“什么樣子”;而接口是從對象交互時(shí)需要的行為中抽象出來的,關(guān)注對象交互時(shí)需要的行為。

還是舉個(gè)例子吧。

例如,有一群具體的司機(jī)和好多輛具體的汽車,我們可以從司機(jī)中抽象出“司機(jī)”這個(gè)類,從汽車抽象出“汽車”這個(gè)類,這種抽象是“體征范疇”的,抽象的目的僅僅是幫助記憶、認(rèn)識,完全和交互沒有關(guān)系。而當(dāng)考慮到交互——司機(jī)需要駕駛汽車,于是抽象出一個(gè)“可駕駛”這個(gè)接口。注意,一但“可駕駛”這個(gè)接口被抽象出來,就完全和司機(jī)以及汽車沒有關(guān)系了,除了汽車,拖拉機(jī)、輪船、飛機(jī)都可以實(shí)現(xiàn)這個(gè)接口,而不一定是司機(jī),會開車的任何人都可以通過“可駕駛”這個(gè)接口去駕駛?cè)魏螌?shí)現(xiàn)“可駕駛”接口的東西。這樣一來,“駕駛”這種交互就完全取決于這個(gè)接口了,這就是“以行為為交互準(zhǔn)則的意思”。

如果明白了這一小節(jié)的內(nèi)容,相信大家再也不會被“接口和類有什么區(qū)別?”、“何時(shí)使用抽象類,何時(shí)使用接口?”這樣的問題迷惑了,而可以揮灑自如的在系統(tǒng)中正確使用接口和類。一個(gè)方法:拿不準(zhǔn)的時(shí)候問問自己,這個(gè)抽象是體征抽象還是行為抽象?是為了記憶、分析、設(shè)計(jì)還是為了交互需要?想明白,再下手。

6.8、依賴是如何被倒置的

弄清楚了接口,下面可以談一個(gè)有名的OO原則了:依賴倒置原則(DIP)。

如上,我們先不說DIP是什么,而是搞清楚DIP的來龍去脈。到時(shí),朋友們自然對DIP就有深刻理解了。我們開始!

首先,我們要說明,依賴是有方向的,客戶類依賴于服務(wù)類。什么是客戶類?如果A類需要B類提供的服務(wù),那么A類就依賴B類,反之不成立。在沒有引入接口前,客戶類“知道”服務(wù)類,而服務(wù)類“不知道”客戶類,就像下面這個(gè)樣子。

圖6.2、沒有接口的依賴

我們看到,司機(jī)作為客戶類,汽車作為服務(wù)類。依賴的方向是從司機(jī)到汽車,以為這里司機(jī)要使用汽車提供的“駕駛”方法操作汽車。這是我們不推薦的方式,因?yàn)椴粔颉八神詈稀薄S谑?,我們將駕駛抽象成接口,依賴變成如下形式。

圖6.3、引入接口后的依賴

如圖6.3所示,我們從這種交互關(guān)系中,抽象出了“可駕駛”這個(gè)接口。注意,此時(shí)兩者誰也不依賴誰,或說誰也不知道誰了。那么為什么司機(jī)可以放心呢?因?yàn)樗揽神{駛接口的存在,他要駕駛的東西一定實(shí)現(xiàn)了這個(gè)接口,甭管是什么,只要實(shí)現(xiàn)了這個(gè)接口,我就能駕駛。其實(shí)這里才體現(xiàn)出接口的哲學(xué)意義。

接口的哲學(xué)意義:對客戶類的保證,對服務(wù)類的約束。

正是接口約束了服務(wù)類必須實(shí)現(xiàn)什么功能,客戶類才可以在不知道具體服務(wù)類的情況下“放心”進(jìn)行交互,因?yàn)榻涌趯蛻纛愄峁┝艘环N保證。希望各位能好好體會接口的這種哲學(xué)意義,這對于對象論的良好運(yùn)行體質(zhì)的理解非常重要。

可是,這樣還不夠,我們還有一個(gè)非常重要的問題沒有討論:誰有權(quán)利定義接口?或者說服務(wù)類和客戶類誰擁有接口?當(dāng)然,理論上時(shí)誰擁有都可以,但卻會對世界的運(yùn)作產(chǎn)生巨大影響。我們先看服務(wù)類擁有接口的情形。

圖6.4、服務(wù)類擁有接口

如圖6.4,由于服務(wù)類擁有制定接口的權(quán)利,所以各個(gè)服務(wù)類都定義了自己的接口,一般情況下他們的接口是不相容的。如圖,司機(jī)可以駕駛汽車,但由于輪船、飛機(jī)各自有自己的可駕駛接口,所以會開汽車未必會開飛機(jī)和輪船,如果要開飛機(jī)或輪船還要一個(gè)個(gè)學(xué),現(xiàn)實(shí)世界中就是這樣一種情況。所以,這種世界的運(yùn)行其實(shí)接口幾乎沒有起到作用,由于服務(wù)類是“大爺”,所以它們可以指定諸多霸王條款,而客戶必須忍氣吞聲去遷就,所以,實(shí)際的依賴方向還是從客戶類到服務(wù)類。

下面在看看客戶類擁有接口會是什么樣子。

圖6.5、客戶類擁有接口

看上圖,客戶終于翻身做主人了,現(xiàn)在客戶擁有定義接口的權(quán)利,服務(wù)類必須無條件實(shí)現(xiàn),這下好了,只要會開汽車,就會開輪船和飛機(jī),因?yàn)榭蛻粲袡?quán)利定義一個(gè)統(tǒng)一的接口,服務(wù)類必須無條件實(shí)現(xiàn)!這樣,三種交通工具的駕駛方法必須完全一致(雖然現(xiàn)實(shí)世界還沒有這樣),這回客戶終于可以揚(yáng)眉吐氣,體會一把“顧客是上帝”的感覺了。

在圖6.5的情況下,司機(jī)可以有權(quán)定義接口,他不必“知道”服務(wù)類,而服務(wù)類必須“知道”客戶定義了什么接口,你有沒有發(fā)現(xiàn),依賴的方向已經(jīng)悄悄倒置過來了!變成服務(wù)類依賴客戶類了(誰知道誰,誰就依賴誰)!這就是“依賴倒置”的由來。不必說,所謂依賴倒置原則就是讓我們必須按圖6.5的方式運(yùn)行世界,而不能按圖6.2,6.3,6.4的方式。下面正式定義依賴倒置原則。

依賴倒置原則(DIP):客戶類和服務(wù)類都應(yīng)該依賴于抽象(接口),并且客戶類擁有接口。

我想,看過上述來龍去脈,已經(jīng)不用我再去解釋這個(gè)原則了吧。

6.9、神秘的統(tǒng)治者

到目前為止,我們基本已經(jīng)搞清楚了對象世界的運(yùn)行機(jī)制。但仍有一個(gè)疑問:我們曾經(jīng)說過,程序世界里對象時(shí)沒有選擇權(quán)的,甚至不知道誰是誰,只知道接口,那么,誰來指定服務(wù)類呢?

例如,上述司機(jī)可以制定接口,所以汽車、飛機(jī)、輪船等可駕駛的東西都要實(shí)現(xiàn),于是司機(jī)可以按照自己制定的方式駕駛東西。但是,司機(jī)不能選擇駕駛什么啊,他根本不知道自己駕駛的是什么,那么,誰制定他是駕駛飛機(jī)、汽車還是輪船呢?

似乎冥冥中,這個(gè)世界存在一個(gè)統(tǒng)治者,它掌管所有對象之間誰和誰交互(只要不違反接口),否則,世界根本沒法正常運(yùn)行。沒錯(cuò),程序世界是有這么一個(gè)統(tǒng)治者,他就是大名鼎鼎的“依賴注入容器(DI)”,也有人叫做“控制反轉(zhuǎn)容器(IoC)”。

什么叫依賴注入?什么叫控制反轉(zhuǎn)?如果你看了上面的文章,那太好理解了,依賴注入就是容器挑選符合接口的服務(wù)類為客戶類提供服務(wù)。例如,上面司機(jī)要一個(gè)可駕駛的東西,容器就會根據(jù)既定規(guī)則選擇一個(gè),可能是飛機(jī)、可能是汽車、也可能是輪船,交給司機(jī)。司機(jī)駕駛就行了,不用管是什么,反正知道這東西肯定實(shí)現(xiàn)了“可駕駛”接口。

讓我們向這個(gè)偉大的統(tǒng)治者致敬吧,沒有他,程序世界可真玩不轉(zhuǎn)了(當(dāng)然,如果某個(gè)程序世界不符合DIP甚至沒接口,都是類之間依賴,那么就不需要依賴注入容器了,不過這么一來,可就是“高耦合”了,是OO所反對的)。

6.10、運(yùn)作起來吧

到了這里,根本不用我廢話說程序世界時(shí)怎么運(yùn)作的了,因?yàn)樯厦娑家呀?jīng)說明白了。不過,我還是用短短幾句話總結(jié)一下吧。

一個(gè)符合OO原則的、低耦合的程序世界的運(yùn)作形式是這樣的:首先參與運(yùn)作的本質(zhì)只有對象,對象不直接依賴,沒有選擇權(quán),互相不知道,而只知道各個(gè)接口??蛻纛愔贫ń涌冢瑢ο箝g通過接口交互,形成運(yùn)作。世界的統(tǒng)治者依賴注入容器決定選擇哪個(gè)服務(wù)類給客戶類使用。

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

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

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