はじめに
Dart 3.3で導入された extension type について考えてみます。
extension type は、既存の型に対して新しい型を定義し、特定の制約や振る舞いを追加できる機能です。
これにより、コードの安全性と可読性が向上します。
使い方を考えてみる
extension type の使い方を4つほど考えてみました。
1. 限定された値の集合を表す型
特定の値の集合のみを許容する型を定義したい場合に便利です。
extension type ID._(String id) {
static const _allowedIds = {'A0001', 'B0001', 'C0002', 'D1001'};
factory ID(String value) {
if (!_allowedIds.contains(value)) {
throw Exception('Invalid ID: $value');
}
return ID._(value);
}
}
main() {
// OK
ID("A0001");
// throw Exception
ID("NGID");
}
2. APIの制限・機能の隠蔽
既存のクラス(特に List や Map などのコレクション)の機能をあえて隠し、特定の操作だけを許可したい場合に最適です。
例えば、「追加はできるが、削除はできないリスト(ログ用など)」や「スタック構造」を作る場合、通常のクラスでラップするとメモリを余分に消費しますが、extension type ならゼロコストです。
// Listをラップしているが、Listのメソッド(add, removeなど)は直接見せない
extension type LogBuffer(List<String> _list) {
// 必要な操作だけを公開する
void addLog(String message) {
_list.add('${DateTime.now()}: $message');
}
// 読み取り専用のビューだけ返すとか
List<String> get entries => List.unmodifiable(_list);
int get count => _list.length;
}
void main() {
final logs = LogBuffer([]);
logs.addLog("Start");
logs.addLog("Processing");
// Listのメソッドは隠蔽されているため、コンパイルエラー
logs.removeAt(0);
print(logs.entries);
}
3. JSON/Map の型安全なラッパー
APIレスポンスなどの Map<String, dynamic> を扱う際、わざわざ fromJson を持つクラスを定義して全プロパティをコピー(インスタンス生成)するのは、巨大なデータだとコストがかかります。
extension type を使うと、Mapの実体のまま、プロパティアクセスのように見せることができ、構造的型付けのような振る舞いを実現できます。
extension type UserJson(Map<String, dynamic> _json) {
String get name => _json['name'] as String;
int get age => _json['age'] as int;
// ネストしたデータもラップして返すことが可能
bool get hasEmail => _json.containsKey('email');
}
void main() {
final apiResponse = {'name': 'Jiro', 'age': 30, 'email': 'jiro@example.com'};
final user = UserJson(apiResponse);
print(user.name);
}
Nullableなプロパティも扱えます。
String? get name => _json['name'] as String?;
4. 単位の取り違え防止
プリミティブ型に意味論的なラベルを貼ります。
例えば、同じ double でも「ピクセル」と「パーセント」や、「幅」と「高さ」を間違えて計算しないようにします。
extension type Px(double value) implements double {
// implements double をつけると、doubleのメソッド(+,-,*,/など)をそのまま使える
// Px同士の足し算だけ許可し、Percentとの計算を防ぐような制御も書ける
Px operator +(Px other) => Px(value + other.value);
}
extension type Percent(double value) {
// パーセントは 0.0 ~ 1.0 の範囲など、独自の計算ロジックを持たせる
double toValue(double total) => total * value;
}
void main() {
final width = Px(100);
final opacity = Percent(0.5);
// コンパイルエラーになる
final result = width + opacity;
print(width + Px(50)); // OK: 150.0
}
まとめ
extension type は、Dartにおいて型安全性とコードの可読性を向上させる強力なツールです。
特に、限定された値の集合を表す型、APIの制限・機能の隠蔽、JSON/Mapの型安全なラッパー、単位の取り違え防止など、様々なシナリオで役立ちます。
適切に活用することで、より堅牢でメンテナンスしやすいコードを書くことができます。
ぜひ試してみてください!