Запізнілий “Патерн Вівторка” #18: Ланцюжок відповідальностей (Chain of Responsibility)

Уявіть, що ви пішли із своїми друзями в кафе. Кафе дещо специфічне – мало місця, і коли вам приносять якусь страву зазвичай доводиться передавати її наступній людині за столом. Ваш найкращий друг сів найближче до краю, тому першим він і отримує до рук замовлення. Так як він спав мало зранку і любить щось поїсти із м’ясом, то він ніколи не передесть вам м’ясної страви і напою із кавою, допоки не вип’є хоч одну чашку чогось. Наступним після друга сидете ви, а далі ваша подружка, яка знаходиться біля стіни. Вона отримає все останньою, але на щастя вона хоче тільки капучіно, та й передавати їй уже не треба нікому.

ЛАНЦЮЖОК ВІДПОВІДАЛЬНОСТЕЙ

Я думаю що весь механізм патерну ланцюжка відповідальностей є зрозумілим – ми маємо набір обробників (handler) або відвідувачів кафе, які вміють обробляти команду або їжу у нашому випадку, а якщо обробити її не вдається, то команда передається наступному обробітнику.

Для прикладу із нашим кафе загальним інтерфейсом відвідувача такого дивного кафе може бути такий базовий клас:

public abstract class WierdCafeVisitor
{
public WierdCafeVisitor CafeVisitor { get; private set; }

protected WierdCafeVisitor(WierdCafeVisitor cafeVisitor)
{
CafeVisitor = cafeVisitor;
}

public virtual void HandleFood(Food food)
{
// If I cannot handle other food, passing it to my successor
if (CafeVisitor != null)
{
CafeVisitor.HandleFood(food);
}
}
}

Як бачимо по замовчуванню їжа просто передається до наступного відвідувача у ланцюжку, якщо такий є.

Тапер глянемо на реалізацію, яка найбільше підходить вашому вибагловому другові:

public class BestFriend : WierdCafeVisitor
{
public List<Food> CoffeeContainingFood { get; private set; }
public BestFriend(WierdCafeVisitor cafeVisitor) : base(cafeVisitor)
{
CoffeeContainingFood = new List<Food>();
}

public override void HandleFood(Food food)
{
if(food.Ingradients.Contains("Meat"))
{
Console.WriteLine("BestFriend: I just ate {0}. It was testy.", food.Name);
return;
}
if (food.Ingradients.Contains("Coffee") && CoffeeContainingFood.Count < 1)
{
CoffeeContainingFood.Add(food);
Console.WriteLine("BestFriend: I have to take something with coffee. {0} looks fine.", food.Name);
return;
}
base.HandleFood(food);
}
}

Реалізації ще двох обробітників – Me та GirlFriend мають бути зрозумілими, але всештаки наведемо реалізацію відвідувача-подружки:

public class GirlFriend : WierdCafeVisitor
{
public GirlFriend(WierdCafeVisitor cafeVisitor) : base(cafeVisitor)
{
}

public override void HandleFood(Food food)
{
if(food.Name == "Cappuccino")
{
Console.WriteLine("GirlFriend: My lovely cappuccino!!!");
return;
}
base.HandleFood(food);
}
}

Все відносно просто – дівчина хоче капучіно, але вона у ланцюжку остання, пому поки друг ви вип’є щось із кавою капучіно вона не отримає.

А тепер використання – створимо два капучіно, два супи і кусок м’яса, створимо наших відвідувачів кафе, та будемо подавати їжу в руки другові:

var cappuccino1 = new Food("Cappuccino", new List<string> {"Coffee", "Milk", "Sugar"});
var cappuccino2 = new Food("Cappuccino", new List<string> {"Coffee", "Milk"});

var soup1 = new Food("Soup with meat", new List<string> {"Meat", "Water", "Potato"});
var soup2 = new Food("Soup with potato", new List<string> {"Water", "Potato"});
var meat = new Food("Meat", new List<string> {"Meat"});

var girlFriend = new GirlFriend(null);
var me = new Me(girlFriend);
var bestFriend = new BestFriend(me);

bestFriend.HandleFood(cappuccino1);
bestFriend.HandleFood(cappuccino2);
bestFriend.HandleFood(soup1);
bestFriend.HandleFood(soup2);
bestFriend.HandleFood(meat);

Вивід:

BestFriend: I have to take something with coffee. Cappuccino looks fine.
GirlFriend: My lovely cappuccino!!!
BestFriend: I just ate Soup with meat. It was testy.
Me: I like Soup. It went well.
BestFriend: I just ate Meat. It was testy.

Як видно із виводу в консоль, дівчина отримала тільки друге капучіно, а ви були змушені їсти суп без м’яса 🙂

Що цікаво, мо ми можемо після моєї дівчини підчепити ще один обробітник – скажімо мішечок для собачки і туди скинути, щось що ніхто не захоче їсти. Для цього прийдеться трішки змінити клас, щоб він мав метод на подобі SetNextVisitor абощо.

Моя табличка Патернів
Developer's RoadMap To Success

2 коментарі

Залишити коментар до Andriy Buday Скасувати коментар

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *