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
EinfachErstellen wir ein Produktinventarsystem mit dem Repository-Muster! Du erstellst eine Abstraktionsschicht, die die Speicherung von Produkten von ihrer Verwendung trennt. Dadurch lassen sich Speicherimplementierungen einfach austauschen, ohne die Geschäftslogik zu ändern.
Du organisierst deinen Code in zwei Dateien:
product_repository.dart: Diese Datei enthält deine Entitäts- und Repository-Klassen. Beginne mit einerProduct-Klasse, diefinal-Felder fürid(int),name(String) undprice(double) besitzt. Definiere anschließend eine abstrakte KlasseProductRepository, die den Vertrag für Datenoperationen festlegt:getById(int id), das ein möglicherweise nullbaresProductzurückgibt,getAll(), das eine Liste von Produkten zurückgibt,save(Product product)zum Speichern eines Produkts unddelete(int id)zum Entfernen eines Produkts. Erstelle schließlich einInMemoryProductRepository, das diese Schnittstelle mithilfe einerMap<int, Product>zur Speicherung implementiert.main.dart: Importiere deine Repository-Datei und demonstriere das Repository-Muster in Aktion. Erstelle einInMemoryProductRepository, speichere es aber in einer Variablen vom TypProductRepository– so wird deutlich, dass dein Code von der Schnittstelle und nicht von der Implementierung abhängt. Speichere drei Produkte:Laptop(id: 1, price: 999.99),Mouse(id: 2, price: 29.99) undKeyboard(id: 3, price: 79.99). Rufe anschließend das Produkt mit der id 2 ab und gib es im FormatFound: [name] - $[price]aus. Lösche das Produkt mit der id 1. Gib zum Schluss die Anzahl der verbleibenden Produkte alsProducts in stock: [count]aus.
Die Stärke dieses Musters besteht darin, dass dein Hauptcode nur die ProductRepository-Schnittstelle kennt. Du könntest später ein DatabaseProductRepository oder ein ApiProductRepository erstellen und es einbinden, ohne die Geschäftslogik ändern zu müssen!
Erwartete Ausgabe:
Found: Mouse - $29.99
Products in stock: 2Probier 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 Schlüsselwort thisMethodenInstanzvariablenGrundlagen von KonstruktorenRückblick – Einfacher Taschenrechner4Null-Sicherheit
Einführung in die Null-SicherheitNullable vs. Non-NullableDie Operatoren ? und !Late-Schlüsselwort und Null-SicherheitNull-bewusste OperatorenNull-Sicherheit in KlassenRückblick – Benutzers профilsystem7Abstrakte Klassen & Interfaces
Abstrakte KlassenAbstrakte MethodenInterfaces in DartImplizite InterfacesImplementieren vs. ErweiternMehrere InterfacesZusammenfassung – Formrechner10Collections & Generics
Übersicht über List, Set und MapTypsichere CollectionsGenerische KlassenGenerische MethodenGenerische EinschränkungenIterable & IteratorRückblick – Generischer Speicher13Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungErweiterungsmethodenAufrufbare KlassenVersiegelte Klassen (Dart 3)Records (Dart 3)Muster & Matching (3.0)Enums mit Methoden16Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen2Konstruktoren in Dart
StandardkonstruktorBenannte KonstruktorenInitialisierungslistenKonstantenkonstruktorenFactory-KonstruktorenWeiterleitende KonstruktorenRückblick – Shape Builder5Kapselung
Öffentliche vs. private MemberDie _-Präfix-KonventionBibliotheksweite SichtbarkeitTiefergehende Getter & SetterInformationsverbergungRückblick – Studierendendaten8Mixins
Einführung in MixinsMixins erstellenMehrere Mixins verwendenDas Schlüsselwort „on“ in MixinsMixin vs. VererbungMixin vs. SchnittstelleRückblick – Tiersystem11Spezielle Methoden
toString()-ÜberschreibunghashCode- & ==-ÜberschreibungComparable-Schnittstellecall()-MethodenoSuchMethod-ÜberschreibungWiederholung – Benutzerdefinierte Collection14Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFabrikmusterBeobachtermusterStrategiemuster17Abschließende Herausforderungen
E-Learning-PlattformBankingsystemSpielcharaktersystemFahrzeugvermietung3Klassen-Eigenschaften
Instanz- vs. statische MemberFinal- und Const-FelderLate-VariablenStatische Methoden und FelderGetter und SetterRückblick – Bankkontoverwaltung6Vererbung
Grundlegende VererbungDas Schlüsselwort superMethodenüberschreibungDie Annotation @overrideDas Schlüsselwort final für KlassenKonstruktoren und VererbungZusammenfassung – Mitarbeiterhierarchie9Polymorphismus
Grundlagen des PolymorphismusPolymorphismus über InterfacesLaufzeit-TypüberprüfungDie Operatoren is & asCovariant-SchlüsselwortZusammenfassung – Zahlungsprozessor12Asynchrone OOP
Futures & async/awaitStream-GrundlagenStream-ControllerAsynchrone KonstruktorenAsync in KlassenmethodenZusammenfassung – Datenabruf15Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterState-MusterComposite-MusterRepository-MusterÜbe selbstständig: Online-Dart-Compiler