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 の比較
| 特徴 | extends | implements |
|---|---|---|
| 数の制限 | 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 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 を同時に使用できます:
実例
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
他の拡張