Wzorzec polecenia
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 96 z 104.
Wzorzec Command enkapsuluje żądanie jako obiekt, umożliwiając parametryzowanie klientów różnymi żądaniami, kolejkowanie operacji lub obsługę funkcji cofania. Oddziela obiekt wywołujący operację od tego, który wie, jak ją wykonać.
Ten wzorzec ma cztery kluczowe elementy: interfejs Command z metodą execute(), Konkretne polecenia, które implementują określone działania, Odbiorcę, który wykonuje właściwą pracę, oraz Inicjatora, który uruchamia polecenia:
#include <iostream>
#include <memory>
#include <vector>
// Odbiornik – wie, jak wykonywać operacje
class Light {
public:
void turnOn() { std::cout << "Light is ON\n"; }
void turnOff() { std::cout << "Light is OFF\n"; }
};
// Interfejs Command
class Command {
public:
virtual void execute() = 0;
virtual void undo() = 0;
virtual ~Command() = default;
};
// Konkretne polecenia
class LightOnCommand : public Command {
Light& light;
public:
LightOnCommand(Light& l) : light(l) {}
void execute() override { light.turnOn(); }
void undo() override { light.turnOff(); }
};
class LightOffCommand : public Command {
Light& light;
public:
LightOffCommand(Light& l) : light(l) {}
void execute() override { light.turnOff(); }
void undo() override { light.turnOn(); }
};
// Wywołujący
class RemoteControl {
std::vector<std::unique_ptr<Command>> history;
public:
void pressButton(std::unique_ptr<Command> cmd) {
cmd->execute();
history.push_back(std::move(cmd));
}
void pressUndo() {
if (!history.empty()) {
history.back()->undo();
history.pop_back();
}
}
};Każde polecenie przechowuje referencję do swojego odbiorcy i wywołuje odpowiednią metodę podczas wykonywania. Inicjator nie wie, jaka akcja zostanie wykonana — po prostu wywołuje execute(). Przechowując polecenia w historii, możesz łatwo zaimplementować cofanie, wywołując metodę undo() każdego polecenia.
Użyj wzorca Command, gdy potrzebujesz kolejkować operacje, zaimplementować cofanie i ponawianie lub oddzielić nadawcę żądania od jego obsługi.
Wyzwanie
ŁatwyZbudujmy edytor tekstu z funkcją cofania, wykorzystując wzorzec Command. Utworzysz system, w którym operacje edycji (takie jak wstawianie i usuwanie tekstu) są enkapsulowane jako obiekty poleceń, co pozwala użytkownikom wykonywać działania, a następnie cofać je w odwrotnej kolejności, tak jak w prawdziwym edytorze tekstu.
Rozmieścisz swój kod w czterech plikach:
TextDocument.h: Utwórz klasę odbiorcy, która przechowuje właściwą treść tekstu i nią manipuluje.Klasa
TextDocumentpowinna przechowywać treść dokumentu jako ciąg znaków. Zaimplementuj następujące metody:insertText(const std::string& text): dopisuje tekst na końcu treścideleteText(int count): usuwa ostatniecountznaków z treści (nie robi nic, jeśli liczba znaków przekracza długość treści)getContent(): zwraca bieżącą treść jako stałe odwołanie
Command.h: Zdefiniuj interfejs polecenia i konkretne klasy poleceń.Utwórz abstrakcyjną klasę
Commandz czysto wirtualnymi metodamiexecute()iundo()oraz wirtualnym destruktorem.Następnie zaimplementuj dwa konkretne polecenia:
InsertCommand: przyjmuje odwołanie doTextDocumenti ciąg znaków do wstawienia. Po wykonaniu wstawia tekst. Po cofnięciu usuwa taką samą liczbę znaków, jaka została wstawiona.DeleteCommand: przyjmuje odwołanie doTextDocumenti liczbę znaków do usunięcia. Musi przechowywać usunięty tekst, aby móc go przywrócić po cofnięciu. Po wykonaniu usuwa znaki z końca. Po cofnięciu ponownie wstawia zapisany tekst.
TextEditor.h: Utwórz obiekt wywołujący, który zarządza wykonywaniem poleceń i ich historią.Klasa
TextEditorpowinna przechowywać odwołanie doTextDocumentoraz historię wykonanych poleceń w wektorzeunique_ptr<Command>. Zaimplementuj:executeCommand(std::unique_ptr<Command> cmd): wykonuje polecenie i dodaje je do historiiundo(): cofa ostatnie polecenie i usuwa je z historii (nie robi nic, jeśli historia jest pusta)showContent(): wyświetla bieżącą treść dokumentu albo[empty], jeśli treść jest pusta
main.cpp: Zademonstruj wzorzec Command z funkcją cofania.Wczytaj trzy dane wejściowe:
- Pierwszy tekst do wstawienia (ciąg znaków)
- Drugi tekst do wstawienia (ciąg znaków)
- Liczba znaków do usunięcia (liczba całkowita)
Utwórz obiekt
TextDocumenti obiektTextEditor. Następnie wykonaj kolejno poniższe operacje, wyświetlając treść po każdym kroku:- Wstaw pierwszy tekst, a następnie wyświetl treść
- Wstaw drugi tekst, a następnie wyświetl treść
- Usuń podaną liczbę znaków, a następnie wyświetl treść
- Cofnij raz, a następnie wyświetl treść
- Cofnij jeszcze raz, a następnie wyświetl treść
Na przykład przy danych wejściowych Hello, World i 3:
Hello
Hello World
Hello Wo
Hello World
HelloPrzy danych wejściowych Code, Editor i 6:
Code
CodeEditor
Code
CodeEditor
CodeZwróć uwagę, że każde polecenie wie, jak cofnąć własne działanie. DeleteCommand pamięta, co usunęło, aby móc to przywrócić, a InsertCommand wie dokładnie, ile znaków usunąć podczas cofania. Na tym polega siła wzorca Command: operacje stają się obiektami pierwszej klasy, które można przechowywać, wykonywać i cofać.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"
int main() {
// Wczytaj dane wejściowe
std::string firstText;
std::string secondText;
int deleteCount;
std::getline(std::cin, firstText);
std::getline(std::cin, secondText);
std::cin >> deleteCount;
// TODO: Utwórz TextDocument i TextEditor
// TODO: Wykonaj operacje po kolei, wyświetlając zawartość po każdej z nich:
// 1. Wstaw pierwszy tekst, a następnie wyświetl zawartość
// 2. Wstaw drugi tekst, a następnie wyświetl zawartość
// 3. Usuń określoną liczbę znaków, a następnie wyświetl zawartość
// 4. Cofnij operację raz, a następnie wyświetl zawartość
// 5. Cofnij operację jeszcze raz, a następnie wyświetl zawartość
// Wskazówka: Użyj std::make_unique<InsertCommand>(...) oraz
// std::make_unique<DeleteCommand>(...) do tworzenia poleceń
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów11Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieMixiny z użyciem CRTPIdiom PimplWymazywanie typówKlasy enum i silne typowanieObsługa wyjątków w OOPWłasne hierarchie wyjątków14Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytuRAII jako wzorzec3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online