OO真經——關於面向對象的哲學體系及科學體系的探討(下)



真經第六章——運作 Moving

      “運動是絕對的——牛頓”

6.1、導言

      在前五章中,我們從世界觀的這話題開始,逐步引出了抽象、層次、繼承和耦合。這些內容,形成了對象論中關於世界的結構體系。
      然而,要想真正描述一個世界,僅有結構式不行的。開始我們說過,世界觀主要關注兩個方面:一是世界是什麼樣子的(結構),另一個就是世界時如何演進的(運作)。現在,我們來討論對象論中關於世界運作的理論。
      這裏首先要指出一點,“對象論”是關於程序世界(即將一個軟件系統看成一個世界)的世界觀,而非關於現實世界的,所以,將對象論應用於現實世界時,往往會有所偏頗。其實前面的某些地方已經體現出這一點,而在運作理論這裏,會體現的尤其明顯。但是爲了直觀起見,我依然會將對象論應用於現實世界去舉例子,當然我會非常謹慎和小心,並且會明確指出對象論應用於現實世界的偏頗在哪裏。

6.2、世界本沒有類

      對象論認爲:世界的演進,是而且只是各種對象通過互相調用其他對象的公開服務而完成交互。

      注意,是對象交互,而不是類交互!沒錯,類之間是永遠不可能交互的。因爲不論是現實世界還是程序世界,從來不存在具體的類。類只是抽象思維作用於對象的產物,它幫助我們理解、記憶、分析和設計。類是抽象的概念,它“客觀”存在,但不是“具體”的存在。
      例如,現實世界中,我們可以找出很多個“具體的蘋果”對象,但是你能找出一個東西,說它是“蘋果”這個類嗎?你這一輩子吃的每一個蘋果,都是一個具體的蘋果對象,從來沒有具體的“蘋果類”和你交互過。再上升一點,你一生交互過的所有東西,都是對象,而沒有一個具體的類。“類”不過是你的抽象思維作用於對象形成的幫助你理解認識世界的抽象概念罷了。“類們”從不曾和你真正交互。
      程序世界中也是一樣,程序運行起來,從來都是具體對象之間的交互,類只是幫助你分析設計的概念工具罷了。
      認識到上面幾點對於理解對象論的世界運行理論非常重要,時刻銘記,參與真正世界運行的,只有對象,沒有類!對象在世界中,類在我們心中!
      這一小節的標題是“世界本沒有類”,代表兩個意思:一是世界“本來”沒有類,二是世界“本質”沒有類。
      你可能會問,在第五章“耦合”中,不是說依賴關係是“兩個類因爲可能交互而產生的關係”嗎?其實,確切點說,應該是“兩個類所能映射到的對象因爲可能交互而產生的關係”,本質上,依賴本來是對象間的依賴,只不過在抽象時被同時抽象到類裏面了。

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

      雖然在對象論裏,現實世界和抽象世界的基本運作機理是一樣的,但程序世界和現實世界在具體運作上有很大差別。首先,我要告訴你,程序世界時多麼的大同和和諧!

      程序世界與現實世界第一點區別:現實世界的依賴以對象爲單位,程序世界的依賴以類爲單位。

      沒明白這意味着什麼?
      舉個例子,在現實世界中,是不是關係很重要啊。爲什麼?因爲你認識的人多,可依賴的人就多。例如你生病了,如果你有個醫生朋友,看病就方便很多;如果你要打官司,而你又恰巧認識律師朋友,是不是很爽呢;如果你想上清華大學,剛好清華大學校長是你親戚,那一切就好辦多了是吧。
      爲什麼會這樣?究其本質,是因爲現實世界中對象間的依賴是以對象爲單位的,這種依賴關係不會隨着泛化過程而被泛化到類裏面去。例如,有一個人現在在北京航空航天大學上學,從這“一個人可”以泛化出“人”這個類,而北航可以泛化出“大學”這個類,但這個具體的人和北航的這種關係可沒有被泛化到兩個類中,也就是說,並不是每一個“人對象”都可以去任何一個“大學對象”去上學的。
      不過,如果是程序世界裏,上面的推理是可行的,因爲程序世界中對象間的依賴是以類爲單位的,這種依賴關係會隨着泛化過程而被泛化到類裏面去。並且,只要兩個類建立了依賴,那麼兩個類之間的所有對象都兩兩依賴了。換句話說,在程序世界裏,只要有一個“人”和一個“大學”發生了聯繫,那麼這種聯繫就被泛化到類中了,隨後,所有的“人”都可以上“任何”的大學。

6.1

