28 грудня 2010 р.

"Патерн Вівторка" #20: Абстракнта Фабрика (Abstract Factory)

Уявімо, що ви прийшли в іграшковий магазин (відіграючи роль діда мороза) і хочете накупляти іграшок дітям (не обов’язково своїм). Одна дитина любить плюшеві іграшки, вона часто із ними лягає у ліжко спати. А інша дитина страшна розбишака, ламає все на світі, рве м’які іграшки і зазвичай віддає перевагу гратися із твердими, дерев’яними іграшками. Двоє дітей хочуть ведмедика і котика і ще купу інших тваринок. На щастя магазин має широкий асортимент іграшок і ви вдосталь закупилися. В один мішок ви накидали дерев’яних іграшок, а в інший плюшевих.

Таким чином, коли ви підійшли до першої дитини, яка любить м’які іграшки, ви витягали із свого мішка спочатку плюшевого ведмедя, а далі плюшевого котика і так далі. Аналогічно ви підійшли до іншої дитини і подарували їй дерев’яного ведмедика і котика, і собаку і слона…

АБСТРАКТНА ФАБРИКА




Абстрактна фабрика – це дизайн патерн, що поставляє інтерфейс для створення сімейств об’єктів.

В нашому прикладі сімейством є іграшки із базовими класами Ведмедика (Bear), Кота (Cat). Абстрактною фабрикою є мішок. Одна із конкретних фабрик повертає дерев’яні іграшки, а інша повертає плюшеві. Тому якщо одна дитина просить котика то їй вернуть котика у відповідності до інстційованого мішка із ігрушками.

Я надіюся, що приклад із аналогіями видався гарним. Ну що подивимося трохи коду?

Абстрактна фабрика та конкретні реалізації (мішки)

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

// abstract factory
    // абстракна фабрика
    public interface IToyFactory
    {
        Bear GetBear();
        Cat GetCat();
    }
    // concrete factory
    // конкретна фабрика
    public class TeddyToysFactory : IToyFactory
    {
        public Bear GetBear()
        {
            return new TeddyBear();
        }
        public Cat GetCat()
        {
            return new TeddyCat();
        }
    }
    // concrete factory
    // конкретна фабрика
    public class WoodenToysFactory : IToyFactory
    {
        public Bear GetBear()
        {
            return new WoodenBear();
        }
        public Cat GetCat()
        {
            return new WoodenCat();
        }
    }

Уже зрозуміло, що як тільки ми маємо якийсь екземпляр фабрики, ми можемо плодити сімейство потрібних іграшок. Тому глянемо на використання:

// lets start with wooden factory
            // спочатку створимо дерев'яну фабрику
            IToyFactory toyFactory = new WoodenToysFactory();

            Bear bear = toyFactory.GetBear();
            Cat cat = toyFactory.GetCat();
            Console.WriteLine("I've got {0} and {1}", bear.Name, cat.Name);
            // Output/Вивід: [I've got Wooden Bear and Wooden Cat]

            /*-------------- 
             somewhere else in the code...
             десь далі в коді...
            ----------------*/

            // and now teddy one
            // а тепер плюшеву
            IToyFactory toyFactory = new TeddyToysFactory();

            Bear bear = toyFactory.GetBear();
            Cat cat = toyFactory.GetCat();
            Console.WriteLine("I've got {0} and {1}", bear.Name, cat.Name);
            // Output/Вивід: [I've got Teddy Bear and Teddy Cat]

Два куски коду майже абсолютно ідентичні – різниця тільки в конктетному “мішку”.

Якщо вас ще цікавлять реалізації іграшок-тваринок, то вони доволі наївні.

public abstract class AnimalToy
    {
        protected AnimalToy(string name)
        {
            Name = name;
        }
        public string Name { get; private set; }
    }
    public abstract class Cat : AnimalToy
    {
        protected Cat(string name) : base(name) { }
    }
    public abstract class Bear : AnimalToy
    {
        protected Bear(string name) : base(name) { }
    }
    class WoodenCat : Cat
    {
        public WoodenCat() : base("Wooden Cat") { }
    }
    class TeddyCat : Cat
    {
        public TeddyCat() : base("Teddy Cat") { }
    }
    class WoodenBear : Bear
    {
        public WoodenBear() : base("Wooden Bear") { }
    }
    class TeddyBear : Bear
    {
        public TeddyBear() : base("Teddy Bear") { }
    }

