Должны ли типы иметь методы в ориентированном на данные дизайне?

В настоящее время мое приложение состоит из трех типов классов. Это должно следовать ориентированному на данные дизайну, пожалуйста, исправьте меня, если это не так. Это три типа классов. Примеры кода не так важны, вы можете пропустить их, если хотите. Они просто для того, чтобы произвести впечатление. У меня вопрос, должен ли я добавлять методы в мои классы типов?

Типы просто держат ценности.

struct Person {
Person() : Walking(false), Jumping(false) {}
float Height, Mass;
bool Walking, Jumping;
};

Модули реализовать одну отличительную функциональность каждый. Они могут получить доступ ко всем типам, так как они хранятся по всему миру.

class Renderer : public Module {
public:
void Init() {
// init opengl and glew
// ...
}
void Update() {
// fetch all instances of one type
unordered_map<uint64_t, *Model> models = Entity->Get<Model>();
for (auto i : models) {
uint64_t id = i.first;
Model *model = i.second;
// fetch single instance by id
Transform *transform = Entity->Get<Transform>(id);
// transform model and draw
// ...
}
}
private:
float time;
};

Менеджеры вид помощников, которые вводятся в модули через базу Module учебный класс. Вышеуказанное используется Entity это экземпляр менеджера сущностей. Другие менеджеры охватывают обмен сообщениями, доступ к файлам, хранилище sql и так далее. Короче говоря, все функции, которые должны быть разделены между модулями.

class ManagerEntity {
public:
uint64_t New() {
// generate and return new id
// ...
}
template <typename T>
void Add(uint64_t Id) {
// attach new property to given id
// ...
}
template <typename T>
T* Get(uint64_t Id) {
// return property attached to id
// ...
}
template <typename T>
std::unordered_map<uint64_t, T*> Get() {
// return unordered map of all instances of that type
// ...
}
};

Теперь у вас есть представление о моем текущем дизайне. Теперь рассмотрим случай, когда тип требует более сложной инициализации. Например, Model введите только что сохраненные идентификаторы OpenGL для его текстур и буферов вершин. Фактические данные должны быть загружены на видеокарту раньше.

struct Model {
// vertex buffers
GLuint Positions, Normals, Texcoords, Elements;
// textures
GLuint Diffuse, Normal, Specular;
// further material properties
GLfloat Shininess;
};

В настоящее время существует Models модуль с Create() функция, которая заботится о настройке модели. Но так я могу создавать модели только из этого модуля, а не из других. Должен ли я переместить это в тип класса Model при этом усложняя это? Я, хотя из определений типов, как интерфейс раньше.

3

Решение

Во-первых, вам не обязательно применять ориентированный на данные дизайн везде. В конечном счете, это оптимизация, и даже критически важная для кода база по-прежнему содержит множество частей, которые не получают от нее никакой выгоды.

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

Представьте себе 4-компонентное (RGBA) 32-битное изображение с плавающей запятой, но по любой причине с использованием только 8-битной альфы (извините, это глупый пример). Если бы мы даже использовали базовый struct для пиксельного типа мы бы обычно требовали значительно больше памяти, используя пиксельную структуру из-за заполнения структуры, необходимого для выравнивания.

struct Image
{
struct Pixel
{
float r;
float g;
float b;
unsigned char alpha;
// some padding (3 bytes, e.g., assuming 32-bit alignment
// for floats and 8-bit alignment for unsigned char)
};
vector<Pixel> Pixels;
};

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

struct Image
{
vector<float> rgb;
vector<unsigned char> alpha;
};

… и именно так мы должны думать изначально: о данных, разметке памяти. Конечно, изображения уже обычно представлены эффективно, и алгоритмы обработки изображений уже реализованы для обработки большого количества пикселей в массе.

Тем не менее, дизайн, ориентированный на данные, выводит это на более высокий уровень, чем обычно, применяя этот вид представления даже к вещам, которые значительно более высокого уровня, чем пиксель. Аналогичным образом, вы могли бы извлечь выгоду из моделирования ParticleSystem вместо одного Particle оставить такую ​​передышку для оптимизаций или даже People вместо Person,

Но вернемся к примеру с изображением. Это может означать отсутствие DOD:

struct Image
{
struct Pixel
{
// Adjust the brightness of this pixel.
void adjust_brightness(float amount);

float r;
float g;
float b;
};
vector<Pixel> Pixels;
};

Проблема с этим adjust_brightness Метод заключается в том, что он разработан с точки зрения интерфейса для работы с одним пикселем. Это может затруднить применение оптимизаций и алгоритмов, которые выигрывают от одновременного доступа к нескольким пикселям. Между тем как то так:

struct Image
{
vector<float> rgb;
};
void adjust_brightness(Image& img, float amount);

… может быть написано так, чтобы получить доступ к нескольким пикселям одновременно. Мы могли бы даже представить это с помощью представителя SoA:

struct Image
{
vector<float> r;
vector<float> g;
vector<float> b;
};

… что может быть оптимальным, если ваши точки доступа относятся к последовательной обработке. Детали не имеют большого значения. Для меня важно то, что ваш дизайн оставляет перед собой простор для оптимизации. Для меня ценность DOD заключается в том, что если продумать этот тип мышления заранее, вы получите такие типы интерфейсов, которые оставят вам передышку для дальнейшей оптимизации по мере необходимости без навязчивых изменений дизайна.

