4. Привязки (Bindings) в StrangeIoC

Привязки (Bindings) лежат в основе Strange и дают ключ к пониманию принципов его функционирования. Работая со Strange вы будете часто создавать различные привязки.

Создать привязку — означает связать что-то с чем-то. В Strange можно связывать один элемент с другим элементом, один элемент с несколькими другими элементами, несколько элементов с другими несколькими элементами. В привязках может участвовать все что угодно — можно привязать класс к интерфейсу (класс должен реализовывать этот интерфейс), интерфейс или класс к инстансу класса, сигнал к команде и т.д. У привязок есть две основные характеристики: что связано и как связано. Эти и некоторые другие характеристики задаются в момент создания привязок. Привязки Strange состоят из 2 обязательных и 1 необязательной частей. Обязательные части — это ключ (key) и значение (value).  По ключу происходит резолвинг (resolving, разрешение) привязки, то есть получение значения привязки. Каждой привязке можно задать имя (необязательный параметр).

Привязки создаются с помощью привязчиков (Binders). Привязчики — специальные классы-фабрики, основной задачей которых является создание привязок, задание их параметров, хранение и резолвинг. И привязка и резолвинг, то есть получение значения привязки, происходит по внутренней логике привязчика. Привязчики будут детально описаны в следующем разделе, а пока продолжим с привязками.

Обобщенная схема создания привязки выглядит следующим образом:

Binder.Bind<KEY>().To<VALUE>().ToName(«NAME«);

Для чего же необходимы привязки и в каких случаях они могут быть использованы на практике? Для ответа на этот вопрос, обратимся к простому примеру. Представим, что перед вами стоит не лёгкая задача создать игру, в которой игрок, управляя космическим аппаратом оснащенном различного вида оружием, должен поражать вражеские космические корабли и спутники, так же оснащенные средствами нападения и защиты. Сильно упрощая ситуацию, можно написать, что-то подобное следующему коду:

    /// <summary>
    /// Базовый класс для всех видов вооружения
    /// </summary>
    public abstract class SpaceGun
    {
        public virtual void FireOn(Spaceship target)
        {
            target.TakeDamage(Damage);
        }
 
        public abstract int Damage { get; }
    }
    public class SpaceBlaster : SpaceGun
    {
        public override void FireOn(Spaceship target)
        {
            base.FireOn(target);
            Debug.Log("[SpaceBlaster::FireOn] " + target.Name);
        }
 
        public override int Damage
        {
            get { return 15; }
        }
    }
 
    public class SuperMegaSpaceCannon : SpaceGun
    {
        public override void FireOn(Spaceship target)
        {
            base.FireOn(target);
            Debug.Log("[SuperMegaSpaceCannon::FireOn] " + target.Name);
        }
 
        public override int Damage
        {
            get { return 25; }
        }
    }
 
    /// <summary>
    /// Базовый класс для всех видов космических кораблей
    /// </summary>
    public abstract class Spaceship
    {
        protected Spaceship(string name, int health, int armor, SpaceGun gun)
        {
            Name = name;
            Health = health;
            IsAlive = Health > 0;
            Gun = gun;
        }
 
        public virtual void TakeDamage(int damageAmount)
        {
            if (!IsAlive) return;
            Health -= damageAmount;
            IsAlive = Health > 0;
        }
 
        public string Name  { get; private set; }
 
        public bool IsAlive { get; private set; }
 
        public int Health { get; private set; }
 
        public SpaceGun Gun { get; private set; }
    }
 
    /// <summary>
    /// Корабль игрока
    /// </summary>
    public class PlayerSpaceship : Spaceship
    {
        public PlayerSpaceship(string name, int health, int armor, SpaceGun gun)
            : base(name, health, armor, gun)
        {
        }
    }
 
    /// <summary>
    /// Вражеские корабль
    /// </summary>
    public class EnemySpaceship : Spaceship
    {
        public EnemySpaceship(string name, int health, int armor, SpaceGun gun)
            : base(name, health, armor, gun)
        {
        }
    }

Воспользоваться созданными типами можно было-бы следующим образом:

    public class Programm : MonoBehaviour
    {
        public void Start()
        {
            var megaCannon = new SuperMegaSpaceCannon();
            var player = new PlayerSpaceship("Player", 200, 100, megaCannon);
 
            var spaceBlaster = new SpaceBlaster();
            var enemy = new EnemySpaceship("Angry Enemy", 100, 100, spaceBlaster);
 
            player.Gun.FireOn(enemy);
        }
    }

Приведенный код очень далек от идеального, но позволяет понять проблемы, решить которые и призваны привязки Strange. Итак, весь процесс программирования можно свести к следующей простой схеме: вы описываете типы данных (классы) и обозначаете отношения (наследование, композиция, ассоциация и др.) между ними, то есть связываете их. В результате получается программа – набор связанных классов и их экземпляров. В приведенном выше примере классы Spaceship, PlayerSpaceship, EnemySpaceship состоят в отношении наследования, так же, как и классы SpaceGun, SpaceBlaster и SuperMegaCannon. Базовые классы Spaceship и SpaceGun объединены отношением композиции. Класс Program связан с классами SpuperMegaSpaceCannon, PlayerSpaceship, SpaceBlaster, EnemySpaceship, он создает экземпляры этих классов, а также наследует классу MonoBehaviour.

По мере усложнения программы, количество классов и связей между ними и их экземплярами значительно увеличивается. И это приводит к определенного рода проблемам. Предположим, что после продолжительной работы над игрой, вы имеете 15 различных классов космических кораблей и 50 различных видов вооружения. В добавок, представим, что требования к геймплею значительно изменились:

  1. Вражеские корабли должны создаваться динамически, их количество заранее не известно;
  2. Игрок может поменять тип используемого оружия в процессе игры;
  3. Необходимо реализовать возможность изменить сложность игры «на лету»;
  4. Сложность игры влияет на тип вооружения используемого всеми кораблями. Изменив уровень сложности нужно изменить оружие у всех уже созданных кораблей.

А также, геймдизайнер решил, что SpaceBlaster и SuperMegaSpaceCannon больше не нужны и соответствующие классы необходимо удалить.

Если программа была построена способом подобным приведенному выше, то можно считать, что программисты попали в довольно затруднительную ситуацию, ибо теперь, им нужно удалить все упоминания классов SpaceBlaster и SuperMegaSpaceCannon по всему коду программы, и, если это вообще возможно, изменить способ получения ссылок на экземпляры оружия в экземплярах космических кораблей. К тому же, сам процесс создания инстансов кораблей станет намного сложнее.

Механизм привязок призван помочь избежать подобных проблем. Процесс их создания и работы с привязчиками рассмотрен в следующем разделе.