Menu
Coddy logo textTech

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() を取得します

それでは、それぞれをいつ使うべきでしょうか?異なるクラスが同じメソッドシグネチャを必要とするが、実装が異なる場合は、インターフェースを使用します。クラスがまったく同じ動作を共有する必要がある場合は、ミックスインを使用します。

特徴インターフェースミックスイン
提供するもの契約のみ契約 + コード
実装自分で記述する含まれている
キーワードimplementswith
最適な用途異なる振る舞い、同じAPI共有される振る舞い

両方のアプローチを組み合わせることもできます。インターフェースを使用してクラスができることを定義し、適切な場合にはミックスインを使用して共通の実装を提供します。Dartでは、両方を組み合わせる際、常に withimplements の前に置きます:

class MyClass with SomeMixin implements SomeInterface {
  // withはimplementsの前に記述します
}
challenge icon

チャレンジ

簡単

インターフェースとミックスインをいつ使用すべきかを示す通知システムを構築しましょう。実装が異なる必要がある通知用のインターフェースと、すべての通知タイプで同一であるべき共有のロギング動作のためのミックスインを作成します。

コードを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() を取得します

それぞれの使い分け:

  • 異なるクラスが同じメソッドシグネチャを必要とするが、実装が異なる場合は、インターフェースを使用します。
  • クラスがまったく同じ振る舞いを共有する必要がある場合は、ミックスインを使用します。
機能インターフェースミックスイン
提供するものコントラクトのみコントラクト + コード
実装自分で書く含まれている
キーワードimplementswith
最適な用途異なる振る舞い、同じ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') を呼び出す
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン