Yazılım geliştirme sürecinde, bir işlemi tetikleyen tarafın (invoker) bu işlemi gerçekleştiren taraftan (receiver) bağımsız olması gereken durumlarla sıkça karşılaşılır. Örneğin bir metin editöründeki araç çubuğu düğmeleri, klavye kısayolları ve bağlam menüsü aynı "kopyala" işlemini farklı yollarla tetikler; ancak hepsinin aynı mantığı doğrudan çağırması, tetikleyicileri işlemin gerçekleştirildiği nesneye sıkıca bağlar. Üstüne bir de undo/redo, işlem kuyruğu veya loglama gereksinimleri eklendiğinde bu bağ her şeyi daha da girift hale getirir.
Bu durumda Command Pattern, bir isteği (request) kendi içinde tüm bilgileri (alıcı, metot, parametreler) taşıyan bağımsız bir nesneye dönüştüren davranışsal bir tasarım desenidir. Böylece istekler parametreleştirilebilir, kuyruğa alınabilir, loglanabilir ve geri alınabilir (undo). Bununla birlikte Open-Closed prensibine uyumluluğu sayesinde yeni komutlar eklemek mevcut kodu değiştirmeyi gerektirmez.
GoF yapısına göre ICommand arayüzü Execute ve isteğe bağlı Undo metodlarını tanımlar. ConcreteCommand bu arayüzü implemente eder ve işlemi gerçekleştirecek olan Receiver nesnesini içinde barındırır. Invoker bir komutu saklayıp tetikleyen taraftır; kimin ne yaptığını bilmez. Client ise komutları oluşturarak invoker'a teslim eder.
public interface ICommand
{
void Execute();
void Undo();
}
public class Receiver
{
public void Action(string operation)
{
Console.WriteLine($"Receiver: '{operation}' işlemi gerçekleştirildi.");
}
public void ReverseAction(string operation)
{
Console.WriteLine($"Receiver: '{operation}' işlemi geri alındı.");
}
}
public class ConcreteCommand : ICommand
{
private readonly Receiver _receiver;
private readonly string _operation;
public ConcreteCommand(Receiver receiver, string operation)
{
_receiver = receiver;
_operation = operation;
}
public void Execute() => _receiver.Action(_operation);
public void Undo() => _receiver.ReverseAction(_operation);
}
public class Invoker
{
private ICommand? _command;
public void SetCommand(ICommand command) => _command = command;
public void ExecuteCommand() => _command?.Execute();
public void UndoCommand() => _command?.Undo();
}Aşağıdaki örnekte bir akıllı ev (smart home) sistemi modellenmiştir. Işıklar ve klima birer Receiver'dır; her cihaz için ayrı ConcreteCommand'lar tanımlanır. Bir RemoteControl (Invoker) bu komutları tetikler ve son komutu geri alabilir. Son olarak tüm kayıtlı komutları sırayla çalıştırabilen bir makro (macro) komutu da gösterilmiştir.
// Receiver'lar
public class Light
{
private readonly string _location;
public Light(string location) => _location = location;
public void TurnOn() => Console.WriteLine($" {_location} lambası açıldı.");
public void TurnOff() => Console.WriteLine($" {_location} lambası kapatıldı.");
}
public class AirConditioner
{
public void SetTemperature(int degree) =>
Console.WriteLine($" Klima {degree}°C'ye ayarlandı.");
public void TurnOff() =>
Console.WriteLine(" Klima kapatıldı.");
}Her cihaz işlemi için bir ConcreteCommand tanımlanır; Undo metodu işlemi tersine çevirir.
public class LightOnCommand : ICommand
{
private readonly Light _light;
public LightOnCommand(Light light) => _light = light;
public void Execute() => _light.TurnOn();
public void Undo() => _light.TurnOff();
}
public class LightOffCommand : ICommand
{
private readonly Light _light;
public LightOffCommand(Light light) => _light = light;
public void Execute() => _light.TurnOff();
public void Undo() => _light.TurnOn();
}
public class AirConditionerOnCommand : ICommand
{
private readonly AirConditioner _ac;
private readonly int _degree;
public AirConditionerOnCommand(AirConditioner ac, int degree)
{
_ac = ac;
_degree = degree;
}
public void Execute() => _ac.SetTemperature(_degree);
public void Undo() => _ac.TurnOff();
}RemoteControl, son çalıştırılan komutu hafızasında tutarak undo desteği sağlar.
public class RemoteControl
{
private readonly Stack<ICommand> _history = new();
public void Press(ICommand command)
{
command.Execute();
_history.Push(command);
}
public void PressUndo()
{
if (_history.TryPop(out var last))
last.Undo();
else
Console.WriteLine(" Geri alınacak işlem yok.");
}
}Birden fazla komutu tek bir komutmuş gibi çalıştıran MacroCommand ile tüm ev tek tuşla kontrol edilebilir.
public class MacroCommand : ICommand
{
private readonly List<ICommand> _commands;
public MacroCommand(List<ICommand> commands) => _commands = commands;
public void Execute()
{
foreach (var command in _commands)
command.Execute();
}
public void Undo()
{
foreach (var command in Enumerable.Reverse(_commands))
command.Undo();
}
}program.cs
internal class Program
{
static void Main(string[] args)
{
var livingRoomLight = new Light("Oturma odası");
var bedroomLight = new Light("Yatak odası");
var ac = new AirConditioner();
var remote = new RemoteControl();
Console.WriteLine("=== Tekli Komutlar ===");
remote.Press(new LightOnCommand(livingRoomLight));
remote.Press(new AirConditionerOnCommand(ac, 22));
remote.Press(new LightOnCommand(bedroomLight));
Console.WriteLine("\n=== Undo (Son 2 İşlem) ===");
remote.PressUndo();
remote.PressUndo();
Console.WriteLine("\n=== Makro Komut: Herkesi Uyut ===");
var goodNightMacro = new MacroCommand(new List<ICommand>
{
new LightOffCommand(livingRoomLight),
new LightOffCommand(bedroomLight),
new AirConditionerOnCommand(ac, 19)
});
remote.Press(goodNightMacro);
Console.WriteLine("\n=== Makroyu Geri Al ===");
remote.PressUndo();
}
}çıktı
=== Tekli Komutlar ===
Oturma odası lambası açıldı.
Klima 22°C'ye ayarlandı.
Yatak odası lambası açıldı.
=== Undo (Son 2 İşlem) ===
Yatak odası lambası kapatıldı.
Klima kapatıldı.
=== Makro Komut: Herkesi Uyut ===
Oturma odası lambası kapatıldı.
Yatak odası lambası kapatıldı.
Klima 19°C'ye ayarlandı.
=== Makroyu Geri Al ===
Klima kapatıldı.
Yatak odası lambası açıldı.
Oturma odası lambası açıldı.
Command tasarım deseni, bir isteği nesneye dönüştürerek tetikleyiciyi (invoker) ile alıcıyı (receiver) birbirinden ayırır. Bu ayrım sayesinde yeni komut eklemek yalnızca ICommand'ı implemente eden yeni bir sınıf yazmak anlamına gelir; mevcut invoker ve receiver kodları değişmez (Open-Closed Principle). Komutların nesneleşmesi; undo/redo geçmişi, işlem kuyruğu, loglama ve MacroCommand gibi bileşik komutlar inşa etmeyi de doğal olarak mümkün kılar.
Pratikte Command Pattern, GUI araç çubukları ve klavye kısayolları, transaction tabanlı işlemler, iş kuyruğu (job queue) sistemleri ve oyun input yönetimi gibi alanlarda yaygın biçimde kullanılır. Uygularken her komutun küçük ve tek bir işi yapacak şekilde tutulması, undo mantığının Execute ile simetrik yazılması ve history yığınının boyutunun kontrol altında tutulması iyi uygulamalardır.