😊

Flutter: 抽象クラスとミックスインについて簡単にまとめる

に公開

はじめに

Flutterでコードを効率的に書くための二つの重要な概念「抽象クラス」と「ミックスイン」について説明します。

抽象クラスは「こういう機能を持つべき」というルールを定義する設計図のようなもので、ミックスインは既存のクラスに「機能を混ぜ込む」ための仕組みです。
この二つを適切に使い分けることで、コードの重複を減らしたり、メンテナンスしやすいアプリを作る事が出来ます。

特に複数の画面で共通の機能を実装する場合(mixin)や、関連するクラスに一貫したルールを設定したい場合(Abstract Class)に役立ちます。

本題

抽象クラス(Abstract Class)

そもそも Abstract(アブストラクト)は「抽象的な、観念的な,理論的な」という意味です。
通常の class を設計図のようなものだとすると、Abstract は完全ではない設計図のようなものです。
抽象クラスは「こういう機能を持つべき」というルールを定義し、具体的な実装は子クラスに任せるクラスです。

特徴

  • インスタンス化できない(new Abstract()のように直接使えない)
  • 子クラスが実装すべきメソッドを定義できる
  • 共通の機能も提供できる
  • 定数をまとめる場所としても使える

例(タスク管理アプリ)

// タスク種別の抽象クラス
abstract class TaskType {
  String get name;  // 子クラスで実装必須
  Color get color;  // 子クラスで実装必須
  
  // 共通の機能
  Widget buildIcon() {
    return Container(
      color: color,
      child: Text(name[0]), // 最初の文字をアイコンとして表示
    );
  }
}

// 具体的な実装クラス
class WorkTask extends TaskType {
  
  String get name => '仕事';
  
  
  Color get color => Colors.blue;
}

class PersonalTask extends TaskType {
  
  String get name => 'プライベート';
  
  
  Color get color => Colors.green;
}

使用場面

  • 関連するクラスに共通のルールを設定したいとき
  • 複数の実装方法があるけど、基本的な構造は同じにしたいとき
  • (実際に個人開発でよく使ってます)アプリで使うカラーやテキストスタイルを定数としてグループ化したいとき
// アプリ全体で使う色の定数
abstract class AppColors {
  // プライベートコンストラクタでインスタンス化を防ぐ
  AppColors._();
  
  // 基本色
  static const Color primary = Color(0xFF4CAF50);
  static const Color secondary = Color(0xFF2196F3);
  static const Color error = Color(0xFFE53935);
  
  // テキスト色
  static const Color textPrimary = Color(0xFF212121);
  static const Color textSecondary = Color(0xFF757575);
  
  // 背景色
  static const Color background = Color(0xFFFFFFFF);
  static const Color cardBackground = Color(0xFFF5F5F5);
}

// 使用例
Container(
  color: AppColors.primary,
  child: Text('タスク', style: TextStyle(color: AppColors.textPrimary)),
)

抽象クラスと通常クラスの違い

  • 抽象クラスは abstract キーワードを使用
  • 抽象メソッドは本体を持たない(実装は子クラスで行う)
  • 抽象クラスは直接インスタンス化できない

ミックスイン(mixin)

mixin は「混ぜ込む」という意味で、既存のクラスに機能を追加するための仕組みです。
Dart は単一継承(一つの親クラスしか持てない)ですが、ミックスインを使うと複数の場所から機能を取り込めます。
その際、withキーワードを使います。

下記、Mermaid 記法を使ったイメージ図。

図の説明

  • Screen: すべての画面の基本となる抽象クラスです
  • CartFeature: カートに商品を追加する機能を提供するmixinです
  • FavoriteFeature: お気に入りに商品を追加する機能を提供するmixinです
  • ProductListScreen: 商品一覧を表示する画面で、Screenを継承しFavoriteFeaturemixinしています
  • ProductDetailScreen: 商品詳細を表示する画面で、Screenを継承し、CartFeatureFavoriteFeatureの両方をmixinしています
  • 継承関係: 矢印「<|--」は継承を表しています
  • with関係: 点線矢印「<|..」はmixinの使用を表しています

特徴

  • 複数のクラスで同じ機能を共有できる
  • クラスに「混ぜ込む」ことで機能を追加する
  • 状態(変数)とメソッド(関数)の両方を含められる
  • 継承とは異なり、複数のミックスインを使える

例(タスク管理アプリ)

// ローディング表示機能のミックスイン
mixin LoadingMixin {
  bool _isLoading = false;
  
  bool get isLoading => _isLoading;
  
  void showLoading() {
    _isLoading = true;
    // ローディング表示処理
  }
  
  void hideLoading() {
    _isLoading = false;
    // ローディング非表示処理
  }
}

// タスク操作機能のミックスイン
mixin TaskOperationMixin {
  void saveTask(Task task) {
    // タスク保存処理
  }
  
  void deleteTask(String taskId) {
    // タスク削除処理
  }
}

// タスク一覧画面
class TaskListScreen extends StatelessWidget with LoadingMixin, TaskOperationMixin {
  void loadTasks() {
    showLoading();
    // タスク読み込み処理
    hideLoading();
  }
}

// タスク詳細画面
class TaskDetailScreen extends StatelessWidget with LoadingMixin, TaskOperationMixin {
  void updateTask(Task task) {
    showLoading();
    saveTask(task);
    hideLoading();
  }
}

こういう場面で使う

  • 複数の画面で同じ機能を使いたいとき
    • 例:ローディング表示、エラーハンドリング
  • 継承関係がないクラス間で機能を共有したいとき
    • 例:異なる種類の画面でも同じアニメーション効果を使いたい
  • コードの重複を避けたいとき
    • 例:複数の画面でフォーム検証ロジックを共有する

抽象クラスとミックスインの使い分け

特徴 抽象クラス ミックスイン
目的 共通の型と基本実装の提供 機能の追加・拡張
継承 単一継承のみ 複数適用可能
インスタンス化 不可 単体では不可
コンストラクタ 持てる 持てない
使用場面 「〜である」関係 「〜できる」関係

Reference Site

https://aichi.blog/dart-basics-constructors-inheritance-abstract-mixin/?utm_source=rss&utm_medium=rss&utm_campaign=dart-basics-constructors-inheritance-abstract-mixin

Discussion