Dart語法篇之面向對象繼承和Mixins(六)

簡述:bash

上一篇文章中咱們詳細地介紹了Dart中的面向對象的基礎,這一篇文章中咱們繼續探索Dart中面向對象的重點和難點(繼承和mixins). mixins(混合)特性是不少語言中都是沒有的。這篇文章主要涉及到Dart中的普通繼承、mixins多繼承的形式(實際上本質並非真正意義的多繼承)、mixins線性化分析、mixins類型、mixins使用場景等。ide

1、類的單繼承

一、基本介紹

Dart中的單繼承和其餘語言中相似,都是經過使用 extends 關鍵字來聲明。例如函數

class Student extends Person {//Student類稱爲子類或派生類,Person類稱爲父類或基類或超類。這一點和Java中是一致的。
    ...
}
複製代碼

二、繼承中的構造函數

  • 子類中構造函數會默認調用父類中無參構造函數(通常爲主構造函數)
class Person {
  String name;
  String age;

  Person() {
    print('person');
  }
}

class Student extends Person {
  String classRoom;
  Student() {
    print('Student');
  }
}

main(){
  var student = Student();//構造Student()時會先調用父類中無參構造函數,再調用子類中無參構造函數
}
複製代碼

輸出結果:源碼分析

person
Student

Process finished with exit code 0
複製代碼
  • 若父類中沒有默認無參的構造函數,則須要顯式調用父類的構造函數(能夠是命名構造函數也能夠主構造函數或其餘), 而且在初始化列表的尾部顯式調用父類中構造函數, 也便是類構造函數 :後面列表的尾部
class Person {
  String name;
  int age;

  Person(this.name, this.age); //指定了帶參數的構造函數爲主構造函數,那麼父類中就沒有默認無參構造函數
  //再聲明兩個命名構造函數
  Person.withName(this.name);

  Person.withAge(this.age);
}

class Student extends Person {
  String classRoom;

  Student(String name, int age) : super(name, age) { //顯式調用父類主構造函數
    print('Student');
  }

  Student.withName(String name) : super.withName(name) {} //顯式調用父類命名構造函數withName

  Student.withAge(int age) : super.withAge(age) {} //顯式調用父類命名構造函數withAge
}

main() {
  var student1 = Student('mikyou', 18);
  var student2 = Student.withName('mikyou');
  var student3 = Student.withAge(18);
}
複製代碼
  • 父類的構造函數在子類構造函數體開始執行的位置調用,若是有初始化列表,初始化列表會在父類構造函數執行以前執行
class Person {
  String name;
  int age;

  Person(this.name, this.age); //指定了帶參數的構造函數爲主構造函數,那麼父類中就沒有默認無參構造函數
}

class Student extends Person {
  final String classRoom;

  Student(String name, int age, String room) : classRoom = room, super(name, age) {//注意super(name, age)必須位於初始化列表尾部
    print('Student');
  }
}

main() {
  var student = Student('mikyou', 18, '三年級八班');
}
複製代碼

2、基於Mixins的多繼承

除了上面和其餘語言相似的單繼承外,在Dart中還提供了另外一繼承的機制就是基於Mixins的多繼承,可是它不是真正意義上類的多繼承,它始終仍是隻能有一個超類(基類)post

一、爲何須要Mixins?

爲何須要Mixins多繼承?它實際上爲了解決單繼承所帶來的問題,咱們不少語言中都是採用了單繼承+接口多實現的方式。可是這種方式並不能很好適用於全部場景。ui

假設一下下面場景,咱們把車進行分類,而後下面的顏色條表示各類車輛具備的能力。this

咱們經過上圖就能夠看到,這些車輛都有一個共同的父類 Vehicle,而後它又由兩個抽象的子類: MotorVehicleNonMotorVehicle 。有些類是具備相同的行爲和能力,可是有的類又有本身獨有的行爲和能力。好比公交車 Bus 和摩托車 Motor 都能使用汽油驅動,可是摩托車 Motor 還能載貨公交車 Bus 卻不能夠。spa

若是僅僅是單繼承模型下沒法把部分子類具備相同行爲和能力抽象放到基類,由於對於不具備該行爲和能力的子類來講是不妥的,因此只能在各自子類另外實現。那麼就問題來了,部分具備相同能力和行爲的子類中都要保留一份相同的代碼實現。這就是產生冗餘,忽然以爲單繼承模型有點雞肋,食之無味棄之惋惜。翻譯

