Repository-Muster
Teil des Abschnitts Objektorientierte Programmierung der Dart-Journey von Coddy — Lektion 100 von 110.
Das Repository-Pattern trennt die Datenzugriffslogik von der Geschäftslogik, indem es eine Abstraktionsschicht zwischen Ihrer Anwendung und den Datenquellen erstellt. Anstatt Ihre Klassen direkt mit Datenbanken, APIs oder Dateien interagieren zu lassen, arbeiten sie mit einem Repository, das alle Datenoperationen übernimmt.
Das Muster definiert eine Schnittstelle für Datenoperationen, und konkrete Implementierungen übernehmen den eigentlichen Speichermechanismus. Dies macht es einfach, Datenquellen auszutauschen, ohne Ihre Geschäftslogik zu ändern:
// Entität
class User {
final int id;
final String name;
User(this.id, this.name);
}
// Repository-Schnittstelle
abstract class UserRepository {
User? getById(int id);
List<User> getAll();
void save(User user);
void delete(int id);
}
// In-Memory-Implementierung
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
}Ihre Geschäftslogik hängt nur von der Schnittstelle UserRepository ab. Sie könnten später ein DatabaseUserRepository oder ApiUserRepository erstellen, das dieselbe Schnittstelle implementiert, und die Implementierungen austauschen, ohne den Code zu ändern, der das Repository verwendet. Dieses Muster ist essenziell für testbare, wartbare Anwendungen, bei denen sich Datenquellen im Laufe der Zeit ändern können.
Aufgabe
EinfachLassen Sie uns ein Produktinventarsystem mit dem Repository-Muster erstellen! Sie werden eine Abstraktionsschicht erstellen, die die Speicherung von Produkten von deren Verwendung trennt, sodass Speicherimplementierungen einfach ausgetauscht werden können, ohne die Geschäftslogik zu ändern.
Sie werden Ihren Code in zwei Dateien organisieren:
product_repository.dart: Diese Datei enthält Ihre Entitäts- und Repository-Klassen. Beginnen Sie mit einerProduct-Klasse, diefinal-Felder fürid(int),name(String) undprice(double) hat. Definieren Sie dann eine abstrakte KlasseProductRepository, die den Vertrag für Datenoperationen deklariert:getById(int id)gibt ein nullableProductzurück,getAll()gibt eine Liste von Produkten zurück,save(Product product)zum Speichern eines Produkts unddelete(int id)zum Entfernen eines Produkts. Erstellen Sie schließlich einInMemoryProductRepository, das diese Schnittstelle unter Verwendung einerMap<int, Product>zur Speicherung implementiert.main.dart: Importieren Sie Ihre Repository-Datei und demonstrieren Sie das Repository-Muster in Aktion. Erstellen Sie einInMemoryProductRepository, aber speichern Sie es in einer Variablen vom TypProductRepository– dies zeigt, wie Ihr Code von der Schnittstelle und nicht von der Implementierung abhängt. Speichern Sie drei Produkte:Laptop(id: 1, price: 999.99),Mouse(id: 2, price: 29.99) undKeyboard(id: 3, price: 79.99). Rufen Sie dann das Produkt mit der id 2 ab und geben Sie es im FormatFound: [name] - $[price]aus. Löschen Sie das Produkt mit der id 1. Geben Sie schließlich die Anzahl der verbleibenden Produkte alsProducts in stock: [count]aus.
Die Stärke dieses Musters liegt darin, dass Ihr Hauptcode nur die ProductRepository-Schnittstelle kennt. Sie könnten später ein DatabaseProductRepository oder ApiProductRepository erstellen und es austauschen, ohne die Geschäftslogik zu ändern!
Erwartete Ausgabe:
Found: Mouse - $29.99
Products in stock: 2Spickzettel
Das Repository-Pattern trennt die Datenzugriffslogik von der Geschäftslogik, indem es eine Abstraktionsschicht zwischen Ihrer Anwendung und den Datenquellen erstellt.
Definieren Sie ein Interface für Datenoperationen:
// Entität
class User {
final int id;
final String name;
User(this.id, this.name);
}
// Repository-Interface
abstract class UserRepository {
User? getById(int id);
List<User> getAll();
void save(User user);
void delete(int id);
}Erstellen Sie konkrete Implementierungen, die den tatsächlichen Speichermechanismus verwalten:
// In-Memory-Implementierung
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);
}Verwenden Sie das Repository über sein Interface:
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
}Die Geschäftslogik hängt nur vom Interface ab, was es Ihnen ermöglicht, Implementierungen auszutauschen (z. B. DatabaseUserRepository, ApiUserRepository), ohne den Code zu ändern, der das Repository verwendet.
Probier es selbst
import 'product_repository.dart';
void main() {
// Repository erstellen - beachte, dass der Typ das Interface ist, nicht die Implementierung
// TODO: Erstelle ein InMemoryProductRepository und speichere es in einer ProductRepository Variable
// TODO: Speichere drei Produkte:
// - Laptop (id: 1, price: 999.99)
// - Mouse (id: 2, price: 29.99)
// - Keyboard (id: 3, price: 79.99)
// TODO: Rufe das Produkt mit id 2 ab und gib es aus: "Found: [name] - $[price]"
// TODO: Lösche das Produkt mit id 1
// TODO: Gib die Anzahl der verbleibenden Produkte aus: "Products in stock: [count]"
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienBibliotheken & ImportsEinführung in die OOPKlassen vs. ObjekteDas this-SchlüsselwortMethodenInstanzvariablenKonstruktor-GrundlagenWiederholung – Einfacher Taschenrechner4Null-Sicherheit
Einführung in Null-SicherheitNullable vs. Non-NullableDie ? und ! OperatorenLate-Schlüsselwort & Null-SicherheitNull-aware OperatorenNull-Sicherheit in KlassenZusammenfassung – Benutzerprofil-System7Abstrakte Klassen & Interfaces
Abstrakte KlassenAbstrakte MethodenInterfaces in DartImplizite InterfacesImplementieren vs. ErweiternMehrfache InterfacesRückblick – Shape Calculator10Collections & Generics
Überblick über List, Set und MapTypsichere CollectionsGenerische KlassenGenerische MethodenGenerische ConstraintsIterable & IteratorZusammenfassung – Generische Speicherung13Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums mit Methoden16Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen2Konstruktoren in Dart
Standard-KonstruktorBenannte KonstruktorenInitialisierungslistenKonstante KonstruktorenFactory-KonstruktorenWeiterleitende KonstruktorenZusammenfassung – Shape Builder5Kapselung
Öffentliche vs. private MemberDie _ Präfix-KonventionSichtbarkeit auf Library-EbeneGetter & Setter im DetailInformation HidingRückblick – Schülerdatensätze8Mixins
Einführung in MixinsMixins erstellenMehrere Mixins verwendenDas on-Schlüsselwort in MixinsMixin vs. VererbungMixin vs. InterfaceZusammenfassung - Tiersystem11Spezielle Methoden
toString() überschreibenhashCode & == überschreibenComparable-Interfacecall()-MethodenoSuchMethod überschreibenZusammenfassung – Eigene Collection14Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterObserver-MusterStrategy-Muster3Klasseneigenschaften
Instanz- vs. statische MemberFinal & Const FelderLate-VariablenStatische Methoden & FelderGetter und SetterZusammenfassung – Bankkonto-Manager6Vererbung
Grundlagen der VererbungDas super-SchlüsselwortMethoden überschreibenDie @override-AnnotationDas final-Klassen-SchlüsselwortKonstruktoren & VererbungRückblick – Mitarbeiter-Hierarchie9Polymorphismus
Polymorphismus-GrundlagenPolymorphismus über InterfacesTypprüfung zur LaufzeitDie is & as OperatorenDas covariant SchlüsselwortZusammenfassung – Payment Processor12Asynchrone OOP
Futures & async/awaitStreams GrundlagenStream ControllerAsynchrone KonstruktorenAsync in KlassenmethodenZusammenfassung – Data Fetcher15Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterZustandsmusterComposite-MusterRepository-Muster