A Command, vagy magyarul Parancs viselkedési minta lehetővé teszi, hogy egy metódust becsomagoljunk egy objektumba. Különösen olyan nyelvek esetén volt, vagy van jelentősége, amelyek nem támogatják a delegáltak és a lambda metódusok koncepcióját.
A minta megkönnyÃti a generikus komponensek létrehozását. A .NET beépÃtetten tartalmaz egy Command interfész implementációt, ami az ICommand nevet kapta és a System.Windows.Input névtérben lakik, ami a WPF egyik névtere.
Azonban ez nem azt jelenti, hogy nem tudnánk WPF-en kÃvül alkalmazni. Ennek a mintának jelentÅ‘sége grafikus alkalmazás fejlesztés során van, az MVVM minta erÅ‘sen épÃt rá. Tételezzük fel, hogy az alkalmazásunkban ugyan annak az akciónak kellene megtörténnie több esemény hatására. Legyen ez a mentés. Menteni tipikusan a Fájl menübÅ‘l és a CTRL+S billentyű kombináció segÃtségével szoktunk, illetve ha még van az alkalmazásban egy menüsor, akkor azon is elÅ‘fordulhat az ikonja.
Mivel több helyrÅ‘l is ki lehet adni ugyan azt a parancsot, célszerű lenne az ehhez tartozó kódot egy újra felhasználható osztályba szervezni, hogy ne szétszórva legyen a kód az alkalmazásunkban. Ebben segÃt nekünk a command tervezési minta.
internal interface ICommand
{
string Name { get; }
void Execute();
}
internal class HelloCommand : ICommand
{
public string Name => "hello";
public void Execute() => Console.WriteLine("Hello command!");
}
internal class ExitCommand : ICommand
{
public string Name => "exit";
public void Execute() => Environment.Exit(0);
}
public class CommandRunner
{
private List<ICommand> _commands;
public CommandRunner()
{
_commands = new List<ICommand>
{
new HelloCommand(),
new ExitCommand(),
};
}
public void Run()
{
while (true)
{
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
{
continue;
}
var command = _commands.Where(cmd => cmd.Name == input).FirstOrDefault();
if (command == null)
{
Console.Write($"Unknown command: {input}");
continue;
}
command.Execute();
}
}
}
A fenti kód egy minimális shell implementációt valósÃt meg. A CommandRunner osztály felelÅ‘s a commandok futtatásáért és létrehozásáért. Egy valós alkalmazásban azonban ez az osztály konstruktor argumentumként kapná meg a futtatható commandok listáját. A példában a command implementációk önmagukban végzik a munkát, de szintén egy valós alkalmazás esetén további domain specifikus osztályokra támaszkodnának a végrehajtásához.
A XAML alapú platformok (MAUI/WPF/Xamarin) esetén gyakran lehet találkozni a DelegateCommand, RelayCommand kifejezésekkel. Ezek lényegében olyan command implementációk, amelyek egy delegate-et kapnak a konstruktorban és command futtatására szolgáló metódus ezt fogja meghÃvni. Ez a megközelÃtés kifejezetten hasznos MVVM minta esetén.
Előnyök
- Single responsibility: Algoritmusok és műveletek leválaszthatóak a felhasználás helyétől
- Open/Closed elvet segÃti: Újabb műveletek vezethetÅ‘ek be a meglévÅ‘k módosÃtása nélkül
- Ha öröklést is bevezetjük, akkor több kisebb műveletből megalkotható egy komplexebb
Hátrányok
- A kód bonyolultabbá válik, mivel egy kompletten új réteget vezetünk be