🛡️

sealed classを探求する③

2024/09/18に公開

extention typeと組み合わせてみた

あまり情報がないDart3.0。自分のメモ用に色々作ってみる。もしかしたら仕事で使うとき自分のパソコンで見れないから💦
今の会社でFlutterはやる予定でしたがやっておりません💦

https://dart.dev/language/extension-types
https://dart.dev/language/class-modifiers#sealed

Swift書いているときによくクラスやstructにextensionを使うのですが、Dartでも使うのかな。
extensionでクラスとかを拡張するけど中に書いてるメソッドを直接呼ぶので、{}の外の名前つけたものは使わない。

// Extension typeを使用して都道府県の追加情報を定義
extension type PrefectureInfo(Prefecture base) {
  // 地方区分を返すメソッド
  Region get region => switch (base) {
    Hokkaido() => Region.hokkaido,
    Tokyo() => Region.kanto,
    Osaka() => Region.kinki,
    // ... 他の都道府県も同様に定義 ...
    _ => throw UnimplementedError('未定義の都道府県です'),
  };

こちらが全体のコード。47都道府県もないが本当に作ったら面白いアプリできるかも?

import 'package:flutter/material.dart';

// 基本的な都道府県の情報を持つシールドクラス
sealed class Prefecture {
  String get name;
  String get capital;
  IconData get icon;
}

// 都道府県クラスの定義(一部のみ表示)
class Hokkaido extends Prefecture {
   String get name => '北海道';
   String get capital => '札幌市';
   IconData get icon => Icons.ac_unit;
}

class Tokyo extends Prefecture {
   String get name => '東京都';
   String get capital => '東京';
   IconData get icon => Icons.location_city;
}

class Osaka extends Prefecture {
   String get name => '大阪府';
   String get capital => '大阪市';
   IconData get icon => Icons.restaurant;
}

// ... 他の44府県も同様に定義 ...

// 地方区分を表す列挙型
enum Region {
  hokkaido,
  tohoku,
  kanto,
  chubu,
  kinki,
  chugoku,
  shikoku,
  kyushu
}

// Extension typeを使用して都道府県の追加情報を定義
extension type PrefectureInfo(Prefecture base) {
  // 地方区分を返すメソッド
  Region get region => switch (base) {
    Hokkaido() => Region.hokkaido,
    Tokyo() => Region.kanto,
    Osaka() => Region.kinki,
    // ... 他の都道府県も同様に定義 ...
    _ => throw UnimplementedError('未定義の都道府県です'),
  };

  // 人口密度を返すメソッド(仮のデータ)
  double get populationDensity => switch (base) {
    Hokkaido() => 68.6,
    Tokyo() => 6363.1,
    Osaka() => 4655.2,
    // ... 他の都道府県も同様に定義 ...
    _ => 0.0, // デフォルト値
  };

  // 特産品を返すメソッド
  List<String> get specialties => switch (base) {
    Hokkaido() => ['ジンギスカン', 'メロン', '海鮮'],
    Tokyo() => ['もんじゃ焼き', '深川めし', '江戸前寿司'],
    Osaka() => ['たこ焼き', 'お好み焼き', 'くしかつ'],
    // ... 他の都道府県も同様に定義 ...
    _ => [],
  };

  // 観光スポットを返すメソッド
  List<String> get touristSpots => switch (base) {
    Hokkaido() => ['札幌時計台', '小樽運河', '富良野ラベンダー畑'],
    Tokyo() => ['東京スカイツリー', '浅草寺', '渋谷スクランブル交差点'],
    Osaka() => ['大阪城', 'ユニバーサル・スタジオ・ジャパン', '道頓堀'],
    // ... 他の都道府県も同様に定義 ...
    _ => [],
  };

  // 気候情報を返すメソッド
  String get climate => switch (base) {
    Hokkaido() => '冷涼な気候で、夏は涼しく冬は寒い',
    Tokyo() => '温暖な気候で、四季がはっきりしている',
    Osaka() => '温暖な気候だが、夏は蒸し暑く冬は乾燥する',
    // ... 他の都道府県も同様に定義 ...
    _ => '情報なし',
  };
}

class PrefectureApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: PrefectureSelector(),
    );
  }
}

