Шаблон проектирования в C - чтение с нескольких устройств и интерфейсов - PullRequest
2 голосов
/ 10 января 2012

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

Например:

Device_A {
  message type: A
  iface 1: tcp
}

Device_B {
  message type: B
  iface 1: serial
  iface 2: tcp
}
... and so on

В моем основном ...

struct msg_data;
while(user_wants_to_read) {
   read_msg(); // reads and sets data in msg_data
   do_work(msg_data);
}

В ОО-языке я бы использовалшаблон стратегии.Я думаю, что мог бы сделать это с void* read_func;?

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

Ответы [ 3 ]

3 голосов
/ 10 января 2012

Похоже, вам нужно решить две или более различных абстракций:

  1. Различные источники потока (TCP или последовательный).Протокол TCP одинаков для устройства A и B?

  2. Различные типы сообщений, которые структурно различны, но семантически одинаковы.

  3. Различные классы устройств (устройство A против устройства B)

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

Итак, для начала, возможно, абстрагирование потоковой передачи по последовательному каналу и TCP в различные реализации с одним и тем же интерфейсом.Одна реализация, которая знает, как подключать и читать байты из сокета TCP без учета содержимого сообщенияДругой, который умеет читать с последовательного порта.Они должны иметь одинаковый «интерфейс».Вот легкий пример "интерфейса потока байтов" с некоторым взломанным кодом сокета.Прости меня, если это не скомпилировано.Я мог бы иметь опечатку, допустимую в C ++, неправильно в C. В любом случае, это просто пример, демонстрирующий интерфейсы через указатели на таблицы функций.

Я думаю о том, что «как бы это реализовать в C ++?"И тогда я переношу свой ответ в чистую букву "С".(Примечание: я, вероятно, делаю некоторые ошибки объявления ниже.)

struct ByteStreamer;

typedef int (*ReadFunc)(ByteStreamer*, char* buffer, int count);
typedef int (*OpenFunc)(ByteStreamer*, char* url); // maybe 'open' isn't needed if it's handled by the factory
typedef int (*CloseFunc)(ByteStreamer*);
typedef void (*DisposeFunc)(ByteStreamer*);

typedef struct _ByteStreamer
{
    ReadFunc readfunc;
    OpenFunc openfunc;
    CloseFunc closefunc;
    DisposeFunc dispose;

    // private data meant for the "class"
    void* instancedata;
} ByteStreamer;

struct _tcpconnection
{
    int socket;
    sockaddr_in addrRemote;
} TCPConnection;

struct _serialconnection
{
    int filehandle;
    int baud;
} SerialConnection;

// ---------------------------------------

ByteStream* CreateStreamForTCP(const sockaddr_in *pAddr) // pass additional parameter as needed
{
    ByteStreamer* pStream = (ByteStreamre*)malloc(sizeof(ByteStreamer));
    TCPConnection* pTCPConnection = (TCPConnection*)malloc(sizeof(TCPConnection*));
    pTCPConnection->socket = -1;
    pTCPConnection->addrRemote = *pAddr;
    pStream->instancedata = pTCPConnection;
    pStream->ReadFunc = TCPRead;
    pStream->OpenFunc = TCPOpen;
    pStream->CloseFunc = TCPClose;
    pStream->DisposeFunc = TCPDispose;
    pStream->type = STREAM_TYPE_TCP;
    return pStream;
}

int TCPRead(ByteStream* pStream, char* buffer, int count)
{
    return recv(((TCPConnection*)pStream->instancedata)->socket, buffer, count, 0);
}

int TCPOpen(ByteStream* pStream, char* url)
{
    // it's up to you if you want to encapsulate the socket address in url or in the instance data
    TCPConnection* pConn = (TCPConnection*)(pStream->instancedata);
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    connect(&pConn->addrRemote, sizeof(pConn->addrRemote));
    return (pConn->sock >= 0); // true/false return;
}

void TCPClose(ByteStream* pStream)
{
    TCPConnection* pConn = (TCPConnection*)(pStream->instancedata);
    close(pConn->sock);
}
void TCPDispose(ByteStream* pStream)
{
    free(pStream->instancedata);
    free(pStream);
}

Теперь замените весь приведенный выше код TCP на эквивалентную реализацию последовательного порта.Также было бы неплохо реализовать версию структуры ByteStream для «файлового потока» (или «в потоке памяти»).Потому что это будет очень полезно в модульных тестах для кода более высокого уровня.

Так что после того, как вы проработаете все реализации потока байтов, перейдите к разбору специфических сообщений устройства.

typedef struct _Message_A
{
   // A specific data fields    
} Message_A;

struct _Message_B
{
   // B specific data fields
} Message_B;

struct Message
{
   // commonality between Message_A and Message_B
};

typedef (*ReadMessageFromStream)(MessageReader* pReader, Message* pMsg); // pStream is an in-param, pMSg is an out-param.

typedef (*MessageReaderDispose)();

struct MessageReader
{
    ReadMessageFromStream reader;
    MessageReaderDispose dispose;

    // -----------------------------
    ByteStream* pStream;
    void *instancedata;
};  


// function to read a "Message_A" from a stream - and then transpose it to the generic Message type
int ReadMessage_A(ByteStream* pStream, Message* pMsg);
// function to read a "Message_B" from a stream - and then transpose it to the generic Message type
int ReadMessage_B(ByteStream* pStream, Message* pMsg);

ИтакЧто действительно здорово в реализации ReadMessage_A и ReadMessage_B, так это то, что вы можете передать реализацию ByteStream в «файловом потоке» и сделать несколько действительно хороших модульных тестов.Таким образом, когда вы подключаете TCP или последовательную версию, у нее есть высокий шанс просто работать (при условии, что ваш TCP и последовательный код тестируются отдельно).

А затем, возможно, фабричный метод для каждого класса для создания UberReadMessageFromStream:

MessageReader* CreateTCPReaderForDeviceA(DeviceA* pA, sockaddr_in* pAddr)
{
    MessageReader *pMR = (vMessageReader*)malloc(sizeof(MessageReader));
    pMR->pStream = CreateStreamForTCP(pAddr);
    pMR->pStream->Open();
    pMR->reader = ReadMessage_A;
    return pMR;
}

MessageReader* CreateSerialReaderForDeviceB(DeviceB* pB, int comport)
{
    MessageReader *pMR = (vMessageReader*)malloc(sizeof(MessageReader));
    pMR->pStream = CreateStreamForSerial(comport);
    pMR->pStream->Open();
    pMR->reader = ReadMessage_B;
    return pMR;
}

И тогда ваш основной цикл выглядит примерно так:

if ((type == DEVICE_A) && (source == TCP))
    pReader = CreateTCPReaderForDeviceA(pDevice, &addr)
else if ((type == DEVICE_B) && (source == SERIAL))
    pReader = CreateSerialReaderForDeviceB(pDeviceB, 1);

// read the message
Message msg;
pReader->reader(pReader, &msg);
pReader->Dispose(); // free all the data allocated and close connections/files

Ухх ... Я устал набирать эту точку.надеюсь, это поможет.

0 голосов
/ 10 января 2012

А для вашей структуры сообщений вы можете использовать вложенную структуру для эмуляции наследования классов OO

struct base {
    // common members
}

struct child1 {
    struct base;
    // other data members 
}

или просто:

struct child2 {
     // same data members as base
     // other data members
}

использовать базовый * параметр

0 голосов
/ 10 января 2012

Я бы согласился с @rsaxvc. Функциональные указатели, вероятно, лучший способ добиться этого. Поиск в Google показал: Шаблон стратегии в C

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...