Абстрактна фабрика є дуже широковикористовуваним дизайн патерном. Дуже яскравим прикладом буде ADO.NET DbProviderFactory, яка є абстракною фабрикою, що визначає інтерфейси для отримання DbCommand, DbConnection, DbParameter і так далі. Конкретна фабрика SqlClientFactory поверне відповідно SqlCommand, SqlConnection і так далі.

Дякую за те що прочитали цей дизайн патерн із моїм прикладом (надіюся унікальним, якщо ні – то я перевинайшов колесо чи то що).

Моя табличка Патернів


Developer's RoadMap To Success

9 коментарів:

  1. Вибач, за офтоп: виправ всюди ігрушку на іграшку + інстанс - краще замінити на екземпляр, а так цілком непогано.

    ВідповістиВидалити
  2. Дякую. Мені із тим взагалі треба буде багато працювати.

    ВідповістиВидалити
  3. Гарний приклад для пояснення. Було приємно читати. Описану задачку можно вирішити ще використовуючи більш простіший паттерн - Фабричний метод (Factory Method).

    Можливо варто про це сказати.

    ВідповістиВидалити
  4. Фабричний метод повертає конкретну реалізацію тільки для одного класу. Тому якщо нам був б потрібен кіт (а не ціле сімейство іграшок) можна було б використовувати фабричний метод.

    Також якщо говорити про параметризований фабричний метод, то можна було б мати методи

    Cat CreateCat(ToyType toyType)
    Bear CreateCat(ToyType toyType)
    //....

    у кожному методі був б switch який б реагував на wooden або teddy або інший тип ігрушок.

    Така реалізація, м'яко кажучи, не є прикладом гарного дизайну для задоної задачі.

    Дуже дякую за зауваження. Можливо дійсно варто згадувати про інші патерни у застосуванні до тих самих задач.

    ВідповістиВидалити
  5. Roman, ще раз дуже дякую за коментар. Коментарі, що змушують задуматися є дуже корисними.

    ВідповістиВидалити
  6. >> Така реалізація, м'яко кажучи, не є прикладом >> гарного дизайну для задоної задачі.

    Так switch, та константи "wooden або teddy або інший тип" будуть. Але це вже нюанси та плюси і мінуси (мінуси в даному випадку) використання кожного з них.

    ВідповістиВидалити
  7. Обидва патерни і абстрактна фабрика і фабричний метод створюють об'єкти. Просто різними способами.

    Абстрактна фабрика робить - це через наслідування, фабричний метод - через композицію. Що краще вирішувати від ситуації.

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

    І насправді можна обійтись і без switch. Фактично, він буде необхідний якщо використовувати параметризований фабричний метод - тобто передаємо константу або enum і в switch вирішуємо який клас створювати.

    Інший варіант - це не параметризований фабричний метод а утворений шляхом наслідування від базового класу. Це вже більш нагадує абстрактну фабрику, але за винятком того що створюється один продукт, а не сімейство. Фактично якщо розглядати методи GetBear і GetCat окремо або виокремити їх в окремий клас - це будуть фабричні методи.

    Що ще турбує, так це використання hardcoded рядків на кшталт IToyFactory toyFactory = new WoodenToysFactory(); - це аналогічно використанню констант, оскільки всюди у коді буде прив’язка до конкретного екземпляру і існує бажання винести частину «new WoodenToysFactory()», хоча констант тоді (DI/IoС як приклад) не уникнути, але вони будуть лише в одному місці коду.

    І ще один аспект. Для того щоб створювати нові і нові конкретні продукти доведеться створювати нові реалізації інтерфейсу абстрактної фабрики. Тут на допомогу можуть прийти дженеріки (generic). І кількість реалізацій дуже суттєво зменшитися.

    І ще як момент. Тут можна заюзати ще і прототип. Оскільки створювані іграшки відрізняються лише типом – плюшева або дерев’яна. Тому можна клонувати об’єкти і задавати потрібний тип новим об’єктам

    ВідповістиВидалити
  8. Уух! Який комент!

    Я погоджуюся із твоїми думками. Насправді про різні способи вирішення проблем можна дуже багато говорити.

    Дякую.

    ВідповістиВидалити
  9. Я даже с 5 копейками влезу:)

    Так как я сейчас больше в космосе летаю , но вот увдиел спор фабричный метод , против абстрактной фабрики:

    Сейчас вот читаю Apllication Architecture Guide, и там кстати заметил интересную штуку, в секции design practices: prefere composition to inheritance:)

    ВідповістиВидалити