【入門編】Dartの「extension types」とパターンマッチングを組み合わせて、型安全なドメインモデルを構築する – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!FlutterやDartの開発現場で、日夜コードと向き合っている先輩エンジニアです。

皆さんは、Dart 3で導入された「パターンマッチング」や「Extension Types(拡張型)」、もう実際の開発で使ってみましたか?
「名前は聞いたことがあるけれど、なんだか難しそう……」
「今までのクラス(`class`)と何が違うの?」

そんな風に思っていませんか?ご安心ください!ここをクリアすれば、あなたの書くDartコードの安全性とパフォーマンスは、プロのアーキテクチャ水準に一気に引き上げられますよ。

今回は、この2つの強力な機能を組み合わせて、「実行時のオーバーヘッドをゼロにしつつ、型安全性を極限まで高めたドメインモデル」を構築する方法を、基礎から優しく紐解いていきましょう。

—

1. なぜ「型安全性」と「パフォーマンス」の両立が難しいのか?

まずは背景からお話ししますね。
堅牢なアプリを作ろうとすると、私たちはつい次のようなコードを書きがちです。

// 従来のクラスを使ったラップ手法
class UserId {
final String value;
UserId(this.value);
}

class OrderId {
final String value;
OrderId(this.value);
}

「ユーザーID」と「注文ID」をうっかり間違えて渡さないように、別々のクラスで包む(ラップする)のはドメイン駆動設計の基本です。

しかし、これをやるとDart VMのヒープ上に無数の小さなオブジェクトが生成され、メモリ効率やGC(ガベージコレクション)のプレッシャーが増すという代償を払うことになります。

「じゃあ、素の `String` や `int` のプリミティブ型に戻そうか……」
それだと今度は、引数の順番を間違えてバグを生む(型安全性の崩壊)というジレンマにぶつかりますよね。

この「開発時の型安全性(人間にとっての優しさ)」と「実行時のゼロコスト(マシンにとっての優しさ)」のジレンマを鮮やかに解決するのが、Dart 3の Extension Types なのです。

—

2. Extension Types とは何か?(脳内イメージ)

Extension Types(`extension type`)は、一言で言うと「見た目は独自の型だけど、コンパイルされると中身のプリミティブ型に完全に消去(擦り替え)される魔法の箱」です。

イメージ図で考えてみましょう。

[ 開発時の世界 ]
UserId 型 ( “user_123” を包んでいる )
↓ コンパイラがマジックをかける!
[ 実行時の世界(Dart VM) ]
ただの String 型 ( “user_123” )

そう、実行時にはオブジェクトのインスタンスが生成されません。 メモリ上のコストは完全にゼロです。

実際の書き方を見てみましょう。

// Extension Typeの定義
extension type const UserId(String value) {
// バリデーションなどのロジックも持たせられます
bool get isValid => value.startsWith(‘usr_’);
}

`const` コンストラクタを強制できるため、コンパイル時定数としても扱えます。最高にクールだと思いませんか?

—

3. Dart 3 パターンマッチングで型安全に「分解」する

さて、型で綺麗に包んだのはいいけれど、今度はその中身を取り出して「状態に応じた処理」を書きたい場面が出てきますよね。ここで登場するのが パターンマッチング です。

例えば、ECサイトの「注文ステータス」を考えてみましょう。
ステータスには「未払い」「出荷済み」「キャンセル」があり、それぞれ保持するデータが異なります。これを安全にハンドリングしてみます。

実用的なドメインモデルのコード例

以下のコードをよく見てください。コンパイルから実行までの流れがイメージできるように、たっぷりとコメントを載せています。

// 1. 各種IDをExtension Typeでゼロコストに定義
extension type const OrderId(String value) {}
extension type const CustomerId(String value) {}

// 2. 注文ステータスを表現するシーテッドクラス(Sealed class)
sealed class OrderStatus {}

class PendingPayment extends OrderStatus {
final int amount;
PendingPayment(this.amount);
}

class Shipped extends OrderStatus {
final String trackingNumber;
Shipped(this.trackingNumber);
}

class Canceled extends OrderStatus {
final String reason;
Canceled(this.reason);
}

// 3. ドメインロジック:パターンマッチングによる安全な分岐
void processOrder(OrderId orderId, OrderStatus status) {
// switch文が式(Expression)として使えるのもDart 3の魅力!
String message = switch (status) {
// PendingPayment型を分解しつつ、中身のamountを取り出す
PendingPayment(:final amount) =>
‘注文 [${orderId.value}] は未払いです。請求額: ¥$amount’,

// Shipped型を分解
Shipped(:final trackingNumber) =>
‘注文 [${orderId.value}] は発送済みです。追跡番号: $trackingNumber’,

// Canceled型を分解
Canceled(:final reason) =>
‘注文 [${orderId.value}] はキャンセルされました。理由: $reason’,
};

print(message);
}

// 4. 実行エントリポイント
void main() {
// 実行時には余計なオブジェクトは作られず、純粋なStringとして扱われます
var myOrderId = OrderId(‘ORD-2023-001’);

var status = Shipped(‘1234-5678-9012’);

processOrder(myOrderId, status);
// 出力結果:
// 注文 [ORD-2023-001] は発送済みです。追跡番号: 1234-5678-9012
}

—

4. ここでハッとする!陥りやすい文法エラーと注意点

Extension typesとパターンマッチングを組み合わせる際、初学者が必ずといっていいほどハマる罠がいくつかあります。事前に知っておけば怖くありませんよ!

注意点1: Extension typeは「継承(extends)」ができない

「`UserId` を継承して `SpecialUserId` を作りたい!」と思うかもしれませんが、Extension typeはあくまで「既存の型をラップして見せ方を変えるもの」です。クラスのような継承関係は作れません(インターフェースのimplementsは可能です)。

注意点2: スイッチの網羅性チェック(Exhaustiveness checking)

Dart 3の強力な機能として、`sealed class` と `switch` を組み合わせたとき、「すべてのパターンを網羅していないと、コンパイルエラーになる」という性質があります。

// もし Canceled のケースを書き忘れると…
String message = switch (status) {
PendingPayment(:final amount) => ‘未払い’,
Shipped(:final trackingNumber) => ‘発送済み’,
// エラー! Canceled が考慮されていません!というコンパイルエラーが出る
};

これにより、「うっかり新しいステータスを追加したのに、処理を書き忘れた!」というバグを、本番リリースどころかコンパイル段階で100%防ぐことができます。これぞ型安全の極みです。

—

5. まとめ:Dartを掌握するということ

今回は、Extension Typesによる「ゼロコストの型安全性」と、Dart 3のパターンマッチングによる「堅牢な状態分解」を組み合わせたアーキテクチャをご紹介しました。

  • Extension Types: 実行時コストをかけずに、プリミティブ型にドメインの意味(型)を持たせる。
  • パターンマッチング (Sealed class × switch): データの分解と網羅的な分岐を安全に行う。

この2つを使いこなせるようになると、あなたの書くDartコードは、パフォーマンスの妥協を一切せずに、圧倒的に堅牢で保守性の高いものへと生まれ変わります。

「ここをクリアすれば、Dartの基本はバッチリマスターできますよ!」
ぜひ、明日からのFlutter/Dart開発にこの知見を取り入れてみてくださいね。それではまた次回のテック記事でお会いしましょう!

タイトルとURLをコピーしました