4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Dart factory

Last updated at Posted at 2024-01-04

Dart の factory とは

プログラミングデザインパターンの「ファクトリパターン」の一部が言語によってサポートされたもの。

ファクトリーパターンとは、クラスのインスタンス化、インスタンス化に伴うロジックをクライアントから隠蔽するためのデザインパターンのこと。

一般的に、プログラムは「具象に依存するよりも、抽象に依存した方が良い」とされていて、ファクトリパターンを使用することで、これが実現できる。

Named Constructor との違い

Named Constructor とは

Dart には Named Constructor と呼ばれる名前付きコンストラクタがあり、factory と似た機能を持っている。

Named Constructor は、一つのクラスが複数のコンストラクタを持てるようにしたものであり、様々な用途に応じたインスタンスを使い分けることができる。

ただし、Named Constructor を持つクラスを継承しても、Named Constructor は継承されないため、継承したい場合は明示的に呼び出す必要があることに注意。

複数のコンストラクタというと、Javaでは、引数の型や数が違うコンストラクタを複数定義するオーバーロードという記法があるが、Dartにはそのような記法は存在しない。

以下では Named Constructor を利用することで、異なるデフォルト値を持ったインスタンスを生成している。

Named Constructor
class Human {
  String name;
  int age;

  // 通常のコンストラクタ
  Human({required this.name, required this.age});
  
  // Named Constructor①
  Human.sample() : this.name = 'sample', this.age = 10;
  
  // Named Constructor②
  Human.test() : this.name = 'test', this.age = 99;  
}

void main() {
  var taro = Human(name: 'taro', age: 30);
  print(taro.name);
  // >> taro
  
  var sample = Human.sample();
  print(sample.name);
  // >> sample
  
  var test = Human.test();
  print(test.name);
  // >> test
}

これと同じことを、「factory」を使用して記述すると以下のようになる。

factory を使用した場合
class Human {
  String name;
  int age;

  // 通常のコンストラクタ
  Human({required this.name, required this.age});
  
  // factoryコンストラクタ①
  factory Human.sample() {
    return Human(name: 'sample', age: 10);
    
  } 
  
  // factoryコンストラクタ②
  factory Human.test() {
    return Human(name: 'test', age: 99);
  }   
}

void main() {
  var taro = Human(name: 'taro', age: 30);
  print(taro.name);
  // >> taro
  
  var sample = Human.sample();
  print(sample.name);
  // >> sample
  
  var test = Human.test();
  print(test.name);
  // >> test
}

この2つの例では、Named Constructor と factory どちらを選ぶべきかがよくわからない。また両者に違いがないように見えるが、実際には以下のような違いがある。

Named Constructor との違い①

Named Constructor によるコンストラクタでは、明示的にインスタンスを返却する必要はないのに対して、factory によるコンストラクタでは、明示的にreturn文でインスタンスを返却する必要がある。

Named Constructor との違い②

Named Constructor によるコンストラクタは、定義されたクラスと同じ型のインスタンスしか返却できないのに対して、factory によるコンストラクタでは、定義されたクラス型および、定義されたクラスのサブクラス型のインスタンスを返すことができる。

factory はサブクラスを返却できる
class Human {
  String name;
  int age;

  // 通常のコンストラクタ
  Human({required this.name, required this.age});

  // factoryコンストラクタ
  factory Human.getInstance({required bool isJapanese}){
    if(isJapanese){
      // ここでは Japanese 型を返している
      return Japanese(name: 'taro', age: 30);
    } else {
      // ここでは Foreigner 型を返している
      return Foreigner(name: 'gest', age: 50);
    }
  }
}

// サブクラス
class Japanese extends Human {
  Japanese({required super.name, required super.age});
}

// サブクラス
class Foreigner extends Human {
  Foreigner({required super.name, required super.age});
}

void main() {
  var taro = Human.getInstance(isJapanese: true);
  print(taro.name);  
  // >> taro
  
  var gest = Human.getInstance(isJapanese: false);
  print(gest.name);
  // >> gest
}

Named Constructor との違い③

factory によるコンストラクタは、静的メソッドという扱いになる。インスタンスがクラスから複数生成されるのに対して、静的メソッドはクラスに対して唯一ひとつしか存在しない。そのため、factory コンストラクタ内では、クラスのインスタンスメンバ(フィールドやメソッド)にアクセスすることはできない。代わりに、factoryコンストラクタ内でインスタンスを自ら生成する必要がある。

一方、Named Constructor によるコンストラクタ内では、インスタンスのメンバにアクセスすることができる。

factory の使い所

キャッシュ

factory を使用することで、インスタンスをキャッシュしておくことができる。これによって、インスタンス生成に負荷のかかるクラス、同じ目的で頻繁に何度も利用されるクラスのインスタンスを効率的に再利用することができる。

factory でキャッシュを実現するには、過去に生成したインスタンスを再利用するために、内部的に静的なフィールドに保持しておかなければならない。

factory を利用したキャッシュ
void main() {
  var result1 = Result(param: 10);
  print(result1.data.toString());
  
  var result2 = Result(param: 20);
  print(result2.data.toString());  
  
  var result3 = Result(param: 10);
  print(result3.data.toString());
  
  // >>インスタンスが生成されました!
  // >> 20
  
  // >> インスタンスが生成されました!
  // >> 30
  
  // >> 20
}

class Result {
  // キャッシュ
  static final Map<int, Result> _cache = {};
  
  late int data;
  
  factory Result({required int param}){
    // キャッシュ(_cache)にインスタンスがあれば再利用し、なければ新規作成する
    return _cache.putIfAbsent(param, () => Result._(param));
  }
  
  // プライベートなコンストラクタ
  Result._(int param){
    // ここで重たい処理が実行されているとする(ここの処理を何度も実行したくない)
    data = param + 10;
    print('インスタンスが生成されました!');
  } 
}

シングルトンパターン

シングルトンパターンとは、ファクトリパターンと同様にプログラミングのデザインパターンの一つ。インスタンスが唯一1つしか存在しないクラスを実現することができる。

実現方法が似ているため、キャッシュと同じようにも思えるが、キャッシュが一時的なデータの保存・再利用を目的としているのに対して、シングルトンパターンでは単一のインスタンスを共有することに焦点が当たっている。キャッシュで保持されるデータは、不要になれば削除することもできるが、シングルトンパターンでは、アプリケーションが終了するまで存在し続ける必要がある。

factory を利用したシングルトンパターン
import 'dart:math';
  
void main() {
  var instance1 = Singleton();
  print(instance1.data.toString());
  
  var instance2 = Singleton();
  print(instance2.data.toString());
  
  var instance3 = Singleton();
  print(instance3.data.toString());
  
  // >> インスタンスが生成されました!
  // >> 12
  // >> 12
  // >> 12
}

class Singleton {
  int data;
  
  // 唯一無二のインスタンスを保持する static な変数
  static Singleton? _instance;
  
  factory Singleton(){
    // 複数のインスタンスが生成されていないことの確認用に、インスタンス生成時にランダムな値を生成しておく
    _instance ??= Singleton._(Random().nextInt(100));
    return _instance!;
  }
  
  // プライベートなコンストラクタ
  Singleton._(this.data){
    print('インスタンスが生成されました!');
  }
}

サブクラス選択のロジック隠蔽

「Named Constructor との違い②」のように、インスタンスをどのクラスから生成するかのロジックをクライアント側から隠したい、カプセル化したい場合などに利用できる。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?