Элегантный объектно-ориентированный доступ к элементу без большого числа получателей / мутаторов

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

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

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

Я мог бы сделать класс пакета другом класса, из которого он извлекает / записывает данные, но мне всегда говорили избегать использования друзей, так как это нарушает объектно-ориентированные принципы.

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

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

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

Сокращенный пример класса пакета tx в настоящее время:

class PacketTx {
private:
uint8_t buffer[MAX_PACKET_SIZE];   // MAX_PACKET_SIZE is 200
public:
PacketTx(object1 *firstObject,
object2 *secondObject,
object3 *thirdObject
// ...etc...
)

void sendPacket(void);
};

void PacketTx::sendPacket(void) {
uint32_t i = 0;

int16_t tempVar1 = firstObject->getVar1();
uint32_t tempVar2 = secondObject->getVar2();
uint8_t tempVar3 = firstObject->getVar3();
int32_t tempVar4 = thirdObject->getVar4();
// ...etc...

memcpy(buffer + i, &tempVar1, sizeof(tempVar1));
i += sizeof(tempVar1);

memcpy(buffer + i, &tempVar2, sizeof(tempVar2));
i += sizeof(tempVar2);

memcpy(buffer + i, &tempVar3, sizeof(tempVar3));
i += sizeof(tempVar3);

memcpy(buffer + i), &tempVar4, sizeof(tempVar4));
i += sizeof(tempVar4);

// ...etc...

for(uint32_t j = 0; j < i; ++j)
putc(static_cast<char>(buffer[i]));
}

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

4

Решение

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

Serial -> Packet.buffer () -> Object (и наоборот)

Моя первоначальная мысль использовала boost::tuple и наличие типов соответствует порядку данных в пакете. (Но, возможно, более 50 членов / записей в кортеже, что может сильно ударить во время компиляции)

например

tuple<float, int, std::string> a(1.0f,  2, std::string("Some Words");
ostr << a;

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

enum Fields {Price,Amount,Reason};
typedef boost::tuple<...> MyTuple;

void MyClass::getData( MyTuple& t )
{
t<Price>() = mPrice;
t<Amount>() = mAmount;
t<Reason>() = mSomeReason;
}

Примечание будет работать в обратном порядке.

Не уверен, что это полностью соответствует тому, что вы пытались сделать, но может вызвать новые мысли?

2

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

Композиция — это то, что вам нужно здесь:

class packet {
enum PacketType {
type1,
...
};
std::vector<unsigned char> buffer;
PacketType id;
public:
void setData(std::vector<unsigned char> buffer);
void setType(PacketType type);
std::vector<usigned char> getData();
PacketType getType();
//serialize Packet;
};

class composition_packet {
//other packet related detail.
std::vector<packet> data;
public:
void addPacket(packet p);
void send();
//serialize composition_packet
};

Идея состоит в том, чтобы сделать несколько пакетов по мере необходимости, но отправить их как один большой пакет (composition_packet). Другой концепцией здесь является то, что пакет отвечает только за сериализацию, а не за типы данных, о которых он должен знать. Данные в пакет должны быть уже сериализованы объектом, который знает, как его сериализовать.


struct foo {
std::string str;
uint_8 u8;
uint_32 u32;

packet getPacket() {
//simple dumb serialization
std::stringstream ss(str);
ss << "|";  //delimit character
ss << u8 << "|";
ss << u32;
std::string s(ss.str());
std::vector<unsigned char> data(s.begin(), s.end());
packet p;
p.setType(packet::fooType);
p.setData(data);
return p;
}

};
1

По вопросам рекламы [email protected]