リポジトリパターン
CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 100/110。
リポジトリパターンは、アプリケーションとデータソースの間に抽象化レイヤーを作成することで、データアクセスロジックをビジネスロジックから分離します。クラスがデータベース、API、またはファイルと直接やり取りする代わりに、すべてのデータ操作を処理するリポジトリを介して動作します。
このパターンはデータ操作のためのインターフェースを定義し、具体的な実装が実際のストレージメカニズムを処理します。これにより、ビジネスロジックを変更することなく、データソースを簡単に切り替えることができます。
// エンティティ
class User {
final int id;
final String name;
User(this.id, this.name);
}
// リポジトリインターフェース
abstract class UserRepository {
User? getById(int id);
List<User> getAll();
void save(User user);
void delete(int id);
}
// インメモリ実装
class InMemoryUserRepository implements UserRepository {
final Map<int, User> _users = {};
@override
User? getById(int id) => _users[id];
@override
List<User> getAll() => _users.values.toList();
@override
void save(User user) => _users[user.id] = user;
@override
void delete(int id) => _users.remove(id);
}
void main() {
UserRepository repo = InMemoryUserRepository();
repo.save(User(1, 'Alice'));
repo.save(User(2, 'Bob'));
print(repo.getById(1)?.name); // Alice
print(repo.getAll().length); // 2
}ビジネスロジックは、UserRepositoryインターフェースにのみ依存します。後で、同じインターフェースを実装するDatabaseUserRepositoryやApiUserRepositoryを作成し、リポジトリを使用するコードを一切変更することなく実装を入れ替えることができます。このパターンは、データソースが時間の経過とともに変更される可能性がある、テスト可能でメンテナンス性の高いアプリケーションにとって不可欠です。
チャレンジ
簡単リポジトリパターンを使用して、製品在庫システムを構築しましょう!製品の保存方法と使用方法を分離する抽象化レイヤーを作成することで、ビジネスロジックを変更せずにストレージの実装を簡単に切り替えられるようにします。
コードを2つのファイルに整理します:
product_repository.dart: このファイルには、エンティティクラスとリポジトリクラスが含まれます。まず、id(int)、name(String)、price(double) のfinalフィールドを持つProductクラスから始めます。次に、データ操作のコントラクトを宣言する抽象クラスProductRepositoryを定義します。これには、nullableなProductを返すgetById(int id)、製品のリストを返すgetAll()、製品を保存するsave(Product product)、および製品を削除するdelete(int id)が含まれます。最後に、ストレージとしてMap<int, Product>を使用してこのインターフェースを実装するInMemoryProductRepositoryを作成します。main.dart: リポジトリファイルをインポートし、リポジトリパターンの動作を実演します。InMemoryProductRepositoryを作成しますが、それをProductRepository型の変数に格納してください。これにより、コードが実装ではなくインターフェースに依存していることが示されます。3つの製品を保存します:Laptop(id: 1, price: 999.99)、Mouse(id: 2, price: 29.99)、およびKeyboard(id: 3, price: 79.99)。次に、id 2 の製品を取得し、Found: [name] - $[price]の形式で出力します。id 1 の製品を削除します。最後に、残りの製品の数をProducts in stock: [count]として出力します。
このパターンの強力な点は、メインコードが ProductRepository インターフェースのみを認識していることです。後で DatabaseProductRepository や ApiProductRepository を作成し、ビジネスロジックを一切変更せずにそれらを差し替えることができます!
期待される出力:
Found: Mouse - $29.99
Products in stock: 2チートシート
リポジトリパターンは、アプリケーションとデータソースの間に抽象化レイヤーを作成することで、データアクセスロジックをビジネスロジックから分離します。
データ操作のためのインターフェースを定義します:
// エンティティ
class User {
final int id;
final String name;
User(this.id, this.name);
}
// リポジトリインターフェース
abstract class UserRepository {
User? getById(int id);
List<User> getAll();
void save(User user);
void delete(int id);
}実際のストレージメカニズムを処理する具体的な実装を作成します:
// インメモリ実装
class InMemoryUserRepository implements UserRepository {
final Map<int, User> _users = {};
@override
User? getById(int id) => _users[id];
@override
List<User> getAll() => _users.values.toList();
@override
void save(User user) => _users[user.id] = user;
@override
void delete(int id) => _users.remove(id);
}インターフェースを介してリポジトリを使用します:
void main() {
UserRepository repo = InMemoryUserRepository();
repo.save(User(1, 'Alice'));
repo.save(User(2, 'Bob'));
print(repo.getById(1)?.name); // Alice
print(repo.getAll().length); // 2
}ビジネスロジックはインターフェースにのみ依存するため、リポジトリを使用するコードを変更することなく、実装(例:DatabaseUserRepository、ApiUserRepository)を入れ替えることができます。
自分で試してみよう
import 'product_repository.dart';
void main() {
// リポジトリを作成します - 型は実装ではなくインターフェースであることに注意してください
// TODO: InMemoryProductRepository を作成し、ProductRepository 変数に格納します
// TODO: 3つの製品を保存します:
// - Laptop (id: 1, price: 999.99)
// - Mouse (id: 2, price: 29.99)
// - Keyboard (id: 3, price: 79.99)
// TODO: id が 2 の製品を取得し、"Found: [name] - $[price]" と出力します
// TODO: id が 1 の製品を削除します
// TODO: 残りの製品数を出力します: "Products in stock: [count]"
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4Null Safety
Null Safety 入門Nullable と Non-Nullable? 演算子と ! 演算子late キーワードと Null SafetyNull-Aware 演算子クラスにおける Null Safetyまとめ:ユーザープロフィールシステム10コレクションとジェネリクス
List, Set, Map の概要型安全なコレクションジェネリッククラスジェネリックメソッドジェネリクスの制約Iterable と Iteratorまとめ:ジェネリックなストレージ