第8章 Dart面向?qū)ο?-- 類的繼承與混入(Mixin)

繼承(extends)是面向?qū)ο箝_(kāi)發(fā)方法中非常重要的一個(gè)特征务甥,繼承體現(xiàn)著現(xiàn)實(shí)世界中“一般”與“特殊”的關(guān)系达椰。對(duì)于擁有“一般”性質(zhì)的類我們稱之為“父類”或者“超類”耕蝉,擁有“特殊”性質(zhì)的類我們稱之為“子類”铐刘。比如“動(dòng)物”和“鳥(niǎo)”歧杏,動(dòng)物是一般的概念镰惦,鳥(niǎo)是特殊的概念,可以通過(guò)“鳥(niǎo)是一種特殊的動(dòng)物”這句話邏輯是否成立來(lái)判斷繼承關(guān)系是否成立犬绒。

1. Dart中的繼承

與Java語(yǔ)言類似旺入,Dart語(yǔ)言標(biāo)榜自己為“單繼承”,也就是一個(gè)類只能有一個(gè)直接的父類凯力。如果一個(gè)類沒(méi)有顯式地聲明父類茵瘾,那么它會(huì)默認(rèn)繼承Object類。此外Dart語(yǔ)言又提供了混入(Mixin)的語(yǔ)法咐鹤,允許子類在繼承父類時(shí)混入其他類拗秘。關(guān)于混入(Mixin)的理解,請(qǐng)參照本章第7節(jié)的內(nèi)容理解祈惶。
Dart語(yǔ)言中使用extends作為繼承關(guān)鍵字雕旨,子類會(huì)繼承父類的數(shù)據(jù)和函數(shù)。
示例代碼:貓類繼承動(dòng)物類

main() {
  var a = new Animal();
  var b = new Cat();
  a.name = "動(dòng)物";
  b.name = "貓";
  b.color = "黑色";
  a.eat();
  b.eat();
  b..climb();
}

class Animal{
  String name;
  void eat(){
    print("${name}:進(jìn)食");
  }
}

class Cat extends Animal{
  String color;
  void climb(){
    print("${color}的${name}:爬樹(shù)");
  }
}

運(yùn)行結(jié)果

運(yùn)行結(jié)果

2. 函數(shù)重寫

重寫在面向?qū)ο笾畜w現(xiàn)的現(xiàn)實(shí)意義是“子類與父類在同一行為上有不同的表現(xiàn)形式”捧请。同Java語(yǔ)言類似凡涩,Dart語(yǔ)言也支持函數(shù)的重寫,子類重寫父類的函數(shù)后疹蛉,對(duì)象調(diào)用的即為子類的同名函數(shù)活箕。
演示示例:在第1節(jié)的例子的Cat類中重寫Animal類的eat函數(shù)。

main() {
  var a = new Animal();
  var b = new Cat();
  a.name = "動(dòng)物";
  b.name = "貓";
  b.color = "黑色";
  a.eat();
  b.eat();
  b..climb();
}

class Animal{
  String name;
  void eat(){
    print("${name}:進(jìn)食");
  }
}

class Cat extends Animal{
  String color;
  @override
  void eat(){//子類重寫父類的eat方法
    print("${color}的${name}:吃魚(yú)");
  }
  void climb(){
    print("${color}的${name}:爬樹(shù)");
  }
}

運(yùn)行結(jié)果

運(yùn)行結(jié)果

3. 操作符重寫

同C++語(yǔ)言類似,Dart語(yǔ)言支持操作符的重寫,常規(guī)的四則運(yùn)算和比較運(yùn)算符都可以進(jìn)行重寫堕绩,其中只有!=不可重寫,a!=b相當(dāng)于!(a==b)的語(yǔ)法糖巡通。
示例代碼:
重寫了==和+的Rectangle類,當(dāng)兩個(gè)對(duì)象的width和height一致認(rèn)為它們是“相等”的,兩個(gè)Rectangle相加則將兩者的width和height進(jìn)行相加后得到新的Rectangle對(duì)象

main() {
  var a = new Rectangle(10,10);
  var b = new Rectangle(5, 5);
  var c = new Rectangle(10, 10);

  print(a == b);
  print(a == c);
  var d = a + b;
  print("${a.width}:${a.height}");
  print("${d.width}:${d.height}");
  print(a == d);
}