//單繼承模型的普通實現
abstract class Vehicle {}

abstract class MotorVehicle extends Vehicle {}

abstract class NonMotorVehicle extends Vehicle {}

class Motor extends MotorVehicle {
  void petrolDriven() => print("汽油驅動");

  void passengerService() => print('載人');

  void carryCargo() => print('載貨');
}

class Bus extends MotorVehicle {
  void petrolDriven() => print("汽油驅動");

  void electricalDriven() => print("電能驅動");

  void passengerService() => print('載人');
}

class Truck extends MotorVehicle {
  void petrolDriven() => print("汽油驅動");

  void carryCargo() => print('載貨');
}

class Bicycle extends NonMotorVehicle {
  void electricalDriven() => print("電能驅動");

  void passengerService() => print('載人');
}

class Bike extends NonMotorVehicle {
  void passengerService() => print('載人');
}
複製代碼

能夠從上述實現代碼來看發現,有不少相同冗餘代碼實現,請注意這裏所說的相同代碼是連具體實現都相同的。不少人估計想到一個辦法那就是將各個能力提高成接口,而後各自的選擇去實現相應能力。可是咱們知道即便抽成了接口,各個實現類中仍是須要寫對應的實現代碼,冗餘仍是沒法擺脫。不妨咱們來試試用接口:code

//單繼承+接口多實現
abstract class Vehicle {}

abstract class MotorVehicle extends Vehicle {}

abstract class NonMotorVehicle extends Vehicle {}

//將各自的能力抽成獨立的接口,這樣的好處就是能夠從抽象角度對不一樣的實現類賦予不一樣接口能力,
// 職責更加清晰,可是這個只是一方面的問題,它仍是沒法解決相同能力實現代碼冗餘的問題
abstract class PetrolDriven {
  void petrolDriven();
}

abstract class PassengerService {
  void passengerService();
}

abstract class CargoService {
  void carryCargo();
}

abstract class ElectricalDriven {
  void electricalDriven();
}

//對於Motor賦予了PetrolDriven、PassengerService、CargoService能力
class Motor extends MotorVehicle implements PetrolDriven, PassengerService, CargoService {
  @override
  void carryCargo() => print('載貨');//仍然須要重寫carryCargo

  @override
  void passengerService() => print('載人');//仍然須要重寫passengerService

  @override
  void petrolDriven() => print("汽油驅動");//仍然須要重寫petrolDriven
}

//對於Bus賦予了PetrolDriven、ElectricalDriven、PassengerService能力
class Bus extends MotorVehicle implements PetrolDriven, ElectricalDriven, PassengerService {
  @override
  void electricalDriven() => print("電能驅動");//仍然須要重寫electricalDriven

  @override
  void passengerService() => print('載人');//仍然須要重寫passengerService

  @override
  void petrolDriven() => print("汽油驅動");//仍然須要重寫petrolDriven
}

//對於Truck賦予了PetrolDriven、CargoService能力
class Truck extends MotorVehicle implements PetrolDriven, CargoService {
  @override
  void carryCargo() => print('載貨');//仍然須要重寫carryCargo

  @override
  void petrolDriven() => print("汽油驅動");//仍然須要重寫petrolDriven
}

//對於Bicycle賦予了ElectricalDriven、PassengerService能力
class Bicycle extends NonMotorVehicle implements ElectricalDriven, PassengerService {
  @override
  void electricalDriven() => print("電能驅動");//仍然須要重寫electricalDriven

  @override
  void passengerService() => print('載人');//仍然須要重寫passengerService
}

//對於Bike賦予了PassengerService能力
class Bike extends NonMotorVehicle implements PassengerService {
  @override
  void passengerService() => print('載人');//仍然須要重寫passengerService
}

複製代碼

針對相同實現代碼冗餘的問題,使用Mixins就能很好的解決。它能複用類中某個行爲的具體實現而不是像接口僅僅從抽象角度規定了實現類具備哪些能力,至於具體實現接口方法都必須重寫,也就意味着即便是相同的實現還得從新寫一遍。一塊兒看下Mixins改寫後代碼:

//mixins多繼承模型實現
abstract class Vehicle {}

abstract class MotorVehicle extends Vehicle {}

abstract class NonMotorVehicle extends Vehicle {}

//將各自的能力抽成獨立的Mixin類
mixin PetrolDriven {//使用mixin關鍵字代替class聲明一個Mixin類
  void petrolDriven() => print("汽油驅動");
}

