Mixins

什麼是 Mixins

Mixin 是一種程式碼複用機制,能夠在彼此不相關的類別繼承體系當中共用程式碼,目的是批次為類別提供成員實作。概念上類似多重繼承,但它主要用途是為類別附加各種模組化功能。

使用 Mixin

透過關鍵字 with,後面接一個或多個 Mixin 名稱。

class Musician extends Performer with Musical {
  // 程式碼
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}
Code language: Dart (dart)

定義 Mixin

使用 mixin 關鍵字進行宣告。

補充說明:mixin class(Dart 3.0 以上版本)可同時作為一般類別、Mixin 使用。

強制規範

  1. mixin / mixin class 不能撰寫 extends
  2. 不可宣告任何產生建構子(generative constructor)
mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

class Musician with Musical {}

void main() {
  var m = Musician();
  m.canConduct = true;
  m.entertainMe(); // Waving hands
}
Code language: Dart (dart)

執行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Musician is playing
Musician is playingCode language: CSS (css)

Mixin 需要呼叫外部成員

Mixin 自身無法實作的方法 / 屬性

Mixin 經常需要呼叫本身沒有實作的成員,底下提供四種標準做法。

在 Mixin 內宣告抽象成員

在 Mixin 宣告抽象方法,所有套用此 Mixin 的類別都必須實作這個抽象方法

mixin Musician {
  // 抽象方法,使用端必須實作
  void playInstrument(String instrumentName);

  void playPiano() {
    playInstrument('Piano');
  }
  void playFlute() {
    playInstrument('Flute');
  }
}

// 使用類別必須覆寫實作
class Virtuoso with Musician {
  @override
  void playInstrument(String instrumentName) {
    print('Plays the $instrumentName beautifully');
  }
}

void main() {
  var artist = Virtuoso();
  artist.playPiano();
  artist.playFlute();
}
Code language: Dart (dart)
Plays the Piano beautifully
Plays the Flute beautifully

透過抽象 Getter 存取宿主類別狀態

Mixin 定義抽象 getter,藉此讀取宿主類別的欄位,實現跨類別狀態存取。

/// 可以混入任何具有 name 屬性的類別,自動提供以 name 為基礎的 == 與 hashCode
mixin NameIdentity {
  String get name;

  @override
  int get hashCode => name.hashCode;

  @override
  bool operator ==(Object other) =>
      other is NameIdentity && name == other.name;
}

class Person with NameIdentity {
  final String name;
  Person(this.name);
}

void main() {
  var p1 = Person("Alice");
  var p2 = Person("Alice");
  var p3 = Person("Bob");
  print(p1 == p2); // true
  print(p1 == p3); // false
}
Code language: JavaScript (javascript)

Mixin 實作介面 implements

Mixin 使用 implements 約束介面,強制套用的類別補齊介面所有方法。

abstract interface class Tuner {
  void tuneInstrument();
}

mixin Guitarist implements Tuner {
  void playSong() {
    tuneInstrument();
    print('Strums guitar majestically.');
  }
}

class PunkRocker with Guitarist {
  @override
  void tuneInstrument() {
    print("Don't bother, being out of tune is punk rock.");
  }
}

void main() {
  PunkRocker().playSong();
}
Code language: JavaScript (javascript)
Don't bother, being out of tune is punk rock.
Strums guitar majestically.

on 子句:限制 Mixin 僅能被特定父類別的子類別使用

on 的作用:只有繼承指定型別的類別,才能套用這個 Mixin

同時 Mixin 內部可以呼叫父類別成員、執行 super.xxx()

只有當你需要在 Mixin 內使用 super 呼叫父類別方法時,才需要使用 on

class Musician {
  void musicianMethod() {
    print('Playing music!');
  }
}

// 僅能套用在 Musician 的子類別
mixin MusicalPerformer on Musician {
  void performerMethod() {
    print('Performing music!');
    super.musicianMethod(); // 呼叫被 on 約束的父類別方法
  }
}

// SingerDancer 繼承 Musician,因此可以套用 MusicalPerformer
class SingerDancer extends Musician with MusicalPerformer {}

void main() {
  SingerDancer().performerMethod();
}
Code language: Dart (dart)
Performing music!
Playing music!

SingerDancer 套用了 MusicalPerformer,因此可以使用裡面的 performerMethod 方法

混入的概念大致如此,一個類別可以直接取用另一個 Mixin 內的方法,直接進行使用。

⚠️ 錯誤範例:

如果類別沒有繼承 Musician,直接撰寫 with MusicalPerformer 會發生編譯錯誤。


class / mixin / mixin class 的差異

  1. mixin:只能透過 with 使用,不能使用 extends 繼承
  2. class:可使用 extends,無法直接當作 mixin 使用
  3. mixin class
    • 可以透過 with 作為 Mixin 使用
    • 也可以透過 extends 做為一般父類別

規範同樣適用:

  • mixin / mixin class 不能撰寫 extends、with
  • class / mixin class 不能使用 on
mixin class Musician {
  void play() {
    print("Musician is playing");
  }
}

// 作為 Mixin 使用
class Novice1 with Musician {}

// 作為一般父類別繼承
class Novice2 extends Musician {}

void main() {
  Novice1().play();
  Novice2().play();
}
Code language: Dart (dart)

執行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Musician is playing
Musician is playing
Code language: CSS (css)

Mixins

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *