日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
熱點(diǎn)推薦——難免的尷尬:代碼依賴

有時(shí)候一味地追求"降低代碼依賴"反而會(huì)使系統(tǒng)更加復(fù)雜,我們必須在"降低代碼依賴"和"增加系統(tǒng)設(shè)計(jì)復(fù)雜性"之間找到一個(gè)平衡點(diǎn),而不應(yīng)該去盲目追求"六人定理"那種設(shè)計(jì)境界。

我們提供的服務(wù)有:成都做網(wǎng)站、網(wǎng)站制作、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、蕉嶺ssl等。為1000多家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的蕉嶺網(wǎng)站制作公司

   注:"六人定理"指:任何兩個(gè)人之間的關(guān)系帶,基本確定在六個(gè)人左右。兩個(gè)陌生人之間,可以通過(guò)六個(gè)人來(lái)建立聯(lián)系,此為六人定律,也稱作六人法則。

12.1 從面向?qū)ο箝_(kāi)始

在計(jì)算機(jī)科技發(fā)展歷史中,編程的方式一直都是趨向于簡(jiǎn)單化、人性化,"面向?qū)ο缶幊?正是歷史發(fā)展某一階段的產(chǎn)物,它的出現(xiàn)不僅是為了提高軟件開(kāi)發(fā) 的效率,還符合人們對(duì)代碼世界和真實(shí)世界的統(tǒng)一認(rèn)識(shí)觀。當(dāng)說(shuō)到"面向?qū)ο?,出現(xiàn)在我們腦海中的詞無(wú)非是:類,抽閑,封裝,繼承以及多態(tài),本節(jié)將從對(duì)象基 礎(chǔ)、對(duì)象擴(kuò)展以及對(duì)象行為三個(gè)方面對(duì)"面向?qū)ο?做出解釋。

注:面向?qū)ο笾械?面向"二字意指:在代碼世界中,我們應(yīng)該將任何東西都看做成一個(gè)封閉的單元,這個(gè)單元就是"對(duì)象"。對(duì)象不僅僅可以代表一個(gè)可以看得見(jiàn)摸得著的物體,它還可以代表一個(gè)抽象過(guò)程,從理論上講,任何具體的、抽象的事物都可以定義成一個(gè)對(duì)象。

12.1.1 對(duì)象基礎(chǔ):封裝

和現(xiàn)實(shí)世界一樣,無(wú)論從微觀上還是宏觀上看,這個(gè)世界均是由許許多多的單個(gè)獨(dú)立物體組成,小到人、器官、細(xì)胞,大到國(guó)家、星球、宇宙, 每個(gè)獨(dú)立單元都有自己的屬性和行為。仿照現(xiàn)實(shí)世界,我們將代碼中有關(guān)聯(lián)性的數(shù)據(jù)與操作合并起來(lái)形成一個(gè)整體,之后在代碼中數(shù)據(jù)和操作均是以一個(gè)整體出現(xiàn), 這個(gè)過(guò)程稱為"封裝"。封裝是面向?qū)ο蟮幕A(chǔ),有了封裝,才會(huì)有整體的概念。

圖12-1 封裝前后

如上圖12-1所示,圖中左邊部分為封裝之前,數(shù)據(jù)和操作數(shù)據(jù)的方法沒(méi)有相互對(duì)應(yīng)關(guān)系,方法可以訪問(wèn)到任何一個(gè)數(shù)據(jù),每個(gè)數(shù)據(jù)沒(méi)有訪問(wèn)限制,顯得雜 亂無(wú)章;圖中右邊部分為封裝之后,數(shù)據(jù)與之關(guān)聯(lián)的方法形成了一個(gè)整體單元,我們稱為"對(duì)象",對(duì)象中的方法操作同一對(duì)象的數(shù)據(jù),數(shù)據(jù)之間有了"保護(hù)"邊 界。外界可以通過(guò)對(duì)象暴露在外的接口訪問(wèn)對(duì)象,比如給它發(fā)送消息。

通常情況下,用于保存對(duì)象數(shù)據(jù)的有字段和屬性,字段一般設(shè)為私有訪問(wèn)權(quán)限,只準(zhǔn)對(duì)象內(nèi)部的方法訪問(wèn),而屬性一般設(shè)為公開(kāi)訪問(wèn)權(quán)限,供外界訪問(wèn)。方法就是對(duì)象的表現(xiàn)行為,分為私有訪問(wèn)權(quán)限和公開(kāi)訪問(wèn)權(quán)限兩類,前者只準(zhǔn)對(duì)象內(nèi)部訪問(wèn),而后者允許外界訪問(wèn)。

 
 
  1. //Code 12-1
  2. class Student //***
  3. {
  4.     private string _name; //NO.2
  5.     private int _age;
  6.     private string _hobby;
  7.     public string Name //NO.3
  8.     {
  9.         get
  10.         {
  11.             return _name;
  12.         }
  13.     }
  14.     public int Age
  15.     {
  16.         get
  17.         {
  18.             return _age;
  19.         }
  20.         set
  21.         {
  22.             if(value<=0)
  23.             {
  24.                 value=1;
  25.             }
  26.             _age = value;
  27.         }
  28.     }
  29.     public string Hobby
  30.     {
  31.         get
  32.         {
  33.             return _hobby;
  34.         }
  35.         set
  36.         {
  37.             _hobby = value;
  38.         }
  39.     }
  40.     public Student(string name,int age,string hobby)
  41.     {
  42.         _name = name;
  43.         _age = age;
  44.         _hobby = hobby;
  45.     }
  46.     public void SayHello() //NO.4
  47.     {
  48.         Console.WriteLine(GetSayHelloWords());
  49.     }
  50.     protected virtual string GetSayHelloWords() //NO.5
  51.     {
  52.         string s = "";
  53.         s += "hello,my name is " + _name + ",\r\n",
  54.         s += "I am "+_age + "years old," + "\r\n";
  55.         s += "I like "+_hobby + ",thanks\r\n";
  56.         return s;
  57.     }
  58. }