mixin PassengerService {//使用mixin關鍵字代替class聲明一個Mixin類
  void passengerService() => print('載人');
}

mixin CargoService {//使用mixin關鍵字代替class聲明一個Mixin類
  void carryCargo() => print('載貨');
}

mixin ElectricalDriven {//使用mixin關鍵字代替class聲明一個Mixin類
  void electricalDriven() => print("電能驅動");
}

class Motor extends MotorVehicle with PetrolDriven, PassengerService, CargoService {}//利用with關鍵字使用mixin類

class Bus extends MotorVehicle with PetrolDriven, ElectricalDriven, PassengerService {}//利用with關鍵字使用mixin類

class Truck extends MotorVehicle with PetrolDriven, CargoService {}//利用with關鍵字使用mixin類

class Bicycle extends NonMotorVehicle with ElectricalDriven, PassengerService {}//利用with關鍵字使用mixin類

class Bike extends NonMotorVehicle with PassengerService {}//利用with關鍵字使用mixin類
複製代碼

能夠對比發現Mixins類能真正地解決相同代碼冗餘的問題,並能實現很好的複用;因此使用Mixins多繼承模型能夠很好地解決單繼承模型所帶來冗餘問題。

二、Mixins是什麼?

用dart官網一句話來歸納: Mixins是一種能夠在多個類層次結構中複用類代碼的方式

  • 基本語法

方式一: Mixins類使用關鍵字 mixin 聲明定義

mixin PetrolDriven {//使用mixin關鍵字代替class聲明一個Mixin類
  void petrolDriven() => print("汽油驅動");
}

class Motor extends MotorVehicle with PetrolDriven {//使用with關鍵字來使用mixin類
    ...
}

class Petrol extends PetrolDriven{//編譯異常,注意:mixin類不能被繼承
    ...
}

main() {
    var petrolDriven = PetrolDriven()//編譯異常,注意:mixin類不能實例化
}
複製代碼

方式二: Dart中的普通類看成Mixins類使用

class PetrolDriven {
    factory PetrolDriven._() => null;//主要是禁止PetrolDriven被繼承以及實例化
    void petrolDriven() => print("汽油驅動");
}

class Motor extends MotorVehicle with PetrolDriven {//普通類也能夠做爲Mixins類使用
    ...
}
複製代碼

三、使用Mixins多繼承的場景

那麼問題來了,何時去使用Mixins呢?

當想要在不一樣的類層次結構中多個類之間共享相同的行爲時或者沒法合適抽象出部分子類共同的行爲到基類中時. 好比說上述例子中在 MotorVehicle (機動車)和 Non-MotorVehicle (非機動車)兩個不一樣類層次結構中,其中 Bus (公交車)和 Bicycle (電動自行車)都有相同行爲 ElectricalDrivenPassengerService. 可是很明顯你沒法把這個兩個共同的行爲抽象到基類 Vehicle 中,由於這樣的話 Bike (自行車)繼承 Vehicle 會自動帶有一個 ElectricalDriven 行爲就比較詭異。因此這種場景下mixins就是一個不錯的選擇能夠跨類層次之間複用相同行爲的實現

四、Mixins的線性化分析

在說Mixins線性化分析以前,一塊兒先來看個例子

class A {
   void printMsg() => print('A'); 
}
mixin B {
    void printMsg() => print('B'); 
}
mixin C {
    void printMsg() => print('C'); 
}

class BC extends A with B, C {}
class CB extends A with C, B {}

main() {
    var bc = BC();
    bc.printMsg();
    
    var cb = CB();
    cb.printMsg();
}
複製代碼

不妨考慮下上述例子中應該輸出啥呢?

輸出結果:

C
B

Process finished with exit code 0
複製代碼

爲何會是這樣的結果?實際上能夠經過線性分析獲得輸出結果。理解Mixin線性化分析有一點很重要就是: 在Dart中Mixins多繼承並非真正意義上的多繼承,實際上仍是單繼承;而每次Mixin都是會建立一個新的中間類。而且這個中間類老是在基類的上層

關於上述結論可能有點難以理解,下面經過一張mixins繼承結構圖就能清晰明白了:

經過上圖,咱們能夠很清楚發現Mixins並非經典意義上得到多重繼承的方法。 Mixins是一種抽象和複用一系列操做和狀態的方式,而是生成多箇中間的mixin類(好比生成ABC類,AB類,ACB類,AC類)。它相似於從擴展類得到的複用,但因爲它是線性的,所以與單繼承兼容

