July 30, 2023

Dart3のSealedクラスを使う

Dart3はFlutter側に結構大きな変更があって、なかなかメインプロダクトでバージョンを上げられなかったので使うのが遅れてしまいました。
Dart3で追加されたSealedクラスを見てみます。

用途としてはKotlinのSealedと同じです。 今まではDart自体にUnionの機能がなかったので、freezedパッケージ を使って再現するのが一般的だったかと思いますが、ついに言語自体でサポートされました。

sealed classだとtoStringが実装されていないので、そのままfreezedでも良いのですが、build runnerを走らせなくても良いのは使い勝手が良いなという感想です。

使い方

一般的な書き方はこんな感じです。
Kotlinとほぼ同じですね。

sealed class Hoge {
  const Hoge();
}

class Foo extends Hoge {
  const Foo();
}

class Bar extends Hoge {
  const Bar();
}

これだけだとEnumと変わらないので、プロパティを持つこともできます。 それぞれ別のプロパティを持つことも可能です。
普通のクラスになるので、そのままデフォルトのプロパティを持ったりもできます。

sealed class Hoge {
  const Hoge();
}

class Foo extends Hoge {
  const Foo({this.foo = false});

  final bool foo;
}

class Bar extends Hoge {
  const Bar();
  
  final bool bar = false;
}

親クラスに共通のプロパティを持つこともできますし、名前付き引数を使わなくても大丈夫です。

sealed class Hoge {
  const Hoge(this.hoge);

  final bool hoge;
}

class Foo extends Hoge {
  const Foo(super.hoge);
}

class Bar extends Hoge {
  const Bar(super.hoge);
}

ただこれだとprintしてもInstanceになってしまうのは注意してください。

final foo = Foo(true);
print(foo); 

// Instance of 'Foo'

そのため、こんな感じで toString をそれぞれ実装する必要があります。

sealed class Hoge {
  const Hoge();
}

class Foo extends Hoge {
  const Foo({this.foo = false});

  final bool foo;
  
  @override
  String toString() => 'Foo(foo: $foo)';
}

class Bar extends Hoge {
  const Bar();

  final bool bar = false;
  
  @override
  String toString() => 'Bar(Bar: $bar)';
}
final foo = Foo(true);
print(foo); 

//  Foo(foo: true)

利用側

利用側はswitchで場合分けができます。
コンパイラがsealedクラスを把握しているため、defaultも実装しなくて良いです。

void hoge(Hoge hoge) {
  switch (hoge) {
    case Foo():
      print(hoge.foo);
    case Bar():
      print(hoge.bar);
  }
}

他にもいくつかDart3の機能を使っていますが、全体的に良いアップデートで使いやすくなりましたね!

© AAkira 2023