Dart インターフェースと Mixin

Dart は単一継承のみをサポートしていますが、インターフェースと Mixin を使うことで柔軟に多重のコード再利用を実現できます。

この章では、implements を使ってインターフェースを実装する方法、mixin によるコード再利用、および with キーワードについて説明します。


implements によるインターフェースの実装

Dart では、すべてのクラスが暗黙的にインターフェースを定義しています。

implements キーワードを使うと、あるクラスに別のクラスのインターフェースを実装させることができ、しかも複数実装できます。

実例

// 「プリント可能」なインターフェースを定義
class Printable {
  // インターフェースのメソッドにはデフォルト実装がない
  // 実装者はすべてのメンバーをオーバーライドする必要がある
  void printContent() {
    // このメソッド本体は implements 時には無視される
  }
}

// 「保存可能」なインターフェースを定義
class Savable {
  void save() {}
}

// implements を使って複数のインターフェースを実装
class Document implements Printable, Savable {
  String title;
  String content;

  Document(this.title, this.content);

  // Printable インターフェースが要求するすべてのメソッドを実装する必要がある
  @override
  void printContent() {
    print('--- ドキュメント: $title ---');
    print(content);
    print('--- サンプル プリント終了 ---');
  }

  // Savable インターフェースが要求するすべてのメソッドを実装する必要がある
  @override
  void save() {
    print('ドキュメント "$title" をディスクに保存しました');
  }
}

void main() {
  var doc = Document('Dart チュートリアル', 'これは Dart 入門ガイドです');

  // ポリモーフィズム:Document は Printable でもあり Savable でもある
  Printable printable = doc;
  printable.printContent();

  Savable savable = doc;
  savable.save();
}
--- 文档: Dart 教程 ---
这是一份 Dart 入门指南
--- サンプル 打印结束 ---
文档 "Dart 教程" 已保存到磁盘

implements と extends の重要な違い:extends は親クラスのすべての実装(メソッド本体)を継承しますが、implements はインターフェースのシグネチャのみを継承するため、すべてのメソッドを再実装する必要があります。また、extends は1つのクラスしか継承できませんが、implements は複数のインターフェースを実装できます。

extends と implements の比較

特徴extendsimplements
数の制限1つしか継承できない複数実装できる
メソッドの実装親クラスの実装を継承するすべてのメソッドを自分で実装する必要がある
コンストラクタsuper() を呼び出せるコンストラクタは継承されない
使用シーン「〜である」関係「何ができるか」の契約

複数インターフェースの実装

Dart は複数のインターフェースを実装できます。これは単一継承の制限を打破する主な方法です。

実例

// 複数の振る舞いインターフェースを定義
class Flyable {
  void fly() {}
}

class Swimmable {
  void swim() {}
}

class Walkable {
  void walk() {}
}

// アヒルは飛ぶ、泳ぐ、歩くができる
class Duck implements Flyable, Swimmable, Walkable {
  String name;

  Duck(this.name);

  @override
  void fly() {
    print('$name は空を飛んでいます');
  }

  @override
  void swim() {
    print('$name は水面を泳いでいます');
  }

  @override
  void walk() {
    print('$name は陸上をよちよち歩いています');
  }
}

// 魚は泳ぐことしかできない
class Fish implements Swimmable {
  @override
  void swim() {
    print('魚は水中を泳いでいます');
  }
}

void main() {
  var duck = Duck('サンプル アヒル');

  // アヒルは複数の役割を担える
  (duck as Flyable).fly();
  (duck as Swimmable).swim();
  (duck as Walkable).walk();

  // 型チェック
  print('アヒルは飛べるか? ${duck is Flyable}');
  print('アヒルは泳げるか? ${duck is Swimmable}');

  var fish = Fish();
  print('魚は飛べるか? ${fish is Flyable}');
}
サンプル 鸭 在天空中飞翔
サンプル 鸭 在水面上游泳
サンプル 鸭 在陆地上摇摇晃晃地走
鸭子能飞吗? true
鸭子能游吗? true
鱼能飞吗? false

Mixin によるコード再利用

Mixin は、複数のクラス間でコードを再利用するための仕組みです。「複数のクラスが同じメソッドを共有する必要があるが、継承には適さない」という問題を解決します。