圖6.1、兩個世界中依賴的區別

      看圖6.1,假設世界上只有三個人和三所大學。在現實世界中,小龍女考上了清華,不過這和其他人其他大學一點關係也沒有,這種關係並沒有體現在類上,看,兩個類沒有任何聯繫。但在程序世界中,小龍女考上了清華,一下子人和大學兩個類就關聯起來了,接着,張無忌和郭靖這兩個不好好學習的學生也沾了光,和三所大學都聯繫起來了。(提示:其實這裏和第四章講到的OCP和LSP聯繫非常緊密,讀者可以聯繫OCP和LSP兩個原則自己思考一下爲什麼程序世界會這樣。)
      你知道了吧,在程序世界裏,全世界的醫生隨你看,律師隨你用,大學隨你上,美食隨你吃!多麼和諧大同的美好世界!

      看了上面對程序世界的描述,你是不是已經垂涎三尺了?恨不得自己變成一段代碼,跑到程序世界裏。不過彆着急,事情也許沒有你想象的那麼美好。下面我們來看另一個程序世界與現實世界的區別。

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

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

      程序世界裏的對象沒有選擇權。

      爲什麼會這樣?因爲如果對象有選擇權,就沒法貫徹OCP了!你要是活在程序世界裏,不但給你包辦婚姻,連吃飯、上學……一切的一切,你都得服從包辦,對象一點點選擇權也沒有。至於誰給你包辦的,那是後話。
      看了這些,你還敢去程序世界嗎?不過這還不是最恐怖的,告訴你更恐怖的一點:

      程序世界裏的對象不認識對象。

      沒錯,良好的面向對象提倡對象不認識對象!很不可思議?其實,這就是所謂的“低耦合”,我們喊了那麼多年的“低耦合”,到底什麼是低耦合?所謂低耦合,就是先剝奪對象的選擇權,再剝奪對象的感覺。對象間誰也不認識誰,只知道對象能提供什麼服務。
      我們現在瞭解了程序世界是什麼樣子了,下面,我們討論程序世界爲什麼要這樣。

6.5、有奶就是娘

      中國有句俗語,叫“有奶就是娘”,往往用來諷刺那種六親不認,兩面三刀,誰給好處就跟誰的無恥小人。不過,面向對象可是非常提倡“有奶就是娘”的行爲。如果我們的程序都能做到“有奶就是娘”的地步,那就真是實現了“低耦合”這一教義了,套用梁朝偉的話,在程序世界裏,有奶就是孃的行爲“是美德”。

      要理解上述道理,我們要先拋卻我們腦中的道德、廉恥等概念,從本質上看看“有奶就是娘”體現了什麼哲學道理。
      “有奶就是娘”,純從字面解釋,是說任何一個人,只要能給奶喝,就當做自己親孃。上升到哲學層面,是說這麼一個意思:不以其他對象實體本身爲交互準則,而以其他對象的行爲作爲交互準則,與一個對象是否進行交互純粹是從其行爲判斷,而不對對象本體有任何概念。
      這種處事哲學,在現實生活中是最被人鄙夷的,但在程序世界裏確是最提倡的。如果一個程序世界裏,所有對象都能以“有奶就是娘”的哲學去處事,那麼,這就是一個最美好運作方式。

6.6、接口橫空出世

      上文說到,程序世界中提倡的運作方式是“有奶就是娘”的方式,但要真正實現這種方式,似乎還少點東西。我們回顧一下,世界本來只有對象,我們從對象中抽象出了類,這就是目前我們眼中的世界。這樣,我們的交互,要麼以對象爲準則,要麼以類爲準則。
      以對象爲準則,顯然是不行的,因爲我們說了,對象間根本互不認識。以類爲準則,理論上可行,但這樣有問題,就是類本身是對象“實體的抽象”,是爲了更好記憶、描述和認識世界而創建的對象,歸根到底,還是“實體”範疇的概念,所以在哲學上還是和“以行爲作爲交互準則”向左。

      認識到以上困難,就能認識到,目前我們的世界還無法實現以行爲爲交互準則,於是,我們需要爲世界再衍生一些內容。第二章說過,世界本身只有對象,而衍生其他概念的基本方法是抽象。所以,這裏我們當然要用抽象衍生一些概念出來。進一步,類是對象“實體”的抽象,而我們需要的是以行爲爲交互準則,很自然的,我們完全可以創建一種新概念,這種概念是行爲的抽象,這種新概念,就是接口(Interface)。

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

      這裏要說明,接口和類雖然都是從對象上通過抽象衍生出的概念,但兩者本質不同,是從對象的兩個不同的哲學角度和動機,抽象出的不同概念,並形成世界兩個完全不同的方面(Aspect)。至於兩者具體有什麼區別,下一小節詳細討論。

