I have a Message
class which can contain multiple types of payloads (or sometimes no payload), each derived from a common Payload
class. However, this becomes problematic because the Message
class wants to know about the Payload
subclasses for various reasons such as:
-
Checking for
Message
equality<code>if (message parts besides the payload are equal) {switch(type) {case Payload::Type::RESPONSE:return *static_cast<ResponsePayload*>(payload.get())== *static_cast<ResponsePayload*>(o.payload.get());break;case Payload::Type::SETUP:return *static_cast<SetupPayload*>(payload.get())== *static_cast<SetupPayload*>(o.payload.get());break;...}}</code><code>if (message parts besides the payload are equal) { switch(type) { case Payload::Type::RESPONSE: return *static_cast<ResponsePayload*>(payload.get()) == *static_cast<ResponsePayload*>(o.payload.get()); break; case Payload::Type::SETUP: return *static_cast<SetupPayload*>(payload.get()) == *static_cast<SetupPayload*>(o.payload.get()); break; ... } } </code>if (message parts besides the payload are equal) { switch(type) { case Payload::Type::RESPONSE: return *static_cast<ResponsePayload*>(payload.get()) == *static_cast<ResponsePayload*>(o.payload.get()); break; case Payload::Type::SETUP: return *static_cast<SetupPayload*>(payload.get()) == *static_cast<SetupPayload*>(o.payload.get()); break; ... } }
-
Deserializing (since the deserialization methods are static because the payloads are immutable)
<code>switch(type) {case Payload::Type::RESPONSE:load = ResponsePayload::fromJSON(payloadValue);break;case Payload::Type::SETUP:load = SetupPayload::fromJSON(payloadValue);break;...case Payload::Type::START:case Payload::Type::STOP:case ...:break; // Load stays nulldefault:THROW(Exception, "Error in program logic: we forgot to parse some payload");}</code><code>switch(type) { case Payload::Type::RESPONSE: load = ResponsePayload::fromJSON(payloadValue); break; case Payload::Type::SETUP: load = SetupPayload::fromJSON(payloadValue); break; ... case Payload::Type::START: case Payload::Type::STOP: case ...: break; // Load stays null default: THROW(Exception, "Error in program logic: we forgot to parse some payload"); } </code>switch(type) { case Payload::Type::RESPONSE: load = ResponsePayload::fromJSON(payloadValue); break; case Payload::Type::SETUP: load = SetupPayload::fromJSON(payloadValue); break; ... case Payload::Type::START: case Payload::Type::STOP: case ...: break; // Load stays null default: THROW(Exception, "Error in program logic: we forgot to parse some payload"); }
-
Ensuring a
Payload
is attached to aMessage
while constructing it:<code>switch(type) {case Payload::Type::RESPONSE:case Payload::Type::SETUP:case ...:ENFORCE(IOException, payload != nullptr, "For this message type, a payload is required.");break;case Payload::Type::START:case Payload::Type::STOP:case ...:ENFORCE(IOException, payload == nullptr, "For this message type, the payload should be null");break;default:THROW(Exception, "Error in program logic: we forgot to handle some payload");}</code><code>switch(type) { case Payload::Type::RESPONSE: case Payload::Type::SETUP: case ...: ENFORCE(IOException, payload != nullptr, "For this message type, a payload is required."); break; case Payload::Type::START: case Payload::Type::STOP: case ...: ENFORCE(IOException, payload == nullptr, "For this message type, the payload should be null"); break; default: THROW(Exception, "Error in program logic: we forgot to handle some payload"); } </code>switch(type) { case Payload::Type::RESPONSE: case Payload::Type::SETUP: case ...: ENFORCE(IOException, payload != nullptr, "For this message type, a payload is required."); break; case Payload::Type::START: case Payload::Type::STOP: case ...: ENFORCE(IOException, payload == nullptr, "For this message type, the payload should be null"); break; default: THROW(Exception, "Error in program logic: we forgot to handle some payload"); }
Alarm bells are going are going off in my head – this violates SOLID like there’s no tomorrow and obviously doesn’t scale well since I have to add case
statements every time I add a new payload. Is there a cleaner approach I could take?
1
Alarm bells should be going off. But some top of my head initial suggestions (meaning I didn’t take the time to come up with the best solution, just a much better one than you’ve got).
1 – Why is there a separate Message and Payload class, aren’t they the same thing?
2 – Include the equality check in the subclasses where they take a payload instance as the parameter. Subclasses only know about their own type. If you pass a payload that isn’t the right type then the type conversion will fail and you know they aren’t equal.
3 – Create a Payload factory instead of creating all the various types in the base message class.
4 – Subclass the Message class if it requires parameters to be built properly.
2