mixin キーワードで Mixin を定義し、with キーワードでクラスにミックスインします。

実例

// mixin キーワードで定義
mixin Logger {
  // Mixin のメソッドは複数のクラスで再利用できる
  void log(String message) {
    print('[${DateTime.now()}] $message');
  }

  void logError(String message) {
    print('[ERROR ${DateTime.now()}] $message');
  }
}

mixin TimestampFormatter {
  String formatTimestamp(DateTime dt) {
    return '${dt.year}-${dt.month.toString().padLeft(2, '0')}'
        '-${dt.day.toString().padLeft(2, '0')}';
  }
}

// with キーワードで Mixin をミックスイン
class UserService with Logger, TimestampFormatter {
  void createUser(String name) {
    log('ユーザー作成: $name');
    var now = DateTime.now();
    print('作成日時: ${formatTimestamp(now)}');
  }
}

class OrderService with Logger {
  void createOrder(String orderId) {
    log('注文作成: $orderId');
  }

  void cancelOrder(String orderId) {
    logError('注文キャンセル失敗: $orderId');
  }
}

void main() {
  var userService = UserService();
  userService.createUser('サンプル');

  var orderService = OrderService();
  orderService.createOrder('ORD-001');
  orderService.cancelOrder('ORD-001');
}
[2026-06-12 10:30:00.000] 创建用户: サンプル
创建时间: 2026-06-12
[2026-06-12 10:30:00.001] 创建订单: ORD-001
[ERROR 2026-06-12 10:30:00.001] 取消订单失败: ORD-001

UserService と OrderService は異なるビジネス領域に属しており、継承を使って Logger を共有するのは適切ではありません。

Mixin を使うことで、両方ともログ機能を取得でき、しかも互いに影響しません。

Mixin はコンストラクタを持つことができず、インスタンス化もできません。その役割は純粋です:再利用可能なメソッドを提供し、状態の初期化には関与しません。

Mixin の制限条件

Mixin は on キーワードを使って、特定の型のクラスでのみ使用できるように制限できます。

実例

// 基本クラス
class Animal {
  String name;
  Animal(this.name);
}

// この Mixin は Animal とそのサブクラスにのみミックスインできる
mixin FlyableMixin on Animal {
  void fly() {
    print('$name が飛び立ちました!');
  }
}

// Bird は Animal なので FlyableMixin を使える
class Bird extends Animal with FlyableMixin {
  Bird(String name) : super(name);
}

// 次の行はエラーになる:Car は Animal ではないので FlyableMixin を使えない
// class Car with FlyableMixin {} // エラー!

void main() {
  var bird = Bird('サンプル 小鳥');
  bird.fly();

  // FlyableMixin のメソッドは Animal のプロパティ name にアクセスできる
  print('小鳥の名前: ${bird.name}');
}
サンプル 小鸟 飞起来了!
小鸟的名字: サンプル 小鸟

class、Mixin、extends、implements の完全な組み合わせ

Dart では、1つのクラスで extends、with、implements を同時に使用できます:

実例

mixin Jumpable {
  void jump() => print('ジャンプ!');
}

mixin Runnable {
  void run() => print('走って進め!');
}

class Animal {
  void breathe() => print('呼吸...');
}

// Animal を継承し、Jumpable と Runnable をミックスインし、Comparable を実装
class Athlete extends Animal
    with Jumpable, Runnable
    implements Comparable<Athlete> {
  String name;
  int score;

  Athlete(this.name, this.score);

  @override
  int compareTo(Athlete other) => score.compareTo(other.score);

  void showSkills() {
    breathe();
    run();
    jump();
  }
}

void main() {
  var a1 = Athlete('サンプル 選手A', 95);
  var a2 = Athlete('選手B', 88);

  a1.showSkills();
  print('${a1.name} vs ${a2.name}: ${a1.compareTo(a2) > 0 ? "A 勝ち" : "B 勝ち"}');
}
呼吸...
跑步前进!
跳起来!
サンプル 选手A vs 选手B: A 胜

完全な宣言の順序:class サブクラス extends 親クラス with Mixin1, Mixin2 implements インターフェース1, インターフェース2

他の拡張