17 серпня 2010 р.

"Патерн Вівторка" #3: Флайвейт (Flyweight)

Уявіть, що ви розробляєте якогось бота до онлайн-ігрушки. У вас уже є веб клієнт, який на кожну відповідь (response) від сервера парсить HTML, у якому є записані Юніти гри. Гра має близько 50 різних тваринок, але коли ви розпарсуєте відповідь, то ви можете отримати цілу гору інстансів однієї і тієї ж тваринки, і ще цілу купу інстансів інших тварей.

Якщо ваш бот шпіляє дуже інтенсивно, то аплікація буде просто заганятися кожного разу створювати велику кількість інстансів кожного із Юнітів. Але що цікаво, самі Юніти мають одні і ті ж початкові значення Здоров'я та одне й те саме Зображення. Звичайно із плином гри, Здоров'я зменшується, але картинка, що зображає тварь то одна й та ж сама!
Це може призвести до неефективного використання пам'яті. То як ми можемо зробити загальну інформацію про зображення тваринки (і т.п.) спільною для кожного окремого Юніта одного типу?

ФЛАЙВЕЙТ

1) Перший варіант із створенням об'єктів Зображення для кожного Юніта.


Базовий клас для Юнітів:  

    public abstract class Unit
    {
        public string Name { get; protected set; }
        public int Health { get; protected set; }
        public Image Picture { get; protected set; }
    }

І два породжені класи, які зображають Гобліна (Goblin) та Дракона (Dragon) із їхніми початковими значеннями Здоров'я (Health) та Зображення (Picture). Наведемо код Гобліна. Тут слід звернути увагу на те, що зображення Гобліна є дуже велике, тому буде займати багато пам'яті.

    public class Goblin : Unit
    {
        public Goblin()
        {
            Name = "Goblin";
            Health = 8;
            Picture = Image.Load("Goblin.jpg");
        }
    }
 Парсер насправді імітує якусь роботу по створенню об'єктів:

    public class Parser
    {
        public List<Unit> ParseHTML()
        {
            var units = new List<Unit>();

            for (int i = 0; i < 150; i++)
                units.Add(new Dragon());

            for (int i = 0; i < 500; i++)
                units.Add(new Goblin());

            Console.WriteLine("Dragons and Goblins are parsed from HTML page.");

            return units;
        }
    }

То ж ми створили 150 Драконів і 500 Гоблінів, і це забрало нам аж... 439 Mb.



2) То як той Флайвейт працює? (не думаю що когось пів гіга зжертої пам'яті влаштовує)

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

    public class UnitImagesFactory
    {
        public static Dictionary<Type, Image> Images = new Dictionary<Type, Image>();

        public static Image CrateDragonImage()
        {
            if (!Images.ContainsKey(typeof(Dragon)))
            {
                Images.Add(typeof(Dragon), Image.Load("Dragon.jpg"));
            }
            return Images[typeof(Dragon)];
        }

        public static Image CrateGoblinImage()
        {
            if (!Images.ContainsKey(typeof(Goblin)))
            {
                Images.Add(typeof(Goblin), Image.Load("Goblin.jpg"));
            }
            return Images[typeof(Goblin)];
        }
    }

І змінимо конструктори Гобліна і Дракона, щоб вони використовували нашу фабрику.


    public class Goblin : Unit
    {
        public Goblin()
        {
            Name = "Goblin";
            Health = 8;
            Picture = UnitImagesFactory.CreateGoblinImage();
        }
    }

Глянемо на UML діаграму цієї чудасії:


Як можна бачити, наша UML діаграма не відповідає класичній діаграмі  із GoF книжки, але можна б сказати, що нам слід бути до цього готовими. В реальному світі реалізація патерну часто відрізняється від того, що описано у всіма відомій книзі. Можна дуже дивуватися тому, що люди чітко пробують дотриматися такої ж структури - часто вона буває занадто загальною. Оригінальна стаття автора про Flyweight більш відповідала класичній діаграмі, але там не було добре ясно, що Dog та Dragon є репрезентаціями Info-класів для тваринок.
   
Тепер в ран-таймі наш чудо-бот зжерає тільки 7 Mb:



Якщо вам сподобалася стаття, то дайте про це знати. Також можна прочитати про інші патерни на блозі автора тут.

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

  1. Хто добереться до коментарів, я б хотів, щоб дав відповідь на таке питання: Чи є клас UnitImagesFactory якимось із факторі дизайн патернів із книжки GoF? Аргументи в додачу буде дуже добре.

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

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

    ВідповістиВидалити
  3. ее... я не використовував гугл транслейт. Я перекладав вночі, тому може й багато описок. Знову ж таки питання: чи нормальний стиль у якому той патерн описаний? Я можу попробувати зробити його більш офіційним.

    ВідповістиВидалити
  4. Ну і крім того в оригінальній статті був дещо інший приклад. Тут я його змінив.

    ВідповістиВидалити
  5. як на мене стиль нормальний, так тримати. Більш офіційно можна описати наступний патерн, побачимо що скажуть користувачі.

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

    ВідповістиВидалити
  6. В нас на проекті деякий набір GoF дизайн патернів таки використовується, але дійсно їх багато не запхаєш. А якщо таки запхаєш, то потім із тим кодом дуже фігово розбиратися.

    ВідповістиВидалити
  7. Може порадиш що почитати, думаю такі речі не тільки мене цікавлять... Зараз почав читати "Применение DDD и шаблонов проектирования". Думаю час від часу робити міні звіт в блог по ній, хотя не факт що дочитаю бо я так зрозумів що вони розрахована на трохи вищий рівень ніж зараз у мене.

    ВідповістиВидалити
  8. "Чи є клас UnitImagesFactory якимось із факторі дизайн патернів із книжки GoF?"

    так, маэмо по суті паттерн Factory Method + кеш

    ВідповістиВидалити
  9. Так це Factory Method. Я хотів бути певним, що ніхто не буде казати що це не той патерн, плутаючи його із параметризованим фекторі методом. Також є така штука як CreateMethod, але вона створює об'єкти зазвичай свого класу і практично завжди є статичними методами.

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