Skip to content

IItemDb

Почему адаптация EF сопряжена с рядом трудностей?

Дело в том, что Unity - это уже Framework, т.е. каркас, который в отличии от библиотеки вызывает ваш код, а не вы код библиотеки. Реализуется это через наследование, например от MonoBehaviour. Реализовать поверх этого второй, третий каркас уже проблематично, как минимум потому, что нельзя наследоваться от двух каркасных классов одновременно (множественное наследование классов в C# запрещено). Поэтому еденственный способ реализовать "второй" каркас это попросить разработчика реализовать интерфейс. Но почему этот, казалось бы правильный вариант в общем случае, не используют разработчики Framework`ов? Потому что реализовывать интерфейс вручную — это достаточно сложно и требует от разработчика глубокого понимания внутреннего устройства фреймворка. Именно поэтому COM-объекты с их интерфейсами (например, IUnknown) ушли в историю — они требовали слишком много рутины. Учитывая эти сложности, мы предлагаем смешанный подход.

Интерфейс IItemDb

    public interface IItemDb
    {
        public ItemDb item { get; }
    }

Это очень простой интерфейс. Для его реализации нужно всего лишь предоставить объект типа ItemDb. Таким образом, вы можете указать, что ваш класс реализует этот интерфейс. Если вы унаследуете реализацию от родительского класса — вы автоматически реализуете интерфейс (потому что родитель уже его реализовал). Или вы можете заменить наследование на агрегацию, самостоятельно реализовав свойство item.

Универсальная сущность Item

Чтобы связать наш каркас, который позволит сохранять объекты через EF с каркасом Unity, мы предоставляем класс Item.

    public abstract class Item : MonoBehaviour, IItemDb
    {
        /// Уникальный индентификатор объекта в мире
        public int Id { get { return itemDb.Id; } set { itemDb.Id = value;  } }
        /// Группа объекта
        public int GroupId = -1;
        /// Имя префаба
        public string ModelName = "";

        private ItemDb itemDb = new ItemDb();
        public ItemDb item { get { return itemDb; } }
    }

Это наследник от MonoBehaviur, но добавляющий минимальный набор полей и реализующий интерфейс IItemDb, создающий пока еще "загадочный" объект класса ItemDb.

    public class ItemDb : ItemDb<int>, IItemDb
    {
        public ItemDb item
        {
            get { return this; }
        }
    }

Он же в свою очередь наследуется от нашего сущностного класса ItemDb, а в качестве реализации IItemDb возвращает самого себя. Таким образом, данный подход позволяет создать двойной фреймворк, где один (Unity) управляет жизненным циклом объектов, а второй (наш) — управляет сохранением их состояния. При этом мы не нарушаем ограничение на множественное наследование, потому что второй фреймворк работает через интерфейс, а не через наследование.

    public class ItemDb<K>
    {
        /// <summary>
        /// Уникальный индентификатор объекта в мире
        /// </summary>
        public K Id { get; set; }

        public static DbContext db;

        public void SaveGraph<T>(T root)
        {
            Save(root);          // рекурсивно присоединяет все объекты
            db.SaveChanges();    // единственный вызов
        }
    }

Формализм патерна

    public interface IAspect
    {
        public Aspect aspect { get; }
    }
    public class Aspect : Framework2, IAspect
    {
        public Aspect aspect
        {
            get { return this; }
        }
    }

    public class Framework1 { }
    public class Framework2 { }

    public class Adapter : Framework1, IAspect
    {
        private Aspect aspect_ = new Aspect();
        public Aspect aspect { get { return aspect_; } }
    }

Интерфейс определяет контракт доступа к объекту, инкапсулирующему состояние для хранения. Класс, унаследованный от каркаса №1 (например, MonoBehaviour), реализует этот интерфейс, расширяет базовый тип собственными идентификаторами (игровыми атрибутами) и агрегирует экземпляр класса, наследующего от сущностной модели каркаса №2 (например, ItemDb), который, в свою очередь, также реализует тот же интерфейс, возвращая себя. Таким образом, внешняя оболочка скрывает внутреннюю реализацию хранения, обеспечивая единообразный доступ к данным через интерфейс, и позволяет двум каркасам сосуществовать без множественного наследования, связывая их через композицию и общий контракт.

«Паттерн „Entity Aspect“ можно рассматривать как реализацию аспекта сохранения состояния в Unity. В отличие от классических АОП-фреймворков, которые полагаются на прокси или генерацию кода, наш подход использует интерфейс и композицию, что позволяет интегрировать аспект в экосистему Unity без потери контроля над объектами и их жизненным циклом.»