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:
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
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
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
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.
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
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).