【Dart】シンプルに派生クラスを作る(別名の代わり)
クラスに別名を付けることができれば後述の問題が解決するのですが、今のところできないため、代わりに 元のクラスと機能は同じままで継承するシンプルな方法がないか調べてまとめた記事です。
※タイトルに「複製」という言葉を使っていましたが、同じものが出来上がるわけではないので変更しました。 id:ntaoo さんありがとうございました!
悩んだ問題
Flutter で provider を使うときに少し悩むことがあります。
同じ型の値を複数渡せないという問題です。
List
や Map
に入れるとできるのですが、ちょっとぎこちないやり方に思えます。
List 等を使わない場合、次のようになります。
MultiProvider( providers: [ Provider<TextEditingController>( builder: (_) => TextEditingController(), // a ), Provider<TextEditingController>( builder: (_) => TextEditingController(), // b ), ], child: Hoge(), )
class Hoge extends StatelessWidget { @override Widget build(BuildContext context) { // 二つとも b のオブジェクトになってしまう final titleController = Provider.of<TextEditingController>(context); final bodyController = Provider.of<TextEditingController>(context); return ...; } }
使用箇所に最も近い TextEditingController
(b のほう)しか取れません。
解決策
これは同じ型だから起こります。
それなら異なる型名を使えばいいですね。
そこで、クラスにエイリアス(別名)を付けることを考えたのですが、現時点ではできないことがわかりました。
代わりに同じ中身の別クラスを作ることなら可能です。
1) べたな方法
class TitleEditingController extends TextEditingController {}
2) 別の方法
class TitleEditingController = TextEditingController with Type;
with Type とは
もし 2 で with Type
を付けて Type
というものを mixin しないとエラーが出ます。
Classes can only mixin other classes.
Type
の定義を見ると、中身のない抽象クラスになっています。
abstract class Type {}
形だけでも mixin しないといけないということですね。
これはこれであまり美しくない感じがしますが、仕方ないですね。
注意
先ほども書いたのですが、これはエイリアスではありません。
既成のクラスに Mixin 適用して作った新たなクラス/型です。
typedef for simple type aliases · Issue #2626 · dart-lang/sdk · GitHub
class Foo = Bar with Mixin syntax is used to create a new type. Such that Foo != Bar
元の型とは当然別物として扱われます。
名前だけ異なる同じ型だと考えて使うと失敗する場合があるので注意してください。
class Foo = Bar with Type; print(Foo == Bar); // false(同じ型ではない) Bar bar = Foo(); // OK Foo foo = bar; // OK foo = Bar(); // エラー
作ったクラスのオブジェクトを元のクラスの型として扱うこと(アップキャスト)は可能です。
そのため、TextEditingController
を渡す部分に代わりに TitleEditingController
を渡すことができます。
ダウンキャストについては、アップキャストしたものを本来の型にダウンキャストするのはOKですが、いきなりダウンキャストするのはエラーになります。
制限
プリミティブな型には使えない
プリミティブな型にも使えると便利なのですが、それはできないようです。
例えば、int
を基にして myInt
を作ろうとしてもエラーになります。
// Classes can't extend 'int'. というエラーが出る class myInt = int with Type;
親のコンストラクタに引数があるなら継承する方法は不向き
親クラスが引数のあるコンストラクタを持っている場合、子クラスで親のコンストラクタを呼ばないといけないため、結局シンプルにできません。
Mixin する方法では const でなくなる
親が const
コンストラクタを持っていても、with Type
のほうの方法で作ったクラスのコンストラクタは const
になりませんでした。
今後の改善
クラスにもプリミティブな型にも typedef
でエイリアスを付けられるようになりそうです。
このことは先ほどと同じ issue でコメントされています。
As of 02bb437 (Jan 11, 2019) it is part of the language specification that you can write type aliases like typedef C = D
; and typedef D = Map<X, List >;, etc. There's an implementation plan. Right now some other things are being pushed harder than this feature, but it's accepted and in the pipeline.
Note that you can not use this feature to create new types (similar to Haskell's newtype or Pascal's type Temperature = Integer;), it creates an additional notation for an existing type. So if you declare typedef D = C; then it is type correct to have things like D d = C();.
typedef for simple type aliases · Issue #2626 · dart-lang/sdk
・https://github.com/dart-lang/sdk/issues/2626#issuecomment-464307003
・https://github.com/dart-lang/sdk/issues/2626#issuecomment-464638272
続きの議論は下記 issue で行われています。
定義の例
typedef C = D<int>; typedef E<X extends num> = Map<X, List<X>>; typedef UserId = int;
このように定義した型は、新たな型という扱いではなくちゃんと別名になるようです。
しかしまだ仕様が確定したわけではないと思います。
上記コメントの時点から半年以上経っていて、いつになるのかわかりません。
他の新機能の導入に人手を取られているようです。
気長に待ちましょう。
Dart 2.13 以降の typedef
Dart 2.13 にて typedef
が関数以外の型にも使えるようになりました。
しかし typedef
はあくまで別名であって元の型と同一なので、Provider.of<T>
を使うときに別名で区別しようとしてもダメでした。