class Rectangle{
  int width;
  int height;

  Rectangle(this.width,this.height){}

  @override
  bool operator ==(dynamic other) {
    if(other is! Rectangle){
      return false;
    }
    Rectangle temp = other;
    return (temp.width == width && temp.height == height);
  }

  @override
  Rectangle operator +(dynamic other){
    if(other is! Rectangle){
      return this;
    }
    Rectangle temp = other;
    return new Rectangle( this.width + temp.width, this.height + temp.height);
  }
}

運(yùn)行結(jié)果

運(yùn)行結(jié)果

4. 抽象類

抽象abstract是面向?qū)ο笾械囊粋€(gè)非常重要的概念,通常用于描述父類擁有一種行為但無(wú)法給出細(xì)節(jié)實(shí)現(xiàn)版仔,而需要通過(guò)子類來(lái)實(shí)現(xiàn)抽象的細(xì)節(jié)。這種情況下父類被定義為抽象類误墓,子類繼承父類后實(shí)現(xiàn)其中的抽象方法。
同Java語(yǔ)言類似益缎,Dart中的抽象類也使用abstract來(lái)實(shí)現(xiàn)谜慌,不過(guò)抽象函數(shù)無(wú)需使用abstract,直接給出定義不給出方法體實(shí)現(xiàn)即可
抽象類中可以有數(shù)據(jù)莺奔,可以有常規(guī)函數(shù)欣范,可以有抽象函數(shù)变泄,但抽象類不能實(shí)例化。子類繼承抽象類后必須實(shí)現(xiàn)其中的抽象函數(shù)恼琼。
演示示例:Dog類繼承Animal抽象類

main(){
  var d = new Dog();
  d.name = "dog";
  d.eat();
  d.display();
}

abstract class Animal{
  String name;  //數(shù)據(jù)
  void display(){  //普通函數(shù)
    print("名字是:${name}");
  }
  void eat(); //抽象函數(shù)
}

class Dog extends Animal{
  @override
  void eat() { //實(shí)現(xiàn)抽象函數(shù)
    print("eat");
  }
}

運(yùn)行結(jié)果

運(yùn)行結(jié)果

5. 接口

Dart語(yǔ)言中沒(méi)有接口(interface)的關(guān)鍵字妨蛹,但是有實(shí)現(xiàn)(implements)關(guān)鍵字,Dart中可以將類(是否為抽象無(wú)關(guān))當(dāng)做隱式接口直接使用晴竞,當(dāng)需要使用接口時(shí)蛙卤,可以聲明類來(lái)代替。(個(gè)人認(rèn)為抽象類更適合對(duì)接口的理解)
演示示例:
在第4節(jié)的示例基礎(chǔ)上噩死,添加swimable類和walkable類作為接口使用颤难,Dog類繼承Animal類并實(shí)現(xiàn)swimable和walkable接口

main(){
  var d = new Dog();
  d.name = "dog";
  d.eat();
  d.display();
  d.swim();
  d.walk();
}

abstract class Animal{
  String name;
  void display(){
    print("名字是:${name}");
  }
  void eat(); //抽象方法
}

abstract class swimable{ //抽象類作為接口
  void swim();
}

class walkable{ //普通類作為接口
  void walk(){}
}

class Dog extends Animal implements swimable, walkable{
  @override
  void eat() {
    print("eat");
  }
  @override
  void swim() {
    print("swim");
  }
  @override
  void walk() {
    print("walk");
  }
}

運(yùn)行結(jié)果

運(yùn)行結(jié)果

6. 泛型繼承

上一章中,提到了泛型編程中的繼承問(wèn)題已维,在使用泛型編程時(shí)也可以使用extends關(guān)鍵字約束輸入泛型的類型行嗤。
演示示例
被泛型約束的函數(shù),當(dāng)傳入泛型int時(shí)垛耳,只能計(jì)算整數(shù)加法栅屏,且在聲明對(duì)象x時(shí)泛型只能傳入num類型及其子類

main(){
  var a = 3;
  var b = 3.14;
  var c = 4;
  var x = DataUtil<int>();
  var f = x.addition(a, c);
  print(f);
}

class DataUtil<T extends num>{
  T addition(T a, T b){
    return a+b;
  }
}

運(yùn)行結(jié)果:7

