Reputation: 16148
This is not so much of a technical question, but rather a c++ design question.
Frequently it seems that I have to design programs which have to manage some a protocol which has some sort of connection, parsing stage and abstract view. Typically I try and design my programs with separation of concerns at the forefront.
I keep ending out with "stacks" of objects, a system sits ontop of a parser, which in turns sits ontop of a connection (often there are more layers). These objects then use member function calls to call a layer below it (Tx), and uses callbacks (std::function
, usually) to capture information coming from the other directions (Rx).
This design seems really subpar, as it add complexity, and each layer has to have a progressively bigger constructor and so on. Also because the connection usually uses something like ASIO, the callbacks are generally on different threads so it's hard to reason about thread saftey.
Is there a design patterm, or idiom that represent this structure/functionality better?
A simple example
class basic_connection {
basic_connection(std::string address);
void send(std::string);
std::function<void(std::string)> on_receive;
};
I have a few classes like this, which hold that layer's state, and are glue together by their public member functions and callbacks.
The layer above this, receives command data processes for the network and calls basic_connection::send
. And takes the raw data from basic_connection
and converts into commands for the layer above it unprocessed.
Another issue that I forgot to mention is that you end up forwarding some of the interface though the stack, for example, a top layer still needs to know the connection status.
Upvotes: 13
Views: 1117
Reputation: 68638
It sounds like what you are trying to do is what is normally refered to as "constructing a pipeline".
Here is one simple way to connect two layers:
class I
{
virtual void a() = 0;
virtual void b() = 0;
}
class X
{
I& m_i;
X(I& i) : m_i(i) {}
void onRecv(const char* data, size_t len)
{
for (size_t p = 0; p < len; p++)
switch (data[p])
{
case 'a': m_i.a(); break;
case 'b': m_i.b(); break;
}
}
}
class Y : public I
{
void a() { ... }
void b() { ... }
}
int main()
{
X x;
Y y(x);
while (...)
x.onRecv(data,len);
}
Upvotes: 1
Reputation: 34628
It seems to me that what you need is additional abstraction. I would begin by designing a general type to describe what a layer actually is and (if appropriate) refine that for each particular layer without taking into account your concrete protocols for these layers. So, you could say a layer-k protocol needs an object of type layer-(k-1).
From your description I assume your higher layers are constructing their immediate lower layer which makes the constructors inflate. Simply ask for a reference (probably best implemented by a shared_ptr
or unique_ptr
) for the next lower layer in your constructor and have the interface user bother about its instantiation.
Since you defined an abstract interface, you can still use the lower layer polymorphically, without having to bother with how it's implemented and what particular lower layer protocol is used.
For reception, you typically need callbacks which can be implemented in the same fashion. You can even install these in the constructor of higher layer objects and remove them in the destructor.
If you know at design time which protocol will play with which other protocol, you could also replace the polymorphic calls by making your protocol implementation a template that receives its lower protocol something like this: Tcp<Ip<Ethernet<Device>>>
.
Upvotes: 0
Reputation: 18358
Without having a set of requirements it's hard to recommend anything. However, from the high level description in your question it seems that you might want to use the model-view-controller pattern, maybe in conjunction with others. Remember, design patterns are your friends and you're the one who decides if usage is appropriate and to what degree. Design pattern are very easy to abuse and it's happening all the time.
Upvotes: 1