class PrefectureSelector extends StatefulWidget {
  
  _PrefectureSelectorState createState() => _PrefectureSelectorState();
}

class _PrefectureSelectorState extends State<PrefectureSelector> {
  PrefectureInfo currentPrefecture = PrefectureInfo(Hokkaido());

  Widget _buildPrefectureWidget(PrefectureInfo prefectureInfo) {
    return SingleChildScrollView(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(prefectureInfo.base.icon, size: 80),
          Text(prefectureInfo.base.name, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
          Text('県庁所在地: ${prefectureInfo.base.capital}'),
          Text('地方: ${prefectureInfo.region.toString().split('.').last}'),
          Text('人口密度: ${prefectureInfo.populationDensity.toStringAsFixed(1)} 人/km²'),
          Text('特産品: ${prefectureInfo.specialties.join(", ")}'),
          Text('観光スポット: ${prefectureInfo.touristSpots.join(", ")}'),
          Text('気候: ${prefectureInfo.climate}'),
        ],
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('都道府県情報')),
      body: Center(
        child: _buildPrefectureWidget(currentPrefecture),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            // ランダムに都道府県を変更
            currentPrefecture = PrefectureInfo([Hokkaido(), Tokyo(), Osaka()][DateTime.now().second % 3]);
          });
        },
      ),
    );
  }
}

void main() {
  runApp(PrefectureApp());
}

コードの説明

はい、Dart 3.0で導入されたextension typeを使用した例を作成できます。extension typeは、既存の型に新しい機能や振る舞いを追加する強力な方法を提供します。都道府県アプリケーションの文脈で、extension typeを使用して都道府県に関連する追加の機能を実装する例を示しましょう。

import 'package:flutter/material.dart';

// 基本的な都道府県の情報を持つシールドクラス
sealed class Prefecture {
  String get name;
  String get capital;
  IconData get icon;
}

// 都道府県クラスの定義(一部のみ表示)
class Hokkaido extends Prefecture {
   String get name => '北海道';
   String get capital => '札幌市';
   IconData get icon => Icons.ac_unit;
}

class Tokyo extends Prefecture {
   String get name => '東京都';
   String get capital => '東京';
   IconData get icon => Icons.location_city;
}

class Osaka extends Prefecture {
   String get name => '大阪府';
   String get capital => '大阪市';
   IconData get icon => Icons.restaurant;
}

// ... 他の44府県も同様に定義 ...

// 地方区分を表す列挙型
enum Region {
  hokkaido,
  tohoku,
  kanto,
  chubu,
  kinki,
  chugoku,
  shikoku,
  kyushu
}

// Extension typeを使用して都道府県の追加情報を定義
extension type PrefectureInfo(Prefecture base) {
  // 地方区分を返すメソッド
  Region get region => switch (base) {
    Hokkaido() => Region.hokkaido,
    Tokyo() => Region.kanto,
    Osaka() => Region.kinki,
    // ... 他の都道府県も同様に定義 ...
    _ => throw UnimplementedError('未定義の都道府県です'),
  };

  // 人口密度を返すメソッド(仮のデータ)
  double get populationDensity => switch (base) {
    Hokkaido() => 68.6,
    Tokyo() => 6363.1,
    Osaka() => 4655.2,
    // ... 他の都道府県も同様に定義 ...
    _ => 0.0, // デフォルト値
  };

  // 特産品を返すメソッド
  List<String> get specialties => switch (base) {
    Hokkaido() => ['ジンギスカン', 'メロン', '海鮮'],
    Tokyo() => ['もんじゃ焼き', '深川めし', '江戸前寿司'],
    Osaka() => ['たこ焼き', 'お好み焼き', 'くしかつ'],
    // ... 他の都道府県も同様に定義 ...
    _ => [],
  };