Полиморфизм

Классический пример полиморфизма имеет тенденцию также фокусироваться на этом гранулированном единомышленнике, как Dog наследуется Mammal, В играх, которые иногда могут привести к появлению узких мест, когда разработчикам приходится бороться с системой типов, сортируя полиморфные базовые указатели по подтипам, чтобы улучшить временную локальность виртуальной таблицы, пытаясь сделать данные определенным подтипом (Dogнапример, непрерывно распределяются с помощью пользовательских распределителей для улучшения пространственной локализации в каждом экземпляре подтипа и т. д.

Нет необходимости в этом бремени, если мы моделируем на более грубом уровне. Вы можете иметь Dogs наследование аннотации Mammals, Теперь стоимость виртуальной отправки снижена до одного раза за тип млекопитающего, не один раз на млекопитающее, и все млекопитающие определенного типа могут быть представлены эффективно и непрерывно.

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

Дизайн интерфейса

До сих пор дизайн интерфейса связан с DOD, по крайней мере, насколько я вижу, и у вас могут быть методы в ваших классах. По-прежнему очень важно спроектировать надлежащие высокоуровневые интерфейсы, и вы все равно можете использовать виртуальные функции и шаблоны и быть очень абстрактными. Практическая разница, на которой я бы сосредоточился, заключается в том, что вы разрабатываете совокупные интерфейсы, как в случае с adjust_brightness метод выше, который оставляет вам передышку для оптимизации без каскадных изменений дизайна по всей вашей кодовой базе. Мы разрабатываем интерфейс обрабатывать несколько пикселей всего изображения вместо того, которое обрабатывает один пиксель за раз.

Конструкции интерфейса DOD часто предназначены для массовой обработки и, как правило, таким образом, чтобы иметь оптимальную структуру памяти для наиболее критичных по производительности последовательных циклов линейной сложности, которые должны иметь доступ ко всему.

Так что, если мы возьмем ваш пример с Modelчего не хватает, так это агрегированной стороны интерфейса.

struct Models {
// Methods to process models in bulk can go here.

struct Model {
// vertex buffers
GLuint Positions, Normals, Texcoords, Elements;
// textures
GLuint Diffuse, Normal, Specular;
// further material properties
GLfloat Shininess;
};

std::vector<Model> models;
};

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

Горячее / холодное расщепление

Глядя на ваш Person класс, вы все еще можете думать о классическом интерфейсе (даже если интерфейс здесь только данные). Опять же, DOD будет в первую очередь использовать struct в целом, только если это была оптимальная конфигурация памяти для наиболее критичных к производительности циклов. Речь идет не о логической организации для людей, а об организации данных для машин.

struct Person {
Person() : Walking(false), Jumping(false) {}
float Height, Mass;
bool Walking, Jumping;
};

Сначала давайте рассмотрим это в контексте:

struct People {
struct Person {
Person() : Walking(false), Jumping(false) {}
float Height, Mass;
bool Walking, Jumping;
};
};

В этом случае все ли поля часто доступны вместе? Допустим, гипотетически, что ответ — нет. Эти Walking а также Jumping поля доступны только иногда (холодно), в то время как Height а также Mass к нему обращаются постоянно (горячо). В этом случае потенциально более оптимальное представление может быть:

struct People {
vector<float> HeightMass;
vector<bool> WalkingJumping;
};

Конечно, вы можете создать две отдельные структуры, указать одну точку на другую и т. Д. Ключ заключается в том, что вы разрабатываете это в конечном итоге с точки зрения разметки памяти / производительности, в идеале с хорошим профилировщиком в руке и четким пониманием общие пути кода пользователя.

С точки зрения интерфейса, вы разрабатываете интерфейс с акцентом на обработку людей, а не человек.

Эта проблема

С этим из пути, к вашей проблеме:

Я могу создавать модели только из этого модуля, а не из других. Нужно ли мне
переместить это к типу класса Модель, в то время как это усложняет?

Это скорее забота о разработке подсистем. Так как ваш Model rep — это все о данных OpenGL, вероятно, они должны принадлежать модулю, который может их правильно инициализировать / уничтожить / визуализировать. Это может быть даже частная / скрытая деталь реализации этого модуля, после чего вы применяете настрой DOD в рамках реализации модуля.

Однако интерфейс, доступный внешнему миру для добавления моделей, уничтожения моделей, их рендеринга и т. Д., В конечном итоге должен быть спроектирован для массового использования. Думайте об этом как о создании высокоуровневого интерфейса для контейнера, в котором методы, которые вы захотите добавить для каждого элемента, вместо этого заканчивают тем, что принадлежали к контейнеру, как в нашем примере изображения выше с adjust_brightness,

Сложная инициализация / уничтожение часто требует индивидуального подхода к проектированию, но ключ заключается в том, что вы делаете это через объединенный интерфейс. Здесь вы все еще можете отказаться от стандартного конструктора и деструктора для Model в пользу инициализации при добавлении графического процессора Model рендеринг, очистка ресурсов графического процессора при удалении его из списка. Это несколько напоминает кодирование в C-стиле для отдельного типа (например, персонажа), хотя вы все равно можете получить очень изощренные навыки C ++ для агрегатного интерфейса (люди, например).

У меня вопрос, должен ли я добавлять методы в мои классы типов?

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

3

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector