Menu
Coddy logo textTech

Komut Kalıbı

Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 96 / 104.

Komut deseni (Command pattern), bir isteği bir nesne olarak kapsüller; bu da istemcileri farklı isteklerle parametrelendirmenize, işlemleri kuyruğa almanıza veya geri alma işlevini desteklemenize olanak tanır. Bir işlemi tetikleyen nesne ile o işlemin nasıl gerçekleştirileceğini bilen nesneyi birbirinden ayırır.

Desen dört ana bileşene sahiptir: execute() metoduna sahip bir Command arayüzü, belirli eylemleri uygulayan Concrete Commands, asıl işi gerçekleştiren bir Receiver ve komutları tetikleyen bir Invoker:

#include <iostream>
#include <memory>
#include <vector>

// Alıcı - işlemlerin nasıl gerçekleştirileceğini bilir
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

// Komut arayüzü
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

// Somut Komutlar
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(); }
};

// Çağırıcı (Invoker)
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();
        }
    }
};

Her komut, alıcısına bir referans saklar ve yürütüldüğünde uygun metodu çağırır. Çağırıcı, hangi eylemin gerçekleşeceğini bilmez; sadece execute() metodunu çağırır. Komutları bir geçmişte saklayarak, her komutun undo() metodunu çağırıp geri alma işlemini kolayca uygulayabilirsiniz.

İşlemleri kuyruğa almanız, geri al/yinele (undo/redo) özelliğini uygulamanız veya bir isteğin göndericisini işleyicisinden ayırmanız gerektiğinde Command desenini kullanın.

challenge icon

Görev

Kolay

Komut (Command) desenini kullanarak geri alma (undo) özelliğine sahip bir Metin Düzenleyici inşa edelim. Düzenleme işlemlerinin (metin ekleme ve silme gibi) komut nesneleri olarak kapsüllendiği, kullanıcıların eylemleri yürütmesine ve ardından bunları ters sırada geri almasına olanak tanıyan bir sistem oluşturacaksınız; tıpkı gerçek bir metin düzenleyici gibi.

Kodunuzu dört dosya halinde düzenleyeceksiniz:

  • TextDocument.h: Gerçek metin içeriğini tutan ve işleyen alıcı (receiver) sınıfını oluşturun.

    TextDocument sınıfınız, belgenin içeriğini bir dize (string) olarak saklamalıdır. Şu metotları uygulayın:

    • insertText(const std::string& text) — metni içeriğin sonuna ekler
    • deleteText(int count) — içeriğin sonundan count kadar karakteri siler (eğer count içerik uzunluğunu aşarsa hiçbir şey yapmaz)
    • getContent() — mevcut içeriği bir sabit referans (const reference) olarak döndürür
  • Command.h: Komut arayüzünü ve somut komut sınıflarını tanımlayın.

    Saf sanal (pure virtual) execute() ve undo() metotlarına ve bir sanal yıkıcıya (virtual destructor) sahip soyut bir Command sınıfı oluşturun.

    Ardından iki somut komut uygulayın:

    • InsertCommand — bir TextDocument referansı ve eklenecek bir dize alır. Yürütüldüğünde metni ekler. Geri alındığında, eklenen karakter sayısı kadar karakteri siler.
    • DeleteCommand — bir TextDocument referansı ve silinecek karakter sayısını alır. Geri alma işleminde geri yükleyebilmek için silinen metni saklaması gerekir. Yürütüldüğünde, sondaki karakterleri kaldırır. Geri alındığında, saklanan metni tekrar içeri ekler.
  • TextEditor.h: Komut yürütmeyi ve geçmişi yöneten çağırıcıyı (invoker) oluşturun.

    TextEditor sınıfınız bir TextDocument referansı tutmalı ve unique_ptr<Command> türünde bir vektör kullanarak yürütülen komutların geçmişini tutmalıdır. Şunları uygulayın:

    • executeCommand(std::unique_ptr<Command> cmd) — komutu yürütür ve geçmişe ekler
    • undo() — en son komutu geri alır ve geçmişten kaldırır (geçmiş boşsa hiçbir şey yapmaz)
    • showContent() — mevcut belge içeriğini yazdırır veya içerik boşsa [empty] yazdırır
  • main.cpp: Geri alma özelliğine sahip Komut desenini gösterin.

    Üç girdi okuyun:

    1. Eklenecek ilk metin (string)
    2. Eklenecek ikinci metin (string)
    3. Silinecek karakter sayısı (integer)

    Bir TextDocument ve bir TextEditor oluşturun. Ardından her adımdan sonra içeriği göstererek şu işlemleri sırasıyla gerçekleştirin:

    1. İlk metni ekleyin, ardından içeriği gösterin
    2. İkinci metni ekleyin, ardından içeriği gösterin
    3. Belirtilen sayıda karakteri silin, ardından içeriği gösterin
    4. Bir kez geri alın, ardından içeriği gösterin
    5. Bir kez daha geri alın, ardından içeriği gösterin

