Behavioral — Davranışsal Desenler
Command
Komut Deseni
Amaç: Bir isteği, tüm bilgisiyle birlikte bağımsız bir nesneye dönüştürmek; böylece istekleri kuyruğa almak, kaydetmek ve geri almak (undo) mümkün olur.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Bir restoranda garson siparişinizi alır ve bir sipariş fişine yazar. Fiş mutfağa gider, sırayla asılır ve aşçı sırası gelince yemeği hazırlar.
Burada önemli olan şu: garsonun yemek yapmayı bilmesine gerek yok, aşçının da sizi tanımasına gerek yok. Aradaki her şeyi fiş taşır: ne istendiği, kaç adet, hangi masa. Fiş beklemeye alınabilir, iptal edilebilir, gün sonunda kaç sipariş verildiği fişlerden sayılabilir.
Bir de "Geri Al" düğmesi
Bir yazı programında Ctrl+Z'ye bastığınızda son yaptığınız işlem geri alınır. Bu mümkündür çünkü program yaptığınız her işlemi bir "fiş" gibi kaydetmiştir ve her fiş kendini nasıl geri alacağını bilir.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
Command, bir işlemi (metot çağrısını) parametreleriyle birlikte bir nesne içine kapsüller. Bu nesnenin genellikle tek bir Execute() metodu (ve isteğe bağlı Undo()) vardır.
Katılımcılar
- Command:
Execute()/Undo()arayüzü. - ConcreteCommand: Receiver'a referans tutar ve işlemi ona yaptırır.
- Receiver: Asıl işi bilen nesne (ör.
TextDocument). - Invoker: Komutu tetikleyen nesne; komutun ne yaptığını bilmez (ör. buton, menü, kuyruk işleyici,
CommandManager). - Client: Komutları oluşturur ve invoker'a verir.
Kullanım alanları
- Undo / Redo mekanizmaları
- İş kuyrukları (job queue), zamanlanmış görevler, makro kaydı
- WPF / MAUI'deki
ICommandarayüzü (MVVM) - CQRS mimarisindeki Command nesneleri (
CreateOrderCommand) bu fikrin mimari düzeydeki yansımasıdır.
Avantajlar
- İsteği yapan (invoker) ile işi yapan (receiver) birbirinden ayrılır.
- Komutlar birleştirilebilir (makro komut = Composite + Command).
- Yeni komut eklemek mevcut kodu değiştirmez (Open/Closed).
Dezavantajlar
- Her işlem için yeni bir sınıf gerekir; kod hacmi artar.
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Basit bir metin editörü. Metin ekleme ve silme işlemleri komut olarak tutulacak; böylece Geri Al ve Yinele desteklenecek.
Adım 1 Receiver: asıl işi bilen sınıf
public class TextDocument
{
private readonly System.Text.StringBuilder _text = new();
public void Insert(int position, string value) => _text.Insert(position, value);
public void Delete(int position, int length) => _text.Remove(position, length);
public string Substring(int position, int length) =>
_text.ToString(position, length);
public int Length => _text.Length;
public override string ToString() => _text.ToString();
}Adım 2 Command arayüzü ve somut komutlar
Her komut, işi geri almak için gereken bilgiyi kendi içinde saklar.
public interface ICommand
{
string Name { get; }
void Execute();
void Undo();
}
public class AppendTextCommand : ICommand
{
private readonly TextDocument _doc;
private readonly string _text;
private int _position;
public AppendTextCommand(TextDocument doc, string text) { _doc = doc; _text = text; }
public string Name => $"Ekle(\"{_text}\")";
public void Execute() { _position = _doc.Length; _doc.Insert(_position, _text); }
public void Undo() => _doc.Delete(_position, _text.Length);
}
public class DeleteLastCommand : ICommand
{
private readonly TextDocument _doc;
private readonly int _count;
private string _deleted = "";
public DeleteLastCommand(TextDocument doc, int count) { _doc = doc; _count = count; }
public string Name => $"Sil({_count})";
public void Execute()
{
int start = _doc.Length - _count;
_deleted = _doc.Substring(start, _count); // geri alabilmek için sakla
_doc.Delete(start, _count);
}
public void Undo() => _doc.Insert(_doc.Length, _deleted);
}Adım 3 Invoker: komutları çalıştıran ve geçmişi tutan sınıf
CommandManager komutların ne yaptığını bilmez; sadece Execute ve Undo çağırır.
public class CommandManager
{
private readonly Stack<ICommand> _undo = new();
private readonly Stack<ICommand> _redo = new();
public void Run(ICommand command)
{
command.Execute();
_undo.Push(command);
_redo.Clear(); // yeni işlem yapılınca yinele geçmişi silinir
Console.WriteLine($" Çalıştı : {command.Name}");
}
public void Undo()
{
if (_undo.TryPop(out var command))
{
command.Undo();
_redo.Push(command);
Console.WriteLine($" Geri al : {command.Name}");
}
}
public void Redo()
{
if (_redo.TryPop(out var command))
{
command.Execute();
_undo.Push(command);
Console.WriteLine($" Yinele : {command.Name}");
}
}
}Adım 4 Kullanım
Tüm parçaları bir araya getiren istemci (client) kodu:
var doc = new TextDocument();
var editor = new CommandManager();
editor.Run(new AppendTextCommand(doc, "Merhaba"));
editor.Run(new AppendTextCommand(doc, " dünya"));
editor.Run(new AppendTextCommand(doc, "!!!"));
Console.WriteLine($"Metin: \"{doc}\"");
editor.Run(new DeleteLastCommand(doc, 2));
Console.WriteLine($"Metin: \"{doc}\"");
editor.Undo();
editor.Undo();
Console.WriteLine($"Metin: \"{doc}\"");
editor.Redo();
Console.WriteLine($"Metin: \"{doc}\"");Konsol çıktısı
Çalıştı : Ekle("Merhaba")
Çalıştı : Ekle(" dünya")
Çalıştı : Ekle("!!!")
Metin: "Merhaba dünya!!!"
Çalıştı : Sil(2)
Metin: "Merhaba dünya!"
Geri al : Sil(2)
Geri al : Ekle("!!!")
Metin: "Merhaba dünya"
Yinele : Ekle("!!!")
Metin: "Merhaba dünya!!!"Kodda dikkat edilmesi gerekenler
CommandManagerhiçbir zamandoc.Insertveyadoc.Deleteçağırmıyor; tüm bilgi komut nesnelerinin içinde.DeleteLastCommand, sildiği metni saklıyor; geri alma işlemi bu sayede mümkün.- Komutlar nesne olduğu için bir listeye kaydedilip daha sonra (ör. makro olarak) tekrar çalıştırılabilir.