上面代碼Code 12-1將學(xué)生這個(gè)人群定義成了一個(gè)Student類(***處),它包含三個(gè)字段:分別為保存姓名的_name、保存年齡的_age以及保存愛(ài)好的 _hobby字段,這三個(gè)字段都是私有訪問(wèn)權(quán)限,為了方便外界訪問(wèn)內(nèi)部的數(shù)據(jù),又分別定義了三個(gè)屬性:分別為訪問(wèn)姓名的Name,注意該屬性是只讀的,因 為正常情況下姓名不能再被外界改變;訪問(wèn)年齡的Age,注意當(dāng)給年齡賦值小于等于0時(shí),代碼自動(dòng)將其設(shè)置為1;訪問(wèn)愛(ài)好的Hobby,外界可以通過(guò)該屬性 對(duì)_hobby字段進(jìn)行完全訪問(wèn)。同時(shí)Student類包含兩個(gè)方法,一個(gè)公開(kāi)的SyaHello()方法和一個(gè)受保護(hù)的 GetSayHelloWords()方法,前者負(fù)責(zé)輸出對(duì)象自己的"介紹信息",后者負(fù)責(zé)格式化"介紹信息"的字符串。Student類圖見(jiàn)圖 12-2:

圖12-2 Student類圖

    注:上文中將類的成員訪問(wèn)權(quán)限只分為兩個(gè)部分,一個(gè)對(duì)外界可見(jiàn),包括public;另一種對(duì)外界不可見(jiàn),包括private、protected等。

注意類與對(duì)象的區(qū)別,如果說(shuō)對(duì)象是代碼世界對(duì)現(xiàn)實(shí)世界中各種事物的一一映射,那么類就是這些映射的模板,通過(guò)模板創(chuàng)建具體的映射實(shí)例:

圖12-3 對(duì)象實(shí)例化

我們可以看到代碼Code 12-1中的Student類既包含私有成員也包含公開(kāi)成員,私有成員對(duì)外界不可見(jiàn),外界如需訪問(wèn)對(duì)象,只能調(diào)用給出的公開(kāi)方法。這樣做的目的就是將外界 不必要了解的信息隱藏起來(lái),對(duì)外只提供簡(jiǎn)單的、易懂的、穩(wěn)定的公開(kāi)接口即可方便外界對(duì)該類型的使用,同時(shí)也避免了外界對(duì)對(duì)象內(nèi)部數(shù)據(jù)不必要的修改和訪問(wèn)所 造成的異常。

封裝的準(zhǔn)則:

封裝是面向?qū)ο蟮?**步,有了封裝,才會(huì)有類、對(duì)象,再才能談繼承、多態(tài)等。經(jīng)過(guò)前人豐富的實(shí)踐和總結(jié),對(duì)封裝有以下準(zhǔn)則,我們?cè)谄綍r(shí)實(shí)際開(kāi)發(fā)中應(yīng)該盡量遵循這些準(zhǔn)則:

1)一個(gè)類型應(yīng)該盡可能少地暴露自己的內(nèi)部信息,將細(xì)節(jié)的部分隱藏起來(lái),只對(duì)外公開(kāi)必要的穩(wěn)定的接口;同理,一個(gè)類型應(yīng)該盡可能少地了解其它類型, 這就是常說(shuō)的"迪米特法則(Law of Demeter)",迪米特法則又被稱作"最小知識(shí)原則",它強(qiáng)調(diào)一個(gè)類型應(yīng)該盡可能少地知道其它類型的內(nèi)部實(shí)現(xiàn),它是降低代碼依賴的一個(gè)重要指導(dǎo)思想, 詳見(jiàn)本章后續(xù)介紹;

2)理論上,一個(gè)類型的內(nèi)部代碼可以任意改變,而不應(yīng)該影響對(duì)外公開(kāi)的接口。這就要求我們將"善變"的部分隱藏到類型內(nèi)部,對(duì)外公開(kāi)的一定是相對(duì)穩(wěn)定的;

3)封裝并不單指代碼層面上,如類型中的字段、屬性以及方法等,更多的時(shí)候,我們可以將其應(yīng)用到系統(tǒng)結(jié)構(gòu)層面上,一個(gè)模塊乃至系統(tǒng),也應(yīng)該只對(duì)外提供穩(wěn)定的、易用的接口,而將具體實(shí)現(xiàn)細(xì)節(jié)隱藏在系統(tǒng)內(nèi)部。

封裝的意義:

封裝不僅能夠方便對(duì)代碼對(duì)數(shù)據(jù)的統(tǒng)一管理,它還有以下意義:

1)封裝隱藏了類型的具體實(shí)現(xiàn)細(xì)節(jié),保證了代碼安全性和穩(wěn)定性;

2)封裝對(duì)外界只提供穩(wěn)定的、易用的接口,外部使用者不需要過(guò)多地了解代碼實(shí)現(xiàn)原理也不需要掌握復(fù)雜難懂的調(diào)用邏輯,就能夠很好地使用類型;

