這篇文章的標題叫“面向物件”,本來是想起名叫“面向物件程式設計”。但是寫著寫著就發現程式設計太大了,不是短短几個段落就能夠概括的,借用《程式碼大全2》中的一個標題來說"設計是個無章法的過程(即使它能得出清爽的成果)”,設計本來就不是一個一蹴而就的事情,整個過程應該是在不斷的設計、評估、討論、試錯、測試程式碼以及修改測試程式碼中不斷演化而來的。自己本身能力就不夠,如果強行要用幾百個字說明白的話,就有點為賦新詞強說愁的意思了。但是我會在接下來的系列文章裡會用一些例項或者曾經做過的設計來試著總結一下對程式設計的理解,並試著抽取一下關於程式設計的方法或原則。 閒話少敘,這篇文章從什麼是面向物件、面向物件的三大特徵、如何進行物件的抽取設計以及面向物件的原則四個方面來說一下我理解的面向物件。 什麼是面向物件 前段時間部門在開職責劃分工作會的時候總工說。你們都是開發轉的管理,都理解什麼是面向物件程式設計。什麼是面向物件程式設計哪?無非就是封裝繼承多型,咱們這次做職責劃分就是一次封裝。部門層面把每個職位做一個概括說明這就是封裝,你們各個生產線再根據自己的實際需要選擇自己需要的職位職責詳細說明並細化到每個人這就是繼承和多型。如果那個部門專案緊或者人員進行跨生產線調動我的要求就是就位以後很快就能有符合公司要求的產出,這個就是可替換原則。 確實是這樣的,面向物件與其說一種工具不如說是一種思考方式,是用程式設計的眼光看待現實的世界。把現實世界的事物按照一定的方法和規則在不同層次上進行抽象,在抽象的時候我們只需要關注事物的核心屬性和行為而忽略其他的細節。開發人員將這些屬性和方法再體現在一個個的模組、類或者子程式裡面,透過他們之間有序的呼叫,最終完成現實世界在計算機裡的構造。這裡的不同層次是指在不同的視角下,比如上面的職責劃分從單個員工的層面來說可能需要考慮個人的現有能力,發展意願、潛力等,如果從生產線的層面來說就需要考慮生產線產品對技術的要求,人員的配置等,從部門的層面來說可能就需要考慮成本、產值等其他的事情了。如果對應到程式設計實現上可能就是需要在子程式介面、類介面以及模組介面這些層面進行考慮了。 面向物件的三大特徵 面向物件的三大特徵是封裝性、繼承性、多型性。但是我認為繼承和多型應該是因果的關係,因為面向物件可以繼承所以有了多型。這裡就把繼承和多型放到一塊來說一下。 先說封裝,封裝就是隱藏細節,讓使用者只看到被允許看到的,不能看到任何實現的細節。那麼如何去把握那些可以被允許那些是細節那,或者換個說法就是如何進行有效的封裝那?有一個最簡單的辦法就是先把可訪問性降一級看看能不能實現功能。比如把public 變為protected或者private試試,如果可以那就降。另外就是不要暴露成員資料,對於需要暴露的也優先考慮只提供讀介面。如果覺的這個也不好把握的話,那就採用介面+實現類的程式設計方式。但是我覺的這樣會增加很多多餘的檔案而且也有點偏離介面的用途(下結會詳細說一下介面和抽象類),所以我建議對於模組來說只對外提供一個介面或一組小的介面,內部實現類的話設定為庫內(C#)或包內(Java)可見就行了,不用專門的建立一個介面(這種情況只適用於一個人開發的模組,多人合作的話建議對模組再次細分,直到可以明細化到每個人去實現)。 另外還有一個需要注意的地方就是不要去假設封裝物件的使用者,只對上下文(模組)或引數(類:如果類所屬模組有上下文的話建議類的引數也要是上下文)負責,這樣也可以儘量減少物件與物件之間的耦合度。 再說一下繼承,封裝可以是模組層面的也可以說是類層面的。但繼承的話一般就是指類與類之間的一種特殊關係了。他的作用就是一個類對另一個類的細化,他們擁有共同的介面、一部分共同的資料成員和內部實現。繼承能夠把這些放到一個基類裡面,避免了程式碼和資料的重複。本人並不太贊同普通類之間的繼承。一般優先考慮介面的繼承,其次考慮抽象類的繼承,實在沒有別的選擇情況下再考慮普通類之間的繼承。在使用繼承的時候也要注意下一些事情,第一個就是繼承的類要全部實現基類的介面。第二個就是對於能抽取的方法都抽取到基類裡面。最後一個也是最重要的一個就是不要用多重繼承。另外如果一個基類只有一個實現類的話就不要繼承了,在一個類裡面實現就行了。那麼在什麼情況下會建議使用繼承,就是在類裡面有太多的swith case或者 else if的時候就可以考慮繼承了。 基類被子類繼承後,每個子類都是基類的一個形態,這種不同形態就是多型。上面說到“如果一個基類只有一個實現類的話就不要繼承了”。所有也可以說只要有繼承就有多型。 如何進行物件的抽取設計 上面講到了面向物件以及面向物件的三大特徵。那麼在我們實際運用中如何按面向物件的方法對事物進行抽象和構建。我認為應該考慮以下的一些步驟。在第二段的時候說程式設計是一個反覆迭代不斷試錯的過程。所以這些步驟只是一個建議,不必按特定的順序來,他們也可能被反覆的使用。相信每個成熟的程式設計師都會有自己一套成熟的設計思路的。步驟共分為兩個部分識別現實的物件以及用程式構造物件。 1、從不同層次對現實世界進行抽象,提取出他們的核心屬性以及核心行為。 2、確定物件之間的功能邊界以及依賴關係。 3、定義物件的核心屬性(資料)和操作(方法)。 4、定義物件的公有和私有部分。 5、定義介面及其所需要的引數,並抽取成上下文。 6、定義主要子程式及其實現細節。 面向物件的部分原則 古代官場將做事要三思而行,三思就是思危,思變,思退,凡事從這三個方面綜合考慮最終來決定如何應對將要處理的事情。同樣我們在進行面向物件程式設計的的時候也有一些可以參考的標準。實際開發的時候需要根據這些標準權衡利弊以後再決定如何進行設計開發。同樣的道理當我們去判斷一個程式一個模組或一個類好壞的時候也是要從這些標準來進行考慮判斷。面向物件有六大設計原則在這裡我只撿比較重要的四個來說一下。另外兩個在核心思想上和這四個有些重複,所以就略過不提了。 1、單一職責原則:這裡不能把它理解為只做一件事情,而是要結合上面物件的功能邊界來說,應該是隻做物件功能邊界內的事情。功能邊界的選擇決定了封裝的質量,邊界太大了容易造成物件內聚性的減弱,而範圍太小又容易造成系統物件過多進一步造成系統耦合性過高。 2、開發封閉原則:這裡的開放是指對物件的操作方法修改的開放,當有新的需求變化就可以對現有的程式碼進行修改和擴充套件。封閉是有兩個方面,一個是範圍的封閉,一但物件的範圍確定了就不能再進行任何修改了,另外一個封閉是指對介面的封閉,可以慎重的進行介面的增加,但是對於已釋出的介面修改是要盡力避免的或者是絕對禁止的。 3、里氏替換原則:它的核心思想就是 對於基類中定義的所有子程式,用它的任何一個子類時他們的介面含義都應該是相同的。比如一個FileDownload基類以及FtpDownload、ShareDownload、HttpDownload三個子類。使用者應該能夠任意呼叫三個子類裡的download介面來進行文件下載。 4、介面隔離原則:它的核心思想是不要定義一個大的介面,而要使用一組小而專業的介面。看定義貌似是和單一職責原則有點衝突,其實他們是從不同的角度來對抽象的審視,單一性原則是從封裝的角度保證物件的純潔性,而介面隔離原則是從對外開放的角度來考慮,讓使用者只使用可以使用的那一部分功能。比如:對一件代售的商品而言,作為買方只需要檢視他的價格,但是對於店主來說除了可以看價格以外還可以修改它的價格,檢視和修改都是對這一個物件的操作,但對於不用的使用者來說就有不同的操作許可權了。
這篇文章的標題叫“面向物件”,本來是想起名叫“面向物件程式設計”。但是寫著寫著就發現程式設計太大了,不是短短几個段落就能夠概括的,借用《程式碼大全2》中的一個標題來說"設計是個無章法的過程(即使它能得出清爽的成果)”,設計本來就不是一個一蹴而就的事情,整個過程應該是在不斷的設計、評估、討論、試錯、測試程式碼以及修改測試程式碼中不斷演化而來的。自己本身能力就不夠,如果強行要用幾百個字說明白的話,就有點為賦新詞強說愁的意思了。但是我會在接下來的系列文章裡會用一些例項或者曾經做過的設計來試著總結一下對程式設計的理解,並試著抽取一下關於程式設計的方法或原則。 閒話少敘,這篇文章從什麼是面向物件、面向物件的三大特徵、如何進行物件的抽取設計以及面向物件的原則四個方面來說一下我理解的面向物件。 什麼是面向物件 前段時間部門在開職責劃分工作會的時候總工說。你們都是開發轉的管理,都理解什麼是面向物件程式設計。什麼是面向物件程式設計哪?無非就是封裝繼承多型,咱們這次做職責劃分就是一次封裝。部門層面把每個職位做一個概括說明這就是封裝,你們各個生產線再根據自己的實際需要選擇自己需要的職位職責詳細說明並細化到每個人這就是繼承和多型。如果那個部門專案緊或者人員進行跨生產線調動我的要求就是就位以後很快就能有符合公司要求的產出,這個就是可替換原則。 確實是這樣的,面向物件與其說一種工具不如說是一種思考方式,是用程式設計的眼光看待現實的世界。把現實世界的事物按照一定的方法和規則在不同層次上進行抽象,在抽象的時候我們只需要關注事物的核心屬性和行為而忽略其他的細節。開發人員將這些屬性和方法再體現在一個個的模組、類或者子程式裡面,透過他們之間有序的呼叫,最終完成現實世界在計算機裡的構造。這裡的不同層次是指在不同的視角下,比如上面的職責劃分從單個員工的層面來說可能需要考慮個人的現有能力,發展意願、潛力等,如果從生產線的層面來說就需要考慮生產線產品對技術的要求,人員的配置等,從部門的層面來說可能就需要考慮成本、產值等其他的事情了。如果對應到程式設計實現上可能就是需要在子程式介面、類介面以及模組介面這些層面進行考慮了。 面向物件的三大特徵 面向物件的三大特徵是封裝性、繼承性、多型性。但是我認為繼承和多型應該是因果的關係,因為面向物件可以繼承所以有了多型。這裡就把繼承和多型放到一塊來說一下。 先說封裝,封裝就是隱藏細節,讓使用者只看到被允許看到的,不能看到任何實現的細節。那麼如何去把握那些可以被允許那些是細節那,或者換個說法就是如何進行有效的封裝那?有一個最簡單的辦法就是先把可訪問性降一級看看能不能實現功能。比如把public 變為protected或者private試試,如果可以那就降。另外就是不要暴露成員資料,對於需要暴露的也優先考慮只提供讀介面。如果覺的這個也不好把握的話,那就採用介面+實現類的程式設計方式。但是我覺的這樣會增加很多多餘的檔案而且也有點偏離介面的用途(下結會詳細說一下介面和抽象類),所以我建議對於模組來說只對外提供一個介面或一組小的介面,內部實現類的話設定為庫內(C#)或包內(Java)可見就行了,不用專門的建立一個介面(這種情況只適用於一個人開發的模組,多人合作的話建議對模組再次細分,直到可以明細化到每個人去實現)。 另外還有一個需要注意的地方就是不要去假設封裝物件的使用者,只對上下文(模組)或引數(類:如果類所屬模組有上下文的話建議類的引數也要是上下文)負責,這樣也可以儘量減少物件與物件之間的耦合度。 再說一下繼承,封裝可以是模組層面的也可以說是類層面的。但繼承的話一般就是指類與類之間的一種特殊關係了。他的作用就是一個類對另一個類的細化,他們擁有共同的介面、一部分共同的資料成員和內部實現。繼承能夠把這些放到一個基類裡面,避免了程式碼和資料的重複。本人並不太贊同普通類之間的繼承。一般優先考慮介面的繼承,其次考慮抽象類的繼承,實在沒有別的選擇情況下再考慮普通類之間的繼承。在使用繼承的時候也要注意下一些事情,第一個就是繼承的類要全部實現基類的介面。第二個就是對於能抽取的方法都抽取到基類裡面。最後一個也是最重要的一個就是不要用多重繼承。另外如果一個基類只有一個實現類的話就不要繼承了,在一個類裡面實現就行了。那麼在什麼情況下會建議使用繼承,就是在類裡面有太多的swith case或者 else if的時候就可以考慮繼承了。 基類被子類繼承後,每個子類都是基類的一個形態,這種不同形態就是多型。上面說到“如果一個基類只有一個實現類的話就不要繼承了”。所有也可以說只要有繼承就有多型。 如何進行物件的抽取設計 上面講到了面向物件以及面向物件的三大特徵。那麼在我們實際運用中如何按面向物件的方法對事物進行抽象和構建。我認為應該考慮以下的一些步驟。在第二段的時候說程式設計是一個反覆迭代不斷試錯的過程。所以這些步驟只是一個建議,不必按特定的順序來,他們也可能被反覆的使用。相信每個成熟的程式設計師都會有自己一套成熟的設計思路的。步驟共分為兩個部分識別現實的物件以及用程式構造物件。 1、從不同層次對現實世界進行抽象,提取出他們的核心屬性以及核心行為。 2、確定物件之間的功能邊界以及依賴關係。 3、定義物件的核心屬性(資料)和操作(方法)。 4、定義物件的公有和私有部分。 5、定義介面及其所需要的引數,並抽取成上下文。 6、定義主要子程式及其實現細節。 面向物件的部分原則 古代官場將做事要三思而行,三思就是思危,思變,思退,凡事從這三個方面綜合考慮最終來決定如何應對將要處理的事情。同樣我們在進行面向物件程式設計的的時候也有一些可以參考的標準。實際開發的時候需要根據這些標準權衡利弊以後再決定如何進行設計開發。同樣的道理當我們去判斷一個程式一個模組或一個類好壞的時候也是要從這些標準來進行考慮判斷。面向物件有六大設計原則在這裡我只撿比較重要的四個來說一下。另外兩個在核心思想上和這四個有些重複,所以就略過不提了。 1、單一職責原則:這裡不能把它理解為只做一件事情,而是要結合上面物件的功能邊界來說,應該是隻做物件功能邊界內的事情。功能邊界的選擇決定了封裝的質量,邊界太大了容易造成物件內聚性的減弱,而範圍太小又容易造成系統物件過多進一步造成系統耦合性過高。 2、開發封閉原則:這裡的開放是指對物件的操作方法修改的開放,當有新的需求變化就可以對現有的程式碼進行修改和擴充套件。封閉是有兩個方面,一個是範圍的封閉,一但物件的範圍確定了就不能再進行任何修改了,另外一個封閉是指對介面的封閉,可以慎重的進行介面的增加,但是對於已釋出的介面修改是要盡力避免的或者是絕對禁止的。 3、里氏替換原則:它的核心思想就是 對於基類中定義的所有子程式,用它的任何一個子類時他們的介面含義都應該是相同的。比如一個FileDownload基類以及FtpDownload、ShareDownload、HttpDownload三個子類。使用者應該能夠任意呼叫三個子類裡的download介面來進行文件下載。 4、介面隔離原則:它的核心思想是不要定義一個大的介面,而要使用一組小而專業的介面。看定義貌似是和單一職責原則有點衝突,其實他們是從不同的角度來對抽象的審視,單一性原則是從封裝的角度保證物件的純潔性,而介面隔離原則是從對外開放的角度來考慮,讓使用者只使用可以使用的那一部分功能。比如:對一件代售的商品而言,作為買方只需要檢視他的價格,但是對於店主來說除了可以看價格以外還可以修改它的價格,檢視和修改都是對這一個物件的操作,但對於不用的使用者來說就有不同的操作許可權了。