2015-02-01 4 views
2

У меня есть класс со многими объектами, которые я хотел бы сгруппировать в некоторый тип контейнера, а также получить к ним доступ с некоторым типом идентификатора.Что такое лучший дизайн на C++ для размещения объектов класса?

class Menu { 
Object title; 
Object play; 
Object instructions; 
Object pause; 
... 
}; 

Имея каждый объект, указанный в классе, как показано выше, это хорошо, потому что я могу получить доступ к ним как, menu.title, но тогда я должен перепечатать каждое имя, чтобы добавить его в контейнер, vector.push_back(title).

Ниже показано, как я всегда решал проблему. Я использую целочисленное значение перечисления для доступа к соответствующему индексу. objects[TITLE] или objects[PLAY]

class Menu { 
std::vector<Object> objects; 
}; 

enum ObjectTypes { 
TITLE, PLAY, INSTRUCTIONS, PAUSE, ... 
}; 

Я вообще не нравится этот подход, поскольку он кажется косвенным, и при использовании вложенных классов и перечислений, идентификаторы могут стать длинными и громоздкими. Я прихожу из C и несколько новичок в C++. У кого-нибудь есть более практичный и/или элегантный способ решить эту проблему? C++ 11 и выше приветствовали!

+1

Вы считаете «std :: map»? Это ассоциативный контейнер, что означает, что вы можете ссылаться на объекты с помощью меню ["title"] 'where' "title" 'является' std :: string' – yizzlez

+0

Вы можете создавать методы, которые являются ссылками в хорошо известных местах, или если это массив, было бы прямо связать их во время построения. –

+1

Это может быть std :: map . – BitTickler

ответ

1

Подход, который вы используете, в порядке. Если вы хотите избежать громоздких идентификаторов, вы можете сделать временную ссылку, чтобы держать вещи более краткими. Например, вместо вызова:

menu.objects[PAUSE].foo(); 
menu.objects[PAUSE].bar(); 
menu.objects[PAUSE].baz(); 

... вы могли бы сделать это, когда это необходимо:

Object & pause = menu.objects[PAUSE]; 
pause.foo(); 
pause.bar(); 
pause.baz(); 

и он будет работать так же, но без всех избыточных символов.

0

Я думаю, что ваш подход в порядке. Использование std::map<ObjectType, Object> вместо std::vector может быть немного более безопасным для типов.

В любом случае, если вы хотите сэкономить немного набрав вы можете перегрузить operator[] на Menu:

Object& operator[](index_type i){ return objects[i]; } 
const Object& operator[](index_type i) const { return objects[i]; } 

Then you can writemenu[TITLE].

Кроме этого, я не вижу, как это может быть менее громоздким, там нет избыточной информации, и, как указал Джереми, если вам нужен объект несколько раз, вы всегда можете создать локальную ссылку auto& title = menu[TITLE];.

В зависимости от того, что других обязанностей Menu имеет, возможно, вам не нужен Menu класса на всех, и вы можете просто использовать map или vector напрямую?

0

Лучшее решение вашего вопроса зависит в основном от ваших случаев использования. Я вижу два основных случая использования:

  1. Вы хотите представить «функцию» из «устройства», так что вы можете достичь читаемого кода при манипулировании, что «устройства» из вашего кода. Например, устройство MediaPlayer имеет операции воспроизведения, остановки, паузы. Но вы упустили возможность просто добавлять функции-члены к вашему объекту «устройство», например Play(), потому что вы хотите повторно использовать свой игровой код для другого устройства, например, тюнерного устройства. Кроме того, вы хотите применить операции ко всем или к подмножеству этих «функций», например Enable()/Disable()/ToString()/Trigger(), Configure() ..., поэтому функция-член подход не является благоприятным.
  2. Вы хотите создать объектную модель документа, которая больше ориентирована на данные. Например, документ Xml.

Из того, что вы написали в своем вопросе, я предполагаю, что у вас есть случай использования 1 в виду. Тип Object имеет все общие операции, в которых вы нуждаетесь.
Тем не менее, тогда существуют различия между всеми этими «функциями».

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

// Example of "annoying": If you have more than 1 "device", 
// you have to write such a function for each of them. 
// Also, there is that fishy bit with Object.Id - the association between 
// your configuration data and your object you want to configure. 
void ConfigureMenu(std::vector& menuItems, MenuConfigurationData& configData) 
{ 
    for(auto& menuItem : menuItems) 
    { 
     menuItem.Configure(configData[menuItem.Id]); // spoiler! 
    } 
} 

Также , Я склонен думать, что даже сейчас у вас есть код, который не отображается в вашем вопросе, который настраивает ваши объекты.

Имея это в виду, вы можете избавиться от идеи написать 1 тип класса на «устройство» вручную. Следующее устройство/меню нужно будет обрабатывать одинаково, но с выделенным большим количеством кодировок.

Итак, мой совет для вас - избавиться от вашего class Menu навсегда, чтобы абстрагировать вашу проблему и моделировать вашу проблему, а именно: Object - это ваша «функция», а устройство - это просто набор функций/объектов. Затем ваш class Menu просто станет экземпляром, названным Menu.

typedef uint32_t FunctionId; // for example uint32_t... 
typedef std::map<FunctionId,Object> Device; // aka. Menu. 

Затем в функции конфигурации вы, скорее всего, в любом случае, вы передаете в экземпляре этой Device карты и ваша функция конфигурации заполняет ее объект, правильно настроена.

// those enums are still specific to your concrete device (here Menu) but 
// you can consider the time it takes writing them an investment which will 
// pay off later when you write your code, using your functions. 
// You assign the function id which is used in your meta-data. 
enum class MenuFunctions : FunctionId { play = ..., title = ..., instructions, ... }; 

// "generic" configuration function. 
// Does not only configure your Object, but also puts it inside the device. 
void ConfigureDevice(Device& device, ConfigData& configData) 
{ // ... 
} 

И позже в вашем коде, вы можете получить доступ к функциям, как это:

menu[MenuFunctions::play].Trigger(); 

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

Со всем этим на месте использование «вложенных классов» становится просто вопросом создания коллекций экземпляров устройства. Теперь, поскольку все ваши устройства одного типа, вы можете создавать и подгруппировать их на досуге.

0

Несколько различных подходов ниже. Я не говорю, что «лучше». Для всех есть плюсы и минусы.

A) Вместо того, чтобы использовать вектор (например, class Menu { std::vector<Object> objects; };), используйте массив class Menu {std::array<Object,NObjectTypes> objects;};, когда ваш элемент подсвечен.

B) Просто используйте класс, но предоставить API для возврата std::array<> ссылок на ваши объекты:

class Menu { 
Object title; 
Object play; 
Object instructions; 
Object pause; 
... 
std::array<Object*,NObjects> allObjects(); 
}; 

C) std::tuple может быть полезно, если ваши типы не все равно.


Для меню я часто буду с «А».

Смежные вопросы