対象読者
Dart言語について特に知っている必要はありませんが、他の言語の基本を知っている方を対象に説明いたします。特に、JavaScriptやTypeScriptもしくは、Javaなど言語を使ってプログラミングしたことがある方であれば、より理解がしやすくなります。
Factoryコンストラクタ
前回、Dartにおけるコンストラクタの定義方法について紹介しました。今回は、そこで紹介できなかったFactoryコンストラクタについて紹介します。
Factoryコンストラクタは、コンストラクタと言っても他のコンストラクタと大きく役割が異なります。説明だけではわかりにくいので、実際のコードなどを示しながら紹介します。
Factoryコンストラクタの作り方
Factoryコンストラクタの実装例がリスト1です。
abstract class PasswordHash{ // (1)Factoryキーワードをつけたコンストラクタを用意する factory PasswordHash(){ // (2)インスタンスを作成してreturnする return DefaultHash(); } }
Factoryコンストラクタを定義するには(1)のようにコンストラクタの前にFactoryキーワードを指定します。そして、実際のインスタンスを(2)のようにreturnで返します。このように、作り方は非常に簡単です。(2)で作成するインスタンスは必ずしも定義したクラスそのものである必要はありません。この仕組みを応用することで設計の自由度が広がります。
応用例(Factoryパターン)
先ほどのサンプルコードは、Factoryパターンというプログラミング手法を使ったコード例です。この「Factory」とは、日本語で言えば「工場」の意味を示しますが、プログラミングの世界では、図1のように呼び出し元が、具体的な実装クラスを意識せずに利用させるようにするものを「Factoryパターン」と呼びます。
実際の利用シーンでは、returnするインスタンスを作成する工程が複雑になることもあるため、Factory(工場)という用語が用いられています。
このFactoryパターンでの応用例がリスト2です。
// (1) abstractクラスとして制限をかける abstract class PasswordHash{ factory PasswordHash(){ return DefaultHash(); } // 省略 } // (2) 実装クラスを定義 class DefaultHash implements PasswordHash{ // (省略) } main(){ // (3) 利用者は DefaultHash というクラスの存在を知らなくてよい var hash = PasswordHash(); hash.convert("pswd"); }
(1)でabstractとしてクラスを作成しているのは、このクラス自体が実装を持たないクラスであることを示すためです。abstractクラスはインスタンス化できません。そのため、通常は(3)のような使い方はできません。しかし、それを可能にしているのがFactoryコンストラクタです。
(2)では、実装クラスを定義しています。インターフェースをそろえる必要があるので、PasswordHashをインターフェースとして利用しています(extendsを利用した継承でもこのケースではほぼ同様ですが、今回はより制限をはっきりさせるためにimplementsを利用しています)。extendsとimplementsによるクラスの扱い方については前回紹介したので、そちらを参照ください。
(3)のようにPasswordHashをインスタンス化していますが、実際のインスタンスはDefaultHashクラスのインスタンスです。しかし、そのことを開発者が意識する必要がないのが、Factoryパターンのポイントです。
応用例(singletonパターン)
もう1つの例として、singletonクラスの作成を紹介します。singletonパターンとは、図2の通り、1つしかインスタンスが生成されないことを保証するためのプログラミング手法です。
利用時にいくつものインスタンスを作成しても、実際にはインスタンスが他と共有されています。そのため、キャッシュ機能や共有データ、処理の効率化などいろいろなシーンで応用できるテクニックの1つです。
リスト3はsingletonパターンでFactoryコンストラクタを使った場合のコード例です。
class Singleton{ // (1) staticとしてインスタンスを事前に作成 static final Singleton _instance = Singleton._internal(); int _counter = 0; // (2) Factoryコンストラクタ factory Singleton(){ return _instance; } // (3) 内部で利用する別名コンストラクタ Singleton._internal(); int increment(){ return ++_counter; } } main(){ // (4) 利用するコード var inst1 = Singleton(); var inst2 = Singleton(); print(inst1.increment()); // 1となる print(inst2.increment()); // 2となる }
(1)でstaticな変数としてインスタンスを作成します。実際にインスタンスを作成する際には、(3)の別名コンストラクタを用いて作成します。続いて(2)のコンストラクタでは、(1)で作成したインスタンスを返すようにします。実際に利用する場合、(4)のように複数のインスタンスを作成しているように見えても、実際のインスタンスは1つとして扱えます。