iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
0
自我挑戰組

從零開始的Flutter世界系列 第 8

Day08 Dart 語言介紹(六) 抽象類別、介面、混合類別

抽象類別 (Abstract Class)

  1. 與一般類別一樣,具有field,constructors,methods

  2. 關鍵在於一定會有抽象方法(只有方法名,無內容) → 型態 方法名( [引數] ) ;

  3. 只要有一個抽象方法,類別一定也要改為抽象 → abstract class

  4. 變成抽象類別後,本身無法實體化,即無法被 new,只剩提供繼承的功能,去當父類別的角色,或做為宣告的類別。但在Dart 語言中,並未定義介面interface這個關鍵字,而是讓類別有Implicit interfaces 隱式介面的特性,及類別可以當介面使用,故抽象類別常拿來定義介面interface使用 (介面等等會介紹)

  5. 繼承抽象類別的子類別

    1. 一定要將父類別的抽象方法都實作出來 (即父類別的所有抽象方法都要被override),才可編譯
    2. 子類別也為抽象類別 (不推薦,不能被實體化)
    // 這個類是抽象類,不能實體化
    abstract class AbstractContainer {
      // 定義建構式、變數、方法等等
    
      void updateChildren(); // 抽象方法.
      void show() {
        print('AbstractContainer show');
      }
    }
    
    class SpecializedContainer extends AbstractContainer {
      // 定義更多的建構式、變數、方法等等
    
      //一定要將父類別的抽象方法都實作
      @override
      void updateChildren() {
        print('SpecializedContainer implement updateChildren');
      }
    }
    
    main() {
    //   AbstractContainer a = AbstractContainer(); //會報錯,Error: The class 'AbstractContainer' is abstract and can't be instantiated.
    
      SpecializedContainer s = SpecializedContainer();
      s.updateChildren(); //印出 SpecializedContainer implement updateChildren
      s.show(); //印出 AbstractContainer show
    }
    

介面 (Interface)

每個類別都會背地裡定義一個包含所有 instance 例項成員的介面 (Implicit interfaces),也就是說類別本身也可以是介面,只要用 implements 類別就能將類別當作是interface 使用,如果有類別去實作這個介面,它就必須要去override掉介面裡的每個函式,包括Field 區的getter 和setter

// 把Person 當介面去實作的話
class Person {
  final _name; //實作的類別需要實作 _name 的getter (常數)
  
  // 介面不包含建構式
  Person(this._name);

  // 實作的類別需要實作此方法
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// 把Person 當介面去實作的類別
class Impostor implements Person {
  @override
  get _name => '';
  
  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy'))); //印出Hello, Bob. I am Kathy.
  print(greetBob(Impostor())); //印出 Hi Bob. Do you know who I am?
}

簡而言之就是不管介面本身屬性、方法定義了什麼,只要被當介面去實作了,實作它的類別就必須把介面中的所有方法和欄位都重新覆寫實作,並且一個類別可實現多個介面

abstract class Animal {}

abstract class Run {
  void run();
}

abstract class Swim {
  void swim();
}

abstract class Fly {
  void fly();
}

class Frog extends Animal implements Run, Swim {
  @override
  void run() {
    print('I can run.');
  }

  @override
  void swim() {
    print('I can swim.');
  }
}

class Fish extends Animal implements Swim {
  @override
  void swim() {
    print('I can swim.');
  }
}

class Swan extends Animal implements Fly, Run, Swim {
  @override
  void fly() {
    print('I can fly.');
  }

  @override
  void run() {
    print('I can run.');
  }

  @override
  void swim() {
    print('I can swim.');
  }
}

多型、抽象類別、介面實作範例:

//設計一個Animal 抽象類別,並把之後要新建的動物系列類別的想設計的共同屬性、方法拉出來,以方便之後繼承使用
abstract class Animal {
  String _name;
  String _gender;
  int _age;

  String get _species; //繼承Animal 的類別,需要實作的getter 方法
  List<String> get _dietList;

  Animal(this._name, this._gender, this._age);

  void introduce() {
    print('My name is $_name, $_age years old, and $_gender.');
  }
}

abstract class Mammals {
  //哺乳類,要給哺乳類系列的動物當介面實作,其每個實作的類別皆有這些特性,但每個類別去實作時又有獨特性
  String hairAndFur();
  //...
}

abstract class Birds {
  int flySpeed();
  //...
}

class Bear extends Animal implements Mammals {
  Bear(String name, String gender, int age) : super(name, gender, age);

  @override
  String get _species => 'Mammals';

  @override
  List<String> get _dietList => ['fruit', 'fish', 'grass'];

  @override
  String hairAndFur() {
    return 'black and hairy';
  }

  @override
  void introduce() {
    super.introduce();
    print(
        'The Bear, species is $_species, main food are ${_dietList.join(", ")}, has ${hairAndFur()} hair and fur');
  }
}

class Monkey extends Animal implements Mammals {
  Monkey(String name, String gender, int age) : super(name, gender, age);

  @override
  String get _species => 'Mammals';

  @override
  List<String> get _dietList => ['fruit', 'bug'];

  @override
  String hairAndFur() {
    return 'brown and hairy';
  }

  @override
  void introduce() {
    super.introduce();
    print(
        'The Monkey, species is $_species, main food are ${_dietList.join(", ")}, has ${hairAndFur()} hair and fur');
  }
}

class Eagle extends Animal implements Birds {
  Eagle(String name, String gender, int age) : super(name, gender, age);

