🌏
【Dart3.3】Extension typesについて
概要
Dart3.3
から利用可能になった機能の一つの拡張型。
※以下公式より抜粋
拡張型は、表現型と呼ばれる、基になる型のオブジェクトで使用できる一連の操作 (またはインターフェイス) に規律を適用します。
拡張型のインターフェイスを定義するときは、表現型の一部のメンバーを再利用したり、他のメンバーを省略したり、他のメンバーを置き換えたり、新しい機能を追加したりすることを選択できます。
要は、ラップした表現型を拡張型として定義し、意味のある型にして表現することができるよというもの。
...言葉だけではわかりづらいかもしれないので実際に実装を確認していきます。
実装
宣言
/// [Hoge]という拡張型を定義
/// 表現型[int]をラップ
extension type Hoge(int count) {
// ...
}
上記定義をすることにより
-
int get count
のように、暗黙的なゲッターが定義される- 厳密には
final
フィールドとして定義されている
- 厳密には
-
Hoge(int count) : count = count
のような、暗黙的なコンストラクターが定義される
そのため、以下のような記述が可能となる
void main() {
final hoge = Hoge(100);
final castHoge = hoge as int;
print(castHoge); // 100
}
コンストラクター
拡張メソッドとは違い、拡張型自体でコンストラクターを定義することが可能。
また、プライベートコンストラクターやファクトリーメソッドを定義することも可能
/// 複数のコンストラクターを定義できる
extension type Hoge(int count) {
Hoge.i(this.count);
Hoge.l(String length) : count = int.parse(length);
}
/// プライベートコンストラクターとして外からの参照を非表示にすることも可能
extension type const Fuga._(int count) {
Fuga.i(this.count);
/// factory methodを定義することも可能
factory Fuga.normal() {
return Fuga.i(100);
}
}
メンバー
基本的なメンバーの宣言が可能
extension type Hoge(int count) {
Hoge get myCount => this;
bool isMyCount(int value) => count == value;
}
void main {
final hoge = Hoge(100);
print(hoge.myCount); // 100
print(hoge.isMyCount(99)); // false
}
implements
以下のようなことが利用できる
- 拡張タイプにサブタイプ関係を導入することが可能
- 表現オブジェクトのメンバーを拡張型インターフェイスに追加可能
要は、Hoge
という拡張型に、表現型int
のメンバーを利用することが可能となったり
Dependency Inversion(依存性逆転の原則)
の様な、設計をすることも可能となる。
extension type Hoge(int count) implements int {
}
void main() {
final hoge = Hoge(100);
// [int]型のメンバー[isOdd]を使用することが可能となる
print(hoge.isOdd); // false
}
使用例
こちらの記事で紹介したResult
型をExtension types
を使って表現してみました。
class ApiRequest {
Result<bool, Exception> request() {
try {
// send api request...
return const Result.success(true);
} on Exception catch (e) {
return Result.failure(e);
}
}
}
/// Success / Failureをを返すfactory methodを定義する親拡張クラス
/// Resutl型自体はプライベートな拡張型として定義
extension type const Result<S, E extends Exception>._(
({S? value, E? exception}) _) {
const factory Result.success(S value) = Success<S, E>;
const factory Result.failure(E exception) = Failure<S, E>;
}
/// generic Sを返す拡張型Success
extension type const Success<S, E extends Exception>._(
({S? value, E? exception}) _) implements Result<S, E> {
const Success(S value) : this._((value: value, exception: null));
}
/// generic Eを返す拡張型Failure
extension type const Failure<S, E extends Exception>._(
({S? value, E? exception}) _) implements Result<S, E> {
const Failure(E exception) : this._((value: null, exception: exception));
}
やっていることは、sealed
クラスで定義したResult
型と何ら変わりわないです。
個人的には、Extension types
で定義したResult
型の方が良いなと感じました。
理由としては以下があがりますが、好みは分かれると思いますので、Result
型を検討している方は、参考にしていただけたら嬉しいです。
- 拡張型
Result
に各継承関係にある拡張型をfactory method
として宣言的に定義 / 集約できる -
Swift
に似たような呼び出しができて好き(Swiftの方が型省略ができるからもっと好きではある)
所感
Dart3.3
やFlutter3.19.0
では他にも新機能やレンダリングの改善などさまざまな改修が入りましたが、
Extension types
が個人的には嬉しいポイントでしたので紹介させていただきました。
他にも使用例などありましたらコメントなどいただけたら嬉しいです。
Discussion
間違いではないのですが、
final
フィールドですね。明示的なextension typeコンストラクタ(兼型(フィールドを含む)定義)です。
コメントありがとうございます!
おっしゃる通りですね!
公式の内容に惑わされていました。
finalフィールドでもありgetter property(厳密には違いますが)でもありますね。
こちらは暗黙的にextension type内でconstructorが定義されているものかと思うのですが(厳密には表現宣言自体が暗黙的なコンストラクター)、
明示的に宣言もできる(private constructorに対して)ためその様な表現となる
という解釈で齟齬なさそうでしょうか?(私の見落としなどでしたらご指摘いただきましと幸いです。)