Дизайн Патерн - ВІДВІДУВАЧ (Visitor)
Уявімо собі, що ви нарешті спромоглися створити свою власну компанію, і оскільки вона пристойного розміру, ви вирішили орендувати для неї цілу будівлю. Оскільки у нас держава дуже хороша і дбає про підприємства, щоб у них усе відповідало вимогам, постійно висилаються всякі перевірки. Причому правила, по яких перевіряють ваше підприємство, постійно міняються. Найближчим часом вам слід буде прийнятати багато відвідувачів (visitors), таких як електрик (electrician), сантехнік (plumber), податківець і так далі... Усі вони будуть перевіряти вашу будівлю вздовж і в поперек, проходячи від поверха до поверха від кімнати до кімнати. Я так здогадуюся, що якась схема класів у вас уже появилася у голові. Якщо так, то у мене є наступне питання: де має жити логіка певної перевірки будівлі? Чи має будівля знати як перевіряти електричні щитки, чи це має знати електрик, або чи має знати кімната як перевірити включателі, чи це так само робота електрика? Звичайно, що електрик , який і є відвідувачем, інкапсулює логіку перевірки певних елементів (elements) вашої будівлі.
Відвідувач (Visitor) - це дизайн патерн, який дозволяє відділити певний алгоритм від елементів, на яких алгоритм має бути виконаний, таким чином ми можемо легко додати або ж змінити алгоритм без змін до елементів системи. Як на мене це і є однією із найбільш помітних переваг цього патерну.
public interface IVisitor { void Visit(OfficeBuilding building); void Visit(Floor floor); void Visit(Room room); }
public interface IElement { void Accept(IVisitor visitor); }
public 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)); } }
public 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); } } }
Елементи програми - діаграма
Нічого особливого? Ну якщо ні, то глянемо на код використання дизайн патерну.
Використання
Маємо будівлю із 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);
UML діаграма класів
Чим більше я пишу про дизайн патерни, тим більше я розумію що UML діаграми часто можуть ввести в оману. Ті діаграми, що представлені в GoF книжці насправді хороші, але вони зображають одні із найбільш частіших випадків застосування певного патерну. Таким чином Відвідувача найчастіше зображають як один базовий клас із двома похідними. Базовий визначає, що похідні мають реалізовувати "відвідування" елементів системи. Елемент системи може мати декілька реалізацій також. Загалом можна глянути на таку діаграму нижче: