初めに
Flutter開発をしていると、見やすく、使いやすいコードを書くことに悩むことがあります。
そこで今回は、「プログラミングの抽象化」 という概念を活用し、コードの整理や再利用性を高める方法に挑戦しました。
抽象化とは何かを理解し、実際にFlutterで適用できるサンプルコードを作成して、どのように活用できるのかを整理してみました。
本編
プログラミングにおける抽象化
抽象化とは、複数の具体的なものから共通点を抽出し、汎用的な概念として定義・名付けすることです。
例:ビール、お茶、水などは「飲み物」、おにぎり、ラーメン、パンなどは「食べ物」として抽象化できる
プログラミングにおいての抽象化も、複数のものから共通点を抽出し、概念付けを行います。ここでいう「もの」とは、UIコンポーネントだけでなく、機能、データ管理システム、アルゴリズムなども含まれます。
エンジニアが抽象化する目的は、使用者に複雑な詳細を隠蔽し、シンプルで汎用的に利用できる形にすることです。
例えば、車はタイヤやエンジンなど複数のパーツで構成されていますが、運転者はエンジンの詳細な動作を知らなくても、「アクセルを踏む」「ブレーキをかける」といった基本操作だけで運転できます。これと同じように、プログラミングにおいても抽象化により、細かい実装を隠し、必要な操作だけを提供することで、より扱いやすい形にすることができます。
Flutterにおける抽象化
FlutterはDartというオブジェクト指向プログラミング(OOP)をサポートする言語で開発されており、オブジェクトやクラスを中心に構築されています。クラスやオブジェクトを通じて、現実世界の概念や機能を「具体化」しています。Flutterの開発においては、具体化したUIや機能を抽象化し、それらを組み合わせて開発していきます。
Flutterでなぜ抽象化が必要なのか?
Flutterにおける抽象化の主な目的は以下の通りです:
- コードの再利用性向上:共通の機能を抽象化することで、重複を減らします
- 可読性の向上:コードの構造が明確になり、理解しやすくなります
- 保守性の向上:抽象クラスを変更するだけで、派生クラスにも影響を与えられます
- テストの容易化:依存関係を分離しやすくなり、テストが容易になります
Flutterでの抽象化例
以下に、具体的な抽象化の手法を3つ紹介します。
- classを使った例
Flutterでは、同じ種類のUIコンポーネントを何度も作るとコードが冗長になります。
例えば、ボタンのスタイルを統一するために、CustomButton クラスを作成して再利用できるようにします。
//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で実装することで統一的に扱えるようにします。
// **共通の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を使ってさらに共通の機能を追加できます。
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公式には、他にも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