語言中的用語並非共通的,在不一樣語言中,同一個用語的含義可能會有很大差異。java
C++的設計者本賈尼·斯特勞斯特盧普對類和繼承給予了正面確定,然而,「面向對象」這個詞的發明者艾倫·凱(Alan kay,他同時也是 Smalltalk 語言的設計者)卻持有不一樣的意見,他對類和繼承持否認立場。編程
咱們是怎樣理解世界的呢?咱們將生活中碰見的事物總結爲特定的「物」的概念,它們就是諸如桌子、椅子、銀行貸款、公式、人、多項式、三角形、晶體管之類的東西。咱們的思考、語言以及行動就是創建在指示、說明和操做這些所謂的「物」的基礎之上。咱們在用計算機解決問題的時候,有必要將現實世界中的「物」的模型在計算機中創建起來。閉包
大部分語言的程序設計中,類並非不可或缺的,但 Java 語言是例外。Java 語言「把類定義爲部件,將其組裝起來便是程序設計」。所以,在用 Java 語言編寫程序時類是必要的。其餘諸如 C++、Python、Ruby 這樣的語言,在編寫程序時既可使用類也能夠不使用類。函數
那是使用類好呢,仍是不使用也能夠呢?工具
這取決於要編寫的程序。若是僅是小規模的程序,不必使用類的狀況居多。也有人認爲,在多人分工協做編寫的大型程序中,使用類來劃分責任範圍比較好。圖形用戶界面的編寫中面向對象的特性彷佛很是管用。好比設計一個按鈕,須要有放置按鈕的座標和按鈕的寬、高等值,也須要有表達按鈕按下時的動做的函數。將實現按鈕所必需的這些要素統一到類中,編寫程序就會變得簡單起來。spa
除類以外,還有幾種其餘的方式。設計
模塊(module)。模塊本來是一種將相關聯的函數集中到一塊兒的功能。在 Perl 語言中相似的功能被稱爲包(package)。Perl 語言在引入面向對象時,採用了把用來歸集函數的包和用來歸集變量的散列(hash)綁定在一塊兒的方法。code
把函數和變量放入散列中。這是 JavaScript 等語言採用的方法。對象
閉包(closure)。使用函數執行時的命名空間來歸集變量的方法。這種方法主要在函數式語言中使用。繼承
>爲何把這稱爲閉包?一個包含了自由變量的開放表達式,它和該自由變量的約束環境組合在一塊兒後,實現了一種封閉的狀態。 >類的存在只不過是由於人們以爲有了它編寫程序會更方便些,而約定的一種事項。它並非什麼物理法則或宇宙真理,僅僅是人們的一種約定而已。因此,爲了理解爲何會有這樣一種約定,咱們須要考慮語言設計者的意圖。
類
C++ 語言和 Java 語言的類具備如下幾個做用:
整合體的生成器
可行操做的功能說明
代碼再利用的單位
繼承的不一樣實現策略。繼承的實現策略大致能夠分爲三種。
通常化與專門化
第一種策略是在父類中實現那些通常化的功能,在子類中實現那些專門的個性化的功能。其設計方針就是子類是父類的專門化。
共享部分的提取
第二種策略是從多個類中提取出共享部分做爲父類。它和通常化與專門化的考慮很不同。對於子類是否爲父類的一種,它的答案是否認的。這種提取出共享部分的設計方針是習慣了函數的一種考慮問題的方法。
差別實現
第三種策略認爲繼承以後僅實現有變動的那些屬性會帶來效率的提升。它把繼承做爲實現方式再利用的途徑,旨在使編程實現更加輕鬆。的確有不少這樣的狀況。但這些狀況下一般子類都不是父類的一種。
方法的多樣意味着控制的複雜,自由度過高每每會須要咱們去限制。
好比說 goto 。
尤爲是第三種使用方法——繼承已有的類並實現差別部分,這種編程風格會形成多層級的繼承樹,很容易致使代碼理解困難。
這裏就提到了編程中很重要的一點,就是可讀性。
這個原則能夠表述爲:假設對於 T 類型的對象 x,屬性 q(x) 恆爲真。若是 S 爲 T 的派生類,那麼 S 類型的對象 y 的屬性 q(y) 也必須恆爲真。
這句話換種表達就是,對於類 T 的對象必定成立的條件,對於類 T 的子類 S 的對象也必須成立。
爲了保證類的繼承關係和類型的父子關係這兩種關係之間的一致性,有必要遵照這一原則。這一原則也能夠表達爲繼承必須是 is-a 關係。把子類 S 的全部對象都看做是父類 T 的對象而不會有任何問題,必需要作到這一點。
這一約束條件是很是嚴格的。當要繼承某種類時,須要考慮該類是否能夠被繼承。假設繼承的時候考慮的屬性可使里氏置換原則成立。可是在隨後的程序編寫過程當中,須要的屬性可能會愈來愈多。隨着屬性的增長,置換原則就有可能被打破。是在設計階段就把全部屬性列出來,只有當置換原則絕對不被打破時纔去繼承呢?仍是在開發階段若是發現新的屬性就放棄類的繼承呢?無論哪一種方式都很費勁。
現實世界中一種事物有可能屬於多種分類。爲了實現對這種現實狀況的模擬,做爲工具的程序設計語言是否是應該支持對多個類的繼承呢?這就是多重繼承的初衷。
多重繼承對於實現方式再利用很是便利。
多重繼承看起來真的很方便。可是,使用多重繼承時該如何解決名字解釋的問題呢?當問到類中 x 值是什麼時,該如何回答呢?
首先,若是這個類自己知道答案,就直接給出回答。其次,若是這個類自己不知道答案,就去問它的父類再給出回答。
可是以下狀況就麻煩了。
解決方法 1:禁止多重繼承。Java 語言中就禁止了類的多重繼承。只要不承認類的多重繼承這種方式,就不會有上述問題。這樣能夠把問題解決得很乾脆,只是會以失去多重繼承的良好便利性爲代價。
委託。取而代之發展起來的概念是委託。這種方法定義了具備待使用實現方式的類的對象,而後根據須要使用該對象來處理。使用繼承後,從類型到命名空間都會被一塊兒繼承,從而致使問題的發生,這種方法只是停留在使用對象的層面上。
public class TestDelegate { public static void main(String[] args){ new UseInheritance().useHello(); // -> hello! new UseDelegate().useHello(); // -> hello! } } class Hello{ ❶ public void hello(){ System.out.println("hello!"); } } class UseInheritance extends Hello { ❷ public void useHello(){ hello(); ❸ } } class UseDelegate { ❹ Hello h = new Hello(); ❺ public void useHello(){ h.hello(); ❻ } }
顯示「Hello !」的方法 hello 爲類 Hello 所持有(❶)。類 UseInheritance 經過繼承類 Hello 自身也持有了方法 hello(❷)並加以使用(❸)。與之不一樣,類 UseDelegate 並無繼承類 Hello(❹),而是經過句❺持有了類 Hello 的對象。當有須要使用時經過句❻將須要的處理委託給該對象操做。
與從多個類中繼承實現強耦合的方式相比,使用委託進行耦合的方式顯然要更好一些。對於委託的使用,也不須要在源代碼中寫死,而是能夠經過配置文件在合適的時候注入運行時中去。這個想法催生了依賴注入(Dependency Injection)的概念。
* 接口。剛剛提到 Java 語言中禁止了多重繼承,但它也具有實現多重繼承的功能。這就須要藉助接口(interface)。Java 語言中類的繼承用 extends,接口的繼承用 implements 來區別表示。另外接口的繼承也稱爲實現。接口是沒有實現方式的類。它的功能僅僅在於說明繼承了該接口的類必須持有某某名字的方法。多重繼承中發生的問題是多種實現方式相沖突時選取哪一個的問題。而在接口的多重繼承中,儘管有多個持有某某方法的信息存在,但這僅僅代表持有某某方法,不會形成任何困擾。
public class TestMultiImpl implements Foo, Bar { public void hello(){ System.out.println("hello!"); } } interface Foo { public void hello(); } interface Bar { public void hello(); }
類 TestMultiImpl 繼承了 Foo 和 Bar 兩個接口。若是這個類中不實現 public void hello (),編譯時將出現「沒有實現應該實現的方法」這樣的錯誤。也就是說,繼承了接口 Foo 後,這個類就做爲一種類型表現出必須持有 public void hello () 的特色,可讓編譯器對它進行類型檢查。
Java 語言爲了僅實現功能上的多重繼承引入了接口。PHP 語言和 Java 語言同樣不承認多重繼承,並從 2004 年發佈的 PHP5 開始引入了接口的概念。
解決方法 2:按順序進行搜索
曾經也有些語言試圖經過明肯定義搜索順序來解決衝突問題。
出現過深度優先搜索法,重載和菱形繼承時會很麻煩。以及廣度優先。
以及後來的C3線性化。
* 父類不比子類先被檢查 * 若是是從多個類中繼承下來則優先檢查先書寫的類
解決方法 3:混入式處理
本來,問題是指從一個類到它的祖先類有多種追溯方法。定義僅包含所需功能的類並把它與須要添加這些功能的更大的類糅合在一塊兒。把這種設計方針、混入式處理方式和用來混入的小的類統稱爲混入處理(Mix-in)。
一般這些小型的類最小限度地定義了一些方法,起到了做爲代碼再利用單位的做用。爲了代表這一點,Python 語言會在該類的名字中加上 MixIn 來標識。
Ruby 語言採用的規則是:類是單一繼承的而模塊則能夠任意數量地作混入式處理。模塊沒法建立實例,但能夠像類同樣擁有成員變量和方法。也就是說,模塊實質上是從類中去除了實例建立功能。即便類的多重繼承被禁止了,經過使用模塊的 Mix-In 方式照樣能夠實現對實現方式的再利用。
解決方法 4:Trait
類具備兩種截然相反的做用。一種是用於建立實例的做用,它要求類是全面的、包含全部必需的內容的、大的類。另外一種是做爲再利用單元的做用,它要求類是按功能分的、沒有多餘內容的、小的類。當類用於建立實例時,做爲再利用單元來講就顯得太大了。既然如此,若是把再利用單元的做用特別化,設定一些更小的結構(特性=方法的組合)是否是能夠呢?這就是 Trait 的初衷。
已有的 Trait,經過改寫某些方法定義新的 Trait 實現繼承。還能夠經過組合多個 Trait 實現新的 Trait。這就是 Trait 的概要說明。它一方面把問題穩當地分而治之,一方面又由於功能繁多使人困惑。讀者們想必都還記得 goto 語句就是由於其功能過於強大而退出歷史的舞臺的吧。因此說力量過於強大未必是件好事。
Trait 技術是一個很好的開端。它認爲類同時具備的做爲再利用單元和實例生成器的兩種做用是相反的。或許類這一律念做爲面向對象的根基具備不可動搖的地位。然而這一律念自己也是從一個雛形慢慢發展得愈來愈複雜,進一步整理以後再逐漸讓渡出某些功能的。如今備受關注的 Trait 和一些其餘概念也必將不斷地演變下去。通過長時間琢磨沉澱,一部分將臻於成熟被推廣使用,最後將變成如今的靜態做用域和 while 語句那樣被認爲是理所固然的存在。