3)封裝保證了代碼模塊化,提高了代碼復(fù)用率并確保了系統(tǒng)功能的分離。

12.1.2 對(duì)象擴(kuò)展:繼承

封裝強(qiáng)調(diào)代碼合并,封裝的結(jié)果就是創(chuàng)建一個(gè)個(gè)獨(dú)立的包裝件:類。那么我們有沒(méi)有其它的方法去創(chuàng)建新的包裝件呢?

在現(xiàn)實(shí)生活中,一種物體往往衍生自另外一種物體,所謂衍生,是指衍生體在具備被衍生體的屬性基礎(chǔ)上,還具備其它額外的特性,被衍生體往往更抽象,而 衍生體則更具體,如大學(xué)衍生自學(xué)校,因?yàn)榇髮W(xué)具備學(xué)校的特點(diǎn),但大學(xué)又比學(xué)校具體,人衍生自生物,因?yàn)槿司邆渖锏奶攸c(diǎn),但人又比生物具體。

圖12-4 學(xué)校衍生圖

如上圖12-4,學(xué)校相對(duì)來(lái)講最抽象,大學(xué)、高中以及小學(xué)均可以衍生自學(xué)校,進(jìn)一步來(lái)看,大學(xué)其實(shí)也比較抽象,因?yàn)榇髮W(xué)還可以有具體的本科、???, 因此本科和??瓶梢匝苌源髮W(xué),當(dāng)然,抽象和具體的概念是相對(duì)的,如果你覺(jué)得本科還不夠具體,那么它可以再衍生出來(lái)一本、二本以及三本。

在代碼世界中,也存在"衍生"這一說(shuō),從一個(gè)較抽象的類型衍生出一個(gè)較具體的類型,我們稱"后者派生自前者",如果A類型派生自B類型,那么稱這個(gè)過(guò)程為"繼承",A稱之為"派生類",B則稱之為"基類"。

    注:派生類又被形象地稱為"子類",基類又被形象地稱為"父類"。

在代碼12-1中的Student類基礎(chǔ)上,如果我們需要?jiǎng)?chuàng)建一個(gè)大學(xué)生(College_Student)的類型,那么我們完全可以從Student類派生出一個(gè)新的大學(xué)生類,因?yàn)榇髮W(xué)生具備學(xué)生的特點(diǎn),但又比學(xué)生更具體:

 
 
  1. //Code 12-2
  2. class College_Student:Student //***
  3. {
  4.     private string _major;
  5.     public string Major
  6.     {
  7.         get
  8.         {
  9.             return _major;
  10.         }
  11.         set
  12.         {
  13.             _major = value;
  14.         }
  15.     }
  16.     public College_Student(string name,int age,string hobby,string major) :base(name,age,hobby) //NO.2
  17.     {
  18.         _major = major;
  19.     }
  20.     protected override string GetSayHelloWords() //NO.3
  21.     {
  22.         string s = "";
  23.         s += "hello,my name is " + Name + ",\r\n",
  24.         s += "I am "+ Age + "years old, and my major is " + _major + ",\r\n";
  25.         s += "I like "+ Hobby + ", thanks\r\n";
  26.         return s;
  27.     }
  28. }

如上代碼Code 12-2所示,College_Student類繼承Student類(***處),College_Student類具備Student類的屬性,比 如Name、Age以及Hobby,同時(shí)College_Student類還增加了額外的專業(yè)(Major)屬性,通過(guò)在派生類中重寫(xiě) GetSyaHelloWords()方法,我們重新格式化"個(gè)人信息"字符串,讓其包含"專業(yè)"的信息(NO.3處),***,調(diào)用 College_Student中從基類繼承下來(lái)的SayHello()方法,便可以輕松輸出自己的個(gè)人信息。

我們看到,派生類通過(guò)繼承獲得了基類的全部信息,之外,派生類還可以增加新的內(nèi)容(如College_Student類中新增的Major屬性), 基類到派生類是一個(gè)抽象到具體的過(guò)程,因此,我們?cè)谠O(shè)計(jì)類型的時(shí)候,經(jīng)常將通用部分提取出來(lái),形成一個(gè)基類,以后所有與基類有種族關(guān)系的類型均可以繼承該 基類,以基類為基礎(chǔ),增加自己特有的屬性。

圖12-5 College_Student類繼承圖

有的時(shí)候,一種類型只用于其它類型派生,從來(lái)不需要?jiǎng)?chuàng)建它的某個(gè)具體對(duì)象實(shí)例,這樣的類高度抽象化,我們稱這種類為"抽象類",抽象類不負(fù)責(zé)創(chuàng)建具 體的對(duì)象實(shí)例,它包含了派生類型的共同成分。除了通過(guò)繼承某個(gè)類型來(lái)創(chuàng)建新的類型,.NET中還提供另外一種類似的創(chuàng)建新類型的方式:接口實(shí)現(xiàn)。接口定義 了一組方法,所有實(shí)現(xiàn)了該接口的類型必須實(shí)現(xiàn)接口中所有的方法:

 
 
  1. //Code 12-3
  2. interface IWalkable
  3. {
  4.     void Walk();
  5. }
  6. class People:IWalkable
  7. {
  8.     //…
  9.     public void Walk()
  10.     {
  11.         Console.WriteLine("walk quickly");
  12.     }
  13. }
  14. class Dog:IWalkable
  15. {
  16.     //…
  17.     public void Walk()
  18.     {
  19.         Console.WriteLine("walk slowly");
  20.     }
  21. }

