Dartのクラス設計は、単なる単一継承にとどまらず、柔軟で再利用性の高いコード構造を実現するための高度な機能を豊富に備えています。
すべてのクラスが暗黙的にインターフェースとして機能する「暗黙的インターフェース」、多重継承のメリットを安全に享受できる「Mixin(ミキシン)」、既存のSDKクラスに新しいメソッドを生やすことができる「拡張メソッド(Extension)」、そしてメソッドやプロパティを持てる「Enhanced Enum」などです。
この章では、Dartならではのオブジェクト設計のベストプラクティスを学びます。
extends(継承)と implements(インターフェース実装)の違いDartでは、すべてのクラスが自動的に 暗黙的インターフェース(Implicit Interface) を定義しています。これにより、任意のクラスを implements の対象として利用できます。
extends (単一継承): 親クラスの実装(コード)を引き継ぎます。implements (インターフェース実装): 親クラスの型シグネチャのみを引き継ぎ、全メソッドを自前で @override 再実装します(複数指定可能)。class Animal {
void speak() {
print('動物が鳴きます');
}
}
// 1. extends: 実装を引き継ぎオーバーライド
class Dog extends Animal {
@override
void speak() {
print('ワンワン!');
}
}
// 2. implements: 型のみ流用し全メソッドを自前実装
class RobotDog implements Animal {
@override
void speak() {
print('ビープ!ワンワン (電子音)');
}
}
void makeNoise(Animal animal) {
animal.speak();
}
void main() {
makeNoise(Dog());
makeNoise(RobotDog());
}dart run extends_implements.dartワンワン! ビープ!ワンワン (電子音)
mixin、with)による機能の注入Mixin は、クラス階層に縛られることなく、複数のクラス間で実装コード(メソッドやプロパティ)を共有・注入するための仕組みです。
mixin キーワードで定義し、クラスに with キーワードで適用します。
// 1. ロギング機能を提供する Mixin
mixin Logger {
void log(String message) {
print('[LOG ${DateTime.now().hour}:${DateTime.now().minute}] $message');
}
}
// 2. 永続化機能を提供する Mixin
mixin Serializable {
Map<String, dynamic> toJson();
}
// クラスに with で複数の Mixin を合成
class AppUser with Logger, Serializable {
final String name;
AppUser(this.name);
void login() {
log('ユーザー $name がログインしました');
}
@override
Map<String, dynamic> toJson() => {'name': name};
}
void main() {
final user = AppUser('Alice');
user.login();
print('JSON: ${user.toJson()}');
}dart run mixins_demo.dart[LOG 5:20] ユーザー Alice がログインしました
JSON: {name: Alice}Extension(拡張メソッド) を使うと、既存のクラス(Dartの組み込み型やサードパーティ製ライブラリのクラス)のソースコードを変更することなく、新しいメソッドやゲッター、演算子を追加できます。
構文は extension ExtensionName on TargetType { ... } です。
// String クラスに拡張メソッドを追加
extension StringExtensions on String {
// ゲッター: 先頭文字を大文字にする
String get capitalize {
if (isEmpty) return this;
return this[0].toUpperCase() + substring(1);
}
// メソッド: 安全に整数に変換する
int? toIntOrNull() => int.tryParse(this);
}
void main() {
String word = 'flutter';
print('capitalize: ${word.capitalize}');
String numStr = '42';
print('toIntOrNull: ${numStr.toIntOrNull()}');
}dart run extension_methods.dartcapitalize: Flutter toIntOrNull: 42
Dart 2.17以降の Enhanced Enum では、列挙型の各値にフィールド(値)、コンストラクタ、ゲッター、メソッドを持たせることができます。
単なる定数の列挙を超えて、型安全で表現力豊かなドメインモデルを簡潔に定義できます。
enum HttpStatus {
ok(200, 'Success'),
notFound(404, 'Not Found'),
serverError(500, 'Internal Server Error');
// フィールド
final int code;
final String description;
// const コンストラクタ
const HttpStatus(this.code, this.description);
// ゲッター
bool get isSuccess => code >= 200 && code < 300;
// メソッド
void log() {
print('[$code] $description (成功: $isSuccess)');
}
}
void main() {
final status = HttpStatus.notFound;
status.log();
print('HttpStatus.ok isSuccess: ${HttpStatus.ok.isSuccess}');
}dart run enhanced_enums.dart[404] Not Found (成功: false) HttpStatus.ok isSuccess: true
この章では、Dartの高度なオブジェクト指向設計機能について学びました。
extends vs implements: extends は実装コードを引き継ぎ、implements は暗黙的インターフェースの型契約のみを満たす。mixin, with): 継承階層とは直交して、複数クラスへ横断的な機能・振る舞いを注入する。extension on): 既存のクラスを変更することなく、便利メソッドやプロパティを外付けで拡張する。次の 第8章 では、Dart 3で導入された sealed, base, interface, final などの クラス修飾子 を使った厳密なドメインモデリングを学びます。
イベントの発生ログを出力する PrintableLogger Mixin を作成し、クラスに適用してください。
mixin PrintableLogger を定義する。
void logEvent(String eventName) を持ち、'[${runtimeType}] イベント発生: $eventName' と出力する。class OrderService with PrintableLogger を定義し、メソッド void checkout(String itemId) 内で logEvent('注文完了: $itemId') を呼び出す。main() で OrderService のインスタンスを作成し、checkout('item_999') を実行する。// ここにコードを書いてください
void main() {
// ここで動作確認を行ってください
}dart run practice7_1.dartString クラスに対して、特定の文字列操作を行う拡張メソッドを実装してください。
extension StringUtils on String を定義する。
bool get isEmail: 文字列に @ と . が含まれているかを簡易判定する。String mask({int visibleCount = 2}): 先頭から visibleCount 文字だけ残し、以降を * でマスクした文字列を返す(文字数が visibleCount 以下の場合はそのまま返す)。main() で 'user@example.com' に対する isEmail の判定結果と、mask() によるマスク文字列(例: 'us**************')を出力する。// ここにコードを書いてください
void main() {
// ここで動作確認を行ってください
}dart run practice7_2.dart