  // 観光スポットを返すメソッド
  List<String> get touristSpots => switch (base) {
    Hokkaido() => ['札幌時計台', '小樽運河', '富良野ラベンダー畑'],
    Tokyo() => ['東京スカイツリー', '浅草寺', '渋谷スクランブル交差点'],
    Osaka() => ['大阪城', 'ユニバーサル・スタジオ・ジャパン', '道頓堀'],
    // ... 他の都道府県も同様に定義 ...
    _ => [],
  };

  // 気候情報を返すメソッド
  String get climate => switch (base) {
    Hokkaido() => '冷涼な気候で、夏は涼しく冬は寒い',
    Tokyo() => '温暖な気候で、四季がはっきりしている',
    Osaka() => '温暖な気候だが、夏は蒸し暑く冬は乾燥する',
    // ... 他の都道府県も同様に定義 ...
    _ => '情報なし',
  };
}

class PrefectureApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: PrefectureSelector(),
    );
  }
}

class PrefectureSelector extends StatefulWidget {
  
  _PrefectureSelectorState createState() => _PrefectureSelectorState();
}

class _PrefectureSelectorState extends State<PrefectureSelector> {
  PrefectureInfo currentPrefecture = PrefectureInfo(Hokkaido());

  Widget _buildPrefectureWidget(PrefectureInfo prefectureInfo) {
    return SingleChildScrollView(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(prefectureInfo.base.icon, size: 80),
          Text(prefectureInfo.base.name, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
          Text('県庁所在地: ${prefectureInfo.base.capital}'),
          Text('地方: ${prefectureInfo.region.toString().split('.').last}'),
          Text('人口密度: ${prefectureInfo.populationDensity.toStringAsFixed(1)} 人/km²'),
          Text('特産品: ${prefectureInfo.specialties.join(", ")}'),
          Text('観光スポット: ${prefectureInfo.touristSpots.join(", ")}'),
          Text('気候: ${prefectureInfo.climate}'),
        ],
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('都道府県情報')),
      body: Center(
        child: _buildPrefectureWidget(currentPrefecture),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            // ランダムに都道府県を変更
            currentPrefecture = PrefectureInfo([Hokkaido(), Tokyo(), Osaka()][DateTime.now().second % 3]);
          });
        },
      ),
    );
  }
}

void main() {
  runApp(PrefectureApp());
}

この例では、extension typeを使用してPrefectureInfoを定義し、以下のような改善と新機能を追加しています:

  1. PrefectureInfo extension type:

    • 基本の Prefecture オブジェクトをラップし、追加の機能を提供します。
    • region, populationDensity, specialties などの既存の機能に加え、touristSpotsclimate という新しい機能を追加しています。
  2. Extension typeの利点:

    • 元の Prefecture クラスを変更せずに新しい機能を追加できます。
    • 型安全性が保たれ、IDEの自動補完機能が使えます。
    • 必要な時だけ追加の機能にアクセスできるため、メモリ効率が良いです。
  3. UI更新:

    • PrefectureInfo の新しい機能を使用して、観光スポットと気候情報を表示するようにUIを更新しています。
  4. 柔軟性:

    • 将来、新しい情報や機能を追加したい場合、PrefectureInfo extension typeに簡単に追加できます。

このアプローチの利点:

  1. 関心の分離: 基本的な都道府県情報と追加の詳細情報を分離して管理できます。
  2. 拡張性: 新しい機能を追加する際に、既存のコードを変更する必要がありません。
  3. 型安全性: コンパイル時にエラーを検出できます。
  4. パフォーマンス: 必要な時だけ追加情報を計算するため、メモリ効率が良いです。

Extension typeは、このような追加の機能や計算を既存の型に付加する際に非常に有用です。都道府県アプリケーションのような複雑なデータモデルを扱う場合、コードの整理と機能の追加が容易になります。

さらに具体的な機能や、異なるアプローチについてご質問があれば、お知らせください。

感想

sealed classの書き方でいい方法ないかとネットの情報探しますが、Koltinの書き方が参考にならないのが悩み💦

Discussion