如上代碼Code 12-3所示,People和Dog類型均實(shí)現(xiàn)了IWalkable接口,那么它們必須都實(shí)現(xiàn)IWalkable接口中的Walk()方法,見(jiàn)下圖12-6:

圖12-6 接口繼承

繼承包括兩種方式,一種為"類繼承",一種為"接口繼承",它們的作用類似,都是在現(xiàn)有類型基礎(chǔ)上創(chuàng)建出新的類型,但是它們也有區(qū)別:

1)類繼承強(qiáng)調(diào)了族群關(guān)系,而接口繼承強(qiáng)調(diào)通用功能。類繼承中的基類和派生類屬于祖宗和子孫的關(guān)系,而接口繼承中的接口和實(shí)現(xiàn)了接口的類型并沒(méi)有這種關(guān)系。

2)類繼承強(qiáng)調(diào)"我是(Is-A)"的關(guān)系,派生類"是"基類(注意這里的"是"代表派生類具備基類的特性),而接口繼承強(qiáng)調(diào)"我能做(Can-Do)"的關(guān)系,實(shí)現(xiàn)了接口的類型具有接口中規(guī)定的行為能力(因此接口在命名時(shí)均以"able"作為后綴)。

3)類繼承中,基類雖然較抽象,但是它可以有具體的實(shí)現(xiàn),比如方法、屬性的實(shí)現(xiàn),而接口繼承中,接口不允許有任何的具體實(shí)現(xiàn)。

繼承的準(zhǔn)則:

繼承是面向?qū)ο缶幊讨袆?chuàng)建類型的一種方式,在封裝的基礎(chǔ)上,它能夠減少工作量、提高代碼復(fù)用率的同時(shí),快速地創(chuàng)建出具有相似性的類型。在使用繼承時(shí),請(qǐng)遵循以下準(zhǔn)則:

1)嚴(yán)格遵守"里氏替換原則",即基類出現(xiàn)的地方,派生類一定可以出現(xiàn),因此,不要盲目地去使用繼承,如果兩個(gè)類沒(méi)有衍生的關(guān)系,那么就不應(yīng)該有繼 承關(guān)系。如果讓貓(Cat)類派生自狗(Dog)類,那么很容易就可以看到,狗類出現(xiàn)的地方,貓類不一定可以代替它出現(xiàn),因?yàn)樗鼉筛揪蜎](méi)有抽象和具體的 層次關(guān)系。

2)由于派生類會(huì)繼承基類的全部?jī)?nèi)容,所以要嚴(yán)格控制好類型的繼承層次,不然派生類的體積會(huì)越來(lái)越大。另外,基類的修改必然會(huì)影響到派生類,繼承層次太多不易管理,繼承是增加耦合的最重要因素。

3)繼承強(qiáng)調(diào)類型之間的通性,而非特性。因此我們一般將類型都具有的部分提取出來(lái),形成一個(gè)基類(抽象類)或者接口。

12.1.3 對(duì)象行為:多態(tài)

"多態(tài)"一詞來(lái)源于生物學(xué),本意是指地球上的所有生物體現(xiàn)出形態(tài)和狀態(tài)的多樣性。在面向?qū)ο缶幊讨卸鄳B(tài)是指:同一操作作用于不同類的實(shí)例,將產(chǎn)生不同的執(zhí)行結(jié)果,即不同類的對(duì)象收到相同的消息時(shí),得到不同的結(jié)果。

多態(tài)強(qiáng)調(diào)面向?qū)ο缶幊讨?,?duì)象的多種表現(xiàn)行為,見(jiàn)下代碼Code 12-4:

 
 
  1. //Code 12-4
  2. class Student //***
  3. {
  4.     public void IntroduceMyself()
  5.     {
  6.         SayHello();
  7.     }
  8.     protected virtual void SayHello()
  9.     {
  10.         Console.WriteLine("Hello,everyone!");
  11.     }
  12. }
  13. class College_Student:Student //NO.2
  14. {
  15.     protected override void SayHello()
  16.     {
  17.         base.SayHello();
  18.         Console.WriteLine("I am a college student…");
  19.     }
  20. }
  21. class Senior_HighSchool_Student:Student //NO.3
  22. {
  23.     protected override void SayHello()
  24.     {
  25.         base.SayHello();
  26.         Console.WriteLine("I am a senior high school student…");
  27.     }
  28. }
  29. class Program
  30. {
  31.     static void Main()
  32.     {
  33.         Console.Title = "SayHello";
  34.         Student student = new Student();
  35.         student.IntroduceMyself(); //NO.4
  36.         student = new College_Student();
  37.         student.IntroduceMyself(); //NO.5
  38.         student = new Senior_HighSchool_Student();
  39.         student.IntroduceMyself(); //NO.6
  40.         Console.Read();    
  41.     }
  42. }

如上代碼Code 12-4所示,分別定義了三個(gè)類:Student(***處)、College_Student(NO.2處)、 Senior_HighSchool_Student(NO.3處),后面兩個(gè)類繼承自Student類,并重寫(xiě)了SayHello()方法。在客戶端代 碼中,對(duì)于同一行代碼"student.IntroduceMyself();"而言,三次調(diào)用(NO.4、NO.5以及NO.6處),屏幕輸出的結(jié)果卻 不相同:

圖12-7 多態(tài)效果

