1.繼承(接口繼承和實現繼承)
繼承是 OO 語言中的一個最為人津津樂道的概念。許多 OO 語言都支持兩種繼承方式:接口繼承和實現繼承。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。如前所述,由于函數沒有簽名,在 ECMAScript 中無法實現接口繼承。 ECMAScript 只支持實現繼承,而且其實現繼承主要是依靠原型鏈來實現的。
2.實現繼承的幾種方式:
方式一:(原型鏈)
ECMAScript 中描述了原型鏈的概念,并將原型鏈作為實現繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。簡單回顧一下構造函數、原型和實例的關系:每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那么,假如我們讓原型對象等于另一個類型的實例,結果會怎么樣呢?顯然,此時的原型對象將包含一個指向另一個原型的指針,相應地,另一個原型中也包含著一個指向另一個構造函數的指針。假如另一個原型又是另一個類型的實例,那么上述關系依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。
實現原型鏈有一種基本模式,其代碼大致如下。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
解釋:
以上代碼定義了兩個類型: SuperType 和 SubType。每個類型分別有一個屬性和一個方法。它們的主要區別是 SubType 繼承了 SuperType,而繼承是通過創建 SuperType 的實例,并將該實例賦給
SubType.prototype 實現的。實現的本質是重寫原型對象,代之以一個新類型的實例。換句話說,原來存在于 SuperType 的實例中的所有屬性和方法,現在也存在于 SubType.prototype 中了。在確立了繼承關系之后,我們給 SubType.prototype 添加了一個方法,這樣就在繼承了 SuperType 的屬性和方法的基礎上又添加了一個新方法。這個例子中的實例以及構造函數和原型之間的關系如圖 6-4 所示。
在上面的代碼中,我們沒有使用 SubType 默認提供的原型,而是給它換了一個新原型;這個新原型就是 SuperType 的實例。于是,新原型不僅具有作為一個 SuperType 的實例所擁有的全部屬性和方法,而且其內部還有一個指針,指向了 SuperType 的原型。最終結果就是這樣的: instance 指向 SubType的 原 型 , SubType 的 原 型 又 指 向 SuperType 的 原 型 。 getSuperValue() 方 法 仍 然 還 在SuperType.prototype 中,但 property 則位于 SubType.prototype 中。這是因為 property 是一個實例屬性,而 getSuperValue()則是一個原型方法。既然 SubType.prototype 現在是 SuperType的實例,那么 property 當然就位于該實例中了。此外,要注意 instance.constructor 現在指向的是 SuperType,這是因為原來 SubType.prototype 中的 constructor 被重寫了的緣故。
原型搜索機制:通過實現原型鏈,本質上擴展了本章前面介紹的原型搜索機制。讀者大概還記得,當以讀取模式訪問一個實例屬性時,首先會在實例中搜索該屬性。如果沒有找到該屬性,則會繼續搜索實例的原型。在通過原型鏈實現繼承的情況下,搜索過程就得以沿著原型鏈繼續向上。就拿上面的例子來說,調用instance.getSuperValue()會經歷三個搜索步驟: 1)搜索實例; 2)搜索 SubType.prototype;3)搜索 SuperType.prototype,最后一步才會找到該方法。在找不到屬性或方法的情況下,搜索過程總是要一環一環地前行到原型鏈末端才會停下來。
1. 別忘記默認的原型
事實上,前面例子中展示的原型鏈還少一環。我們知道,所有引用類型默認都繼承了 Object,而這個繼承也是通過原型鏈實現的。大家要記住,所有函數的默認原型都是 Object 的實例,因此默認原型都會包含一個內部指針,指向 Object.prototype。這也正是所有自定義類型都會繼承 toString()、valueOf()等默認方法的根本原因。所以,我們說上面例子展示的原型鏈中還應該包括另外一個繼承層次。圖 6-5 為我們展示了該例子中完整的原型鏈。
一句話,SubType 繼承了 SuperType,而 SuperType 繼承了 Object。當調用 instance.toString()時,實際上調用的是保存在 Object.prototype 中的那個方法。
2. 確定原型和實例的關系
可以通過兩種方式來確定原型和實例之間的關系。第一種方式是使用instanceof 操作符,只要用這個操作符來測試實例與原型鏈中出現過的構造函數,結果就會返回 true。以下幾行代碼就說明了這一點。
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
由于原型鏈的關系,我們可以說 instance 是 Object、 SuperType 或 SubType 中任何一個類型的實例。因此,測試這三個構造函數的結果都返回了 true。
第二種方式是使用 isPrototypeOf()方法。同樣,只要是原型鏈中出現過的原型,都可以說是該原型鏈所派生的實例的原型,因此 isPrototypeOf()方法也會返回 true,如下所示。
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
3. 謹慎地定義方法
子類型有時候需要重寫超類型中的某個方法,或者需要添加超類型中不存在的某個方法。但不管怎樣,給原型添加方法的代碼一定要放在替換原型的語句之后。來看下面的例子。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
//添加新方法
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
//重寫超類型中的方法
SubType.prototype.getSuperValue = function (){
return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); //false
在以上代碼中,加粗的部分是兩個方法的定義。第一個方法 getSubValue()被添加到了 SubType中。第二個方法 getSuperValue()是原型鏈中已經存在的一個方法,但重寫這個方法將會屏蔽原來的那個方法。 換句話說,當通過 SubType 的實例調用 getSuperValue()時,調用的就是這個重新定義的方法;但通過 SuperType 的實例調用 getSuperValue()時,還會繼續調用原來的那個方法。這里要格外注意的是,必須在用 SuperType 的實例替換原型之后,再定義這兩個方法。還有一點需要提醒讀者,即在通過原型鏈實現繼承時,不能使用對象字面量創建原型方法。因為這
樣做就會重寫原型鏈,如下面的例子所示。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
//使用字面量添加新方法,會導致上一行代碼無效
SubType.prototype = {
getSubValue : function (){
return this.subproperty;
},
someOtherMethod : function (){
return false;
}
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!
以上代碼展示了剛剛把 SuperType 的實例賦值給原型,緊接著又將原型替換成一個對象字面量而導致的問題。由于現在的原型包含的是一個 Object 的實例,而非 SuperType 的實例,因此我們設想中的原型鏈已經被切斷——SubType 和 SuperType 之間已經沒有關系了。
4. 原型鏈的問題
原型鏈雖然很強大,可以用它來實現繼承,但它也存在一些問題。其中,最主要的問題來自包含引用類型值的原型。想必大家還記得,我們前面介紹過包含引用類型值的原型屬性會被所有實例共享;而這也正是為什么要在構造函數中,而不是在原型對象中定義屬性的原因。在通過原型來實現繼承時,原型實際上會變成另一個類型的實例。于是,原先的實例屬性也就順理成章地變成了現在的原型屬性了。下列代碼可以用來說明這個問題。
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//繼承了 SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
問題:
1.這個例子中的 SuperType 構造函數定義了一個 colors 屬性,該屬性包含一個數組(引用類型值)。SuperType 的每個實例都會有各自包含自己數組的 colors 屬性。當 SubType 通過原型鏈繼承了SuperType 之后, SubType.prototype 就變成了 SuperType 的一個實例,因此它也擁有了一個它自己的 colors 屬性——就跟專門創建了一個SubType.prototype.colors 屬性一樣。但結果是什么呢?結果是 SubType 的所有實例都會共享這一個 colors 屬性。 而我們對 instance1.colors 的修改
能夠通過 instance2.colors 反映出來,就已經充分證實了這一點。
2.原型鏈的第二個問題是:在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上,應該說是沒有辦法在不影響所有對象實例的情況下,給超類型的構造函數傳遞參數。有鑒于此,再加上前面剛剛討論過的由于原型中包含引用類型值所帶來的問題,實踐中很少會單獨使用原型鏈。
由于此問題,所以實踐中很少使用原型鏈。
方式二:借用構造函數
在解決原型中包含引用類型值所帶來問題的過程中,開發人員開始使用一種叫做借用構造函數(constructor stealing)的技術(有時候也叫做偽造對象或經典繼承)。這種技術的基本思想相當簡單,即在子類型構造函數的內部調用超類型構造函數。別忘了,函數只不過是在特定環境中執行代碼的對象,因此通過使用 apply()和 call()方法也可以在(將來)新創建的對象上執行構造函數,如下所示:
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//繼承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
代碼中加粗的那一行代碼“借調”了超類型的構造函數。通過使用 call()方法(或 apply()方法也可以),我們實際上是在(未來將要)新創建的 SubType 實例的環境下調用了 SuperType 構造函數。這樣一來,就會在新 SubType 對象上執行 SuperType()函數中定義的所有對象初始化代碼。結果,SubType 的每個實例就都會具有自己的 colors 屬性的副本了。
1. 傳遞參數
相對于原型鏈而言,借用構造函數有一個很大的優勢,即可以在子類型構造函數中向超類型構造函數傳遞參數。看下面這個例子。
function SuperType(name){
this.name = name;
}
function SubType(){
//繼承了 SuperType,同時還傳遞了參數
SuperType.call(this, "Nicholas");
//實例屬性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
以上代碼中的 SuperType 只接受一個參數 name,該參數會直接賦給一個屬性。在 SubType 構造函數內部調用 SuperType 構造函數時,實際上是為 SubType 的實例設置了 name 屬性。為了確保SuperType 構造函數不會重寫子類型的屬性,可以在調用超類型構造函數后,再添加應該在子類型中定義的屬性。
2. 借用構造函數的問題
如果僅僅是借用構造函數,那么也將無法避免構造函數模式存在的問題——方法都在構造函數中定義,因此函數復用就無從談起了。而且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結果所有類型都只能使用構造函數模式。考慮到這些問題,借用構造函數的技術也是很少單獨使用的。
方式三:(組合繼承)
組合繼承(combination inheritance),有時候也叫做偽經典繼承,指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮二者之長的一種繼承模式。其背后的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。這樣,既通過在原型上定義方法實現了函數復用,又能夠保證每個實例都有它自己的屬性。下面來看一個例子。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//繼承屬性
SuperType.call(this, name);
this.age = age;
}
//繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
解釋:
在這個例子中, SuperType 構造函數定義了兩個屬性: name 和 colors。 SuperType 的原型定義了一個方法 sayName()。 SubType 構造函數在調用 SuperType 構造函數時傳入了 name 參數,緊接著又定義了它自己的屬性 age。然后,將 SuperType 的實例賦值給 SubType 的原型,然后又在該新原型上定義了方法 sayAge()。這樣一來,就可以讓兩個不同的 SubType 實例既分別擁有自己屬性——包括 colors 屬性,又可以使用相同的方法了。
優點:
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優點,成為 JavaScript 中最常用的繼承模式。而且, instanceof 和 isPrototypeOf()也能夠用于識別基于組合繼承創建的對象。
方式四:(原型式繼承)
道格拉斯·克羅克福德在 2006 年寫了一篇文章,題為 Prototypal Inheritance in JavaScript (JavaScript中的原型式繼承)。在這篇文章中,他介紹了一種實現繼承的方法,這種方法并沒有使用嚴格意義上的構造函數。他的想法是借助原型可以基于已有的對象創建新對象,同時還不必因此創建自定義類型。為了達到這個目的,他給出了如下函數。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
在 object()函數內部,先創建了一個臨時性的構造函數,然后將傳入的對象作為這個構造函數的原型,最后返回了這個臨時類型的一個新實例。從本質上講, object()對傳入其中的對象執行了一次淺復制。來看下面的例子。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
克羅克福德主張的這種原型式繼承,要求你必須有一個對象可以作為另一個對象的基礎。如果有這么一個對象的話,可以把它傳遞給 object()函數,然后再根據具體需求對得到的對象加以修改即可。在這個例子中,可以作為另一個對象基礎的是 person 對象,于是我們把它傳入到 object()函數中,然后該函數就會返回一個新對象。這個新對象將 person 作為原型,所以它的原型中就包含一個基本類型值屬性
和一個引用類型值屬性。這意味著 person.friends 不僅屬于 person 所有,而且也會被 anotherPerson以及 yetAnotherPerson 共享。實際上,這就相當于又創建了 person 對象的兩個副本。
ECMAScript 5 通過新增 Object.create()方法規范化了原型式繼承。這個方法接收兩個參數:一個用作新對象原型的對象和(可選的)一個為新對象定義額外屬性的對象。在傳入一個參數的情況下,Object.create()與 object()方法的行為相同。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Object.create()方法的第二個參數與Object.defineProperties()方法的第二個參數格式相同:每個屬性都是通過自己的描述符定義的。以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性。例如:
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
{
name: {
value: "Greg"
}
});
alert(anotherPerson.name); //"Greg"
支持版本:支持 Object.create()方法的瀏覽器有 IE9+、 Firefox 4+、 Safari 5+、 Opera 12+和 Chrome。在沒有必要興師動眾地創建構造函數,而只想讓一個對象與另一個對象保持類似的情況下,原型式繼承是完全可以勝任的。缺點:不過別忘了,包含引用類型值的屬性始終都會共享相應的值,就像使用原型模式一樣。
方式五:(寄生式繼承)
寄生式(parasitic)繼承是與原型式繼承緊密相關的一種思路,并且同樣也是由克羅克福德推而廣之的。寄生式繼承的思路與寄生構造函數和工廠模式類似,即創建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最后再像真地是它做了所有工作一樣返回對象。以下代碼示范了寄生式繼承模式。
function createAnother(original){
var clone = object(original); //通過調用函數創建一個新對象
clone.sayHi = function(){ //以某種方式來增強這個對象
alert("hi");
};
return clone; //返回這個對象
}
在這個例子中, createAnother()函數接收了一個參數,也就是將要作為新對象基礎的對象。然后,把這個對象(original)傳遞給 object()函數,將返回的結果賦值給 clone。再為 clone 對象添加一個新方法 sayHi(),最后返回 clone 對象。可以像下面這樣來使用 createAnother()函數:
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
這個例子中的代碼基于 person 返回了一個新對象——anotherPerson。新對象不僅具有 person的所有屬性和方法,而且還有自己的 sayHi()方法。
使用范圍:在主要考慮對象而不是自定義類型和構造函數的情況下,寄生式繼承也是一種有用的模式。前面示范繼承模式時使用的 object()函數不是必需的;任何能夠返回新對象的函數都適用于此模式。
注意:使用寄生式繼承來為對象添加函數,會由于不能做到函數復用而降低效率;這一點與構造函數模式類似。
方式六:(寄生組合式繼承)
前面說過,組合繼承是 JavaScript 最常用的繼承模式;不過,它也有自己的不足。組合繼承最大的問題就是無論什么情況下,都會調用兩次超類型構造函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。沒錯,子類型最終會包含超類型對象的全部實例屬性,但我們不得不在調用子類型構造函數時重寫這些屬性。再來看一看下面組合繼承的例子。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name); //第二次調用 SuperType()
this.age = age;
}
SubType.prototype = new SuperType(); //第一次調用 SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
加粗字體的行中是調用 SuperType 構造函數的代碼。在第一次調用 SuperType 構造函數時,SubType.prototype 會得到兩個屬性: name 和 colors;它們都是 SuperType 的實例屬性,只不過現在位于 SubType 的原型中。當調用 SubType 構造函數時,又會調用一次 SuperType 構造函數,這一次又在新對象上創建了實例屬性 name 和 colors。于是,這兩個屬性就屏蔽了原型中的兩個同名屬性。圖 6-6 展示了上述過程。
如圖 6-6 所示,有兩組 name 和 colors 屬性:一組在實例上,一組在 SubType 原型中。這就是調用兩次 SuperType 構造函數的結果。好在我們已經找到了解決這個問題方法——寄生組合式繼承。
所謂寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。其背后的基本思路是:不必為了指定子類型的原型而調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,然后再將結果指定給子類型的原型。寄生組合式繼承的基本模式如下所示。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //創建對象
prototype.constructor = subType; //增強對象
subType.prototype = prototype; //指定對象
}
這個示例中的 inheritPrototype()函數實現了寄生組合式繼承的最簡單形式。這個函數接收兩個參數:子類型構造函數和超類型構造函數。在函數內部,第一步是創建超類型原型的一個副本。第二步是為創建的副本添加 constructor 屬性,從而彌補因重寫原型而失去的默認的 constructor 屬性。最后一步,將新創建的對象(即副本)賦值給子類型的原型。這樣,我們就可以用調用 inheritPrototype()函數的語句,
去替換前面例子中為子類型原型賦值的語句了,例如:
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //創建對象
prototype.constructor = subType; //增強對象
subType.prototype = prototype; //指定對象
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
這個例子的高效率體現在它只調用了一次 SuperType 構造函數,并且因此避免了在 SubType.prototype 上面創建不必要的、多余的屬性。與此同時,原型鏈還能保持不變;因此,還能夠正常使用instanceof 和 isPrototypeOf()。開發人員普遍認為寄生組合式繼承是引用類型最理想的繼承范式。
首次應該次繼承js庫: YUI 的 YAHOO.lang.extend()方法采用了寄生組合繼承,從而讓這種模式首次出現在了一個應用非常廣泛的 JavaScript 庫中。要了解有關 YUI 的更多信息,請訪問http://developer. yahoo.com/yui/。
總結:
ECMAScript 支持面向對象(OO)編程,但不使用類或者接口。對象可以在代碼執行過程中創建和增強,因此具有動態性而非嚴格定義的實體。在沒有類的情況下,可以采用下列模式創建對象。
? 工廠模式,使用簡單的函數創建對象,為對象添加屬性和方法,然后返回對象。這個模式后來被構造函數模式所取代。
? 構造函數模式,可以創建自定義引用類型,可以像創建內置對象實例一樣使用 new 操作符。不過,構造函數模式也有缺點,即它的每個成員都無法得到復用,包括函數。由于函數可以不局限于任何對象(即與對象具有松散耦合的特點),因此沒有理由不在多個對象間共享函數。
? 原型模式,使用構造函數的 prototype 屬性來指定那些應該共享的屬性和方法。組合使用構造函數模式和原型模式時,使用構造函數定義實例屬性,而使用原型定義共享的屬性和方法。
JavaScript 主要通過原型鏈實現繼承。原型鏈的構建是通過將一個類型的實例賦值給另一個構造函數的原型實現的。這樣,子類型就能夠訪問超類型的所有屬性和方法,這一點與基于類的繼承很相似。原型鏈的問題是對象實例共享所有繼承的屬性和方法,因此不適宜單獨使用。解決這個問題的技術是借用構造函數,即在子類型構造函數的內部調用超類型構造函數。這樣就可以做到每個實例都具有自己的屬性,同時還能保證只使用構造函數模式來定義類型。使用最多的繼承模式是組合繼承,這種模式使用原型鏈繼承共享的屬性和方法,而通過借用構造函數繼承實例屬性。
此外,還存在下列可供選擇的繼承模式。
1.? 原型式繼承,可以在不必預先定義構造函數的情況下實現繼承,其本質是執行對給定對象的淺復制。而復制得到的副本還可以得到進一步改造。
2.? 寄生式繼承,與原型式繼承非常相似,也是基于某個對象或某些信息創建一個對象,然后增強對象,最后返回對象。為了解決組合繼承模式由于多次調用超類型構造函數而導致的低效率問題,可以將這個模式與組合繼承一起使用。
3.? 寄生組合式繼承,集寄生式繼承和組合繼承的優點與一身,是實現基于類型繼承的最有效方式。