Szinte minden programban hatalmas mennyiségű üzleti logika1 található. Nem meglepő módon ott, ahol sok logika található, ott általában nagy komplexitás van. Ha ez túl nagy, akkor a kódunk nehezen érthetővé és nehezen karbantarthatóvá válik. Ennek elkerülésének egyik módja a Chain of Responsibility viselkedési minta alkalmazása.
Ennek segítségével a nagy, komplex logikai feladatokat kisebb, kezelhető lépésekre tudjuk bontani. A minta alkalmazásának első lépése egy interfész definiálása, ami egy metódust definiál. Ennek a metódusnak a visszatérési értéke igaz, ha a műveletsorozat folytatódhat, hamis, ha nem. A metódust célszerű ellátni egy paraméterrel, amin keresztül adatokat fogadhatunk:
interface ICommand<T>
{
bool Execute(T Context);
}
Opcionálisan a futtatás ellenőrzése kiszervezhető egy külön metódusba:
interface ICommand<T>
{
bool CanExecute(T context);
void Execute(T Context);
}
Ha ilyen formában implementáljuk, akkor lényegében már egy másik tervezési mintát kaptunk. Ezt a mintát command-nak, vagy magyarul parancs mintának nevezzük. A .NET keretrendszerben a WPF erősen épít erre a mintára. Lényegében a Chain of Responsibility és a command minták nagyon hasonlóak.
A Chain of Responsibility minta alkalmazására egy példa:
interface ICommand<T>
{
bool CanExecute(T context);
void Execute(T Context);
}
class Command1 : ICommand<List<string>>
{
public bool CanExecute(List<string> context)
{
return context != null;
}
public void Execute(List<string> Context)
{
Context.Add("Command1");
}
}
class Command2 : ICommand<List<string>>
{
public bool CanExecute(List<string> context)
{
return context != null &&
context.Count > 0;
}
public void Execute(List<string> Context)
{
Context.Add("Command2");
}
}
class Runner
{
List<ICommand<List<string>>> _commands;
List<string> _context;
public Runner(params ICommand<List<string>>[] commands)
{
_context = new List<string>();
_commands = new List<ICommand<List<string>>>(commands);
}
public void Run()
{
foreach (var command in _commands)
{
if (!command.CanExecute(_context)) break;
command.Execute(_context);
}
foreach (var item in _context)
{
Console.WriteLine(item);
}
}
}
A kódban a Command interfész itt két metódust definiál és generikus lett. A CanExecute() feladata, hogy a Context által reprezentált lánc állapota alapján eldöntse, hogy az aktuális lépés végrehajtható-e. Ha ez a metódus false értékkel tér vissza, akkor a műveletsor az aktuális lépésnél megszakad. Az Execute metódus végzi a tényleges munkát.
A runner osztály feladata, hogy összefogja a lépéseket és felépítse a sorrendet. A Chain of responsibility klasszikus implementációja egyszeresen láncolt lista, ahol az Execute metódus bool visszatérési értékű és a lánc egy referenciát tartalmaz a következő lépésre.
Ezen megoldást személy szerint azért nem kedvelem, mert ugyan egyszerűbb a lánc szemek felépítése, de akkor is kell egy osztály vagy komponens, ami a műveletek sorrendjét felépíti.
Előnyök
- Bonyolult algoritmusok lépésre bonthatóak
- Single responsibility: Egy osztály egy algoritmus egy lépéséért felelős
- Open/closed elvet elősegíti: Új részek bevezethetőek a meglévőek módosítása nélkül
Hátrányok
- Előfordulhat, hogy ha egy adott bemenetet dolgoz fel a minta, akkor bizonyos bemenetek feldolgozatlanok maradnak és ezeket a hibákat nehezebb lehet megtalálni, mint ha egy helyen lenne az egész algoritmus
-
Az üzleti logika az alkalmazásnak az a rétege, amiben az alkalmazás témakörét érintő számítások, szabályok, logikai összefüggések és ezek megvalósításához használt típusok vannak. Tehát egyszóval olyan osztályok és metódusok, amelyek a többi réteg nélkül is megállnák a helyüket. Általában a program legértékesebb része.↩