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
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 без потери контроля над объектами и их жизненным циклом.»