如上圖12-7所示,三次調(diào)用同一個(gè)方法,不同對(duì)象有不同的表現(xiàn)行為,我們稱之為"對(duì)象的多態(tài)性"。從代碼Code 12-4中可以看出,之所以出現(xiàn)同樣的調(diào)用會(huì)產(chǎn)生不同的表現(xiàn)行為,是因?yàn)榻o基類引用student賦值了不同的派生類對(duì)象,并且派生類中重寫(xiě)了 SayHello()虛方法。

對(duì)象的多態(tài)性是以"繼承"為前提的,而繼承又分為"類繼承"和"接口繼承"兩類,那么多態(tài)性也有兩種形式:

1)類繼承式多態(tài);

類繼承式多態(tài)需要虛方法的參與,正如代碼Code 12-4中那樣,派生類在必要時(shí),必須重寫(xiě)基類的虛方法,***使用基類引用調(diào)用各種派生類對(duì)象的方法,達(dá)到多種表現(xiàn)行為的效果:

2)接口繼承式多態(tài)。

接口繼承式多態(tài)不需要虛方法的參與,在代碼Code 12-3的基礎(chǔ)上編寫(xiě)如下代碼:

 
 
  1. //Code 12-5
  2. class Program
  3. {
  4.     static void Main()
  5.     {
  6.         Console.Title = "Walk";
  7.         IWalkable iw = new People();
  8.         iw.Walk(); //***
  9.         iw = new Dog();
  10.         iw.Walk(); //NO.2
  11.         Console.Read();
  12.     }
  13. }

如上代碼Code 12-5所示,對(duì)于同一行代碼"iw.Walk();"的兩次調(diào)用(***和NO.2處),有不同的表現(xiàn)行為:

圖12-8 接口繼承式多態(tài)

在面向?qū)ο缶幊讨?,多態(tài)的前提是繼承,而繼承的前提是封裝,三者缺一不可。多態(tài)也是是降低代碼依賴的有力保障,詳見(jiàn)本章后續(xù)有關(guān)內(nèi)容。

#p#

12.2 不可避免的代碼依賴

本書(shū)前面章節(jié)曾介紹過(guò),程序的執(zhí)行過(guò)程就是方法的調(diào)用過(guò)程,有方法調(diào)用,必然會(huì)促使對(duì)象跟對(duì)象之間產(chǎn)生依賴,除非一個(gè)對(duì)象不參與程序的運(yùn)行,這樣的 對(duì)象就像一座孤島,與其它對(duì)象沒(méi)有任何交互,但是這樣的對(duì)象也就沒(méi)有任何存在價(jià)值。因此,在我們的程序代碼中,任何一個(gè)對(duì)象必然會(huì)與其它一個(gè)甚至更多個(gè)對(duì) 象產(chǎn)生依賴關(guān)系。

12.2.1 依賴存在的原因

"方法調(diào)用"是最常見(jiàn)產(chǎn)生依賴的原因,一個(gè)對(duì)象與其它對(duì)象必然會(huì)通信(除非我們把所有的代碼邏輯全部寫(xiě)在了這個(gè)對(duì)象內(nèi)部),通信通常情況下就意味著 有方法的調(diào)用,有方法的調(diào)用就意味著這兩個(gè)對(duì)象之間存在依賴關(guān)系(至少要有其它對(duì)象的引用才能調(diào)用方法),另外常見(jiàn)的一種產(chǎn)生依賴的原因是:繼承,沒(méi)錯(cuò), 繼承雖然給我們帶來(lái)了非常大的好處,卻也給我們帶來(lái)了代碼依賴。依賴產(chǎn)生的原因大概可以分以下四類:

1)繼承;

派生類繼承自基類,獲得了基類的全部?jī)?nèi)容,但同時(shí),派生類也受控于基類,只要基類發(fā)生改變,派生類一定發(fā)生變化:

圖12-9 繼承依賴

上圖12-9中,B和C繼承自A,A類改變必然會(huì)影響B(tài)和C的變化。

2)成員對(duì)象;

一個(gè)類型包含另外一個(gè)類型的成員時(shí),前者必然受控于后者,雖然后者的改變不一定會(huì)影響到前者:

圖12-10 成員對(duì)象依賴

如上圖12-10,A包含B類型的成員,那么A就受控于B,B在A內(nèi)部完全可見(jiàn)。

    注:成員對(duì)象依賴跟組合(聚合)類似。

3)傳遞參數(shù);

一個(gè)類型作為參數(shù)傳遞給另外一個(gè)類型的成員方法,那么后者必然會(huì)受控于前者,雖然前者的改變不一定會(huì)影響到后者:

圖12-11 傳參依賴

如上圖12-11,A類型的方法Method()包含一個(gè)B類型的參數(shù),那么A就受控于B,B在A的Method()方法可見(jiàn)。

4)臨時(shí)變量。

任何時(shí)候,一個(gè)類型將另外一個(gè)類型用作了臨時(shí)變量時(shí),那么前者就受控于后者,雖然后者的改變不一定會(huì)影響到前者:

 
 
  1. //Code 12-6
  2. class A
  3. {
  4.     public void DoSomething()
  5.     {
  6.         //…
  7.     }
  8. }
  9. class B
  10. {
  11.     public void DoSomething()
  12.     {
  13.         //…
  14.         A a = new A();
  15.         a.DoSomething();
  16.         //…
  17.     }
  18. }

如上代碼Code 12-6,B的DoSomething()方法中使用了A類型的臨時(shí)對(duì)象,A在B的DoSomething()方法中局部范圍可見(jiàn)。

