Enumは、定数を表現できますが、インスタンス化、サブクラス化、Mix-in、実装はできません。
そのため、Enumに対して、処理を追加する場合、工夫が必要です。
今回は、3つの方法を使って、処理を追加したいと思います。
- 拡張関数
- ユーティリティクラス
- ヘルパークラス
拡張関数を使った変換
拡張関数(extension yyy on xxx)を使って関数をEnumに処理を追加することができます。
メリットは、Enum名をそのまま使えるため、わかりやすいこと。
デメリットは、Enumごとに処理を追加しないといけないこと、staticが使えないこと。
enum Number { One, Two,}
extension on Number {
String get name => this.toString().split(".").last;
}
extension on String {
Number toEnumNumber() {
return Number.values.firstWhere((e) => e.name == this, orElse: () => null);
}
}
void main() {
print(Number.One.name); // One
print('Two'.toEnumNumber()); // Number.Two
}
Number.One.nameは、Enumから文字列をします。わかりやすいですね。
staticが作れないので、Stringクラスに追加する形になります。
Enumが複数ない場合や、複雑な処理をしないのであれば、いい方法だと思います。
ユーティリティクラスを使った変換
ユーティリティクラスを使う方法は、Enumとの相互変換を行うクラスを作成して、それを呼び出す方法です。
staticで定義して、どこでも呼べるようにします。
メリットは、static定義されているので、Enumであれば、どこでも呼べることです。
デメリットは、Enumとの関連性が薄くなります。
enum Number { One, Two,}
class EnumUtils {
static String name(value) {
var sp = value?.toString()?.split('.');
if (sp == null || sp.length != 2) {
return null;
}
return sp[1];
}
static T valueOf<T>(List<T> values, String value) {
if (value == null || values == null) {
return null;
}
return values.firstWhere((e) => EnumUtils.name(e) == value,
orElse: () => null);
}
}
void main() {
print(EnumUtils.name(Number.One)); // One
print(EnumUtils.valueOf(Number.values, 'Two')); // Number.Two
}
Enumのユーティリティクラスなので、使う側にとっては、覚えやすいものになっています。
ただ、enumでの型制限ができないため、実行時までエラーに気づかない可能性があります。
今回は、コードで記載しましたが、EnumとStringの変換だけの「enum_to_string」ライブラリも存在します。
ヘルパークラスを使った変換
ヘルパークラスを使う方法は、Enumと対になるHelperクラスを作成して、それに処理を任せるという方法です。型の制限が強くなります。
enum Number { One, Two,}
abstract class EnumHelper<T> {
List<T> values();
T valueOf(String value) {
if (value == null) {
return null;
}
return values().firstWhere((e) => name(e) == value, orElse: () => null);
}
String name(T value) {
return value?.toString()?.split('.')?.last;
}
}
class NumberHelper extends EnumHelper<Number> {
@override
List<Number> values() => Number.values;
}
void main() {
var helper = NumberHelper();
print(helper.name(Number.One)); // One
print(helper.valueOf('Two')); // Number.Two
}
型の制限がつけられるところがいいところでしょう。
インスタンス化する必要があるので、メモリの問題がありますが、キャッシュなどで対応することも可能です。
おまけ
拡張関数を使った別の方法もあります。
やり方は、簡単で、enumにアクセス用の項目(from)を1つ追加します。
あとは、拡張関数によって、enumで処理したい処理を追加します。
enum Number { from, One, Two,}
extension NumberIndex on Number {
String get name => this.toString().split(".").last;
operator[](String key) =>
Number.values.firstWhere((e) => e.name == key);
}
void main() {
print(Number.from["One"]); // Number.One
}
デメリットとして、余計な項目(from)が1つ増えることです。
また、拡張関数で説明したデメリットがあります。
まとめ
大規模や中規模、大人数での開発であれば、コンパイル時のエラーチェックができるヘルパークラスを使うといいでしょう。
小規模であれば、使い勝手がいいユーティリティクラスでもいいと思います。
個人やEnumをそんなに使わない場合は、コピーしやすく、コードが見やすい拡張関数でもいいと思います。
それぞれ、一長一短があるが、Enumが拡張しにくいので、致し方なしですね。
拡張関数のstaticやGenericsのenum対応など、Dartの仕様追加してもらえたらなぁと思うこの頃です。
コメント