7. 混入(mixin)

本章節(jié)內(nèi)容借鑒了簡(jiǎn)書(shū)用戶Vadaski的文章【譯】Dart | 什么是Mixin,在此對(duì)其表示感謝堂鲜。

問(wèn)題
我們來(lái)看下面這張關(guān)于動(dòng)物(Animal)既琴,哺乳動(dòng)物(Mammal),鳥(niǎo)(Bird)和魚(yú)(Fish)的繼承關(guān)系圖

繼承關(guān)系圖

這里有一個(gè)名為Animal的超類泡嘴,它有三個(gè)子類(Mammal甫恩,Bird和Fish)。在底部酌予,我們有具體的一些子類磺箕。
小方塊代表行為。例如抛虫,

  • 黃色方塊表示具有此行為的類的實(shí)例可以步行(walk)松靡。
  • 藍(lán)色方塊表示具有此行為的類的實(shí)例可以游泳(swim)。
  • 灰色方塊表示具有此行為的類的實(shí)例可以飛行(fly)建椰。

有些動(dòng)物有共同的行為:貓(Cat)和鴿子(Dove)都可以行走雕欺,但是貓不能飛。
這些行為與此分類正交棉姐,因此我們無(wú)法在超類中實(shí)現(xiàn)這些行為屠列。

在Java語(yǔ)言中我們可以借助接口(Interface)來(lái)實(shí)現(xiàn)相關(guān)的設(shè)計(jì),Dart中也可以利用隱式接口來(lái)完成相應(yīng)的設(shè)計(jì)伞矩。
如果不同的子類在某種行為上表現(xiàn)的都不相同笛洛,那么使用接口來(lái)實(shí)現(xiàn)設(shè)計(jì)是一種良好的設(shè)計(jì)。
但如果不同的子類在實(shí)現(xiàn)某種行為上有著同樣的表現(xiàn)乃坤,那么使用接口來(lái)實(shí)現(xiàn)設(shè)計(jì)可能會(huì)造成代碼的冗余苛让。(接口實(shí)現(xiàn)強(qiáng)制重寫函數(shù)

所以除了上述兩種方式沟蔑,我們也可以利用混入方式(Mixin)來(lái)完成相應(yīng)的設(shè)計(jì)

實(shí)現(xiàn)
對(duì)三種行為分別定義三個(gè)類描述它們,分別是Walker狱杰,Swimmer和Flyer

class Walker {
  void walk() {
    print("I'm walking");
  }
}
class Swimmer {
  void swim() {
    print("I'm swimming");
  }
}
class Flyer{
  void fly() {
    print("I'm flying");
  }
}

如果不想這三個(gè)類被實(shí)例化瘦材,可以使用抽象類+工廠方式定義

abstract class Walker {
  factory Walker._() => null; 
  void walk() { 
    print("I'm walking");
  }
}

使用混入的關(guān)鍵字是with,它的后面可以跟隨一個(gè)或多個(gè)類名

class Cat extends Mammal with Walker {}
class Dove extends Bird with Walker, Flyer {}

使用時(shí)允許Cat和Dove調(diào)用Mixin的walk函數(shù)仿畸,不允許Cat調(diào)用未Mixin的Fly函數(shù)

main(){
  Cat cat = Cat();
  Dove dove = Dove();
  cat.walk();
  dove.walk();
  dove.fly();
}

理解
如果Mixin的類和繼承類食棕,或者混入的類之間有相同的方法,在調(diào)用時(shí)會(huì)產(chǎn)生什么樣的情況颁湖,看下面的例子宣蠕。
演示示例
AB和BA類都使用A和B Mixin繼承至P類,但順序不同甥捺。A抢蚀,B和P類都有一個(gè)名為getMessage的方法。

class A {
  String getMessage() => 'A';
}

class B {
  String getMessage() => 'B';
}

class P {
  String getMessage() => 'P';
}

class AB extends P with A, B {}

class BA extends P with B, A {}

void main() {
  String result = '';
  AB ab = AB();
  result += ab.getMessage();
  BA ba = BA();
  result += ba.getMessage();
  print(result);
}

運(yùn)行結(jié)果: BA
為什么會(huì)產(chǎn)生這個(gè)結(jié)果镰禾?

Dart中的Mixins通過(guò)創(chuàng)建一個(gè)新類來(lái)實(shí)現(xiàn)皿曲,該類將mixin的實(shí)現(xiàn)層疊在一個(gè)超類之上以創(chuàng)建一個(gè)新類 ,它不是“在超類中”吴侦,而是在超類的“頂部”屋休,因此如何解決查找問(wèn)題不會(huì)產(chǎn)生歧義。
— Lasse R. H. Nielsen on StackOverflow.'

實(shí)際上备韧,這段代碼

class AB extends P with A, B {}
class BA extends P with B, A {}

在語(yǔ)義上等同于

class PA = P with A;
class PAB = PA with B;

class AB extends PAB {}

class PB = P with B;
class PBA = PB with A;

class BA extends PBA {}

最終的繼承關(guān)系如下圖所示


繼承關(guān)系

很顯然劫樟,最后被繼承的類重寫了上面所有的getMessage方法,可以理解為處于Mixin結(jié)尾的類將前面的getMessage方法都覆蓋(override)掉了

mixin應(yīng)用程序?qū)嵗念愋褪鞘裁矗?/p>

