Entity Component System
ECS для преданных объектно-ориентированных разработчиков.
Если вы являетесь преданным 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 :)