Command-Muster
Teil des Abschnitts Objektorientierte Programmierung der Dart-Journey von Coddy — Lektion 94 von 110.
Das Command-Pattern kapselt eine Anfrage als Objekt und ermöglicht es Ihnen so, Clients mit verschiedenen Anfragen zu parametrisieren, Operationen in eine Warteschlange zu stellen oder eine Rückgängig-Funktionalität zu unterstützen. Anstatt eine Methode direkt aufzurufen, kapseln Sie die Aktion in einem Command-Objekt, das gespeichert, weitergegeben oder später ausgeführt werden kann.
Das Muster umfasst drei Schlüsselkomponenten: ein Command-Interface mit einer execute-Methode, konkrete Befehle (Concrete Commands), die spezifische Aktionen implementieren, und einen Receiver, der die eigentliche Arbeit verrichtet. Ein optionaler Invoker löst Befehle aus, ohne zu wissen, was sie tun:
abstract class Command {
void execute();
}
class Light {
void turnOn() => print('Light is ON');
void turnOff() => print('Light is OFF');
}
class TurnOnCommand implements Command {
final Light light;
TurnOnCommand(this.light);
@override
void execute() => light.turnOn();
}
class TurnOffCommand implements Command {
final Light light;
TurnOffCommand(this.light);
@override
void execute() => light.turnOff();
}
class RemoteControl {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void pressButton() {
_command?.execute();
}
}
void main() {
var light = Light();
var remote = RemoteControl();
remote.setCommand(TurnOnCommand(light));
remote.pressButton(); // Light is ON
remote.setCommand(TurnOffCommand(light));
remote.pressButton(); // Light is OFF
}Die RemoteControl weiß nichts über Lichter – sie führt einfach jeden Befehl aus, der ihr gegeben wird. Diese Entkopplung macht es einfach, neue Befehle hinzuzufügen, ohne den Invoker zu modifizieren. Das Command-Pattern ist ideal für die Implementierung von Undo/Redo-Systemen, Task-Queues oder Makro-Aufzeichnungen, bei denen Aktionen als First-Class-Objekte behandelt werden müssen.
Aufgabe
EinfachLass uns ein Befehlssystem für einen Texteditor mit dem Command-Pattern erstellen! Du wirst ein System entwickeln, das Textoperationen als Befehlsobjekte kapselt, sodass ein Editor verschiedene Aktionen ausführen kann, ohne die Details jeder Operation zu kennen. Dies ist ein klassischer Anwendungsfall für das Command-Pattern – stell es dir wie ein Undo/Redo-System vor, bei dem jede Aktion als Objekt gespeichert wird.
Du wirst deinen Code in zwei Dateien organisieren:
commands.dart: Erstelle hier dein Befehlssystem. Beginne mit einer abstrakten KlasseCommand, die eineexecute()-Methode deklariert. Erstelle dann eineTextDocument-Klasse (den Empfänger), die einString content-Feld (anfänglich leer) enthält und drei Methoden besitzt:append(String text)– fügt Text am Ende des Inhalts hinzuclear()– setzt den Inhalt auf einen leeren Stringshow()– gibtDocument: [content]aus
TextDocumentarbeiten:AppendCommand– nimmt ein Dokument und Text in seinem Konstruktor entgegen und hängt den Text beim Ausführen anClearCommand– nimmt ein Dokument in seinem Konstruktor entgegen und leert es beim AusführenShowCommand– nimmt ein Dokument in seinem Konstruktor entgegen und zeigt dessen Inhalt beim Ausführen an
TextEditor-Klasse (den Aufrufer), die eine Liste von Befehlen speichert. Sie sollte eineaddCommand(Command command)-Methode zum Einreihen von Befehlen und einerunAll()-Methode haben, die alle eingereihten Befehle der Reihe nach ausführt.main.dart: Importiere deine Befehlsdatei und demonstriere das Command-Pattern. Erstelle einTextDocumentund einenTextEditor. Reihe die folgenden Befehle nacheinander ein: hängeHelloan, hängeWorldan, zeige das Dokument an, hänge!an, zeige es erneut an, leere das Dokument und zeige es ein letztes Mal an. Führe dann alle eingereihten Befehle aus.
Beachte, dass der TextEditor nichts über das Anhängen, Leeren oder Anzeigen weiß – er führt einfach die Befehle aus, die ihm gegeben werden. Diese Entkopplung ist der Kern des Command-Patterns!
Erwartete Ausgabe:
Document: Hello World
Document: Hello World!
Document: Spickzettel
Das Command-Pattern (Befehlsmuster) kapselt eine Anfrage als Objekt und ermöglicht es Ihnen, Clients mit verschiedenen Anfragen zu parametrisieren, Operationen in eine Warteschlange zu stellen oder eine Rückgängig-Funktionalität zu unterstützen.
Das Muster umfasst drei Schlüsselkomponenten:
- Command-Schnittstelle mit einer execute-Methode
- Konkrete Befehle (Concrete Commands), die spezifische Aktionen implementieren
- Empfänger (Receiver), der die eigentliche Arbeit verrichtet
- Aufrufer (Invoker) (optional), der Befehle auslöst, ohne zu wissen, was sie tun
Grundstruktur:
abstract class Command {
void execute();
}
class Receiver {
void action() => print('Action performed');
}
class ConcreteCommand implements Command {
final Receiver receiver;
ConcreteCommand(this.receiver);
@override
void execute() => receiver.action();
}
class Invoker {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void trigger() {
_command?.execute();
}
}Beispiel mit einem Lichtsteuerungssystem:
class Light {
void turnOn() => print('Light is ON');
void turnOff() => print('Light is OFF');
}
class TurnOnCommand implements Command {
final Light light;
TurnOnCommand(this.light);
@override
void execute() => light.turnOn();
}
class RemoteControl {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void pressButton() {
_command?.execute();
}
}
void main() {
var light = Light();
var remote = RemoteControl();
remote.setCommand(TurnOnCommand(light));
remote.pressButton(); // Light is ON
}Der Aufrufer weiß nichts über den Empfänger – er führt einfach den Befehl aus, der ihm gegeben wurde. Diese Entkopplung macht es einfach, neue Befehle hinzuzufügen, ohne den Aufrufer zu ändern.
Probier es selbst
import 'commands.dart';
void main() {
// TODO: Erstelle eine TextDocument-Instanz
// TODO: Erstelle eine TextEditor-Instanz
// TODO: Reihe die folgenden Befehle in dieser Reihenfolge ein:
// 1. Hänge "Hello" an
// 2. Hänge " World" an
// 3. Zeige das Dokument an
// 4. Hänge "!" an
// 5. Zeige das Dokument erneut an
// 6. Leere das Dokument
// 7. Zeige das Dokument ein letztes Mal an
// TODO: Führe alle eingereihten Befehle aus
}
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