通常织堂,它是其超類的子類型叠艳,也是mixin名稱本身表示的類的子類型,即原始類的類型易阳。
— dartlang.org

所以這意味著這個(gè)程序的運(yùn)行結(jié)果全部為true

class A {
  String getMessage() => 'A';
}

class B {
  String getMessage() => 'B';
}

class P {
  String getMessage() => 'P';
}

class AB extends P with A, B {}

class BA extends P with B, A {}

void main() {
  AB ab = AB();
  print(ab is P);  //true
  print(ab is A);  //true
  print(ab is B);  //true

  BA ba = BA();
  print(ba is P);  //true
  print(ba is A);  //true
  print(ba is B);  //true
}

最后
完整的實(shí)現(xiàn)示例

abstract class Animal {}
abstract class Mammal extends Animal {}
abstract class Bird extends Animal {}
abstract class Fish extends Animal {}

abstract class Walker {
  factory Walker._() => null;
  void walk() {
    print("I'm walking");
  }
}

abstract class Swimmer {
  factory Swimmer._() => null;
  void swim() {
    print("I'm swimming");
  }
}

abstract class Flyer {
  factory Flyer._() => null;
  void fly() {
    print("I'm flying");
  }
}

class Dolphin extends Mammal with Swimmer {}
class Bat extends Mammal with Walker, Flyer {}
class Cat extends Mammal with Walker {}
class Dove extends Bird with Walker, Flyer {}
class Duck extends Bird with Walker, Swimmer, Flyer {}
class Shark extends Fish with Swimmer {}
class FlyingFish extends Fish with Swimmer, Flyer {}

Mixin類圖


Mixin類圖
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末附较,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子潦俺,更是在濱河造成了極大的恐慌拒课,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件事示,死亡現(xiàn)場(chǎng)離奇詭異早像,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)很魂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門扎酷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人遏匆,你說(shuō)我怎么就攤上這事法挨。” “怎么了幅聘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵凡纳,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我帝蒿,道長(zhǎng)荐糜,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任葛超,我火速辦了婚禮暴氏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绣张。我一直安慰自己答渔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布侥涵。 她就那樣靜靜地躺著沼撕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芜飘。 梳的紋絲不亂的頭發(fā)上务豺,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音嗦明,去河邊找鬼笼沥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛娶牌,可吹牛的內(nèi)容都是我干的奔浅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼裙戏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼乘凸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起累榜,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤营勤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后壹罚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體葛作,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年猖凛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赂蠢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辨泳,死狀恐怖虱岂,靈堂內(nèi)的尸體忽然破棺而出玖院,到底是詐尸還是另有隱情,我是刑警寧澤第岖,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布难菌,位于F島的核電站,受9級(jí)特大地震影響蔑滓,放射性物質(zhì)發(fā)生泄漏郊酒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一键袱、第九天 我趴在偏房一處隱蔽的房頂上張望燎窘。 院中可真熱鬧,春花似錦蹄咖、人聲如沸褐健。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)铝量。三九已至,卻和暖如春银亲,著一層夾襖步出監(jiān)牢的瞬間慢叨,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工务蝠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拍谐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓馏段,卻偏偏與公主長(zhǎng)得像轩拨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子院喜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容