6.7、接口 vs 抽象類

      經常有朋友迷惑一件事情,抽象類和接口有什麼區別?何時使用抽象類,何時使用接口?但從功能來講,抽象類完全可以代替接口,那爲什麼還要有接口呢?這一小節來分析這些問題。

      這裏附帶說一個問題,產生這種疑惑的原因,大多是因爲朋友們已經習慣了學習一個東西時,只看其什麼樣子?怎麼用?而不習慣於弄清楚一個東西起源於哪?出現的動機是什麼?其實,要想學好、用好任何一個東西,後兩個問題更關鍵一些。
      舉個例子,有人發明了吹風機,我們如果只搞清楚其是什麼樣子——“有個把手,有個吹風筒”,以及怎麼用——“打開按鈕能吹出熱風,關閉按鈕就停止了”。如果我們只搞清楚這些,那麼我們八成用不對這個東西,爲什麼?因爲我們根本不知道這東西是怎麼來的,它爲什麼要被髮明出來。也許我們天天拿他吹臉取暖或吹衣服,還一派洋洋得意以爲用的很好的樣子。殊不知這東西其實是用來吹頭髮幫助頭髮快點幹起來的。
      不要笑,這種事經常發生在我們身上。因爲在軟件開發中,有太多的東西,我們只顧着學習其是什麼樣子,怎麼個用法,也許就像吹風機一樣,這些並不複雜,然後我們就把它用到不該用的地方,還以爲自己用得很好。
      用不用得好吹風機,不在於是否熟練掌握開開關關,而在於是不是用它吹頭髮。同理,任何東西用得好不好,不在於是不是熟練掌握用法,而在於是不是用對了地方。而要想用對地方,就要弄清楚這個東西的“怎麼出來的”和“出來是做什麼用的”。

      說了挺多,我們回到接口和抽象類的話題上來。
      首先要說明一點,“抽象類(Abstract Class)”和“類(Class)”在哲學意義上沒什麼區別,其區別僅僅是實現層面上的,即抽象類只不過是一種特殊的類,編程環境強制不準這種類生成實例,哲學意義上兩者沒有任何區別。所以,從哲學層面討論“抽象類與接口對比”和討論“類與接口對比”是等價的。

      類與接口的不同點有以下幾點:
      I. 抽象範疇不同。類是對象“體徵”的抽象,接口是對象行爲的抽象。
      II. 抽象動機不同。抽象出類是爲了幫助記憶、認識世界,抽象出接口是爲了實現低耦合交互。
      III. 關注不同。類關注共同的體徵,接口關注用來交互的行爲。
      IV. 存在範疇不同。類存在於抽象層次樹上,接口存在於接口網。
      V. 應用範疇不同。類應用於結構範疇,是靜態概念,接口應用於運作範疇,是動態概念。

      上面的條目有點學術了,通俗說來,類是從對象實體的的體徵範疇上抽象出來的,用來幫助我們記憶、分析世界不同的對象,主要表明對象“什麼樣子”;而接口是從對象交互時需要的行爲中抽象出來的,關注對象交互時需要的行爲。
      還是舉個例子吧。
      例如,有一羣具體的司機和好多輛具體的汽車,我們可以從司機中抽象出“司機”這個類,從汽車抽象出“汽車”這個類,這種抽象是“體徵範疇”的,抽象的目的僅僅是幫助記憶、認識,完全和交互沒有關係。而當考慮到交互——司機需要駕駛汽車,於是抽象出一個“可駕駛”這個接口。注意,一但“可駕駛”這個接口被抽象出來,就完全和司機以及汽車沒有關係了,除了汽車,拖拉機、輪船、飛機都可以實現這個接口,而不一定是司機,會開車的任何人都可以通過“可駕駛”這個接口去駕駛任何實現“可駕駛”接口的東西。這樣一來,“駕駛”這種交互就完全取決於這個接口了,這就是“以行爲爲交互準則的意思”。

      如果明白了這一小節的內容,相信大家再也不會被“接口和類有什麼區別?”、“何時使用抽象類,何時使用接口?”這樣的問題迷惑了,而可以揮灑自如的在系統中正確使用接口和類。一個方法:拿不準的時候問問自己,這個抽象是體徵抽象還是行爲抽象?是爲了記憶、分析、設計還是爲了交互需要?想明白,再下手。

6.8、依賴是如何被倒置的

      弄清楚了接口,下面可以談一個有名的OO原則了:依賴倒置原則(DIP)。
      如上,我們先不說DIP是什麼,而是搞清楚DIP的來龍去脈。到時,朋友們自然對DIP就有深刻理解了。我們開始!
      首先,我們要說明,依賴是有方向的,客戶類依賴於服務類。什麼是客戶類?如果A類需要B類提供的服務,那麼A類就依賴B類,反之不成立。在沒有引入接口前,客戶類“知道”服務類,而服務類“不知道”客戶類,就像下面這個樣子。

6.2

圖6.2、沒有接口的依賴

      我們看到,司機作爲客戶類,汽車作爲服務類。依賴的方向是從司機到汽車,以爲這裏司機要使用汽車提供的“駕駛”方法操作汽車。這是我們不推薦的方式,因爲不夠“鬆耦合”。於是,我們將駕駛抽象成接口,依賴變成如下形式。