通常情況下,通過(guò)被依賴者在依賴者內(nèi)部可見(jiàn)范圍大小來(lái)衡量依賴程度的高低,原因很簡(jiǎn)單,可見(jiàn)范圍越大,說(shuō)明訪問(wèn)它的概率就越大,依賴者受影響的概率也就越大,因此,上述四種依賴產(chǎn)生的原因中,依賴程度按順序依次降低。

12.2.2 耦合與內(nèi)聚

為了衡量對(duì)象之間依賴程度的高低,我們引進(jìn)了"耦合"這一概念,耦合度越高,說(shuō)明對(duì)象之間的依賴程度越高;為了衡量對(duì)象獨(dú)立性的高低,我們引進(jìn)了"內(nèi)聚"這一概念,內(nèi)聚性越高,說(shuō)明對(duì)象與外界交互越少、獨(dú)立性越強(qiáng)。很明顯,耦合與內(nèi)聚是兩個(gè)相互對(duì)立又密切相關(guān)的概念。

    注:從廣義上講,"耦合"與"內(nèi)聚"不僅適合對(duì)象與對(duì)象之間的關(guān)系,也適合模塊與模塊、系統(tǒng)與系統(tǒng)之間的關(guān)系,這跟前面講"封裝"時(shí)強(qiáng)調(diào)"封裝"不僅僅指代碼層面上的道理一樣。

"模塊功能集中,模塊之間界限明確"一直是軟件設(shè)計(jì)追求的目標(biāo),軟件系統(tǒng)不會(huì)因?yàn)樾枨蟮母淖?、功能的升?jí)而不得不大范圍修改原來(lái)已有的源代碼,換句話說(shuō),我們?cè)谲浖O(shè)計(jì)中,應(yīng)該嚴(yán)格遵循"高內(nèi)聚、低耦合"的原則。下圖12-12顯示一個(gè)系統(tǒng)遵循該原則前后:

圖12-12 高內(nèi)聚、低耦合

如上圖12-12所示,"高內(nèi)聚、低耦合"強(qiáng)調(diào)對(duì)象與對(duì)象之間(模塊與模塊之間)盡可能多地降低依賴程度,每個(gè)對(duì)象(或模塊,下同)盡可能提高自己的獨(dú)立性,這就要求它們各自負(fù)責(zé)的功能相對(duì)集中,代碼結(jié)構(gòu)由"開(kāi)放"轉(zhuǎn)向"收斂"。

"職責(zé)單一原則(SRP)"是提高對(duì)象內(nèi)聚性的理論指導(dǎo)思想之一,它建議每個(gè)對(duì)象只負(fù)責(zé)某一個(gè)(一類)功能。

12.2.3 依賴造成的"尷尬"

如果在軟件系統(tǒng)設(shè)計(jì)初期,沒(méi)有合理地降低(甚至避免)代碼間的耦合,系統(tǒng)開(kāi)發(fā)后期往往會(huì)遇到前期不可預(yù)料的困難。下面舉例說(shuō)明依賴給我們?cè)斐傻?尷尬"。

假設(shè)一個(gè)將要開(kāi)發(fā)的系統(tǒng)中使用到了數(shù)據(jù)庫(kù),系統(tǒng)設(shè)計(jì)階段確定使用SQL Server數(shù)據(jù)庫(kù),按照"代碼模塊化可以提高代碼復(fù)用性"的原則,我們將訪問(wèn)SQL Server數(shù)據(jù)庫(kù)的代碼封裝成了一個(gè)單獨(dú)的類,該類只負(fù)責(zé)訪問(wèn)SQLServer數(shù)據(jù)庫(kù)這一功能:

 
 
  1. //Code 12-7
  2. class SQLServerHelper //***
  3. {
  4.     //…
  5.     public void ExcuteSQL(string sql)
  6.     {
  7.         //…
  8.     }
  9. }
  10. class DBManager //NO.2
  11. {
  12.     //…
  13.     SQLServerHelper _sqlServerHelper; //NO.3
  14.     public DBManager(SQLServerHelper sqlServerHelper)
  15.     {
  16.         _sqlServerHelper = sqlServerHelper;
  17.     }
  18.     public void Add() //NO.4
  19.     {
  20.         string sql = "";
  21.         //…
  22.         _sqlServerHelper.ExcuteSQL(sql);
  23.     }
  24.     public void Delete() //NO.5
  25.     {
  26.         string sql = "";
  27.         //…
  28.         _sqlServerHelper.ExcuteSQL(sql);
  29.     }
  30.     public void Update() //NO.6
  31.     {
  32.         string sql = "";
  33.         //…
  34.         _sqlServerHelper.ExcuteSQL(sql);
  35.     }
  36.     public void Search() //NO.7
  37.     {
  38.         string sql = "";
  39.         //…
  40.         _sqlServerHelper.ExcuteSQL(sql);
  41.     }
  42. }

如上代碼Code 12-7所示,定義了一個(gè)SQL Server數(shù)據(jù)庫(kù)訪問(wèn)類SQLServerHelper(***處),該類專門(mén)負(fù)責(zé)訪問(wèn)SQL Server數(shù)據(jù)庫(kù),如執(zhí)行sql語(yǔ)句(其它功能略),然后定義了一個(gè)數(shù)據(jù)庫(kù)管理類DBManager(NO.2處),該類負(fù)責(zé)一些數(shù)據(jù)的增刪改查 (NO.4、NO.5、NO.6以及NO.7處),同時(shí)該類還包含一個(gè)SQLServerHelper類型成員(NO.3處),負(fù)責(zé)具體SQL Server數(shù)據(jù)庫(kù)的訪問(wèn)。SQLServerHelper類和DBManager類的關(guān)系見(jiàn)下圖12-13:

