Flutter開発で、コードの抽象化についてまとめた

## 初めに
Flutter開発をしていると、見やすく、使いやすいコードを書くことに悩むことがあります。
そこで今回は、「プログラミングの抽象化」 という概念を活用し、コードの整理や再利用性を高める方法に挑戦しました。
抽象化とは何かを理解し、実際にFlutterで適用できるサンプルコードを作成して、どのように活用できるのかを整理してみました。

## 本編

### プログラミングにおける抽象化
抽象化とは、複数の具体的なものから共通点を抽出し、汎用的な概念として定義・名付けすることです。
例：ビール、お茶、水などは「飲み物」、おにぎり、ラーメン、パンなどは「食べ物」として抽象化できる

プログラミングにおいての抽象化も、複数のものから共通点を抽出し、概念付けを行います。ここでいう「もの」とは、UIコンポーネントだけでなく、機能、データ管理システム、アルゴリズムなども含まれます。

エンジニアが抽象化する目的は、使用者に複雑な詳細を隠蔽し、シンプルで汎用的に利用できる形にすることです。
例えば、車はタイヤやエンジンなど複数のパーツで構成されていますが、運転者はエンジンの詳細な動作を知らなくても、「アクセルを踏む」「ブレーキをかける」といった基本操作だけで運転できます。これと同じように、プログラミングにおいても抽象化により、細かい実装を隠し、必要な操作だけを提供することで、より扱いやすい形にすることができます。

## Flutterにおける抽象化
FlutterはDartというオブジェクト指向プログラミング（OOP）をサポートする言語で開発されており、オブジェクトやクラスを中心に構築されています。クラスやオブジェクトを通じて、現実世界の概念や機能を「具体化」しています。Flutterの開発においては、具体化したUIや機能を抽象化し、それらを組み合わせて開発していきます。

##  Flutterでなぜ抽象化が必要なのか？

Flutterにおける抽象化の主な目的は以下の通りです：

- コードの再利用性向上：共通の機能を抽象化することで、重複を減らします
- 可読性の向上：コードの構造が明確になり、理解しやすくなります
- 保守性の向上：抽象クラスを変更するだけで、派生クラスにも影響を与えられます
- テストの容易化：依存関係を分離しやすくなり、テストが容易になります

## Flutterでの抽象化例

以下に、具体的な抽象化の手法を3つ紹介します。

- classを使った例

Flutterでは、同じ種類のUIコンポーネントを何度も作るとコードが冗長になります。
例えば、ボタンのスタイルを統一するために、CustomButton クラスを作成して再利用できるようにします。
    
```dart
//CustomButtonのClass
class CustomButton extends StatelessWidget {
  final String label;
  final VoidCallback onPressed;

  const CustomButton({required this.label, required this.onPressed, super.key});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text(label),
    );
  }
}

// 画面内で使うとき
CustomButton(
  label: "OK",
  onPressed: () => print("OK"),
);

CustomButton(
  label: "NO",
  onPressed: () => print("NO"),
);
```



- Abstractクラスを使った例

APIの種類が増えてくると、それぞれの処理を個別に書くとコードが複雑になります。
abstract classを使って共通のインターフェースを定義し、それぞれのAPIで実装することで統一的に扱えるようにします。
```dart
// **共通のAPI処理を定義**
abstract class ApiService {
  Future<String> fetchData();
}

// **ユーザーAPI（実装クラス）**
class UserAPI implements ApiService {
  @override
  Future<String> fetchData() async {
    await Future.delayed(Duration(seconds: 1)); // 擬似的なAPI待機
    return "ユーザーデータ";
  }
}

// **商品API（実装クラス）**
class ProductAPI implements ApiService {
  @override
  Future<String> fetchData() async {
    await Future.delayed(Duration(seconds: 1)); // 擬似的なAPI待機
    return "商品データ";
  }
}

// **どちらのAPIでも統一的に扱える**
void main() async {
  ApiService userApi = UserAPI();
  ApiService productApi = ProductAPI();

  print(await userApi.fetchData()); // "ユーザーデータ"
  print(await productApi.fetchData()); // "商品データ"
}
```

- mixinを使った例

異なるクラスで共通のログ機能を持たせるときに、mixinを使います。
Dartでは、継承(extends)はクラスに一つしか使用できないので、mixinを使ってさらに共通の機能を追加できます。

```dart
mixin Logger {
  void log(String message) {
    final time = DateTime.now().toIso8601String();
    print('[$time] $message');
  }
}

class ApiService with Logger {
  Future<void> fetchData() async {
    log("Fetching data...");
    await Future.delayed(Duration(seconds: 2)); // Simulated API call
    log("Data fetched successfully");
  }
}

void main() {
  final apiService = ApiService();
  apiService.fetchData();
}


```

## まとめ
Flutter開発において、抽象化は欠かせない概念であり、コードの設計時には常に意識する必要があります。
今回は、Flutterの抽象化方法において数個の例を出しましたが、[Dart公式](https://dart.dev/language/class-modifiers)には、他にもinterfaceクラスやsealdクラスなどあり、学習を進めてそれらついても整理していきたいと思います。

## 参照
https://dart.dev/language/class-modifiershttps://dart.dev/language/class-modifiers
https://qiita.com/hukusuke1007/items/8fd3411d2ef3c0d18429#comment-4c21f10a0b3c67709695
https://qiita.com/cw-yamada/items/87311da52c85ae4146fa
https://qiita.com/haru-qiita/items/03c15dc759c5675dd608