✨
Ghost Typeで疑似的にメソッドのオーバーライドを行いメソッドシグネチャを追加する
YUMEMI Flutter Advent Calendar 2023の9日目の記事です。
たとえばあるクラスのベースクラスを作成する時に、以下のようなコードを書いたとします。
abstract class ModelBase {
Future<void> execute();
}
この時、execute()の引数を変更したいときに、以下のように変更することになります。
abstract class ModelBase {
Future<void> execute({String? name});
}
しかし、この時、ModelBaseを継承している全てのクラスのexecute()の引数も変更することになり、それは本意ではありません。
このような場合には、以下のように変更することで多様な引数に対応できます。
abstract class ModelBase<T> {
Future<void> execute(T args);
}
しかし、この時、ModelBaseを継承している全てのクラスのexecute()の引数も変更することになります。そうなると利用側の修正が必要になります。利用側の修正を回避するためには、以下のように変更することで対応できます。
まず、引数を変更するためのクラスを作成します。
abstract final class Arg {}
/// 引数なし
final class WithoutArg extends Arg {}
/// 引数あり
final class WithArg<T> extends Arg {
WithArg(this.arg);
final T arg;
}
次に、ModelBaseを変更します。
abstract class ModelBase<S extends Arg> {
/// 実装を記述するするメソッド
void impl(S arg);
}
extension ModelWithoutArg on ModelBase<WithoutArg> {
/// WithoutArgの場合は引数なしで実行できる
void execute() => impl(WithoutArg());
}
extension ModelWithArg<T> on ModelBase<WithArg<T>> {
/// WithArgの場合は引数ありで実行できる
void execute(T arg) => impl(WithArg(arg));
}
このようにすることで、ModelBaseを継承しているクラスのexecute()の引数を変更することなく、別のクラスのexecute()の引数を変更することができます。
class AModel implements ModelBase<WithoutArg> {
void impl(WithoutArg arg) {
print('no arg');
}
}
class BModel extends ModelBase<WithArg<String>> {
void impl(WithArg<String> arg) {
print(arg.arg);
}
}
ArgはGhost Typeと行って良いかと思いますが、executeのインターフェースを決定するために利用されます。このようなクラスを作成することで、executeのインターフェースを変更することなく、executeの引数を変更することができます。
実装が複雑になるので、このような実装は必要最小限に留めるべきですが、必要な場合には利用すると良いと思います。
Discussion