圖12-13 依賴于具體

如上圖12-13所示,DBManager類依賴于SQLServerHelper類,后者在前者內(nèi)部完全可見(jiàn),當(dāng)DBManager需要訪問(wèn) SQL Server數(shù)據(jù)庫(kù)時(shí),可以交給SQLServerHelper類型成員負(fù)責(zé),到此為止,這兩個(gè)類型合作得非常好,但是,現(xiàn)在如果我們對(duì)數(shù)據(jù)庫(kù)的需求發(fā)生 變化,不再使用SQL Server數(shù)據(jù)庫(kù),而要求更改使用MySQL數(shù)據(jù)庫(kù),那么我們需要做些什么工作呢?和之前一樣,我們需要定義一個(gè)MySQLHelper類來(lái)負(fù)責(zé) MySQL數(shù)據(jù)庫(kù)的訪問(wèn),代碼如下:

 
 
  1. //Code 12-8
  2. class MySQLHelper
  3. {
  4.     //…
  5.     public void ExcuteSQL(string sql)
  6.     {
  7.         //…
  8.     }
  9. }

如上代碼Code 12-8,定義了一個(gè)專門(mén)訪問(wèn)MySQL數(shù)據(jù)庫(kù)的類型MySQLHelper,它的結(jié)構(gòu)跟SQLServerHelper相同,接下來(lái),為了使原來(lái)已經(jīng)工 作正常的系統(tǒng)重新適應(yīng)于MySQL數(shù)據(jù)庫(kù),我們還必須依次修改DBManager類中所有對(duì)SQLServerHelper類型的引用,將其全部更新為 MySQLHelper的引用。如果只是一個(gè)DBManager類使用到了SQLServerHelper的話,整個(gè)更新工作量還不算非常多,但如果程序 代碼中還有其它地方使用到了SQLServerHelper類型的話,這個(gè)工作量就不可估量,除此之外,我們這樣做出的所有操作完全違背了軟件設(shè)計(jì)中的" 開(kāi)閉原則(OCP)",即"對(duì)擴(kuò)展開(kāi)放,而對(duì)修改關(guān)閉"。很明顯,我們?cè)谠黾有碌念愋蚆ySQLHelper時(shí),還修改了系統(tǒng)原有代碼。

出現(xiàn)以上所說(shuō)問(wèn)題的主要原因是,在系統(tǒng)設(shè)計(jì)初期,DBManager這個(gè)類型依賴了一個(gè)具體類型SQLServerHelper,"具體"就意味著 不可改變,同時(shí)也就說(shuō)明兩個(gè)類型之間的依賴關(guān)系已經(jīng)到達(dá)了"非你不可"的程度。要解決以上問(wèn)題,需要我們?cè)谲浖O(shè)計(jì)初期就做出一定的措施,詳見(jiàn)下一小節(jié)。

#p#

12.3 降低代碼依賴

上一節(jié)末尾說(shuō)到了代碼依賴給我們工作帶來(lái)的麻煩,還提到了主要原因是對(duì)象與對(duì)象之間(模塊與模塊,下同)依賴關(guān)系太過(guò)緊密,本節(jié)主要說(shuō)明怎樣去降低代碼間的依賴程度。

12.3.1 認(rèn)識(shí)"抽象"與"具體"

其實(shí)本書(shū)之前好些地方已經(jīng)出現(xiàn)過(guò)"具體"和"抽象"的詞眼,如"具體的類型"、"依賴于抽象而非具體"等等,到目前為止,本書(shū)還并沒(méi)有系統(tǒng)地介紹這兩者的具體含義。

所謂"抽象",即"不明確、未知、可改變"的意思,而"具體"則是相反的含義,它表示"確定、不可改變"。我們?cè)谇懊嬷v"繼承"時(shí)就說(shuō)過(guò),派生類繼 承自基類,就是一個(gè)"抽象到具體"的過(guò)程,比如基類"動(dòng)物(Animal)"就是一個(gè)抽象的事物,而從基類"動(dòng)物(Animal)"派生出來(lái)的"狗 (Dog)"就是一個(gè)具體的事物。抽象與具體的關(guān)系如下圖12-14:

圖12-14 抽象與具體的相對(duì)性

 注:抽象與具體也是一個(gè)相對(duì)的概念,并不能說(shuō)"動(dòng)物"就一定是一個(gè)抽象的事物,它與"生物"進(jìn)行比較,就是一個(gè)相對(duì)具體的事物,同理"狗"也不一定就是具體的事物,它跟"哈士奇"進(jìn)行比較,就是一個(gè)相對(duì)抽象的概念。

 在代碼中,"抽象"指接口、以及相對(duì)抽象化的類,注意這里相對(duì)抽象化的類并不特指"抽象類"(使用abstract關(guān)鍵字聲明的類),只要一個(gè)類 型在族群層次中比較靠上,那么它就可以算是抽象的,如上面舉的"動(dòng)物(Animal)"的例子;"具體"則指從接口、相對(duì)抽象化的類繼承出來(lái)的類型,如 從"動(dòng)物(Animal)"繼承得到的"狗(Dog)"類型。代碼中抽象與具體的舉例見(jiàn)下表12-1:

表12-1 抽象與具體舉例

序號(hào)

抽象

