I mostly agree with you, but for fun I'll play Devil's Advocate. Explicit interfaces give a single place to look for an explicitly, formally specified contract, telling you what a type is supposed to do. This can be important when you're not the only developer on a project.
Furthermore, these explicit interfaces can be implemented more efficiently than duck typing. A virtual function call has barely more overhead than a normal function call, except that it can't be inlined. Duck typing has substantial overhead. C++-style structural typing (using templates) can generate huge amounts of object file bloat (since each instantiation is independent at the object file level) and doesn't work when you need polymorphism at runtime, not compile time.
Bottom line: I agree that Java-style inheritance and polymorphism can be a PITA and alternatives should be used more often, but it still has its advantages.
Firstly, command classes are decoupled from the medium and protocol. That means you can design the command classes for your own programming convenience, rather than having to design it to match exactly to the specifics of each protocol (which would be impossible, since different protocols may have different bit widths for the same command and field).
When I mention convenience, what I mean is that you can use the maximum bit width you'll ever need for each command's fields.
However, you may still need to have device- or protocol-specific validation code, since each device or protocol imposes its own limits to what values can be in those fields. Unless you don't plan to implement any validation at all.
When it comes to validation, there are several choices:
- Not doing it at all, if you will be doing all of the programming yourself, and if it is a hobby project such that mistakes do not result in damages.
- Validating it eagerly, i.e. in the command class. This may be difficult, since a command class might not know which device or protocol it will be sent to.
- Validating it late, i.e. in the protocol class where the command values are being converted into bytes.
For example, even if a validation rule says that a particular field can only have a value in the range 0 - 100, it doesn't stop you from using a uint32_t
or int32_t
for that field in the command class.
To the second question of having an overloaded method that takes in various built-in number types and append the bytes to an internal byte vector, do notice the caveats.
In my opinion, if you only needs to work with the fundamental integer types, you don't need templates. Instead, you simply provide function overloads for each of the types, and you call the functions with a value of the appropriate type.
void CPacket::addData(uint32_t data) { ... }
void CPacket::addData(int32_t data) { ... }
void CPacket::addData(uint16_t data) { ... }
void CPacket::addData(int16_t data) { ... }
...
Regarding the code inside, there are several choices:
Type punning with union
. This assumes that your code will work exclusively with one byte-endianness, thus not needing to consider the possibility of porting to a different byte-endianness.
union
{
uint32_t value;
uint8_t bytes[4];
} pun = { data };
// after that, add the bytes to the vector one-by-one, according to the byte endianness of the communication.
Explicitly extracting the bytes with endian-agnostic bitwise arithmetic: (see note on casting)
// only if value is unsigned. For signed value, it must first be cast to unsigned
uint8_t byte0 = (uint8_t)value;
uint8_t byte1 = (uint8_t)(value >> 8ul);
uint8_t byte2 = (uint8_t)(value >> 16ul);
uint8_t byte3 = (uint8_t)(value >> 24ul);
Best Answer
Nope. In fact, many of the best systems use both in combination. Containers are worthless if they are not generic- case in point, Java or C#'s containers when those languages were launched.
Indeed, generic programming is virtually identical to OOP, except that it occurs at compilation/interpretation time rather than execution time, which has a large number of advantages, including increased performance, safety, and flexibility.
They are not mutually exclusive, but you should use templates whenever possible to achieve a generic method. Do not ever use inheritance unless you cannot use a template. Inheritance is one of the worst tools that is in the arsenal.
They get absolutely blown out of the water. Some patterns are simply completely worthless to begin with (e.g. Singleton), many others are useless in the face of templates or all possible instances of them are implemented directly by one template, such as Listener.
Don't. Encourage it. Also, I'm just guessing by the names here, but that really just sounds like
std::function<double(args)>
, rather than an actual class.