N
nguillot
Hello
The subject of this post is not really clear.
Let me try to explain:
I often have this kind of problem:
In a system, a block process some data.
The data are sent by a Sender.
They are asynchronously process by a Processor.
The result of the treatments are sent to a Receiver.
Sender
|
| data
|
\/
Processor ------ results -----> Receiver
The receiver receive results as soon as they are available, and let
say it must save them, and save some informations about the data used
to produce the result, an id for instance.
In a previous development, we simply had:
class Data
{
int id_;
public:
int GetId() const;
}
And:
class Result
{
int id_;
public:
int SetId(int);
}
And so, somewhere, the Processor was doing:
Processor:rocessData(const Data& d)
{
Result res;
res.SetId(d.GetId());
// process data...
}
It was a direct approach, but we finally saw the limitations, mainly:
1) if an int is not enough to tag the data, everything is impacted:
the Data, the sender that produce data, the processor that copy the
part of data needed to tag it, the result and the receiver...
2) a developer can use the id (or tag) to deduce some information used
in the processor. The deduction is true one day, but false latter (to
take a sample, we can deduce from a begin and a end iterator the
element count in a container, by "substracting" them: it may be true
with a container type, and false with another container type).
OK. So the issue is: the processor must do a thing (copy a tag) on
something (the data to the result) without knowing about the data more
than what it needs.
Here it needs to know how to copy, and what to copy, from data to
result.
This is typically the usage, IMHO, of an interface.
So the processor, instead of copying an int get with GetId would get a
ITag pointer. It wouldn't know anything about the concrete class
implementing the tag: it can be an int, or something more complex.
And the processor would call ITag::clone or copy...
I intentionally don't try to code something in this way.
My question is: I think this is a common design issue. Is there a
pattern, a guideline, or some best practices about that?
Something I don't like about the ITag interface is: either the Data
class inherits or aggregates it, nothing force the sender to pass an
object with a tag. Ok, the processor won't compile when trying to
access ITag::clone, but the sender code maybe in another compilation
unit, on another project, and the developer is not aware of the
problem.
We could document, but we know that a programming design is better
than an unread documentation to enforce a practice. To say like Herb
Sutter:
"it would be nice to make compilers reject such code instead of just
making tut-tut noises in standardish legalese."
Thank you if you read until here, and thanks in advance for answer (I
hope).
Finally, again one word: I imagined a way to do that, but it has his
problems:
we could enforce to link a data to a tag by declaring a processor
entry point like that:
Processor:rocessData(CTaggedObject<T, Data> data);
where CTaggedObject is
template <typename Tag, typenam Obj> CTaggedObject
{
Tag tag_;
Obj Obj_;
// constrcutor with tag and object and default values...
const Tag& GetTag() const;
const Obj& GetObject() const;
// and the non const version...
}
So the processor would deal with Data to process data, and it copies
the tag without knowing the Tag type.
The result would be provided as a CTaggedObject<SameTagTypeAsData,
Result>.
What is good:
- The processor doesn't know anything about Tag type (with template
functions it should be possible)
What is not good:
- if the processor is in a separate compilation unit (library) the
compiler doesn't know the Tag type used by other compilation units,
so...
- if the processor is seen by its client (the Sender here) through
a functionnal interface, we cannot have virtual template functions!
nor template virtual function, nor function virtual template nor
function template virtual ;-)
OK, now stop and thanks again for the readers.
The subject of this post is not really clear.
Let me try to explain:
I often have this kind of problem:
In a system, a block process some data.
The data are sent by a Sender.
They are asynchronously process by a Processor.
The result of the treatments are sent to a Receiver.
Sender
|
| data
|
\/
Processor ------ results -----> Receiver
The receiver receive results as soon as they are available, and let
say it must save them, and save some informations about the data used
to produce the result, an id for instance.
In a previous development, we simply had:
class Data
{
int id_;
public:
int GetId() const;
}
And:
class Result
{
int id_;
public:
int SetId(int);
}
And so, somewhere, the Processor was doing:
Processor:rocessData(const Data& d)
{
Result res;
res.SetId(d.GetId());
// process data...
}
It was a direct approach, but we finally saw the limitations, mainly:
1) if an int is not enough to tag the data, everything is impacted:
the Data, the sender that produce data, the processor that copy the
part of data needed to tag it, the result and the receiver...
2) a developer can use the id (or tag) to deduce some information used
in the processor. The deduction is true one day, but false latter (to
take a sample, we can deduce from a begin and a end iterator the
element count in a container, by "substracting" them: it may be true
with a container type, and false with another container type).
OK. So the issue is: the processor must do a thing (copy a tag) on
something (the data to the result) without knowing about the data more
than what it needs.
Here it needs to know how to copy, and what to copy, from data to
result.
This is typically the usage, IMHO, of an interface.
So the processor, instead of copying an int get with GetId would get a
ITag pointer. It wouldn't know anything about the concrete class
implementing the tag: it can be an int, or something more complex.
And the processor would call ITag::clone or copy...
I intentionally don't try to code something in this way.
My question is: I think this is a common design issue. Is there a
pattern, a guideline, or some best practices about that?
Something I don't like about the ITag interface is: either the Data
class inherits or aggregates it, nothing force the sender to pass an
object with a tag. Ok, the processor won't compile when trying to
access ITag::clone, but the sender code maybe in another compilation
unit, on another project, and the developer is not aware of the
problem.
We could document, but we know that a programming design is better
than an unread documentation to enforce a practice. To say like Herb
Sutter:
"it would be nice to make compilers reject such code instead of just
making tut-tut noises in standardish legalese."
Thank you if you read until here, and thanks in advance for answer (I
hope).
Finally, again one word: I imagined a way to do that, but it has his
problems:
we could enforce to link a data to a tag by declaring a processor
entry point like that:
Processor:rocessData(CTaggedObject<T, Data> data);
where CTaggedObject is
template <typename Tag, typenam Obj> CTaggedObject
{
Tag tag_;
Obj Obj_;
// constrcutor with tag and object and default values...
const Tag& GetTag() const;
const Obj& GetObject() const;
// and the non const version...
}
So the processor would deal with Data to process data, and it copies
the tag without knowing the Tag type.
The result would be provided as a CTaggedObject<SameTagTypeAsData,
Result>.
What is good:
- The processor doesn't know anything about Tag type (with template
functions it should be possible)
What is not good:
- if the processor is in a separate compilation unit (library) the
compiler doesn't know the Tag type used by other compilation units,
so...
- if the processor is seen by its client (the Sender here) through
a functionnal interface, we cannot have virtual template functions!
nor template virtual function, nor function virtual template nor
function template virtual ;-)
OK, now stop and thanks again for the readers.