面向?qū)ο笫且环N設(shè)計的思想,與具體實現(xiàn)的語言工具無關(guān)浅侨。能讓軟件架構(gòu)更符合人的思維模式证膨,更為清晰明了央勒,更易于理解與維護不见。這是大型軟件必然的選擇。
面向?qū)ο蟮某绦蛟O(shè)計語言必須有描述對象及其相互之間關(guān)系的語言成分崔步。這些程序設(shè)計語言可以歸納為以下幾類:系統(tǒng)中一切事物皆為對象稳吮;對象是屬性及其操作的封裝體;對象可按其性質(zhì)劃分為類井濒,對象成為類的實例灶似;實例關(guān)系和繼承關(guān)系是對象之間的靜態(tài)關(guān)系;消息傳遞是對象之間動態(tài)聯(lián)系的唯一形式瑞你,也是計算的唯一形式;方法是消息的序列者甲。
面向?qū)ο缶幊獭狾bject Oriented Programming春感,簡稱OOP,是一種程序設(shè)計思想过牙。OOP把對象作為程序的基本單元甥厦,一個對象包含了數(shù)據(jù)和操作數(shù)據(jù)的函數(shù)。
面向過程的程序設(shè)計把計算機程序視為一系列的命令集合寇钉,即一組函數(shù)的順序執(zhí)行。為了簡化程序設(shè)計舶赔,面向過程把函數(shù)繼續(xù)切分為子函數(shù)扫倡,即把大塊函數(shù)通過切割成小塊函數(shù)來降低系統(tǒng)的復(fù)雜度。
而面向?qū)ο蟮某绦蛟O(shè)計把計算機程序視為一組對象的集合,而每個對象都可以接收其他對象發(fā)過來的消息撵溃,并處理這些消息疚鲤,計算機程序的執(zhí)行就是一系列消息在各個對象之間傳遞。
萬物皆為對象缘挑,對象是對現(xiàn)實事物的一種抽象集歇,通過程序來實現(xiàn)對事物的描述。面向?qū)ο缶幊痰娜筇卣鳎悍庋b语淘、繼承和多態(tài)诲宇。
I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages (so messaging came at the very beginning -- it took a while to see how to do messaging in a programming language efficiently enough to be useful).
...
OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP.
簡單解釋一下上面的這幾句話的大概意思:OOP應(yīng)該體現(xiàn)一種網(wǎng)狀結(jié)構(gòu),這個結(jié)構(gòu)上的每個節(jié)點“Object”只能通過“消息”和其他節(jié)點通訊惶翻。每個節(jié)點會有內(nèi)部隱藏的狀態(tài)姑蓝,狀態(tài)不可以被直接修改,而應(yīng)該通過消息傳遞的方式來間接的修改吕粗。
我的評注: 在面向過程的方法中纺荧,我們頭腦中首先出現(xiàn)的是類似流程圖的的東西,而采用OOP我們頭腦中首先出現(xiàn)的是類似對象關(guān)系圖的東西颅筋。
面向?qū)ο蟮谋举|(zhì)和面向?qū)ο蟮某绦蛘Z言:
面向?qū)ο蟮谋举|(zhì)是什么宙暇?答案是抽象。從面對的問題域抽象出解決問題所需的對象是面向?qū)ο蠓椒ǖ暮诵乃枷胍楸谩D芊袂‘?dāng)抽象出足夠的對象類型客给,特別是抽象出潛在的對象是決定軟件設(shè)計好壞的關(guān)鍵。如果從更寬泛的角度講肢簿,對我們所面對的復(fù)雜問題進行抽象靶剑,抓住本質(zhì),得出高度精煉的邏輯模型池充,對問題的求解具有重要的意義桩引。從這個角度來說,抽象并不僅僅局限于對象的抽象收夸,也包括流程和更高層的系統(tǒng)結(jié)構(gòu)坑匠。
而封裝、繼承卧惜、多態(tài)是面向?qū)ο蟮奶卣骼遄疲@是采用面向?qū)ο蟮姆椒ㄋ軌蜻_到的效果。注意區(qū)分特點和本質(zhì)咽瓷。
類的定義類具有屬性和行為设凹,它是將數(shù)據(jù)和與數(shù)據(jù)相關(guān)的操作封裝在一起的集合體,類定義中的成員即成員變量和成員函數(shù)茅姜。
面向?qū)ο笫腔谌f物皆對象這個哲學(xué)觀點. 把一個對象抽象成類,具體上就是把一個對象的靜態(tài)特征和動態(tài)特征抽象成屬性和方法,也就是把一類事物的算法和數(shù)據(jù)結(jié)構(gòu)封裝在一個類之中,程序就是多個對象和互相之間的通信組成的.
面向?qū)ο缶哂蟹庋b性,繼承性,多態(tài)性.封裝隱蔽了對象內(nèi)部不需要暴露的細(xì)節(jié),使得內(nèi)部細(xì)節(jié)的變動跟外界脫離,只依靠接口進行通信.封裝性降低了編程的復(fù)雜性. 通過繼承,使得新建一個類變得容易,一個類從派生類那里獲得其非私有的方法和公用屬性的繁瑣工作交給了編譯器. 而 繼承和實現(xiàn)接口和運行時的類型綁定機制 所產(chǎn)生的多態(tài),使得不同的類所產(chǎn)生的對象能夠?qū)ο嗤南⒆鞒霾煌姆磻?yīng),極大地提高了代碼的通用性.
總之,面向?qū)ο蟮奶匦蕴岣吡舜笮统绦虻闹赜眯院涂删S護性闪朱。
更多思考
分離變化
分離變化是貫穿在整個面向?qū)ο笤O(shè)計中的核心思想,具有非常重要的指導(dǎo)意義。
封裝內(nèi)部變化奋姿,擴展外部變化锄开。封裝內(nèi)部變化自不必說,通過繼承和多態(tài)擴展外部變化称诗,這就是面向?qū)ο蟮娜筇卣靼∑笺病C嫦驅(qū)ο笏枷牒艽蟪潭壬暇褪敲嫦蜃兓乃枷耄迅鞣N各樣的變化封裝為對象寓免,合理的分配這些對象癣诱,當(dāng)然這里的合理是有技巧的。
軟件設(shè)計應(yīng)該基于不變的基石再榄,分離變化的意義就在于此狡刘,幫助建立穩(wěn)定模型。
分析變化需要一定的經(jīng)驗困鸥,if-else語句暗示著變化嗅蔬,擴展的業(yè)務(wù)暗示著變化…
轉(zhuǎn)移變化需要一定的技巧,封裝疾就、依賴倒置澜术、增加間接層…
繼承與組合
面向?qū)ο笙到y(tǒng)功能復(fù)用的兩大技術(shù)包括類繼承和對象組合,二者選擇有一句“格言”:優(yōu)先使用對象組合,而不是類繼承(Favor object composition over class inheritance)猬腰。這句話我很贊同鸟废,但我還想強調(diào)的是,二者的使用場景不同姑荷,并不能相互替換:類繼承強調(diào)的是抽象復(fù)用(屬于同一類)而對象組合強調(diào)的是實現(xiàn)復(fù)用(借用一下其他對象的行為實現(xiàn))盒延。而在實際項目中類繼承常常被濫用于實現(xiàn)復(fù)用,我們應(yīng)該消除此類過度繼承鼠冕,這才是我們強調(diào)組合優(yōu)于繼承的最主要原因添寺。
絕大部分設(shè)計模式都基于繼承或者組合實現(xiàn),甚至有的設(shè)計模式比如適配器模式使用繼承和組合都是正確的懈费,分別為類適配器和對象適配器模式计露。而有的設(shè)計模式比如橋梁模式則是典型的同時使用了繼承和組合,分別負(fù)責(zé)抽象部分和實現(xiàn)部分的復(fù)用憎乙。
抽象與擴展
有抽象的地方就有擴展票罐,需要擴展的地方就需要抽象。
面向?qū)ο笏枷朐谶@點上的支持非常好泞边,假如現(xiàn)在有一個Object该押,抽象一下就變成了:
// interface or abstract class
public interface IAbstract {
}
public class ObjectA implements IAbstract {
}
public class ObjectB implements IAbstract {
}
這種結(jié)構(gòu)我這邊給取個名字”O(jiān)U結(jié)構(gòu)”,OU的意思是ObjectUnit繁堡,面向?qū)ο髥卧囊馑忌蛏疲僮髌饋矸浅>唧w乡数,后續(xù)文章中會大量用到這種方法椭蹄。各個設(shè)計模式充斥著大量的這類代碼闻牡,希望大家看到這樣的一組結(jié)構(gòu),可以看成是“一個”類绳矩,一個包含著博大精深的面向?qū)ο笏枷氲念愓秩螅@會大大簡化你對設(shè)計模式的理解。
從具體的接口與實現(xiàn)角度分析翼馆,我們應(yīng)該面向接口編程而不是面向?qū)崿F(xiàn)編程割以。接口本身是抽象,實現(xiàn)就是遵循這種抽象規(guī)則的具體擴展应媚。在面向?qū)ο笾醒狭ぃ琲nteface或者abstract class巧妙的把抽象和擴展連接起來了,這很關(guān)鍵中姜。
對象關(guān)系
在java以及其他的面向?qū)ο笤O(shè)計模式中消玄,類與類之間主要有6種關(guān)系,他們分別是:依賴丢胚、關(guān)聯(lián)翩瓜、聚合、組合携龟、繼承兔跌、實現(xiàn)。他們的耦合度依次增強峡蟋。
當(dāng)我們在討論耦合的時候坟桅,很多時候是在討論對象的關(guān)系。對象關(guān)系主要包括以下五種:實現(xiàn)蕊蝗、繼承仅乓、組合、聚合匿又、關(guān)聯(lián)方灾、依賴。這些關(guān)系的耦合度依次遞減(是不是體現(xiàn)了“組合優(yōu)于繼承”的思想)碌更。
應(yīng)該準(zhǔn)確的使用這些對象關(guān)系裕偿,而避免使用不合適的對象關(guān)系。
其實很多書中痛单,面向?qū)ο蟮年P(guān)系不是六種嘿棘,而是4種:依賴膘滨、關(guān)聯(lián)砸烦、繼承慧邮、實現(xiàn)。聚合和組合是比較特殊的關(guān)聯(lián)關(guān)系稍浆。分為六種的其實就是把關(guān)聯(lián)根據(jù)耦合度從小到大又分為了關(guān)聯(lián)、聚合挤牛、組合臭家。
聚合:通過setter方法從傳入其他類的參數(shù)進來 組合:在構(gòu)造方法中實例化其他類。
Public class People{
Soul soul;
Body body;
//組合關(guān)系中的成員變量一般會在構(gòu)造方法實例化
Public People(){
soul = news Soul();
body = new Body();
}
聚合是一種“整體-部分”的關(guān)系房午,組合也是一種“整體-部分”的關(guān)系矿辽,區(qū)別在于聚合中的整體和部分有著獨立的生命周期,而組合中的整體和部分的是生命周期應(yīng)該時一樣的郭厌,java中實現(xiàn)貌似無法有體現(xiàn)這一點袋倔,而C++中能夠體現(xiàn)。個人認(rèn)為這些區(qū)別更多的體現(xiàn)在語義上折柠,而非實現(xiàn)上宾娜。同時認(rèn)為上面聚合例子不能體現(xiàn)整體與部分的關(guān)系。
對象之間的關(guān)系:依賴(需要某種服務(wù))扇售,關(guān)聯(lián)(對象間有某種對應(yīng)關(guān)系)前塔,聚合,組合缘眶,繼承...
依賴:對象之間最弱的一種關(guān)聯(lián)方式嘱根,是臨時性的關(guān)聯(lián)。代碼中一般指由局部變量巷懈、函數(shù)參數(shù)该抒、返回值建立的對于其他對象的調(diào)用關(guān)系。 依賴一般情況下是以下幾種情況之一:
a顶燕、ClassA中某個方法的參數(shù)類型是ClassB凑保; 這種情況成為耦合;
b涌攻、ClassA中某個方法的參數(shù)類型是ClassB的一個屬性欧引; 這種情況成為緊耦合;
c恳谎、ClassA中某個方法的實現(xiàn)實例化ClassB芝此;
d、ClassA中某個方法的返回值的類型是ClassB因痛;如果出現(xiàn)了上述四種情況之一婚苹,兩個類很有可能就是“依賴”關(guān)系。
大多數(shù)情況下鸵膏,依賴關(guān)系體現(xiàn)在某個類的方法使用另一個類的對象作為參數(shù)膊升。
依賴關(guān)系(Dependency):是類與類之間的連接,依賴總是單向的谭企。依賴關(guān)系代表一個類依賴于另一個類的定義廓译。下面的例子中class A 依賴與class B评肆、C、D非区。
public class A{
public B getB(C c, D d){
E e = new E();
B b = new B(c, d, e);
}
}
關(guān)聯(lián):
對象之間一種引用關(guān)系瓜挽,比如客戶類與訂單類之間的關(guān)系。這種關(guān)系通常使用類的屬性表達院仿。 關(guān)聯(lián)關(guān)系所涉及的兩個類是處于同一層次上的秸抚,而在聚合關(guān)系中速和,兩個類處在不平等的層次上的歹垫,一個代表整體,一個代表部分颠放。(關(guān)聯(lián)與聚合僅僅從語法上是區(qū)分不開的排惨,需要察所涉及的類之間的邏輯關(guān)系。)關(guān)聯(lián)是一種結(jié)構(gòu)關(guān)系碰凶,說明一個事物的對象與另一個事物的對象相聯(lián)系暮芭。給定一個連接兩各類的關(guān)聯(lián),可以從一個類的對象導(dǎo)航到另一個類的對象欲低。關(guān)聯(lián)類通過一條虛線與關(guān)聯(lián)連接關(guān)聯(lián)可以有方向辕宏,即導(dǎo)航。一般不作說明的時候砾莱,導(dǎo)航是雙向的瑞筐,不需要在線上標(biāo)出箭頭。大部分情況下導(dǎo)航是單向的腊瑟,可以加一個箭頭表示聚假。關(guān)聯(lián)在代碼中一般表示為屬性(成員變量),例如下面例子中 class A與B關(guān)聯(lián)public class A{ private B b; } 如果B也關(guān)聯(lián)到A闰非,那么它們就是雙向的關(guān)聯(lián)膘格。public class B{ private A a; }
關(guān)聯(lián)和依賴的區(qū)別:
從類之間關(guān)系的強弱程度來分,關(guān)聯(lián)表示類之間的很強的關(guān)系财松;依賴表示類之間的較弱的關(guān)系瘪贱;
從類之間關(guān)系的時間角度來分,
關(guān)聯(lián)表示類之間的“持久”關(guān)系辆毡,這種關(guān)系一般表示一種重要的業(yè)務(wù)之間的關(guān)系菜秦,需要保存的,或者說需要“持久化”的胚迫,或者說需要保存到數(shù)據(jù)庫中的喷户。比如學(xué)生管理系統(tǒng)中的Student類和Class(班級)類,一個Student對象屬于哪個Class是一個重要的業(yè)務(wù)關(guān)系访锻,如果這種關(guān)系不保存褪尝,系統(tǒng)就無法管理闹获。另外,依賴表示類之間的是一種“臨時河哑、短暫”關(guān)系避诽,這種關(guān)系是不需要保存的,比如Student類和StuEditScreen(學(xué)生登錄界面)類之間就是一種依賴關(guān)系璃谨,StuEditScreen類依賴Student類沙庐,依賴Student對象的信息來顯示編輯學(xué)生信息。
聚合(關(guān)聯(lián)關(guān)系的一種):表示has-a的關(guān)系佳吞,是一種不穩(wěn)定的包含關(guān)系拱雏。聚合類不必對被聚合類負(fù)責(zé)。使用集合屬性表達聚合關(guān)系底扳,當(dāng)對象A被加入到對象B中铸抑,成為對象B的組成部分時,對象B和對象A之間為聚集關(guān)系衷模。聚合是關(guān)聯(lián)關(guān)系的一種鹊汛,是較強的關(guān)聯(lián)關(guān)系,強調(diào)的是整體與部分之間的關(guān)系阱冶。與關(guān)聯(lián)關(guān)系一樣刁憋,聚合關(guān)系也是通過實例變量來實現(xiàn)這樣關(guān)系的。關(guān)聯(lián)關(guān)系和聚合關(guān)系來語法上是沒辦法區(qū)分的木蹬,從語義上才能更好的區(qū)分兩者的區(qū)別至耻。聚合關(guān)系(Aggregation):是關(guān)聯(lián)關(guān)系的一種,是強的關(guān)聯(lián)關(guān)系届囚。聚合是整體與個體之間的關(guān)系有梆。如汽車類與引摯類,輪胎類之間的關(guān)系就是整體與個體的關(guān)系意系。
與關(guān)聯(lián)關(guān)系一樣泥耀,聚合關(guān)系也是通過實例變量來實現(xiàn)的』滋恚空心菱形關(guān)聯(lián)和聚集的區(qū)別:
(1)關(guān)聯(lián)關(guān)系所涉及的兩個對象是處在同一個層次上的痰催。比如人和自行車就是一種關(guān)聯(lián)關(guān)系,而不是聚合關(guān)系迎瞧,因為人不是由自行車組成的夸溶。
聚合關(guān)系涉及的兩個對象處于不平等的層次上,一個代表整體凶硅,一個代表部分缝裁。比如電腦和它的顯示器、鍵盤足绅、主板以及內(nèi)存就是聚集關(guān)系捷绑,因為主板是電腦的組成部分韩脑。
(2)對于具有聚集關(guān)系(尤其是強聚集關(guān)系)的兩個對象,整體對象會制約它的組成對象的生命周期粹污。部分類的對象不能單獨存在段多,它的生命周期依賴于整體類的對象的生命周期,當(dāng)整體消失壮吩,部分也就隨之消失进苍。比如張三的電腦被偷了,那么電腦的所有組件也不存在了鸭叙,除非張三事先把一些電腦的組件(比如硬盤和內(nèi)存)拆了下來觉啊。
四、聚合關(guān)系(Aggregation)
聚合關(guān)系(Aggregation):表示的是整體和部分的關(guān)系递雀,整體與部分 可以分開.
? 聚合關(guān)系(Aggregation) 表示一個整體與部分的關(guān)系柄延。通常在定義一個整體類后,再去分析這個整體類的組成結(jié)構(gòu)缀程,從而找出一些成員類,該整體類和成員類之間就形成了聚合 關(guān)系市俊。
? 在聚合關(guān)系中杨凑,成員類是整體類的一部分,即成員對象是整體對象的一部分摆昧,但是成員對象可以脫離整體對象獨立存在撩满。在UML中,聚合關(guān)系用帶空心菱形的直線表示绅你。
如:電話機包括一個話筒 電腦包括鍵盤伺帘、顯示器,一臺電腦可以和多個鍵盤忌锯、多個顯示器搭配伪嫁,確定鍵盤和顯示器是可以和主機分開的,主機可以選擇其他的鍵盤偶垮、顯示器組成電腦张咳;
五、組合關(guān)系(Composition)
組合關(guān)系(Composition):也是整體與部分的關(guān)系似舵,但是整體與部分不可以分開.
? 組合關(guān)系(Composition)也表示類之間整體和部分的關(guān)系脚猾,但是組合關(guān)系中部分和整體具有統(tǒng)一的生存期。一旦整體對象不存在砚哗,部分對象也將不存在龙助,部分對象與整體對象之 間具有同生共死的關(guān)系。
? 在組合關(guān)系中蛛芥,成員類是整體類的一部分提鸟,而且整體類可以控制成員類的生命周期脆淹,即成員類的存在依賴于整體類。在UML中沽一,組合關(guān)系用帶實心菱形的直線表示盖溺。
如:公司和部門,部門是部分铣缠,公司是整體烘嘱,公司A的財務(wù)部不可能和公司B的財務(wù)部對換,就是說蝗蛙,公司A不能和自己的財務(wù)部分開蝇庭; 人與人的心臟.
組合:表示contains-a的關(guān)系,是一種強烈的包含關(guān)系捡硅。組合類負(fù)責(zé)被組合類的生命周期哮内。也使用集合屬性表達聚合關(guān)系 ,是關(guān)聯(lián)關(guān)系的一種壮韭,是比聚合關(guān)系強的關(guān)系北发。它要求普通的聚合關(guān)系中代表的對象負(fù)責(zé)代表部分的對象的生命周期,合成關(guān)系是不能共享的喷屋。
代表整體的對象需要負(fù)責(zé)保持對象的存活琳拨,在一些情況下負(fù)責(zé)將代表部分的對象湮滅掉。代表整體的對象可以將代表部分的對象傳遞給另一個對象屯曹,由后者負(fù)責(zé)此對象的生命周期狱庇。換言之,代表部分的對象在每一個時刻只能與一個對象發(fā)生合成關(guān)系恶耽,由后者排它的負(fù)責(zé)其生命周期密任,(聚合和組合的明顯的區(qū)別是:如果類B含有A類對象的指針,那算聚合(生存周期不一樣)偷俭,如果類B含有A類對象的變量為屬性浪讳,那么就必為組合(生存周期必須相同)),實心菱形
繼承:表示is-a的關(guān)系社搅,是對象之間耦合度最大的一種關(guān)系驻债,子類繼承父類的所有細(xì)節(jié)。直接使用語言中的繼承表達形葬。 類圖中繼承的表示方法是從子類拉出一條閉合的合呐、單鍵頭(或三角形)的實線指向基類
從使用的頻率來看,關(guān)聯(lián)(包括聚合和組合)關(guān)系是使用最為廣泛的笙以,其次是依賴和繼承
設(shè)計類之間的關(guān)系是遵循的原則:
首先判斷類之間是否是一種“關(guān)聯(lián)”關(guān)系淌实,
若不是再判斷是否是“依賴關(guān)系”,
一般情況下若不是關(guān)聯(lián),就是依賴關(guān)系
聚合關(guān)系建議使用部門與員工來舉例拆祈,組合關(guān)系建議使用公司與部門來舉例恨闪。部門由員工組成,部門解散員工照樣生活放坏。公司由部門組成咙咽,公司破產(chǎn)倒閉,部門則不復(fù)存在淤年。
我覺得這個里面的聚合钧敞,是不是說錯了?聚合難道不是部門和員工之間的關(guān)系么麸粮?應(yīng)該強調(diào)的是同類群體和個體的關(guān)系溉苛,而不是汽車和汽車零件的關(guān)系吧,汽車和汽車零件應(yīng)該是組合關(guān)系吧弄诲?兩者不是一個類型愚战。汽車離不開汽車零件,但是部門和員工之間的關(guān)系可不一樣啊齐遵,部門去掉幾個員工也沒啥關(guān)系啊寂玲。
比較同意你的看法。 聚合是has-a關(guān)系洛搀,集體和個體關(guān)系敢茁。組合是contain-a關(guān)系,整體與部分的關(guān)系留美,關(guān)聯(lián)關(guān)系比聚合強。