Örneğin, Hello, World ve 3 girdileriyle:

Hello
Hello World
Hello Wo
Hello World
Hello

Code, Editor ve 6 girdileriyle:

Code
CodeEditor
Code
CodeEditor
Code

Her komutun kendisini nasıl tersine çevireceğini bildiğine dikkat edin. DeleteCommand neyi sildiğini hatırlar, böylece onu geri yükleyebilir ve InsertCommand geri alırken tam olarak kaç karakteri kaldıracağını bilir. Komut deseninin gücü budur; işlemler saklanabilen, yürütülebilen ve tersine çevrilebilen birinci sınıf nesneler haline gelir.

Kopya kağıdı

Command deseni, bir isteği bir nesne olarak kapsüller; bu da istemcileri farklı isteklerle parametreleştirmenize, işlemleri kuyruğa almanıza veya geri alma (undo) işlevini desteklemenize olanak tanır. Bir işlemi başlatan nesne ile işlemin nasıl gerçekleştirileceğini bilen nesneyi birbirinden ayırır.

Desenin dört ana bileşeni vardır:

  • execute() metoduna sahip Komut arayüzü (Command interface)
  • Belirli eylemleri uygulayan Somut Komutlar (Concrete Commands)
  • Gerçek işi gerçekleştiren Alıcı (Receiver)
  • Komutları tetikleyen Çağırıcı (Invoker)

Temel yapı:

// Alıcı - işlemlerin nasıl gerçekleştirileceğini bilir
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

// Komut arayüzü
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

// Somut Komut
class LightOnCommand : public Command {
    Light& light;
public:
    LightOnCommand(Light& l) : light(l) {}
    void execute() override { light.turnOn(); }
    void undo() override { light.turnOff(); }
};

// Çağırıcı
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();
        }
    }
};

Her komut, alıcısına bir referans saklar ve yürütüldüğünde uygun metodu çağırır. Çağırıcı hangi eylemin gerçekleşeceğini bilmez; sadece execute() metodunu çağırır. Komutları bir geçmişte saklayarak, her komutun undo() metodunu çağırıp geri alma işlemini uygulayabilirsiniz.

İşlemleri kuyruğa almanız, geri al/yinele (undo/redo) özelliğini uygulamanız veya bir isteğin göndericisi ile işleyicisini birbirinden ayırmanız gerektiğinde Komut desenini kullanın.

Kendin dene

#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"

int main() {
    // Girdileri oku
    std::string firstText;
    std::string secondText;
    int deleteCount;
    
    std::getline(std::cin, firstText);
    std::getline(std::cin, secondText);
    std::cin >> deleteCount;

    // TODO: Bir TextDocument ve bir TextEditor oluşturun
    
    // TODO: İşlemleri sırasıyla gerçekleştirin ve her birinden sonra içeriği gösterin:
    // 1. İlk metni ekleyin, ardından içeriği gösterin
    // 2. İkinci metni ekleyin, ardından içeriği gösterin
    // 3. Belirtilen sayıda karakteri silin, ardından içeriği gösterin
    // 4. Bir kez geri alın, ardından içeriği gösterin
    // 5. Bir kez daha geri alın, ardından içeriği gösterin
    
    // İpucu: Komutları oluşturmak için std::make_unique<InsertCommand>(...) ve 
    //       std::make_unique<DeleteCommand>(...) kullanın

    return 0;
}
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Nesne Yönelimli Programlama bölümündeki tüm dersler