→ Пошук по сайту       Увійти / Зареєструватися
Знання Патерни Патерни поведінки — Behavioral patterns

23. Відвідувач — Visitor

23.	Відвідувач — Visitor

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

Найближчим часом вам слід буде прийняти багато відвідувачів (visitors), таких як електрик (electrician), сантехнік (plumber), податківець і так далі... Усі вони будуть перевіряти вашу будівлю вздовж і в поперек, проходячи від поверха до поверха, від кімнати до кімнати. Я підозрюю, що якась певна схема класів у вас уже появилася у голові. Якщо так, то у мене є наступне питання: де має жити логіка певної перевірки будівлі? Чи має будівля знати, як перевіряти електричні щитки, чи це має знати електрик, або чи має знати кімната, як перевірити включателі, чи це так само робота електрика? Звичайно що електрик, який і є відвідувачем, інкапсулює логіку перевірки певних елементів (elements) вашої будівлі.

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

Отже, як і було згадано вище, інкапсульована логіка живе у конкретному відвідувачі. Ця логіка може бути застосована до елементів системи. Основу цього дизайн патерну можна вибудувати на двох інтерфейсах. Ось вони:

Уривок коду 24.1. IVisitor та IElement

 Уривок коду 24.1. IVisitor та IElement
interface IVisitor
{
    void Visit(OfficeBuilding building);
    void Visit(Floor floor);
    void Visit(Room room);
}
interface IElement
{
    void Accept(IVisitor visitor);
}

У нашому прикладі ElectricitySystemValidator є однією із конкретних реалізацій інтерфейсу IVisitor, яка може виглядати як наведений нижче код:

Уривок коду 24.1. Один із відвідувачів → електрик

 class ElectricitySystemValidator : IVisitor
{
    public void Visit(OfficeBuilding building)
    {
        var electricityState = (building.ElectricitySystemId > 1000)
                                                ? "Good" : "Bad";
        Console.WriteLine(
  string.Format("Main electric shield in building {0} is in {1} state.",   
  building.BuildingName, electricityState));
    }
    public void Visit(Floor floor)
    {
        Console.WriteLine(
          string.Format("Diagnosting electricity on floor {0}.", 
          floor.FloorNumber));
    }
    public void Visit(Room room)
    {
        Console.WriteLine(
          string.Format("Diagnosting electricity in room {0}.", room.RoomNumber));
    }
}

Про що говорить нам цей клас? Ми зауважили, що у відповідності до інтерфейсу IVisitor в одному відвідувачі є три методи, кожен із яких описує логіку перевірки для одного із елементів. Виходячи із цього, ми можемо з чистою совістю проводити нашого відвідувача із поверху на поверх та з кімнати до кімнати.

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

До цього часу вже стало зрозуміло, що структура будівлі обхідна. Все починається із будівлі (OfficeBuilding), яка має поверхи (Floors), і кожен із поверхів може мати багато кімнат. Глянемо на імплементацію поверху.

Уривок коду 24.1. Один із елементів будівні — поверх

 class Floor : IElement
{
    private readonly IList<Room> _rooms = new List<Room>();
    public int FloorNumber { get; private set; }
    public IEnumerable<Room> Rooms { get { return _rooms; } }

    public Floor(int floorNumber)
    {
        FloorNumber = floorNumber;
    }
    public void AddRoom(Room room)
    {
        _rooms.Add(room);
    }
    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
        foreach (var room in Rooms)
        {
            room.Accept(visitor);
        }
    }
}

Як можна побачити, цей клас мітить метод Accept, який вимагається інтерфейсом і який приймає відвідувача. В середині цього методу ми виконуємо наш алгорим і, якщо треба, передаємо нашого відвідувача «по колу». Як бачимо, ніякі технічні перевірки не виконуються напряму у цьому класі, тому ми можемо бути певні, що якщо у майбутньому слід буде змінити спосіб перевірки електросистеми у кімнаті, то це буде зроблено у відвідувачі без будь-яких впливів на клас кімнати.

OfficeBuiling є досить подібним класом, хіба що має багато інших додаткових властивостей. Room взагалі є простим класом, який не агрегує чи компонує інших елементів.

Глянемо на код використання дизайн патерну:

Уривок коду 24.1. Маємо будівлю із 2-ма поверхами, на кожному є по 3 кімнати. Запускаємо у будівлю електрика і сантехніка як відвідувачів

 var floor1 = new Floor(1);
floor1.AddRoom(new Room(100));
floor1.AddRoom(new Room(101));
floor1.AddRoom(new Room(102));
var floor2 = new Floor(2);
floor2.AddRoom(new Room(200));
floor2.AddRoom(new Room(201));
floor2.AddRoom(new Room(202));
var myFirmOffice = new OfficeBuilding("[Design Patterns Center]", 25, 990);
myFirmOffice.AddFloor(floor1);
myFirmOffice.AddFloor(floor2);

var electrician = new ElectricitySystemValidator();
myFirmOffice.Accept(electrician);

var plumber = new PlumbingSystemValidator();
myFirmOffice.Accept(plumber);

Вивід:

 Main electric shield in building [Design Patterns Center] is in Bad state. 
Diagnosting electricity on floor 1. 
Diagnosting electricity in room 100. 
Diagnosting electricity in room 101. 
Diagnosting electricity in room 102. 
Diagnosting electricity on floor 2. 
Diagnosting electricity in room 200. 
Diagnosting electricity in room 201. 
Diagnosting electricity in room 202. 
Plumbing state of building [Design Patterns Center] probably is in Good condition, since builing is New. 
Diagnosting plumbing on floor 1. 
Diagnosting plumbing on floor 2. 

UML діаграма класів

Чим більше я пишу про дизайн патерни, тим більше я розумію що UML діаграми часто можуть ввести в оману. Ті діаграми, що представлені в GoF книжці насправді хороші, але вони зображають одні із найбільш частих випадків застосування певного патерну. Таким чином, Відвідувача найчастіше зображають як один базовий клас із двома похідними. Базовий клас визначає, що похідні мають реалізовувати «відвідування» елементів системи. Елемент системи може мати декілька реалізацій також.

UML-діаграма 14. Стандартна діаграма Відвідувача

Але коли я собі думаю, як інакше цей дизайн патерн може бути реалізований, то діаграми будуть виглядати зовсім інакше. Зокрема, із використанням Компонувальника, можна реалізувати тільки один клас для елемента, який є деревовидною структурою і містить вкладені елементи. Або ваша програма може містити один клас для відвідувача і один для елемента. За цим криється патерн, але не так і просто його розгледіти. Хоча й навіщо?...

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

Ще одним недоліком є порушення зв’язності системи, оскільки із використанням цього патерну прийдеться добавляти велику кількість методів в елементи, які будуть відвідуватися.

По матеріалам книги Андрія Будая "Дизайн патерни – просто, як двері". Матеріал розміщується за домовленістю з автором.
Робота представлена за умовами ліцензії Creative Commons Attribution-NonCommercial 3.0 Unported License.

загрузка...
Сторінки, близькі за змістом