上述mixins代碼在語義理解上能夠轉化成下面形式:

//A with B 會產生AB類混合體,B類中printMsg方法會覆蓋A類中的printMsg方法,那麼AB中間類保留是B類中的printMsg方法
class AB = A with B; //AB with C 會產生ABC類混合體,C類中printMsg方法會覆蓋AB混合類中的printMsg方法,那麼ABC中間類保留C類中printMsg方法 class ABC = AB with C; //最終BC類至關於繼承的是ABC類混合體,最後調用的方法是ABC中間類保留C類中printMsg方法,最後輸出C class BC extends ABC {}

//A with C 會產生AC類混合體,C類中printMsg方法會覆蓋A類中的printMsg方法,那麼AC中間類保留是C類中的printMsg方法
class AC = A with C; //AC with B 會產生ACB類混合體,B類中printMsg方法會覆蓋AC混合類中的printMsg方法,那麼ACB中間類保留B類中printMsg方法 class ACB = AC with B; //最終CB類至關於繼承的是ACB類混合體,最後調用的方法是ACB中間類保留B類中printMsg方法,最後輸出B class CB extends ACB {}

複製代碼

五、Mixins中的類型

有了上面的探索,不妨再來考慮一個問題,mixin的實例對象是什麼類型呢? 咱們都知道它確定是它基類的子類型。 一塊兒來看個例子:

class A {
   void printMsg() => print('A'); 
}
mixin B {
    void printMsg() => print('B'); 
}
mixin C {
    void printMsg() => print('C'); 
}

class BC extends A with B, C {}
class CB extends A with C, B {}

main() {
    var bc = BC();
    print(bc is A);
    print(bc is B);
    print(bc is C);
    
    var cb = CB();
    print(cb is A);
    print(cb is B);
    print(cb is C);
}
複製代碼

輸出結果:

true
true
true
true
true
true

Process finished with exit code 0
複製代碼

能夠看到輸出結果全都是true, 這是爲何呢?

其實經過上面那張圖就能找到答案,咱們知道mixin with後就會生成新的中間類,好比中間類AB、ABC. 同時也會生成 一個新的接口AB、ABC(由於全部Dart類都定義了對應的接口, Dart類是能夠直接當作接口實現的)。新的中間類AB繼承基類A以及A類和B類混合的成員(方法和屬性)副本,但它也實現了A接口和B接口。那麼就很容易理解,ABC混合類最後會實現了ABC三個接口,最後BC類繼承ABC類實際上也至關於間接實現了ABC三個接口,因此BC類確定是ABC的子類型,故全部輸出都是true

其實通常狀況mixin中間類和接口都是不能直接引用的,好比這種狀況:

class BC extends A with B, C {}//這種狀況咱們沒法直接引用中間AB混合類和接口、ABC混合類和接口
複製代碼

可是若是這麼寫,就能直接引用中間混合類和接口了:

class A {
  void printMsg() => print('A');
}
mixin B {
  void printMsg() => print('B');
}
mixin C {
  void printMsg() => print('C');
}

class AB = A with B; class ABC = AB with C; class D extends AB {}

class E implements AB {
  @override
  void printMsg() {
    // TODO: implement printMsg
  }
}

class F extends ABC {}

class G implements ABC {
  @override
  void printMsg() {
    // TODO: implement printMsg
  }
}

main() {
  var ab = AB();
  print(ab is A);
  print(ab is B);

  var e = E();
  print(e is A);
  print(e is B);
  print(e is AB);

  var abc = ABC();
  print(abc is A);
  print(abc is B);
  print(abc is C);

  var f = F();
  print(f is A);
  print(f is B);
  print(f is C);
  print(f is AB);
  print(f is ABC);
}
複製代碼

輸出結果:

true
true
true
true
true
true
true
true
true
true
true
true
true

Process finished with exit code 0
複製代碼

參考資料

總結

到這裏,有關dart中面向對象以及mixins的內容就結束。這篇文章已經對Mixins原理進行了詳細以及全面的分析, 再不用擔憂啥時候使用mixins以及用起mixin來內心沒底。面向對象結束,下一篇文章咱們將進入Dart中的類型系統和泛型以及Dart將來版本將要支持的非空和可空類型。

個人公衆號

這裏有最新的Dart、Flutter、Kotlin相關文章以及優秀國外文章翻譯,歡迎關注~~~

Dart系列文章,歡迎查看:

相關文章
相關標籤/搜索