Menu
Coddy logo textTech

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.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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 TextDocument powinna przechowywać treść dokumentu jako ciąg znaków. Zaimplementuj następujące metody:

    • insertText(const std::string& text): dopisuje tekst na końcu treści
    • deleteText(int count): usuwa ostatnie count znakó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ę Command z czysto wirtualnymi metodami execute() i undo() oraz wirtualnym destruktorem.

    Następnie zaimplementuj dwa konkretne polecenia:

    • InsertCommand: przyjmuje odwołanie do TextDocument i 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 do TextDocument i 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 TextEditor powinna przechowywać odwołanie do TextDocument oraz historię wykonanych poleceń w wektorze unique_ptr<Command>. Zaimplementuj:

    • executeCommand(std::unique_ptr<Command> cmd): wykonuje polecenie i dodaje je do historii
    • undo(): 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:

    1. Pierwszy tekst do wstawienia (ciąg znaków)
    2. Drugi tekst do wstawienia (ciąg znaków)
    3. Liczba znaków do usunięcia (liczba całkowita)

    Utwórz obiekt TextDocument i obiekt TextEditor. Następnie wykonaj kolejno poniższe operacje, wyświetlając treść po każdym kroku:

    1. Wstaw pierwszy tekst, a następnie wyświetl treść
    2. Wstaw drugi tekst, a następnie wyświetl treść
    3. Usuń podaną liczbę znaków, a następnie wyświetl treść
    4. Cofnij raz, a następnie wyświetl treść
    5. 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
Hello

Przy danych wejściowych Code, Editor i 6:

Code
CodeEditor
Code
CodeEditor
Code

Zwróć 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;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online