  @override
  String get _species => 'Birds';

  @override
  List<String> get _dietList => ['fish', 'bug', 'rat'];

  @override
  int flySpeed() {
    return 100;
  }

  @override
  void introduce() {
    super.introduce();
    print(
        'The Eagle, species is $_species, main food are ${_dietList.join(", ")}, has ${flySpeed()} km/h fly speed');
  }
}

void main() {
  List<Animal> animals = [ //多型
    Bear('Winnie', 'male', 18),
    Monkey('Goku', 'male', 7),
    Eagle('FatFat', 'female', 12)
  ];

  animals.forEach((animal) => animal.introduce());
  /*
My name is Winnie, 18 years old, and male.
The Bear, species is Mammals, main food are fruit, fish, grass, has black and hairy hair and fur
My name is Goku, 7 years old, and male.
The Monkey, species is Mammals, main food are fruit, bug, has brown and hairy hair and fur
My name is FatFat, 12 years old, and female.
The Eagle, species is Birds, main food are fish, bug, rat, has 100 km/h fly speed
  */
}

混合類別 (Mixin)

  • 讓我們看看上面使用 run()swim()fly()、當介面的例子,我們看到每個去實作介面的類別們,並沒有獨特性,而是需要用同樣的內容去重複的覆寫實作,而為了解決重複冗餘的代碼,方法就是mixin

  • Mixin是一種在多個類別層次結構中讓我們能復用類別代碼的方法,相比約束性很強的接口來說顯得更加有彈性,是能夠為類別新增功能的方法,通常都是將通用 的特性拉出來,寫成一個 mixin,也方便覆用在多個地方。

  • 雖然mixin跟介面一樣,在所有類別皆有隱式定義,但還是建議用明確的mixin去宣告我們的類別,這樣此類別只能被當作mixin使用,就不會被實體化或被繼承 → 

    • 使用mixin宣告
    • extends後、implements前,使用with 混合類別來使用mixin
    abstract class Animal {}
    
    //改為用 mixin
    mixin Run {
      //通用的方法就能定義在這,有需要使用此功能的都能再mixin Run,即能使用
      void run() {
        print('I can run.');
      }
    }
    
    mixin Swim {
      void swim() {
        print('I can swim.');
      }
    }
    
    mixin Fly {
      void fly() {
        print('I can fly.');
      }
    }
    
    class Frog extends Animal with Run, Swim {}
    
    class Fish extends Animal with Swim {}
    
    class Swan extends Animal with Fly, Run, Swim {}
    
    main() {
      Frog froggy = Frog();
      froggy.swim();
      froggy.run();
      /* 印出
      I can swim.
      I can run.
      */
    
      Fish dollarFish = Fish();
      dollarFish.swim();
      //印出 I can swim.
    
      Swan uglyDuck = Swan();
      uglyDuck.fly();
      uglyDuck.run();
      uglyDuck.swim();
      /* 印出
      I can fly.
      I can run.
      I can swim.
      */
    }
    
  • 限定

    我們可以限定拉出來當混合類別的功能行為只能限定在某些類別才能使用:

    abstract class Animal {}
    
    mixin Breathe on Animal {
      //限定只有Animal 類別(或子類別),可以使用此混合類別
      void breathe() {
        print('I can breathe.');
      }
    }
    
    //class Robot with Breathe {} //會報錯,不是Animal 類別
    
    class Person extends Animal with Breathe {} //成功,不會報錯
    
  • 也可以對混合類別做限制:

    像是跑之前,一定要先學走路

    abstract class Animal {}
    
    mixin Walk {
      void walk() {
        print('I can walk.');
      }
    }
    
    mixin Run on Animal, Walk { //我們限定只有Animal 類別(或子類別),而且要有走路的混合類別,才可以使用此混合類別
      void run() {
        print("I can run.");
      }
    }
    
    //class Person extends Animal with Run {} //會報錯
    
    //class Person with Walk, Run {} //會報錯
    
    class Person extends Animal with Walk, Run {} //成功,不會報錯
    
  • 線性化

    問題點:如果with後的多個混合類別中有相同的方法,那麼當調用該方法時,會調用哪個類裡的方法

    例如:

    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); //印出 BA
    }
    

    我們會發現,最後一個mixin的函數,被調用了,這說明最後一個混入的mixins會覆蓋前面一個mixins的特性

    Dart 中的mixin通過創建一個類別來實現,該類將mixin的實現層疊在一個父類別之上以創建一個新類,它不是在父類別中,而是在父類別的頂部

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

    在語義上等同於:

    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 {}
    

    最終的類別層次結構可以用下圖表示:

    Alt text

    來源

    類別 AB,為父類別 P,與A 類別以及B 類別做混合完後的類別

    • mixin可以實現類似多重繼承的功能,但是實際上和多重繼承又不一樣
    • 它與單繼承兼容,因為它是線性的,可當作一條繼承鏈
    • with 混合類別 的順序代表了繼承鏈的繼承順序,而最後面with 的混合類別,會最先執行

總結

這兩天的內容講得比較慢,觀念也比較多,但是很重要,有關於物件導向後面Flutter 也會大量去使用它們,以及之後專案的架構等等,我們需要了解原理到底是怎麼設計跟為什麼這樣設計,養成良好程式碼的習慣


上一篇
Day07 Dart 語言介紹(五) 繼承、多型
下一篇
Day09 Dart 語言介紹(七) 泛型、Extension
系列文
從零開始的Flutter世界30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言