“Патерн Вівторка” #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 коментарів

Add a Comment

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *