🏭

Flutter でのよく見かけるFactory コンストラクトについて

2024/08/13に公開

Factory コンストラクトとは?

公式文書によると、下記の二つのケースに使うキーワードです。

When encountering one of following two cases of implementing a constructor, use the factory keyword:
The constructor doesn't always create a new instance of its class. Although a factory constructor cannot return null, it might return:

  1. an existing instance from a cache instead of creating a new one
  2. a new instance of a subtype

Factoryコンストラクトを付けると、そのクラスは新しいインスタンスを作りません。factoryコンストラクトはnullをリターンすることができないが、下記の二つは可能です。
一つ。 新しいものを作る代わりにキャッシュに存在するインスタンス。
二つ。 サブタイプの新しいインスタンス

なんとか新しいインスタンスは作らないということは分かりますが、実際どの場面で使えばいいのか少し難しく感じたのでじっくり考えたいですね。。。

なので、まず一番基本的な抽象クラスを作ってFactoryの使い方について調べてみました。

1. Vehicleの価格を持ってくる抽象classを考えてみよう。

まずgetPriceというメソッドを持つVehicleという簡単な抽象クラスをつくります。

abstract class Vehicle {
  double getPrice(); 
}

抽象クラスなのでCarクラスを継承するクラスはgetPrice()をoverrideする必要があります。

class Suv implements Vehicle {
  double price = 8.5;
  
  double getPrice(){
    return price;
  }
}

当然mainで実行してみると、Vehicle 1 Price : 8.5 という結果になりますね。

void main() {
  print("Vehicle 1 Price : ${Vehicle().getPrice()}");
  //Vehicle 1 Price : 8.5
}

2. factoryの使い方

本格的にfactoryを使ってみます。 下記のようにFactoryの次にVehicle (抽象クラス名)のメソッドを作成し、この時にリターンはSuv()にします。

ポイントはimplementsしたクラス(Suvクラス)をリターンすることです。

最初に言ったサブタイプの新しいインスタンスが何なのかここで気づきました。
Suv()をリターンすることによって、Vehicle(本タイプ)ではなくてSuv(サブタイプ)をリターンする。

abstract class Vehicle {
  double getPrice();
  factory Vehicle(){
    return Suv(); // ここ
  }
}

これで結果を確認してみましょう。抽象メソッドは実装部がないのにVehicle().getPrice()でimplementsした結果を呼び出すことができます。
問題なく出力されますね。

void main() {
  print("Vehicle 1 Price : ${Vehicle().getPrice()}");
  //Vehicle 1 Price : 8.5
}

しかし、普通implementsするクラスは一つ以上になると思うので、少し修正が必要です。抽象クラスのVehicle()の中にswitch文を入れて、各ケースごとにサブクラスをリターンするように変更しました。これで数多いのサブクラスにも対応できるようになりました。

enum VehicleType {
  Suv,
  Sedan,
  Truck
}
abstract class Vehicle {
  double getPrice();
  factory Vehicle(VehicleType vehicleType){
    switch(vehicleType){
      case VehicleType.Suv:
        return Suv();
      case VehicleType.Sedan:
        return Sedan();
      case VehicleType.Truck:
        return Truck();
    }    
  }
}

class Suv implements Vehicle {
  double price = 8.5;
  
  double getPrice(){
    return price;
  }
}
class Sedan implements Vehicle {
  double price = 5.5;
  
  double getPrice(){
    return price;
  }
}
class Truck implements Vehicle {
  double price = 3.5;
  
  double getPrice(){
    return price;
  }
}

void main() {
  print("Vehicle 1 Price : ${Vehicle(VehicleType.Suv).getPrice()}");
  print("Vehicle 2 Price : ${Vehicle(VehicleType.Sedan).getPrice()}");
  print("Vehicle 3 Price : ${Vehicle(VehicleType.Truck).getPrice()}");

  // Vehicle 1 Price : 8.5
  // Vehicle 2 Price : 5.5
  // Vehicle 3 Price : 3.5
}

3.いつ使うの?

公式文書によると、体表的な使い方の例としてcachingが挙げられます。cachingというのは簡単にいうとローカルに保存しておいて必要な時に再利用することを言います。例えば、メタのプロファイル写真はネットワークからダウンロードされた後、再利用する場合は、local cachingされたイメージを再利用します。

公式文書にもあるLoggerから一部修正してから説明したいと思います。
ポイントはやはりfactory Logger(String name) {return _cache.putIfAbsent(name, () => Logger._internal(name));}のところです。

Loggerをインスタンス化した場合、
1。factory コンストラクトなので、Loggerの内部コンストラクタを呼び出すことが可能。
2。privateコンストラクトであるLogger._internal()に変更したので、Loggerのインスタンスはもう直接に生成することが不可能です。(新しいインスタンス生成が不可能)
3。最初には_cacheは何もないので、Logger._interal(name)_cahceに入れて、リターンします。ここでも_cacheをリターンするのがポイントです。
4。Logger a = Logger('A')を2回以上しても2番により新しいLoggerインスタンスは作られません。

結果的にAというキーを持つLoggerは一つのみです。

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache = <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  Logger._internal(this.name){
    print('New logger created with name $name');
  }

  void log(String msg) {
    if (!mute) print(msg);
  }
}

main() {
  for(int i=0;i<5;i++){
    print('Creating instance $i');
    Logger a = Logger('A');
    print(a);
  }
}

結果的にFactoryキーワードを利用してシングルトンパータンを実装できました。サブタイプの新しいインスタンスをリターンすることによってcaching機能を持つLoggerのところも調べました。まだDartに関して未熟なので他のところではどうやって使われてるのかまだなんですが、DartやFlutterの勉強も頑張りたいと思います。

*Singleton パータンとは?
オブジェクトをただ一つだけ生成してどこでもこのオブジェクトを参照できるようにする開発パータンです。当然一つのオブジェクトだけ生成し、管理するので無駄なメモリ使用を防ぐことができます。もちろんパーフォマンス向上にも役立ち、このオブジェクトの共有も割と簡単にできます。

Discussion