<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    冒號和他的學生們(連載24)——對象封裝

     

     

    冒號和他的學生們

    ——程序員提高班紀事

     

    24.對象封裝

    陰陽地理兩分張,隱者為陰顯者陽                             ——《玉髓經.曜星論》

      

    “用廣東話說,真是有型有料又有性格啊!”嘆號嘖嘖連聲,“這哪里是在設計軟件,分明是在設計心儀的對象嘛。”

    “我們可不就是在談對象設計嗎?”冒號笑著反問,“在OOP的世界里,每位程序員都是造物主。保持熱情、專注力和審美情趣,說不定哪一天就像希臘神話里的皮格瑪利翁一樣,雕塑的美女變活了。”

    “哇,那可就美了!”逗號極盡夸張之調。

    全班哄堂大笑。

    “剛才提到抽象是OOP三大基本特性的基礎,下面我們逐個剖析。”冒號很快收攏了話題,“首當其沖的是封裝性。記得前面談對象范式時,引號曾試圖為我們解釋封裝性,可惜被我無情地打斷了?,F在我們請他繼續講解吧。”

    在眾人逗趣式的掌聲中,引號竟有些靦腆了:“所謂封裝性,就是將數據與相關行為包裝在一起以實現信息隱藏。”

    “幾乎無懈可擊。”冒號贊揚得有些保守,“那么封裝(encapsulation)與信息隱藏information hiding)有區別嗎?”

    “應該是一回事吧。”在冒號的逼視下,引號有些猶豫了,“嗯。。。信息隱藏是一種原則,而封裝是實現這種原則的一種方式。”

    “言之有理!”冒號這回贊揚得很干脆,“盡管大多數參考書對二者不加區分,我還是要解析一番。其實廣義的封裝僅僅只是一種打包,即packagebundle,是密封的但可以是透明的。或者說,封裝就是把一些數據和方法裝在一個封閉的盒子里——可能是黑盒子,也可能是白盒子。從語法上說,這是OOP與諸如C之類的過程式語言最大的不同。請問這帶來什么效果?”

    句號反應很快:“這等于引入了一種新的模塊機制,將相關的數據和作用其上的運算捆綁在一起形成被稱為類的模塊。”

    “回答正確!”冒號很滿意,“剛才我們用C實現了隊列,但由于C不支持封裝,只能以文件形式來劃分模塊,顯然不如劃分那么方便和明晰。此外,封裝還有語法糖(Syntactic sugar)效果。”

    問號好奇地問:“什么是語法糖?是不是很甜?”

    “所謂語法糖,就是一些語法上的甜頭。它不是核心語法,并沒有提供任何額外的功能,只是用起來更簡潔實用、更自然方便,看起來更酷、更炫而已。”冒號有意用時髦的詞匯來填補代溝,“我們知道,過程式函數采用謂語(主語,賓語)的形式,而OOP采用主語.謂語(賓語)的形式。”

    “哦,就是那個狗吃屎和吃狗屎啊,那可不甜。”逗號又來插科打諢。

    眾人笑得前仰后合。

    冒號不為所動:“再拿隊列為例,如果增加一個隊列成員,用剛才的C實現,我們需要寫下:queue_add(queue, item)。假如用Java來實現,只需寫queue.add(item)。由于封裝使add綁定在queue上,一方面可以將對象queue前置,既更符合自然語言,又少敲一個字符;另一方面,這種綁定使add局限于Queue類中,因此不必加上‘queue_’的前綴以防與其他類的方法函數名相沖突。這同樣節省了打字,也使接口更簡單。”

    句號提出:“如果C支持函數重載overload),那么‘queue_’的前綴就可省去。”

    “你說的既對也不對。”冒號辯證地評判,“如果C支持重載,該前綴的確能省去;但從另一角度看,即使JavaC++不支持重載,前綴用樣能省去。因為函數add已經不再是全局函數,Queue類就是其上下文(context)。換句話說,分屬不同類的函數是不可能產生歧義(ambiguity)的,哪怕它們的簽名signature)一模一樣。因此我們要把功勞記在封裝的名下。”

    句號心悅誠服。

    冒號繼續講解:“狹義的封裝是在打包的基礎上加上訪問控制access control),以實現信息隱藏。相對于上述廣義的封裝,不妨認為多了一個將白盒子刷成黑盒子的過程。這一過程可以看作對抽象的一種補充:抽象意味著用戶可以從高層的接口來看待或使用一類對象,而不用關心它底層的實現,而黑盒封裝意味著用戶無權訪問底層的實現。”

    逗號有點茫然:“那談起封裝,究竟指哪一個?”

    “一般所說的封裝大多是狹義的。”冒號回復道,“考試中最無趣的一類試題就是名詞解釋,因為那只能印證記憶,不能印證理解。軟件編程中也有無數的名詞和概念,機械式的記憶沒有任何意義——除了面試時應付某些同樣無趣的考官。我們在這里著意詮釋封裝的概念,不是出于學術理論的目的,而是為了讓大家深刻體會封裝的目的和意義,以便在實踐中靈活運用。”

    問號詢問:“前面提到,代碼既要合法又要合理,那訪問控制還重要嗎?”

    “合法合理是對程序員的要求。對于語言,我們還是希望它盡可能地提供更多的保障。這就好比社會和諧不能只靠法律,但法制當然越健全越好。”冒號解答道,“訪問控制不僅是一種語法限制,也是一種語義規范——標有public的公用接口對代碼閱讀者而言,顯然比注釋文檔更正式更直觀。因此,其重要性是不言而喻的。值得一提的是,訪問控制也不是滴水不漏的。C++用戶可以通過指針來間接訪問private成員,Java也可以通過反射機制來訪問。”

    見眾人頗有疑義,冒號便寫了一段Java代碼——

    // 通過反射機制訪問私有變量
    import java.lang.reflect.*;

    class Private 
    {
        
    private String field = "這是私有變量";

        
    private void method() 
        
    {
            System.out.println(
    "調用私有方法");
        }

    }


    public class AccessTest
    {
        
    public static void main(String[] args) throws Exception
        
    {
            Private privateObj 
    = new Private();

            Field f 
    = Private.class.getDeclaredField("field");
            f.setAccessible(
    true);
            System.out.println(f.get(privateObj));

            Method m 
    = Private.class.getDeclaredMethod("method"new Class[0]);
            m.setAccessible(
    true);
            m.invoke(privateObj, 
    new Object[0]);
        }

    }

     

    冒號講述道:“運行這段代碼,可以看到privateObj的域成員和方法成員都被訪問了。這是一種hack,僅限于特殊用途,不在我們關心之列。問題是,即使不考慮此類非常規做法,要實現信息隱藏也不是件容易的事。”

    嘆號不解:“信息隱藏困難在哪里呢?加上private不就隱藏了成員嗎?”

    “如果所有信息都隱藏了,這個對象還有什么用嗎?”冒號一語破的。

    逗號一愣:“可以用getter方法返回信息啊。”

    冒號更不答話,投影出一段代碼——

    import java.util.Date;
    import java.util.Calendar;

    class User
    {
        
    private Date birthday; /** 生日 */
        
    private boolean sex; /** 性別。true代表男,false代表女 */

        
    public User(Date birthday, boolean sex)
        
    {
            
    this.birthday = birthday;
            
    this.sex = sex;
        }


        
    public Date getBirthday()
        
    {
            
    return birthday;
        }


        
    public void setBirthday(Date birthday)
        
    {
            
    this.birthday = birthday;
        }


        
    public boolean getSex()
        
    {
            
    return sex;
        }


        
    public void setSex(boolean sex)
        
    {
            
    this.sex = sex;
        }


        
    /** 計算年齡,負數表示未知 */
        
    public int computeAge()
        
    {
            
    if (birthday == nullreturn -1

            Calendar dob 
    = Calendar.getInstance();
            dob.setTime(birthday);
            Calendar now 
    = Calendar.getInstance();
           
    int age = now.get(Calendar.YEAR) - dob.get(Calendar.YEAR);
           
    if (now.get(Calendar.DAY_OF_YEAR) < dob.get(Calendar.DAY_OF_YEAR))
                
    --age;
           
    return age;
        }

    }

     

    冒號提問:“這段代碼簡單得勿需多言,請問它的信息隱藏做得如何?”

    眾人目不轉睛地盯了好一陣,無人應答。

    冒號突發驚人之語:“如果我說User所有的方法都違背了信息隱藏原則,你們相信嗎?”

    直直的眼睛全都變圓了。

    引號忽然明白了:“記得書上曾說不能直接返回類的內部對象。GetBirthday返回Date類型的生日,用戶可以在調用此方法后直接對生日進行操作。”

    “說得對極了!”冒號夸贊道,“如果一個方法返回了一個可變(mutable域對象field object)的引用,無異于前門緊閉而后門洞開。解決的方法是防御性復制(defensive copying),即返回一個clone的對象,以免授人以柄(handle)。”

    逗號有些難以置信:“好像這類做法很普通啊。”

    冒號耐心詳解:“首先,請注意可變引用兩個條件,所有基本類型的域不是引用,因而是安全的,而JavaString之類非基本類由于是不可變的immutable),也是安全的。同樣,在C++C#中的非基本類的值類型value type)也不在此列。此外C++中申明了const的指針或引用返回值也能防止客戶修改。其次,普通的做法不代表是正確的。事實上,恕我直言:普通的程序員是不合格的,合格的程序員是不普通的。最后,信息隱藏原則固然極其重要,但也不是金科玉律,在一定條件下也是允許的。比如僅作數據儲存之用的類甚至可以開放所有的域成員,又比如不同類的對象共享同一引用。此外在一定范圍之內為提高效率也可能采取變通之法,當然是在對用戶曉以利害之后。”

    問號舉一反三:“同樣道理,setBirthday也會導致信息泄漏??紤]到Date類型如此常用,Java是不是該引入一個不可變的日期類型呢?”

    嘆號喃喃自語:“getSexsetSex會有什么問題呢?boolean是基本類型啊。”

    冒號提示:“考慮一下性別的可能性。”

    嘆號訝然道:“難不成還有不男不女型?”

    眾皆大笑。

    冒號淡淡一笑:“不排除這種可能。更實際的情況是,有時性別是未知的。”

    句號建議:“可以將小boolean換成大Boolean,多一個null值。”

    冒號進一步指出:“如果想處理三種以上的可能性,可以采用char類型或String類型??傊@是實現細節,最好不要暴露給客戶。因此不妨將getSex換成isMaleisFemale兩個接口。”

    引號細細玩味:“如果isMaleisFemale均返回false,那么性別不是保密就是中性了。至于性別用booleanBoolean、char還是String來實現,用戶是懵然不知的,這樣比直接了當的getSex更隱蔽也更靈活。”

    冒號揭開最后的答案:“方法computeAge的問題不在其實現,而在其命名。該名暗示年齡是計算出來的,這暴露了實現方式,應該改為getAge。請注意,信息隱藏中的信息不僅僅是數據結構,還包括實現方式和策略。試想,如果將來把年齡而不是生日作為User的輸入,用年齡倒推生日,getBirthday是不是要換成computeBirthday呢?”

    嘆號不禁喟曰:“不想如此簡單的getset竟如此講究!”

    通,則大處圓融合一而小處各具其妙;不通,則大處千變萬化而小處無所分別。”冒號又打起了禪語,“領會OOP的精髓絕非一年半載之功,但若以抽象與封裝為鑰,必可早日開啟通達之門。封裝的故事遠未結束,下節課繼續。布置一下課后作業,請將示例中的User類按剛才的提示進行改進。”

    posted on 2008-07-20 16:27 鄭暉 閱讀(2855) 評論(3)  編輯  收藏 所屬分類: 冒號和他的學生們

    評論

    # re: 冒號和他的學生們(連載24)——對象封裝 2008-07-21 08:55 隔葉黃鶯

    我讀來很有味,難道是你的講義,嗯,看起來不太像。
    如果把這真拿到課堂,整幾個月的純理論,還不讓你的學員云里來霧里去。  回復  更多評論   

    # re: 冒號和他的學生們(連載24)——對象封裝 2008-07-29 11:47 Christ Chang

    每次看都會有收獲~  回復  更多評論   

    # re: 冒號和他的學生們(連載24)——對象封裝 2008-07-29 14:00 Mr liang

    好極了!  回復  更多評論   

    導航

    統計

    公告

    博客搬家:http://blog.zhenghui.org
    《冒號課堂》一書于2009年10月上市,詳情請見
    冒號課堂

    留言簿(17)

    隨筆分類(61)

    隨筆檔案(61)

    文章分類(1)

    文章檔案(1)

    最新隨筆

    積分與排名

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 粉色视频免费入口| 99免费观看视频| 久久精品国产亚洲77777| 18勿入网站免费永久| 亚洲精品色在线网站| 亚洲av色影在线| 国产婷婷高清在线观看免费| 天堂在线免费观看| 亚洲伊人久久大香线蕉AV| 激情97综合亚洲色婷婷五| 99久久99久久精品免费看蜜桃| 免费国产污网站在线观看不要卡| 亚洲AV无码一区二区二三区入口 | 四虎影永久在线高清免费| 国产做国产爱免费视频| 亚洲va久久久久| 亚洲成av人片在线观看无码不卡| 在线成人a毛片免费播放| 日本人成在线视频免费播放| 亚洲成a人片在线观看天堂无码| 亚洲va在线va天堂va888www| 国产又大又粗又硬又长免费| 久久久久久精品成人免费图片 | 女人被男人桶得好爽免费视频| 国产自国产自愉自愉免费24区| 亚洲人成电影网站免费| 久久精品a亚洲国产v高清不卡| 亚洲日本一区二区一本一道| 精品久久久久国产免费| 无码一区二区三区免费| 免费无码午夜福利片| 亚洲日本一线产区和二线产区对比| 婷婷精品国产亚洲AV麻豆不片| 亚洲精品国产高清不卡在线| 免费观看大片毛片| 8x网站免费入口在线观看| 色www永久免费| j8又粗又长又硬又爽免费视频| 久久无码av亚洲精品色午夜| 国产成人精品日本亚洲专一区| 亚洲邪恶天堂影院在线观看|