冒號(hào)和他的學(xué)生們(連載26)——訪問控制

           

          冒號(hào)和他的學(xué)生們

           

          26.訪問控制

          夫輕諾必寡信,多易必多難                                    ——《老子·德經(jīng)》

           

          問號(hào)提問:“信息隱藏是否專指用private來控制訪問?”

          “這正是我們的下一個(gè)焦點(diǎn)。”冒號(hào)微頷,“訪問修飾符access modifier)除了可以應(yīng)用于類成員外,在JavaC#中還能應(yīng)用于整個(gè)類。public類自然是公開的,而缺省的類在Java C#中分別僅對(duì)同一packageassembly開放。”

          逗號(hào)不覺有異:“這有什么講究嗎?”

          “當(dāng)然。越是基本的語(yǔ)法越講究,也越容易被忽視。”冒號(hào)的嘴角漾著一絲微笑,“許多人在寫類時(shí)習(xí)慣上來就敲‘public class’,或者通過IDE自動(dòng)生成類的雛形,缺省的一般都是public類。這樣看似無害,但絕非好習(xí)慣。事實(shí)上大多數(shù)類是不需要公開的,這是一種更高層次的信息隱藏。每個(gè)packageassembly,如果設(shè)計(jì)合理,應(yīng)該是具有相關(guān)功能的類和接口的集合。其中不少只是內(nèi)部使用的,毫無必要公諸于眾。”

          引號(hào)了然于心:“這與對(duì)象封裝類似,既向客戶明確了接口類,又可消弭修改內(nèi)部類對(duì)客戶的影響。”

          嘆號(hào)仍心存疑慮:“OOP是鼓勵(lì)和支持重用的。好不容易開發(fā)出來的類卻深藏不露,豈不可惜?客戶也許一時(shí)用不上,但指不定以后會(huì)用上。”

          “問得好!這也是大多數(shù)人的心理。按照你的邏輯,我同樣可以說:這個(gè)類的方法是好不容易開發(fā)出來的,藏起來太可惜了,統(tǒng)統(tǒng)public吧!”冒號(hào)惟妙惟肖地學(xué)著嘆號(hào)的神態(tài)和腔調(diào),把眾人都逗樂了。

          嘆號(hào)心欲辯而口難言。

          “重用是令人興奮的,合理的重用既節(jié)省了開發(fā)時(shí)間,又節(jié)省了維護(hù)時(shí)間,代碼也顯得更簡(jiǎn)潔優(yōu)美,可謂一舉多得。但是——”冒號(hào)一轉(zhuǎn)話鋒,“過猶不及,過度追求重用也會(huì)造成濫用和誤用。一方面,開發(fā)者容易沉溺于局部重用的妙處而忽略整體的設(shè)計(jì),淡忘所開發(fā)類最核心的職責(zé);另一方面,一旦所重用的類或方法發(fā)生改變,所有的重用者均受牽連,先前節(jié)省的時(shí)間或許會(huì)加倍地償還。”

          引號(hào)深有感觸地說:“難就難在如何把握這個(gè)度啊!”

          “任何一門技藝到了高級(jí)階段,都是‘度’的學(xué)問。”冒號(hào)一如既往地推而廣之,“初級(jí)程序員的理想是為所欲為——能用編程解決一切問題;中級(jí)程序員的理想是盡善而為——追求最佳解決方案;高級(jí)程序員的理想是有所為有所不為——重在整體設(shè)計(jì)的選擇,能抵制局部技巧的誘惑;最高理想是無為而無不為——無論宏觀設(shè)計(jì)還是微觀實(shí)現(xiàn),均非刻意選擇,卻自然合度。”

          句號(hào)心念一動(dòng):“這四個(gè)階段可分別用四句廣告詞來代表:從全球通的‘我能’,到奧運(yùn)會(huì)的‘更快、更高、更強(qiáng)’,到安踏的‘我選擇,我喜歡’,最后是馬爹利的‘心意有別,心中有度’。”

           “到底是時(shí)尚小青年,我的推而廣之到你這兒變成了廣而告之啦。”冒號(hào)半夸半謔,“書歸正傳,我們?cè)僬f說類成員的訪問修飾符。”

          冒號(hào)在黑板上畫了一張表格——

          范圍" 語(yǔ)言

          C++

          Java

          C#

          無限制

          Public

          Public

          public

          子類或同一包

          Protected

          protected internal

          同一類或子類

          Protected

          private protected(已棄用)

          protected

          同一包

          package(缺省)

          internal

          同一類

          private(缺省)

          Private

          private(缺省)

          “其中publicprivate是兩個(gè)極端,一個(gè)沒有限制,一個(gè)僅限于同一類。JavaC#C++多了個(gè)包(packageassembly)的概念,相應(yīng)也多了package(缺省)和internal的修飾符。”冒號(hào)解說道,“值得注意的是,Java中的protected相當(dāng)于C#protected internal,不僅可被同類和子類訪問,還能被同一包內(nèi)的任何類訪問。而C++C#中的protected只能被同類和子類訪問,相當(dāng)于Java中曇花一現(xiàn)的private protected。”

          逗號(hào)急欲得知:“選擇這些修飾符有什么訣竅嗎?”

          冒號(hào)回應(yīng):“一個(gè)基本原則是盡可能地使用限制性更強(qiáng)的修飾符。即使以后改變主意,再放寬限制也不遲。相反,將一個(gè)修飾符收窄就要顧忌對(duì)客戶的影響了。尤其是域成員,沒有特殊理由都應(yīng)該是private的,除非類是一個(gè)用作儲(chǔ)存的具體數(shù)據(jù)類型或內(nèi)部類inner class)。在JDK源代碼中有不少packageproctected域成員甚至public域成員,絕不能以之為榜樣。順便說一句,C++C#中缺省的成員修飾符是private,顯然比Java中缺省package更科學(xué)一些。”

          問號(hào)刨根問底:“為什么特別強(qiáng)調(diào)域成員呢?”

          冒號(hào)條分縷析:“域成員代表對(duì)象的狀態(tài),從運(yùn)行方面看,若外界隨意讀取和改動(dòng),將無法保證內(nèi)在邏輯的一致性;從設(shè)計(jì)方面看,屬結(jié)構(gòu)性信息,極易變化;從接口方面看,公開接口都是以方法而非域的形式出現(xiàn)的。這些都要求隱藏域成員。”

          引號(hào)思路很清晰:“既然域成員一般都用private,那關(guān)鍵是如何選擇方法成員的訪問修飾符了。”

          “如果將每個(gè)類看作一個(gè)服務(wù)者,它向不同范圍內(nèi)的客戶承諾不同的服務(wù),或者說提供了層次化的服務(wù)。以Java為例,每一個(gè)public方法成員即是向所有類提供的服務(wù);protected方法成員對(duì)該類的子類和同一package下的類提供服務(wù);默認(rèn)的package方法成員僅對(duì)同一package下的類提供服務(wù);private方法成員則只對(duì)該類本身提供服務(wù)。那么如何具體界定一個(gè)服務(wù)的范圍呢?大多時(shí)候這是一個(gè)偽問題。”說到這里,冒號(hào)有意停頓了一會(huì),查看大家的反應(yīng)。

          眾人臉上果然滿是狐疑之色。

          “因?yàn)楹侠碓O(shè)計(jì)的服務(wù),其內(nèi)容與范圍往往是密不可分的。一個(gè)服務(wù)如果已經(jīng)設(shè)計(jì)好了,甚至已經(jīng)實(shí)現(xiàn)了,而此時(shí)尚未決定其使用者的范圍,是否有些荒謬?當(dāng)然,荒謬其實(shí)也是現(xiàn)實(shí)中的一種常態(tài)。”冒號(hào)語(yǔ)帶反諷,“作為一個(gè)抽象數(shù)據(jù)類型的類,其核心是抽象接口,因此首先應(yīng)該設(shè)計(jì)公共接口,它們的修飾符自然都是public。如果該類是一個(gè)抽象類abstract class)——此‘抽象’與抽象數(shù)據(jù)類型的‘抽象’意義有所不同,暫且不表——那么可能會(huì)有一些為其子類提供的服務(wù),它們的修飾符自然該是protected。”

          問號(hào)聽得仔細(xì):“難道非抽象類就不能有protected的接口嗎?”

          冒號(hào)回道:“從語(yǔ)法上說當(dāng)然可以。但一般不建議繼承非抽象類,因而protected的意義就不大了。至于個(gè)中緣由,留待下節(jié)課再解釋。”

          嘆號(hào)追問:“那package服務(wù)和private服務(wù)呢?”

          冒號(hào)應(yīng)答:“package一般作為library的單位,因此package方法成員存在的唯一理由是僅為該library服務(wù),但這種情況相對(duì)少見。說到private方法成員,已談不上是真正的服務(wù),純粹是實(shí)現(xiàn)細(xì)節(jié)。由于它的改動(dòng)不會(huì)影響客戶,因此采用private訪問控制不需要任何理由,不采用它才需要理由。”

          眾人聽得頻頻點(diǎn)頭。

          冒號(hào)總結(jié)道:“從軟件應(yīng)變的角度來看,訪問控制是對(duì)修改所帶來的副作用的控制。具體地說,如果修改僅僅涉及到private成員,那只要檢查該類的源代碼即可;如果修改涉及到package成員,只需檢查該類所在的package內(nèi)的所有類。雖然這些類可能很多,但仍可控制,畢竟package是封閉的;如果修改涉及到protected成員,則不僅要檢查該類所在的package內(nèi)的所有類,還需檢查該類的子類,如果該類本身是public,涉及的類可以超出該package的范圍,已難以真正掌控;如果修改涉及到public成員,那就意味著任何類都可能調(diào)用該接口,也就可能因此而無法編譯、運(yùn)行和工作。因此訪問控制越松的成員,輻射范圍越廣,軟件重用的效率越高,承擔(dān)的責(zé)任越大,修改的代價(jià)也越大。成熟的程序員對(duì)publicprotected接口的設(shè)計(jì)一定是慎之又慎,往往在其上花的功夫更甚于具體代碼的編寫。”

          逗號(hào)似有些不服:“可是誰(shuí)又能保證接口永遠(yuǎn)不變呢?”

          “所以JavaC#才分別提供了deprecatedobsolete以將方法標(biāo)記為過時(shí)啊。”冒號(hào)笑言以對(duì),“當(dāng)然這是不得已而為之的下策,可以理解為設(shè)計(jì)者對(duì)使用者的一種致歉。”

          見眾人并無共鳴,冒號(hào)心道:響鼓也需重錘,遂言:“看來你們感觸還不深,原因是缺乏一種關(guān)鍵的意識(shí)——客戶意識(shí)。客戶意識(shí)對(duì)一個(gè)程序員的重要性,絲毫不亞于對(duì)一個(gè)企業(yè)的重要性。”

          逗號(hào)忍不住問:“您一再提到客戶,究竟什么是客戶?”

          “這里的客戶當(dāng)然不是一般所指的軟件終端消費(fèi)者,而是指軟件中間消費(fèi)者或重用者,即調(diào)用該軟件的代碼或代碼編寫者。”冒號(hào)作了個(gè)名詞解釋,“客戶意識(shí)的缺乏有幾種原因。首先,不是每個(gè)人都有機(jī)會(huì)開發(fā)大型、關(guān)鍵的軟件,許多程序員的客戶主要是他自己或少數(shù)幾個(gè)開發(fā)組成員,修改軟件影響到的代碼不多,影響到的人也不多,沒有真正吃過設(shè)計(jì)不當(dāng)?shù)目囝^。其次,開發(fā)庫(kù)和框架的人畢竟是少數(shù),大多數(shù)人開發(fā)的代碼主要是自己調(diào)用或針對(duì)終端消費(fèi)者的。即使他們寫了一些可重用的代碼,如果代碼質(zhì)量不被認(rèn)可,也可能無人采用。當(dāng)一個(gè)人習(xí)慣于自彈自唱時(shí),是很難培養(yǎng)客戶意識(shí)的。最后,正如我們?cè)?/span>對(duì)象范式中指出的,過程式編程鼓勵(lì)自頂向下,而OOP鼓勵(lì)自底向上。顯然,頂是底的客戶。問題是許多OOP程序員仍習(xí)慣于過程式編程的風(fēng)格,所設(shè)計(jì)的類或接口主要是調(diào)用其他的類或接口,而不是被調(diào)用。換句話說,他們?cè)O(shè)計(jì)的代碼主要角色是客戶,而不是客戶的服務(wù)者。”

          一番理論令眾人心服口服。

          引號(hào)關(guān)心地問:“如何培養(yǎng)客戶意識(shí)呢?”

          “如果認(rèn)識(shí)到客戶意識(shí)的重要性,培養(yǎng)起來并不難。”冒號(hào)帶著安慰的口吻,“謹(jǐn)記一點(diǎn):輕諾者,必寡信。每一個(gè)public類、每一個(gè)非private成員,都是一份承諾。在沒有明確職責(zé)、沒有準(zhǔn)備承擔(dān)變更后果之前,請(qǐng)采用最嚴(yán)格的訪問控制。有了客戶意識(shí),才有接口責(zé)任感。千萬(wàn)不要為追求廉價(jià)的重用而輕易擴(kuò)大接口范圍,莫以自身之便而致客戶之不便,莫以一時(shí)之便而致長(zhǎng)期之不便。另外,單元測(cè)試對(duì)培養(yǎng)客戶意識(shí)很有幫助。它不僅僅能發(fā)現(xiàn)程序的邏輯缺陷,還能發(fā)現(xiàn)程序的設(shè)計(jì)缺陷。因?yàn)闇y(cè)試代碼就是最典型的客戶代碼,它能讓你站客戶的角度重新審視自己的接口設(shè)計(jì)。”

          posted on 2008-08-03 18:39 鄭暉 閱讀(1955) 評(píng)論(1)  編輯  收藏 所屬分類: 冒號(hào)和他的學(xué)生們

          評(píng)論

          # re: 冒號(hào)和他的學(xué)生們(連載26)——訪問控制 [未登錄] 2008-08-03 21:54 Rebollo

          好,此篇讓我有一種茅塞頓開的感覺,正學(xué)習(xí)JAVA中,為修飾符的變化而必存諸多疑問,而今此些已疑問不再。

          不知道這本書叫《編程那些事兒》如何?呵呵  回復(fù)  更多評(píng)論   

          導(dǎo)航

          統(tǒng)計(jì)

          公告

          博客搬家:http://blog.zhenghui.org
          《冒號(hào)課堂》一書于2009年10月上市,詳情請(qǐng)見
          冒號(hào)課堂

          留言簿(17)

          隨筆分類(61)

          隨筆檔案(61)

          文章分類(1)

          文章檔案(1)

          最新隨筆

          積分與排名

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 肇东市| 荆州市| 黄骅市| 石柱| 长海县| 崇州市| 永城市| 华池县| 黔江区| 延川县| 醴陵市| 金寨县| 左权县| 麻城市| 任丘市| 云梦县| 平利县| 白山市| 名山县| 安平县| 阜阳市| 毕节市| 喀喇沁旗| 永州市| 凤翔县| 隆子县| 乐都县| 沧州市| 南和县| 蚌埠市| 长岛县| 古丈县| 雷州市| 泰宁县| 徐汇区| 玛多县| 衡阳市| 山东| 宜州市| 蒙城县| 景宁|