6.3

圖6.3、引入接口後的依賴

      如圖6.3所示,我們從這種交互關係中,抽象出了“可駕駛”這個接口。注意,此時兩者誰也不依賴誰,或說誰也不知道誰了。那麼爲什麼司機可以放心呢?因爲他知道可駕駛接口的存在,他要駕駛的東西一定實現了這個接口,甭管是什麼,只要實現了這個接口,我就能駕駛。其實這裏才體現出接口的哲學意義。

      接口的哲學意義:對客戶類的保證,對服務類的約束。

      正是接口約束了服務類必須實現什麼功能,客戶類纔可以在不知道具體服務類的情況下“放心”進行交互,因爲接口對客戶類提供了一種保證。希望各位能好好體會接口的這種哲學意義,這對於對象論的良好運行體質的理解非常重要。
      可是,這樣還不夠,我們還有一個非常重要的問題沒有討論:誰有權利定義接口?或者說服務類和客戶類誰擁有接口?當然,理論上時誰擁有都可以,但卻會對世界的運作產生巨大影響。我們先看服務類擁有接口的情形。

6.4

圖6.4、服務類擁有接口

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

6.5

圖6.5、客戶類擁有接口

      看上圖,客戶終於翻身做主人了,現在客戶擁有定義接口的權利,服務類必須無條件實現,這下好了,只要會開汽車,就會開輪船和飛機,因爲客戶有權利定義一個統一的接口,服務類必須無條件實現!這樣,三種交通工具的駕駛方法必須完全一致(雖然現實世界還沒有這樣),這回客戶終於可以揚眉吐氣,體會一把“顧客是上帝”的感覺了。
      在圖6.5的情況下,司機可以有權定義接口,他不必“知道”服務類,而服務類必須“知道”客戶定義了什麼接口,你有沒有發現,依賴的方向已經悄悄倒置過來了!變成服務類依賴客戶類了(誰知道誰,誰就依賴誰)!這就是“依賴倒置”的由來。不必說,所謂依賴倒置原則就是讓我們必須按圖6.5的方式運行世界,而不能按圖6.2,6.3,6.4的方式。下面正式定義依賴倒置原則。

      依賴倒置原則(DIP):客戶類和服務類都應該依賴於抽象(接口),並且客戶類擁有接口。

      我想,看過上述來龍去脈,已經不用我再去解釋這個原則了吧。

6.9、神祕的統治者

      到目前爲止,我們基本已經搞清楚了對象世界的運行機制。但仍有一個疑問:我們曾經說過,程序世界裏對象時沒有選擇權的,甚至不知道誰是誰,只知道接口,那麼,誰來指定服務類呢?
      例如,上述司機可以制定接口,所以汽車、飛機、輪船等可駕駛的東西都要實現,於是司機可以按照自己制定的方式駕駛東西。但是,司機不能選擇駕駛什麼啊,他根本不知道自己駕駛的是什麼,那麼,誰制定他是駕駛飛機、汽車還是輪船呢?
      似乎冥冥中,這個世界存在一個統治者,它掌管所有對象之間誰和誰交互(只要不違反接口),否則,世界根本沒法正常運行。沒錯,程序世界是有這麼一個統治者,他就是大名鼎鼎的“依賴注入容器(DI)”,也有人叫做“控制反轉容器(IoC)”。
      什麼叫依賴注入?什麼叫控制反轉?如果你看了上面的文章,那太好理解了,依賴注入就是容器挑選符合接口的服務類爲客戶類提供服務。例如,上面司機要一個可駕駛的東西,容器就會根據既定規則選擇一個,可能是飛機、可能是汽車、也可能是輪船,交給司機。司機駕駛就行了,不用管是什麼,反正知道這東西肯定實現了“可駕駛”接口。

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

6.10、運作起來吧

      到了這裏,根本不用我廢話說程序世界時怎麼運作的了,因爲上面都已經說明白了。不過,我還是用短短幾句話總結一下吧。

      一個符合OO原則的、低耦合的程序世界的運作形式是這樣的:首先參與運作的本質只有對象,對象不直接依賴,沒有選擇權,互相不知道,而只知道各個接口。客戶類制定接口,對象間通過接口交互,形成運作。世界的統治者依賴注入容器決定選擇哪個服務類給客戶類使用。

      好了,關於程序世界的運作哲理就講到這裏了,大家可以在腦子裏描繪一下上述運作情景,加深印象。

Creative Commons License

本文基於署名-非商業性使用 3.0許可協議發佈,歡迎轉載,演繹,但是必須保留本文的署名張洋(包含鏈接),且不得用於商業目的。如您有任何疑問或者授權方面的協商,請與我聯繫

發佈了16 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章