Entity Component System

ECS для преданных объектно-ориентированных разработчиков.

Nikita Goncharuk
Clean Code

--

Если вы являетесь преданным OOП разработчиком, я думаю, можно с уверенностью предположить, что вы знакомы с концепцией внедрения зависимостей. И я не имею в виду конкретную структуру внедрения зависимостей, я имею в виду концепцию, которую иногда называют инверсией контроля, или Голливудский принцип.

Внедрение зависимостей в ОО используется в основном для отделения кода и повышения его доступности для модульного тестирования и будущих изменений. Большинство классов не являются самодостаточными (по нескольким причинам) и поэтому полагаются на другие классы для хранения данных или предоставления методов. Эти другие классы называются зависимостями.

Если класс сам создает экземпляры своих зависимостей, он становится непрозрачным / неконфигурируемым. Когда мы следуем принципу внедрения зависимостей, мы разоблачаем зависимости класса, делая его прозрачным и настраиваемым извне.

Чтобы еще больше уменьшить связь с определенным классом, рекомендуется определить зависимости как интерфейс.
Таким образом, реализации могут быть предоставлены классу без особых хлопот. Эта методика расширяет возможности, которые я ранее называл доступностью для модульного тестирования и будущих.

В канонической ECS мы не думаем с точки зрения классов, мы думаем с точки зрения сущностей(entities) и систем(systems). Сущность — это логическая совокупность компонентов, а система — это поведение, которая оперирует данными / состоянием. Системы следуют принципу внедрения зависимостей, но в случае системы — это данные. Система описывает, какие данные ей нужны для того, чтобы выполнять над ними операции.

Для заядлых разработчиков мне нужен закаленный объектно-ориентированный пример. Давайте возьмем пример расчета площади. Он используется во многих руководствах по принципам разработки SOLID.

Типичным объектно-ориентированным способом мы определяем интерфейс Shape, который будет иметь метод ComputeArea. Этот метод ComputeArea возвращает вычисленный результат. Каждый класс, который можно рассматривать как форму (например, Круг, Прямоугольник и Треугольник). Реализует Shape и предоставляет собственную реализацию метода ComputeArea.

Сущности (вверху) можно рассматривать как «мешки с компонентами». Системы (внизу) обновляют их. Существует четкое разделение между данными и логикой.

Как я упоминал ранее в ECS, мы не думаем в терминах классов, мы думаем в системах. Система выполняет конкретное преобразование Нам нужно взглянуть на имеющиеся данные и выяснить, какие системы нам необходимо внедрить для расчета площади.

Для площади круга нам нужен радиус. Таким образом, ComputeCircleAreaSystem необходимо запросить все объекты, которые имеют компонент Radius.

ComputeRectangleAreaSystem запрашивает все объекты, имеющие компоненты Width и Height. И ComputeTriangleAreaSystem нуждается во всех объектах, которые имеют компоненты Base и Height.

Мне нравится говорить, что в ECS мы проектируем снизу вверх. Мы смотрим на данные и то, какое поведение зависит от того, какие мы имеем данные.

Теперь вот еще один поворот, о котором вы можете и не подумать, будучи твердолобым OO разработчиком. Система в ECS не возвращает значения. В нашем OO-решении мы определили бы метод genericComputeArea, который возвращал бы вычисленное значение площади. Обычно мы не просто вычисляем значения, мы вычисляем их, чтобы мы могли обрабатывать их дальше. Это означает, что результат вызова метода ComputeArea будет где-то сохранен или передан другому методу.

В ECS, системы, которые вычисляют площадь, к сущности добавляется компонент Area, который они использовали для вычисления площади. Это означает, что:

  • CircleAreaSystem считывает компонентRadius и записывает компонент Area.
  • ComputeRectangleAreaSystem считывает Width иHeight и записываетArea
  • ComputeTriangleAreaSystem считываетBase и Height и записываетArea.

Скажем, мы хотим вычислить сумму всех вычисленных площадей. Для этого нам нужно определить систему ComputeSumOfAllAreasSystem. Эта система получает все объекты, имеющие компонент Area, и может суммировать их, сохраняя результат в отдельном компоненте или создавая побочный эффект, такой как печать на экране, или отправка результата по сети.

Как видите, с помощью ECS мы можем избежать абстракций. ComputeSumOfAllAreasSystem не зависит от абстрактного Shape, у которого есть универсальный метод ComputeArea. Он зависит от компонента Area(данных). Он не заботится о происхождении данных. Значение площади может быть вычислено из данных, основанных на других компонентах, или установленных непосредственно на основе пользовательского ввода, данных, полученных из сети и т.д.

Conclusion

Цели ECS и внедрения зависимостей в объектно-ориентированном подходе схожи, с той разницей, что ECS идет снизу вверх, а объектно-ориентированный подход идет сверху вниз.

Перевод статьи: medium
Help us with claps :)

--

--