Flutterでコンストラクタの学習をしていて、リダイレクトコンストラクタ(Redirecting Constructors)というコンストラクタが出てきました。気になったのと現場でも使う機会があるかもだったのでまとめてみました。
リダイレクトコンストラクタについて知りたい方やコンストラクタの理解を深めたい方の参考になれば幸いです。
リダイレクトコンストラクタとは
リダイレクトコンストラクタ(Redirecting Constructors)とは、Dartのいくつかあるコンストラクタの一つです。
公式には、以下のような記載がありました。
Forwards calls to another constructor in the same class. 同じクラス内の別のコンストラクターに呼び出しを転送します。
自分は何もせず、「呼び出しだけを別のコンストラクタに渡す」というのがforwad(転送する)だと認識しています。
つまり、「自分はオブジェクトを作ったりせず、作る責任を別のコンストラクタに委ねるもの」がリダイレクトコンストラクタになります。
簡単なサンプル
実際にコードを見て理解を深めていきましょう。
下のコードはリダイレクトコンストラクタのシンプルな例になります。
class Cat {
String name;
// redirect construtor
Cat() : this.named("UNKNOWN");
// named constructor
Cat.named(this.name);
}コードの内容
上記のCatクラスの内容としては
- nameというフィールドを持っている
- リダイレクトコンストラクタを定義し、別のコンストラクタに初期化処理を委任
- 名前付きコンストラクタを定義し、初期化を確定させる
となります。
Cat() : this.named("UNKNOWN");このリダイレクトコンストラクタの部分で言うと、
- リダイレクトコンストラクタの書き方として
Cat()のようなコンストラクタ +:を使用したinitilalizer listの構成- initializer listには
this.を使用し同じクラスの別のコンストラクタを呼ぶ
- リダイレクトコンストラクタについて
final cat = Cat();のように引数なしで呼びさせるようにCat()と定義- 下記の場合だと
final cat = Cat();でコンパイルエラーに
class Cat {
final String name;
Cat.named(this.name);
}:を使ってinitializer listを書くthis.namedとthis.を使用しこのクラスの別のコンストラクタに委任するために定義this.named()=Cat.named()と同じ"UNKNOWN"という文字列を引数にセットする初期化処理を別のコンストラクタに委任
と言うイメージで、こちらの名前付きコンストラクタは以下のようなイメージです。
Cat.named(this.name);- こいつが初期化処理の責任を持つ(委任された受ける側)
Cat()ではなく.namedのように他と区別しやすいように名前付きコンストラクタにthis.nameでthis(このクラス)のname フィールドを初期化するCat.named(this.name)は下記と同じ内容でその省略形
// 引数として受け取った(String name)をフィールドthis.nameに代入
Cat.named(String name) {
this.name = name;
}Cat()が呼ばれた場合
final cat = Cat();この時の流れは、以下の通りです。
final cat = Cat();のCat()が呼び出されるthis.name("UNKNOWN")により、Cat.named(”UNKNOWN”)が呼び出されるnameに”UNKNOWN”が代入される- オブジェクト完成
インスタンスであるfinal cat = Cat(); によって呼び出された デフォルトコンストラクタ Cat()は、フィールドを直接初期化せず、Cat.named("UNKNOWN") に処理を委ねているだけになります。
Cat.named(”Tama”)が呼ばれた場合
final cat = Cat.named("Tama");この場合は、直接 Cat.nameが呼び出され、name に ”Tama"が代入される。
リダイレクトコンストラクタを使うメリット
上記のことから、メリットとしては
nameの初期化ロジックを1ヶ所に集約できる- フィールドが増えても修正箇所が増えにくい
finalやconstと組み合わせやすい
のようなメリットがあります。
Dart・Flutterでは、「初期化責任を1つのコンストラクタに集める」という設計が推奨される傾向にあります。
まとめ
- リダイレクトコンストラクタは同じクラス内の別コンストラクタに呼び出しを転送する
- 自身では初期化やロジックを持たない
- 実際にオブジェクトを作るのは、転送先のコンストラクタだけ
- 「生成の責任を委ねる」ための仕組み
この理解ができれば、リダイレクトコンストラクタの良さを知りながらうまく現場などでも活用できるのではないかと思います。
公式のサンプル
別のパターンとしてDart公式にサンプルがあったのでそちらも見てみましょう。 こちらもリダイレクトコンストラクタが「呼び出しを転送するだけ」という理解ができると思います。
class Point {
double x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(double x) : this(x, 0);
}A constructor might redirect to another constructor in the same class. A redirecting constructor has an empty body. The constructor uses
thisinstead of the class name after a colon (:). コンストラクタは、同じクラス内の別のコンストラクタにリダイレクトする場合があります。リダイレクトするコンストラクタの本体は空です。コンストラクタは、コロン (:) の後のクラス名の代わりにthisを使用します。
Point.alongXAxisは何をしていて・何をしていないのか
Point.alongXAxis(double x) : this(x, 0);このコンストラクタは、
xやyを直接初期化していない- 初期化ロジックを持っていない
- コンストラクタ本体
{}を持っていない
です。
代わりにやっていることは、
「Point.alongXAxis への呼び出しをPoint(x, 0) への呼び出しにそのまま渡す」
だけになります。
alongXAxisを呼び出してみる
final p = Point.alongXAxis(5);この時の流れとしては以下のようになる。
Point.alongXAxis(5)
│
▼ forward / delegate
Point(5, 0)
│
▼
x = 5, y = 0最終的に実行される初期化処理は Point(this.x, this.y)のみになる。
リダイレクトコンストラクタのルール
上記2つの通りかつ公式の文章にある通り、リダイレクトコンストラクタを使用する際は以下のようなルールがあります。
- 同じクラス内の別のコンストラクタにのみリダイレクトできる
- コンストラクタ本体
{}は書けない(空である) - initializer list の
:の後にthis(...)を書く - フィールドの初期化や処理は行わない
- 実際にオブジェクトを初期化するのは、リダイレクト先のコンストラクタのみ
公式の英語に対しての言い換えが上記となっています。
| 公式文 | あなたの説明 |
|---|---|
| might redirect | リダイレクトできる |
| empty body | 本体は空 |
| uses this | this(...) を使う |
| same class | 同一クラス内のみ |
まとめ
- Dart公式サンプルでも「呼び出しを転送するだけ」という理解で正しい
forwards/delegatesは同じ設計思想- リダイレクトコンストラクタは生成処理を持たない入口である
- 実際の初期化は、必ず1つのコンストラクタで行われる
最後に
Dartにおけるリダイレクトコンストラクタ(Redirecting Constructors)について説明してきました。いかがでしょうか。やっていることは複雑ではないと思います。ただいろんなコンストラクタの中の一つとして特徴や使用するメリットすると使い方などが変わってくるかなと思います。
どなたかのお役に立てれば幸いです。
ではまた。


コメント