23 листопада 2010 р.

"Патерн Вівторка" #16: Компонувальник (Composite)

Чи ви коли небуть задумувалися чому так багато у цьому світі має деревовидну структуру? Адміністративний устрій країни для прикладу, або ж ваша організація. Топ менеджмент компанії може делегувати роботу робому менеджерам відділів, які відповідно делегують її до ваших прямих менеджерів, а ті в свою чергу дадуть вам якусь роботу. Або для прикладу, XML, має деревовидну структуру, мабуть тому що це найкращий спосіб зберегти дані, що можуть містити дані, які в свою чергу можуть мітити дані, які... Отже, припустимо що вам слід зкомпонувати якийсь складний документ із частин. Деякі застини вміють збирати дані (GatherData), базуючить на відповідній ID-шці. Деякі частини, просто утримують інші частини. Ми побудуємо XML кустарними засобами у цьому прикладі.

КОМПОНУВАЛЬНИК


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

Розглянемо інтерфейс що визначає спільні вимоги до батьків і дітей (:

public interface IDocumentComponent
    {
        string GatherData();
        void AddComponent(IDocumentComponent documentComponent);
    }

А тепер реалізація одного із листків у нашому дереві - CustomerDocumentComponent, яка вміє збирати кусок XML, базуючись на ID-шці замовника.

public class CustomerDocumentComponent : IDocumentComponent
    {
        private int CustomerIdToGatherData { get; set; }

        public CustomerDocumentComponent(int customerIdToGatherData)
        {
            CustomerIdToGatherData = customerIdToGatherData;
        }

        public string GatherData()
        {
            string customerData;
            switch (CustomerIdToGatherData)
            {
                case 41:
                    customerData = "Andriy Buday";
                    break;
                default:
                    customerData = "Someone else";
                    break;
            }

            return string.Format("<Customer>{0}</Customer>", customerData);
        }

        public void AddComponent(IDocumentComponent documentComponent)
        {
            Console.WriteLine("Cannot add to leaf...");
        }
    }

А тепер реалізація нелисткових компонентів нашого дерева. Зверніть увагу, що в методі GatherData, ми просто ітеруємо по всіх "отприсках" і викликаємо наш основний метод для збору даних.

public class DocumentComponent : IDocumentComponent
    {
        public string Name { get; private set; }
        public List<IDocumentComponent> DocumentComponents { get; private set; }

        public DocumentComponent(string name)
        {
            Name = name;
            DocumentComponents = new List<IDocumentComponent>();
        }

        public string GatherData()
        {
            var stringBuilder = new StringBuilder();
            stringBuilder.AppendLine(string.Format("<{0}>", Name));
            foreach (var documentComponent in DocumentComponents)
            {
                documentComponent.GatherData();
                stringBuilder.AppendLine(documentComponent.GatherData());
            }
            stringBuilder.AppendLine(string.Format("</{0}>", Name));

            return stringBuilder.ToString();
        }

        public void AddComponent(IDocumentComponent documentComponent)
        {
            DocumentComponents.Add(documentComponent);
        }
    }

Клеїмо частинки докупи:

var document = new DocumentComponent("ComposableDocument");
            var headerDocumentSection = new HeaderDocumentComponent();
            var body = new DocumentComponent("Body");
            document.AddComponent(headerDocumentSection);
            document.AddComponent(body);

            var customerDocumentSection = new CustomerDocumentComponent(41);
            var orders = new DocumentComponent("Orders");
            var order0 = new OrderDocumentComponent(0);
            var order1 = new OrderDocumentComponent(1);
            orders.AddComponent(order0);
            orders.AddComponent(order1);

            body.AddComponent(customerDocumentSection);
            body.AddComponent(orders);

            string gatheredData = document.GatherData();

            Console.WriteLine(gatheredData);

Вивід певно схожий на XML, принаймні я хотів щоб він був таким.

<ComposableDocument>
<Header><MessageTime>8:47:23</MessageTime></Header>
<Body>
  <Customer>Andriy Buday</Customer>
  <Orders>
   <Order>Kindle;Book1;Book2</Order>
   <Order>Phone;Cable;Headset</Order>
  </Orders>
</Body>
</ComposableDocument>
Варто добавити, що GoF-хлопці пропонують також методи Remove(Component) та GetChild(int) у інтерфейсі компоненту, тому можливо вам захочеться їх добавити. Одне хочу сказати - ніколи не зациклюєйтися на якихось прикладах і однотипних поясненнях. Ваші потреби можуть дещо відрізнятися від того що описано як дизайн патерн, але в той же час воно вам ідеально підходить, просто у вас якийсесь вироджене або ускладнене застосування.

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

2 коментарі: