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

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

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

    冒號和他的學(xué)生們(連載25)——軟件應(yīng)變

     

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

     

    25.軟件應(yīng)變

    潛其心能觀天下之理,定其心能應(yīng)天下之變               ——《呂坤·呻吟語》

     

    第七課剛一開堂,冒號就提了一個問題:“如果把一個Java程序中所有的private關(guān)鍵字換成public,請問該程序還能工作嗎?”

    “應(yīng)該還能工作,除非——此前不能工作。”問號小心翼翼地回答。

    冒號接著問:“既然如此,何必費(fèi)事區(qū)分它們呢?”

    嘆號嘴一撇:“當(dāng)然是為了信息隱藏啰。”

    冒號步步緊逼:“隱藏什么信息呢?又為什么要隱藏?”

    嘆號應(yīng)對:“對象的狀態(tài)需要隱藏。如果一個對象的狀態(tài)直接暴露在外,讓客戶隨意修改,可能會破壞對象的內(nèi)在邏輯。”

    冒號依舊窮追不舍:“那為什么對象的方法有些也需要隱藏?”

    “以前我也有此疑問,看別人代碼時(shí)最感興趣的就是那些私有方法。”引號不打自招。

    逗號逗他:“看來你患有偷窺癖哦。”

    引號暗暗踢了逗號一腳:“現(xiàn)在我明白了,這是為了實(shí)現(xiàn)數(shù)據(jù)抽象,將接口與實(shí)現(xiàn)分離開來。”

    冒號仍不罷休:“這種抽象究竟有何實(shí)際好處?”

    句號搶答:“一方面,抽象接口描述了一個類最本質(zhì)的行為特征;另一方面,具體實(shí)現(xiàn)隨時(shí)可能變動,隱藏它們可以保證這種變動不會波及客戶代碼。”

    “說到點(diǎn)子上了!”冒號終于停止了追問,“軟件與硬件之別,不僅是無形與有形之別,更是變化與固化之別。所謂變化,指源代碼隨時(shí)可能因需而變。一個軟件修改維護(hù)的時(shí)間通常會超過編寫時(shí)間,越復(fù)雜越成熟的程序越是如此。軟件的難點(diǎn)有二:其一是邏輯的復(fù)雜,其二是需求的變化。許多程序員看重前者而看輕后者,大部分時(shí)間花在尋求解決方案上,而不是在選擇解決方案上。他們目眩于奇技淫巧卻不解大巧若拙之妙,殊不知充滿技巧的代碼不僅難于理解而易于出錯,且因其普適性低而受變化的沖擊更大。眾所周知,比武時(shí)最忌招式用老,老即難以變化,一旦為對手看破則后果不堪設(shè)想。同樣,動不動凌空躍起只是影視作品中招徠眼球的花哨場面,實(shí)戰(zhàn)中很少出現(xiàn),蓋因空中不易變招。當(dāng)然凡事皆有度,無一招用老,便無一招用實(shí),難以完成致命一擊。反映在軟件上,那就是過度設(shè)計(jì)會帶來不必要的復(fù)雜和效率損失。”

    眾人均想,又上起久違的武術(shù)課了。

    冒號滔滔不絕:“一言以蔽之,軟件之軟體現(xiàn)在適應(yīng)變化的能力。許多編程設(shè)計(jì)思想包括OOP的思想都是以此為主題的,抽象與封裝便是典型代表。抽象一個對象模型即是將一類對象最本質(zhì)因而最不易變化的部分提煉出來,而封裝——準(zhǔn)確地說是信息隱藏——則是將非本質(zhì)、容易變化的部分隱藏起來,從而將一個類劃分為陰陽兩面。由于變化多發(fā)生在陰面,對外是屏蔽的,因此修改該面毫無累及客戶之憂,由此提高了軟件的抗變能力。有些人誤認(rèn)為信息隱藏是出于軟件安全security)的考慮,實(shí)乃是似是而非的皮相之見。”

    問號提問:“軟件的變化主要有哪些?”

    “軟件的變化大致分兩種:一種是出于內(nèi)在需求而作的結(jié)構(gòu)性變化,通常以改善軟件質(zhì)量為目的,即所謂的重構(gòu)refactoring);一種是出于外在需求而作的功能性變化,通常以滿足客戶需要為目的。理想的抽象與封裝,應(yīng)能完全避免第一類變化對于客戶代碼的影響,也能最大限度地降低第二類變化的副作用。只是知易行難,為細(xì)微的變化而付出巨大代價(jià)的例子比比皆是。‘千年蟲’就是一個最典型的例子,而當(dāng)32 位的IPv4 全部換成128位的IPv6 ,其代價(jià)也不遑多讓。從中可以看出,信息隱藏,尤其是結(jié)構(gòu)性信息隱藏是多么的重要!下面看一個簡單的例子。”冒號打開幻燈片——

    // 用直角坐標(biāo)實(shí)現(xiàn)的復(fù)數(shù)類

    public class Complex

    {

        
    private double x;

        
    private double y;

        
    public Complex(double x, double y)

        
    {

            
    this.x = x;

            
    this.y = y;

        }


        
    public double real() return x; }

        
    public double imaginary() return y; }

        
    public double modulus() return StrictMath.hypot(x, y); }

        
    public double argument() return StrictMath.atan2(y, x); }

        
    public Complex add(Complex other)

        
    {

            
    return new Complex(x + other.x, y + other.y);

        }


        
    public Complex multiply(Complex other)

        
    {

            
    return new Complex(x * other.x - y * other.y,

                               x 
    * other.y + y * other.x);

        }


    }


    “這是一個用直角坐標(biāo)實(shí)現(xiàn)的復(fù)數(shù)Java類,為簡明起見,僅僅實(shí)現(xiàn)了實(shí)部、虛部、模、輻角、加法和乘法等運(yùn)算。同樣地,我們也可以用極坐標(biāo)來實(shí)現(xiàn)。”冒號投影出另一段代碼——

    // 用極坐標(biāo)實(shí)現(xiàn)的復(fù)數(shù)類

    public class Complex

    {

        
    private double r;

        
    private double theta;

        
    public Complex(double x, double y)

        
    {

            r 
    = StrictMath.hypot(x, y);

            theta 
    = StrictMath.atan2(y, x);

        }


        
    public double real() return r * StrictMath.cos(theta); }

        
    public double imaginary() return r * StrictMath.sin(theta); }

        
    public double modulus() return r; }

        
    public double argument() return theta; }

        
    public Complex add(Complex other)

        
    {

            
    return new Complex

                (r 
    * StrictMath.cos(theta) + other.r * StrictMath.cos(other.theta),

                 r 
    * StrictMath.sin(theta) + other.r * StrictMath.sin(other.theta));

        }


        
    public Complex multiply(Complex other)

        
    {

            Complex product 
    = new Complex(00);

            product.r 
    = r * other.r;                

            product.theta 
    = theta + other.theta;   

            
    return product;

        }


    }

    句號似已深明其意:“這兩個類的接口相同而實(shí)現(xiàn)方式不同,它們的區(qū)別是結(jié)構(gòu)性的,而不是功能性的。就實(shí)現(xiàn)效率而論,直角坐標(biāo)便于加減運(yùn)算,而極坐標(biāo)便于乘除、乘方開方等運(yùn)算。實(shí)現(xiàn)者可能會為采用何種方案而舉棋不定,好在由于隱藏了結(jié)構(gòu)性信息,即使以后修改了實(shí)現(xiàn)方案,也不會影響客戶。”

    冒號補(bǔ)充道:“如果將代碼移植到C++,修改了實(shí)現(xiàn)方案,還是可能在一定程度上影響客戶的。”

    嘆號有些驚訝:“為什么?C++不也是OOP語言嗎?”

    冒號解釋:“由于C++需要頭文件,即使私有成員也必須在頭文件中聲明。這意味著改動任何私有數(shù)據(jù)結(jié)構(gòu)甚至私有方法的簽名,所有包含該頭文件的源代碼雖不必改寫,卻需要重新編譯鏈接。這對大型程序來說通常是難以忍受的,同時(shí)也說明設(shè)計(jì)與語言息息相關(guān)的。如果一個設(shè)計(jì)者只是高高在上,完全不考慮語言細(xì)節(jié),難免流于紙上談兵。”

    逗號問道:“為什么Java不需要頭文件呢?”

    “因?yàn)?/span>JavaC#包括D語言中類似頭文件的信息,已經(jīng)在編譯時(shí)自動提取并保存了。”冒號道出緣由,“出于歷史原因和效率上的考慮,C++仍沿用C的頭文件用法,成為除指針和內(nèi)存管理之外最令人頭痛的問題。因此在C++中應(yīng)盡可能地使用前置聲明forward declaration),減少包含的(included)頭文件。另外,可以將一些私有靜態(tài)private static)成員從頭文件轉(zhuǎn)移到實(shí)現(xiàn)代碼中,以匿名命名空間anonymous namespace)的方式來實(shí)現(xiàn)完全隱藏。此外還有一個非常有用的技巧——/handle/body)模式或稱橋梁模式bridge pattern),可以將接口與實(shí)現(xiàn)完全分開。這種模式不僅可以解決C++中的頭文件問題,對Java等不需要頭文件的語言也是有用的。下面我們用這種模式重新實(shí)現(xiàn)Complex類。”

    幻燈一閃,新的源碼出現(xiàn)在眾人眼前——

    // 復(fù)數(shù)計(jì)算接口ComplexImpl

    public interface ComplexImpl

    {

        
    public double real();

        
    public double imaginary();

        
    public double modulus();

        
    public double argument();

        
    public Complex add(Complex other);

        
    public Complex multiply(Complex other);

    }


    // 用直角坐標(biāo)實(shí)現(xiàn)的ComplexImpl

    public class ComplexCartesianImpl implements ComplexImpl

    {

        
    private double x;

        
    private double y;

        
    public ComplexCartesianImpl(double x, double y)

        
    {

            
    this.x = x;

            
    this.y = y;

        }


        
    public double real() return x; }

        
    public double imaginary() return y; }

        
    public double modulus() return StrictMath.hypot(x, y); }

        
    public double argument() return StrictMath.atan2(y, x); }

        
    public Complex add(Complex other)

        
    {

            
    return new Complex(x + other.real(), y + other.imaginary());

        }


        
    public Complex multiply(Complex other)

        
    {

            
    return new Complex(x * other.real() - y * other.imaginary(),

                               x 
    * other.imaginary() + y * other.real());

        }


    }


    // 用極坐標(biāo)實(shí)現(xiàn)的ComplexImpl

    public class ComplexPolarImpl implements ComplexImpl

    {

        
    private double r;

              
    private double theta;

              
    // 以下省略。。。

    }


    // 用橋梁模式實(shí)現(xiàn)的復(fù)數(shù)類

    public class Complex

    {

        
    private ComplexImpl impl;

        
    public Complex(double x, double y)

        
    {

            impl 
    = new ComplexCartesianImpl(x, y);

            
    //或者:impl = new ComplexPolarImpl(x, y);

        }


        
    public double real() return impl.real(); }

        
    public double imaginary() return impl.imaginary(); }

        
    public double modulus() return impl.modulus(); }

        
    public double argument() return impl.argument(); }

        
    public Complex add(Complex other) return impl.add(other); }

        
    public Complex multiply(Complex other) return impl.multiply(other); }

    }

    冒號進(jìn)而指出:“這是橋梁模式的簡化版。稍加改進(jìn),我們不僅可以在編譯期間決定具體實(shí)現(xiàn)方式,甚至可以讓客戶在運(yùn)行期間選擇實(shí)現(xiàn)方式。你們課后不妨試試。”

    引號一拍大腿:“妙!如此既免除了實(shí)現(xiàn)者抉擇的煩惱,也給賦予使用者更大的自由,可謂一舉兩得啊。”

    句號也道:“信息隱藏雖能將抽象接口與具體實(shí)現(xiàn)分離,但仍然封裝在同一類中。橋梁模式則讓二者徹底解耦decouple),增強(qiáng)了對變化的適應(yīng)力,具有更大的靈活性和可擴(kuò)展性。”

    “當(dāng)然這也增加了一定的復(fù)雜性和效率上的損失,具體運(yùn)用時(shí)應(yīng)酌情考量,避免過度設(shè)計(jì)。”冒號提醒道,“最后,如果Complex類需要功能上的變化,比如增加乘方、開方等運(yùn)算,只要不修改現(xiàn)有運(yùn)算的簽名,是不會傷及客戶代碼的。”

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

    評論

    # re: 冒號和他的學(xué)生們(連載25)——軟件應(yīng)變 2008-07-29 17:17 brian-17

    精辟啊   回復(fù)  更多評論   

    # re: 冒號和他的學(xué)生們(連載25)——軟件應(yīng)變 2008-08-02 00:33 Rebollo

    好連載,受教了。初學(xué)JAVA,很多地方似懂非懂,但總覺得有些很不一樣的感覺。就像一個初入江湖的習(xí)武者,雖然得到了一把上乘寶劍,但因自身功力甚淺而無法體會其真正的精妙之處。希望多多指教。  回復(fù)  更多評論   

    # re: 冒號和他的學(xué)生們(連載25)——軟件應(yīng)變 2008-08-08 15:50 Stone_Boy

    Bridge 的設(shè)計(jì)模式。。
    呵呵
    PIMPL的手法用處確實(shí)大啊 ~  回復(fù)  更多評論   

    導(dǎo)航

    統(tǒng)計(jì)

    公告

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

    留言簿(17)

    隨筆分類(61)

    隨筆檔案(61)

    文章分類(1)

    文章檔案(1)

    最新隨筆

    積分與排名

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 久久久久亚洲精品无码网址色欲 | 99久久久国产精品免费蜜臀| 毛片无码免费无码播放| 18以下岁毛片在免费播放| 成年女人毛片免费观看97| 免费A级毛片无码A∨男男| 亚洲老妈激情一区二区三区| 亚洲国产综合精品| 美女羞羞免费视频网站| 国内精品免费视频精选在线观看| 114一级毛片免费| 亚洲国产aⅴ综合网| 久久精品国产亚洲AV嫖农村妇女| 亚洲依依成人亚洲社区| 国产高潮流白浆喷水免费A片 | 91精品导航在线网址免费| 日本不卡在线观看免费v| 久久国产亚洲精品麻豆| 自拍日韩亚洲一区在线| g0g0人体全免费高清大胆视频| 性无码免费一区二区三区在线| 在线免费观看污网站| 亚洲国产综合无码一区| 日本亚洲免费无线码| 久久最新免费视频| 99久久免费精品国产72精品九九| 久久亚洲精品无码播放| 精品亚洲成A人无码成A在线观看| 一区二区三区免费精品视频| 青青青免费国产在线视频小草| 亚洲精品久久久www| 亚洲中文字幕无码av在线| 一日本道a高清免费播放| 免费人成网站在线观看10分钟| 亚洲国产日韩在线观频| 亚洲天堂一区二区| 三级片免费观看久久| 99在线免费观看视频| 久久亚洲高清综合| 亚洲hairy多毛pics大全| 中文字幕一区二区免费|