There a couple of ways to model this. Importantly, you need to understand that the nature of the problem at hand requires some sort of discrimination somewhere. Really, all we can do here is move this discrimination (and validation) around:
Option 1
Using a PayloadFactory
object (much like you have above) where the caller invokes a static create(MessageType, any): Payload
method.
This represents pushing the discrimination as low as possible (hiding it from the caller). Whether the above method includes the validation or you push the validation even further down into something like UDPayload.create(any)
is not entirely relevant. That said, I would try to keep your concrete Payload
objects as clean as possible (so no factory methods).
Pro: This is useful if you expect many callers to this method because you can reuse the discrimination
Con: In the process you lose information. Now the caller will not know the concrete type of the payload returned (i.e. Payload
instead of UDPayload
).
Option 2
We move the discrimination up to the caller and invoke factory methods like: PayloadFactory.createUDPayload(any): UDPayload
and have the factory method do the validation.
This is a little better. Now the caller will know what type of payload they are working with and can act accordingly.
Pro: Now the caller knows the concrete type of Payload
.
Con: It makes the process less portable because the discrimination is not reusable. I know what you are thinking, "What if I just wrap the discrimination in something like Message.create(...)
to make it reusable?". You then run into the same problem as option 1 where you have a Message
but you don't know the concrete type of the Payload
.
Option 3
Instead of handling a Message
holding any Payload
using a single endpoint, we instead move the discrimination to the caller of the caller (which is the sender - so the watch) and move handling to many endpoints.
The watch knows what kind of message it's about to send no? The discrimination has already been done for you. Each endpoint can simply focus on validation and handling.
Pro: Caller knows the concrete type of Payload
. Also maximizes portability because each endpoint could simply invoke the appropriate factory method (e.g. PayloadFactory.createUDPayload(any): UDPayload
).
Con: Not possible unless you control the watch. Requires adding a lot of endpoints/handlers.
Now we just have to ask ourselves some questions. First, what is the utility of the Payload
base-class (and as a corollary the Message
base-class)? Is it useful to know you have a Payload
but not the concrete type? If so, go with option 1 and consider removing the concrete Payload
types altogether. If this endpoint doesn't care what kind of Payload
the Message
is carrying, that is it is not going to be discriminated somewhere deeper in your call stack, don't bother with creating all of the extra noise.
If you handler does care about the concrete type then we should consider 2 or 3. They are about equal in my book. Option 2 means moving the switch
up to your handler (so now the discrimination cannot be reused), while option 3 requires adding a whole slew of endpoints (and is also likely not possible).