MixIn
MixIn是一種設計模式,旨在一個類中盡可能重用繼承多個超類的功能。
在絕大部分面向對象語言中,繼承與實現就是MixIn設計模式的實際應用。例如Java,一個子類可以單繼承一個超類,實現多個接口。到了JDK1.8之后,接口可以有默認方法和靜態方法,更優雅的實現MixIn設計模式。
Dart中的MixIn
在Dart官方文檔中,描述Dart是一門面向對象的語言,同時兼備融合了多種面向對象語言的優點。很自然的,MixIn模式也是Dart語言的特性之一。
在Dart中,類和接口是一類東西,但是implements
關鍵字還是保留下來,也帶有實現的意思,implements
的用法在文章下面再說。
Dart的多繼承,是由extends、with和implements來實現的。同時Dart中也保留了抽象類abstract
的用法。
首先,抽象出一個實際的模型,用Dart去實現這個模型。
- 用意念創建一個交通工具抽象類,具有運輸方法
//交通工具類,擁有運輸功能 abstract class Transportation { //運輸功能 void transport(); }
- 默認創建3個子類去實現這個方法,分別是摩托車,自行車和小轎車,具體實現如下
class Bicycle extends Transportation { @override void transport() { print( "Bicycle:\n動力組件: ${twoWheel()} > 安全指數: ${lowSafetyIndex()} > 動力來源:${bodyEnergy()}"); } String lowSafetyIndex() => "low"; String twoWheel() => "兩個輪子"; String bodyEnergy() => "全靠腿登"; } class Sedan extends Transportation { @override void transport() { print( "Sedan:\n動力組件: ${fourWheel()} > 安全指數: ${middleSafetyIndex()} > 動力來源:${gasolineEnergy()}"); } String middleSafetyIndex() => "middle"; String fourWheel() => "四個輪子"; String gasolineEnergy() => "汽油"; } class Motorcycle extends Transportation { @override void transport() { print( "Motorcycle:\n動力組件: ${twoWheel()} > 安全指數: ${lowSafetyIndex()} > 動力來源:${gasolineEnergy()}"); } String lowSafetyIndex() => "low"; String twoWheel() => "兩個個輪子"; String gasolineEnergy() => "汽油"; }
- 我們打印出來一下這3種交通工具的運輸方式方法
打印如下void main() { Bicycle().transport(); Sedan().transport(); Motorcycle().transport(); }
上面的代碼中,Bicycle和Motorcycle的動力組件一樣是兩個輪子,安全指數都是low。Bicycle: 動力組件: 兩個輪子 > 安全指數: low > 動力來源:全靠腿登 Sedan: 動力組件: 四個輪子 > 安全指數: middle > 動力來源:汽油 Motorcycle: 動力組件: 兩個輪子 > 安全指數: low > 動力來源:汽油
而Sedan和Motorcycle的動力來源一樣是汽油,總結如下表相同類 相同的實現 Bicycle&Motorcycle twoWheel()</br>lowSafetyIndex() Sedan&Motorcycle gasolineEnergy()
而在Dart中,是可以多繼承的,不需要為了實現而多寫一些冗余的代碼。
extends 與 with
同絕大部分的面向對象語言,在Dart中繼承一個超類使用extends
關鍵字。
在上面的交通工具例子中,根據動力組件,安全系數和動力來源可以封裝出來以下六個類
//雙輪交通工具
class TwoWheelTransportation {
String powerUnit() => "兩個輪子";
}
//汽油能源交通工具
class GasolineEnergyTransportation {
String energy() => "汽油";
}
//四輪交通工具,一般來說安全性能為中
class FourWheelTransportation {
String powerUnit() => "四個輪子";
}
//安全指數中等的交通工具兒
class MiddleSafetyIndex{
String safetyIndex() => "middle";
}
//安全指數低的交通工具兒
class LowSafetyIndex{
String safetyIndex() => "low";
}
//人力發動機
class BodyEnergyTransportation {
String energy() => "全靠腿登";
}
然后將Bicycle、Motorcycle、Sedan類的代碼改成一下模樣
class Bicycle extends Transportation
with TwoWheelTransportation, BodyEnergyTransportation, LowSafetyIndex {
@override
void transport() {
print(
"Bicycle:\n動力組件: ${powerUnit()} > 安全指數: ${safetyIndex()} > 動力來源:${energy()}");
}
}
class Motorcycle extends Transportation
with TwoWheelTransportation, GasolineEnergyTransportation, LowSafetyIndex {
@override
void transport() {
print(
"Motorcycle:\n動力組件: ${powerUnit()} > 安全指數: ${safetyIndex()} > 動力來源:${energy()}");
}
}
class Sedan extends Transportation
with
GasolineEnergyTransportation,
FourWheelTransportation,
MiddleSafetyIndex {
@override
void transport() {
print(
"Sedan:\n動力組件: ${powerUnit()} > 安全指數: ${safetyIndex()} > 動力來源:${energy()}");
}
}
打印結果也是相同
Bicycle:
動力組件: 兩個輪子 > 安全指數: low > 動力來源:全靠腿登
Sedan:
動力組件: 四個輪子 > 安全指數: middle > 動力來源:汽油
Motorcycle:
動力組件: 兩個輪子 > 安全指數: low > 動力來源:汽油
以上的寫法,省略了相同的safetyIndex
方法、powerUnit
方法和energy
方法的重復代碼,便于維護。
再舉個栗子,例如有個大興趣發明家發明了一輛四輪的,動力全靠腿蹬的、低安全性的木質汽車。
在抽象這個WoodenSedan
的時候,外觀最接近的應該是Sedan
類,先讓WoodenSedan
繼承Sedan
類,打印結果如下
class WoodenSedan extends Sedan {
@override
void transport() {
print(
"WoodenSedan:\n動力組件: ${powerUnit()} > 安全指數: ${safetyIndex()} > 動力來源:${energy()}");
}
}
WoodenSedan:
動力組件: 四個輪子 > 安全指數: middle > 動力來源:汽油
這個時候,如果是在Java中就需要以多實現的方式去重寫safetyIndex
和energy
方法了。而Dart中只需要使用with
關鍵字在extends
后面添加超類,就可以繼承safetyIndex
和energy
方法了
class WoodenSedan extends Sedan with LowSafetyIndex,BodyEnergyTransportation{
@override
void transport() {
print(
"WoodenSedan:\n動力組件: ${powerUnit()} > 安全指數: ${safetyIndex()} > 動力來源:${energy()}");
}
}
WoodenSedan:
動力組件: 四個輪子 > 安全指數: low > 動力來源:全靠腿登
但是,當一個子類所繼承的不同超類擁有相同簽名的方法的時候,子類實際使用的方法跟超類
的位置有關系。
with的順序問題與implements
-
with
的順序問題
雖說with的寫法很方便,但是還是有一些問題需要注意的。如果2個或多個超類擁有相同簽名的A方法,那么子類會以繼承的最后一個超類中的A方法為準。當然這是子類沒有重寫A方法的前提下,如果子類自己重寫了A方法則以本身的A方法為準。
可參照上述例子的打印結果class WoodenSedan extends Sedan with LowSafetyIndex,GasolineEnergyTransportation,BodyEnergyTransportation{ @override void transport() { print( "WoodenSedan:\n動力組件: ${powerUnit()} > 安全指數: ${safetyIndex()} > 動力來源:${energy()}"); } } WoodenSedan: 動力組件: 四個輪子 > 安全指數: low > 動力來源:全靠腿登
WoodenSedan
類繼承了汽油動力類和體力動力類,但是由于BodyEnergyTransportation
位置比GasolineEnergyTransportation
靠右,所以實際其作用的還是BodyEnergyTransportation
的方法。 -
implements
:不同于with
的直接繼承。implements
要求子類必須擁有所繼承超類中的所有方法,我們可以將直接認為是Java中的implements
關鍵字。
如果將上述WoodenSedan
繼承多個類的關鍵字改為implements
打印結果如下
動力來源超類和安全屬性超類的方法都不起作用了,取而代之的是class WoodenSedan extends Sedan implements LowSafetyIndex,GasolineEnergyTransportation,BodyEnergyTransportation{ @override void transport() { print( "WoodenSedan:\n動力組件: ${powerUnit()} > 安全指數: ${safetyIndex()} > 動力來源:${energy()}"); } } WoodenSedan: 動力組件: 四個輪子 > 安全指數: middle > 動力來源:汽油
Sedan
超類中的方法被打印。如果WoodenSedan
不繼承Sedan
超類,則會編譯報錯
總結
implements多用于強調需要重寫的場合,而extends
與with
則多是為了直接繼承。在編碼過程中,區分好他們implements
和with
的關系也能使得代碼更加易于閱讀與維護