Mixin vs インターフェース
CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 55/110。
ミックスインと継承の比較についてはすでに見ました。次に、ミックスインとインターフェースを比較してみましょう。どちらも複数の型を扱うことができるからです。主な違いは、それらが何を提供するかです。インターフェースは契約を定義し、ミックスインは実装を提供します。
インターフェースを実装する場合、特定のメソッドを提供することを約束しますが、コードはすべて自分自身で記述します。一方、ミックスインを使用する場合は、実際のコードをそのまま利用できます。
// インターフェースのアプローチ - すべてを実装する必要があります
abstract class Printable {
void printInfo();
}
class Report implements Printable {
@override
void printInfo() {
print('Printing report...'); // これを記述します
}
}
// Mixinのアプローチ - 実装が含まれています
mixin Printable {
void printInfo() {
print('Printing...'); // すでに記述されています
}
}
class Report with Printable {} // 自動的に printInfo() を取得しますそれでは、それぞれをいつ使うべきでしょうか?異なるクラスが同じメソッドシグネチャを必要とするが、実装が異なる場合は、インターフェースを使用します。クラスがまったく同じ動作を共有する必要がある場合は、ミックスインを使用します。
| 特徴 | インターフェース | ミックスイン |
|---|---|---|
| 提供するもの | 契約のみ | 契約 + コード |
| 実装 | 自分で記述する | 含まれている |
| キーワード | implements | with |
| 最適な用途 | 異なる振る舞い、同じAPI | 共有される振る舞い |
両方のアプローチを組み合わせることもできます。インターフェースを使用してクラスができることを定義し、適切な場合にはミックスインを使用して共通の実装を提供します。Dartでは、両方を組み合わせる際、常に with を implements の前に置きます:
class MyClass with SomeMixin implements SomeInterface {
// withはimplementsの前に記述します
}チャレンジ
簡単インターフェースとミックスインをいつ使用すべきかを示す通知システムを構築しましょう。実装が異なる必要がある通知用のインターフェースと、すべての通知タイプで同一であるべき共有のロギング動作のためのミックスインを作成します。
コードを3つのファイルに整理します:
notifiable.dart: インターフェースとして機能する抽象クラスNotifiableを定義します。2つの抽象メソッド、send(String message)とStringを返すgetChannel()を宣言する必要があります。異なる通知タイプは、それぞれの特定のチャネルに基づいて、これらのメソッドを異なる方法で実装します。logging.dart: すべての通知が持つべき共有の動作を提供するLoggingミックスインを作成します。[LOG] Sent via [channel]: [message]と出力するlogSent(String channel, String message)メソッドと、[LOG] Timestamp recordedと出力するlogTimestamp()メソッドを含めます。この動作はすべての通知タイプで同一であるため、ミックスインに最適です。main.dart: 両方のファイルをインポートし、インターフェースとミックスインを組み合わせた2つの通知クラスを作成します。Loggingミックスインを使用し、Notifiableを実装するEmailNotificationクラス。そのgetChannel()は'Email'を返し、send()メソッドはSending email: [message]と出力し、次にチャネルとメッセージを指定してlogSent()を呼び出し、最後にlogTimestamp()を呼び出す必要があります。Loggingミックスインを使用し、Notifiableを実装するSMSNotificationクラス。そのgetChannel()は'SMS'を返し、send()メソッドはSending SMS: [message]と出力し、次にチャネルとメッセージを指定してlogSent()を呼び出し、最後にlogTimestamp()を呼び出す必要があります。
EmailNotificationを作成してsend('Hello World')を呼び出し、空行を出力し、SMSNotificationを作成してsend('Quick update')を呼び出します。
インターフェースが各クラスに独自の send() および getChannel() の実装(異なる動作)を強制する一方で、ミックスインが両方で同一に機能するロギングメソッド(共有の動作)をどのように提供しているかに注目してください。
期待される出力:
Sending email: Hello World
[LOG] Sent via Email: Hello World
[LOG] Timestamp recorded
Sending SMS: Quick update
[LOG] Sent via SMS: Quick update
[LOG] Timestamp recordedチートシート
インターフェースはコントラクト(メソッドのシグネチャ)を定義し、ミックスインは実装(実際のコード)を提供します。
インターフェースを実装する場合、すべてのコードを自分で書く必要があります。
abstract class Printable {
void printInfo();
}
class Report implements Printable {
@override
void printInfo() {
print('Printing report...'); // これを自分で書きます
}
}ミックスインを使用すると、実装をそのまま利用できます。
mixin Printable {
void printInfo() {
print('Printing...'); // すでに書かれています
}
}
class Report with Printable {} // 自動的に printInfo() を取得しますそれぞれの使い分け:
- 異なるクラスが同じメソッドシグネチャを必要とするが、実装が異なる場合は、インターフェースを使用します。
- クラスがまったく同じ振る舞いを共有する必要がある場合は、ミックスインを使用します。
| 機能 | インターフェース | ミックスイン |
|---|---|---|
| 提供するもの | コントラクトのみ | コントラクト + コード |
| 実装 | 自分で書く | 含まれている |
| キーワード | implements | with |
| 最適な用途 | 異なる振る舞い、同じAPI | 共有される振る舞い |
両方のアプローチを組み合わせることもできます。組み合わせる場合は、常に implements の前に with を記述します。
class MyClass with SomeMixin implements SomeInterface {
// with は implements の前に記述します
}自分で試してみよう
import 'notifiable.dart';
import 'logging.dart';
// TODO: 以下の EmailNotification クラスを作成してください:
// - Logging ミックスインを使用する (with)
// - Notifiable を実装する (implements)
// - getChannel() が 'Email' を返す
// - send() が 'Sending email: [message]' を出力し、次に logSent() を呼び出し、その後に logTimestamp() を呼び出す
// TODO: 以下の SMSNotification クラスを作成してください:
// - Logging ミックスインを使用する (with)
// - Notifiable を実装する (implements)
// - getChannel() が 'SMS' を返す
// - send() が 'Sending SMS: [message]' を出力し、次に logSent() を呼び出し、その後に logTimestamp() を呼び出す
void main() {
// TODO: EmailNotification を作成し、 send('Hello World') を呼び出す
// TODO: 空行を出力する
// TODO: SMSNotification を作成し、 send('Quick update') を呼び出す
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4Null Safety
Null Safety 入門Nullable と Non-Nullable? 演算子と ! 演算子late キーワードと Null SafetyNull-Aware 演算子クラスにおける Null Safetyまとめ:ユーザープロフィールシステム10コレクションとジェネリクス
List, Set, Map の概要型安全なコレクションジェネリッククラスジェネリックメソッドジェネリクスの制約Iterable と Iteratorまとめ:ジェネリックなストレージ