Интерфейсы в дизайне, ориентированном на данные

Поговорка звучит примерно так:

«Программа для интерфейса / абстракции, а не для реализации».

Все мы знаем интерфейсы как средство разделения в объектно-ориентированном программировании. Как контракт, который выполняет какой-то объект.

Но кое-что, что я не могу понять, это:

Как мне запрограммировать интерфейс / абстракцию в дизайне, ориентированном на данные?

Как в случае с "Drawable", но сейчас я не делаю этого, если это прямоугольник или круг, но он реализует интерфейс "Drawable".

Спасибо


person krakers    schedule 30.12.2018    source источник
comment
Не могли бы вы подробнее рассказать о своем замешательстве? Я не уверен, что вы хотите услышать здесь в качестве ответа.   -  person deceze♦    schedule 30.12.2018
comment
Сами по себе данные бесполезны без методов. Так что нет смысла обсуждать эти концепции таким образом. Или я что-то упускаю? Уточните свой вопрос.   -  person Serg Shevchenko    schedule 30.12.2018


Ответы (1)


Это большой вопрос. Я считаю, что вы спрашиваете, как добиться полиморфизма с помощью дизайна, ориентированного на данные (DOD)?

Краткий ответ: Вы не делаете этого с помощью интерфейсов. Это способ объектно-ориентированного программирования (ООП) для достижения полиморфизма. В DOD полимофизм может быть достигнут с помощью паттерна Entity Component System (ECS).

Длинный ответ (с примерами):

Вот пример полиморфизма в ООП:

public interface Drawable
{
   void Draw();
}

public class Circle: Drawable
{
   public float posX, posY;
   public float radius;

   public void Draw() { /* Draw Circle */ }
}

public class Rectangle: Drawable
{
   public float posX, posY;
   public float width, height;

   public void Draw() { /* Draw Rectangle */ }
}

А вот как добиться полиморфизма с помощью DOD и ECS (псевдокода):

public struct Position { public float x, y; }
public struct Circle { public float radius; }
public struct Rectangle { public float width, height; }

public class DrawCirlceSystem
{
    public void OnUpdate()
    {
        ComponentQuery
            .SelectReadOnly(typeof(Position), typeof(Circle))
            .ForEachEntity((Entity entity, Position position, Circle circle) => {
                /* Draw Circle */
            });
    }
}

public class DrawRectangleSystem
{
    public void OnUpdate()
    {
        ComponentQuery
            .SelectReadOnly(typeof(Position), typeof(Rectangle))
            .ForEachEntity((Entity entity, Position position, Rectangle rectangle) => {
                /* Draw Rectangle */
            });
    }
}

Итак, если у вас был следующий формат данных:

Entity 1: [Position, Circle]
Entity 2: [Position, Circle]
Entity 3: [Position, Rectangle]

DrawCircleSystem будет выполняться только для объектов 1 и 2, а DrawRectangleSystem будет выполняться только для объекта 3. Таким образом, полиморфизм достигается за счет возможности запросов этих систем.

Такое программирование намного производительнее, чем ООП. Но помимо этого, это также делает наш код более масштабируемым и оптимизируемым. Например, если вы хотите реализовать отбраковку, чтобы на самом деле отображались только те сущности, которые находятся в представлении, мы можем легко сделать это с очень небольшими усилиями по рефакторингу. Все, что нам нужно сделать, это ввести новую систему, которая обрабатывает отсечение путем добавления или удаления нового компонента с именем Visible для объектов, которые мы хотим нарисовать:

public struct Visible { }

public class CircleCullingSystem
{
    public void OnUpdate()
    {
        // Give me all Circle entities that are NOT Visible
        ComponentQuery
            .SelectReadOnly(typeof(Position), typeof(Ciricle))
            .Exclude(typeof(Visible))
            .ForEachEntity((Entity entity, Position position, Circle circle) => { 
                // Add 'Visible' component to entity if it's within view range
            });

        // Give me all Circle entities that are Visible
        ComponentQuery
            .SelectReadOnly(typeof(Position), typeof(Ciricle))
            .FilterBy(typeof(Visible))
            .ForEachEntity((Entity entity, Position position, Circle circle) => { 
                // Remove 'Visible' component from entity if it's out of view range
            });

    }
}

А затем мы просто обновляем наш запрос в DrawCirlceSystem, чтобы он фильтровался по компоненту Visible:

public class DrawCirlceSystem
{
    public void OnUpdate()
    {
        // Process all visible circle entities
        ComponentQuery
            .SelectReadOnly(typeof(Position), typeof(Circle))
            .FilterBy(typeof(Visible))
            .ForEachEntity((Entity entity, Position position, Circle circle) => {
                /* Draw Circle */
            });
    }
}

И, конечно, нам нужно будет создать RectangleCullingSystem, похожий на наш CircleCullingSystem, поскольку поведение прямоугольников при отсечении отличается от кругов.

person Vakey    schedule 01.02.2019
comment
Это отличный ответ, извините за задержку с ответом. Однако мне не совсем понятно расположение данных объектов / компонентов в примере. Есть ли массивы компонентов? Некоторые говорят, что сущность должна быть только идентификатором, используемым в качестве ключа в массивах компонентов? - person krakers; 24.02.2019
comment
@krakers Прошу прощения за поздний ответ по этому поводу. Да, есть массивы компонентов. Для DrawCircleSystetm он выполняет итерацию по массиву позиций (который содержит компоненты Position объектов 1 и 2) и массиву Circle (который содержит компоненты Circle объектов 1 и 2). Использование идентификатора объекта для получения нужных вам компонентов - вещь полезная, но для этого распространенного варианта использования идентификатор объекта не требуется, поскольку оба массива выстраиваются в линию друг с другом. Таким образом, индекс 0 этих двух массивов будет компонентами Circle и Position объекта 1. - person Vakey; 18.03.2019