具體

說(shuō)明

1

Interface IWalkable

{

void Walk();

}

class Dog:IWalkable

{

public void Walk()

{

//…

}

}

IWalkable接口是"抽象",實(shí)現(xiàn)IWalkable接口的Dog類是"具體"。

2

class Dog:IWalkable

{

public void Walk()

{

//…

}

}

class HaShiQi:Dog

{

//…

}

Dog類是"抽象",繼承自Dog類的HaShiQi類則是"具體"。

如果一個(gè)類型包含一個(gè)抽象的成員,比如"動(dòng)物(Animal)",那么這個(gè)成員可以是很多種類型,不僅可以是"狗(Dog)",還可以是"貓 (Cat)"或者其它從"動(dòng)物(Animal)"派生的類型,但是如果一個(gè)類型包含一個(gè)相對(duì)具體的成員,比如"狗(Dog)",那么這個(gè)成員就相對(duì)固定, 不可再改變。很明顯,抽象的東西更易改變,"抽象"在降低代碼依賴方面起到了重要作用。

12.3.2 再看"依賴倒置原則"

本書(shū)前面章節(jié)在講到"依賴倒置原則"時(shí)曾建議我們?cè)谲浖O(shè)計(jì)時(shí):

1)高層模塊不應(yīng)該直接依賴于低層模塊,高層模塊和低層模塊都應(yīng)該依賴于抽象;

2)抽象不應(yīng)該依賴于具體,具體應(yīng)該依賴于抽象。

抽象的事物不確定,一個(gè)類型如果包含一個(gè)接口類型成員,那么實(shí)現(xiàn)了該接口的所有類型均可以成為該類型的成員,同理,方法傳參也一樣,如果一個(gè)方法包 含一個(gè)接口類型參數(shù),那么實(shí)現(xiàn)了該接口的所有類型均可以作為方法的參數(shù)。根據(jù)"里氏替換原則(LSP)"介紹的,基類出現(xiàn)的地方,派生類均可以代替其出 現(xiàn)。我們?cè)倏幢菊?2.2.3小節(jié)中講到的"依賴造成的尷尬",DBManager類型依賴一個(gè)具體的SQLServerHelper類型,它內(nèi)部包含了 一個(gè)SQLServerHelper類型成員,DBManager和SQLServerHelper之間產(chǎn)生了一個(gè)不可變的綁定關(guān)系,如果我們想將數(shù)據(jù)庫(kù) 換成MySQL數(shù)據(jù)庫(kù),要做的工作不僅僅是增加一個(gè)MySQLHelper類型。假設(shè)在軟件系統(tǒng)設(shè)計(jì)初期,我們將訪問(wèn)各種數(shù)據(jù)庫(kù)的相似操作提取出來(lái),放到 一個(gè)接口中,之后訪問(wèn)各種具體數(shù)據(jù)庫(kù)的類型均實(shí)現(xiàn)該接口,并使DBManager類型依賴于該接口:

 
 
  1. //Code 12-9
  2. interface IDB //***
  3. {
  4.     void ExcuteSQL(string sql);
  5. }
  6. class SQLServerHelper:IDB //NO.2
  7. {
  8.     //…
  9.     public void ExcuteSQL(string sql)
  10.     {
  11.         //…
  12.     }
  13. }
  14. class MySQLHelper:IDB //NO.3
  15. {
  16.     //…
  17.     public void ExcuteSQL(string sql)
  18.     {
  19.         //…
  20.     }
  21. }
  22. class DBManager //NO.4
  23. {
  24.     //…
  25.     IDB _dbHelper; //NO.5
  26.     public DBManager(IDB dbHelper)
  27.     {
  28.         _dbHelper = dbHelper;
  29.     }
  30.     public void Add() //NO.6
  31.     {
  32.         string sql = "";
  33.         //…
  34.         _dbHelper.ExcuteSQL(sql);
  35.     }
  36.     public void Delete() //NO.7
  37.     {
  38.         string sql = "";
  39.         //…
  40.         _dbHelper.ExcuteSQL(sql);
  41.     }
  42.     public void Update() //NO.8
  43.     {
  44.         string sql = "";
  45.         //…
  46.         _dbHelper.ExcuteSQL(sql);
  47.     }
  48.     public void Search() //NO.9
  49.     {
  50.         string sql = "";
  51.         //…
  52.         _dbHelper.ExcuteSQL(sql);
  53.     }
  54. }

如上代碼Code 12-9所示,我們將訪問(wèn)數(shù)據(jù)庫(kù)的方法放到了IDB接口中(***處),之后所有訪問(wèn)其它具體數(shù)據(jù)庫(kù)的類型均需實(shí)現(xiàn)該接口(NO.2和NO.3處),同 時(shí)DBManager類中不再包含具體SQLServerHelper類型引用,而是依賴于IDB接口(NO.5處),這樣一來(lái),我們可以隨便地將 SQLServerHelper或者M(jìn)ySQLHelper類型對(duì)象作為DBManager的構(gòu)造參數(shù)傳入,甚至我們還可以新定義其它數(shù)據(jù)庫(kù)訪問(wèn)類,只要 該類實(shí)現(xiàn)了IDB接口,

  
 
  1. //Code 12-10
  2. class OracleHelper:IDB //***
  3. {
  4.     //…
  5.     public void ExcuteSQL(
    本文名稱:熱點(diǎn)推薦——難免的尷尬:代碼依賴
    URL地址:http://www.dlmjj.cn/article/dpcjisd.html