Context: design with correlated polymorphic classes
For example, let's consider the following class definitions for representing messages that we would get from some remote service:
class ImageMessage : IMediaMessage
class VideoMessage : IMediaMessage
class DocumentMessage : IMediaMessage
The messages would arrive in a high level encoding such as for example JSON and it looks like this:
{
type: 'image|video|document',
...
}
Then depending on the type, we would use a factory to create an instance of the message of the right concrete type.
Now, we're writing a web application and need to display the messages too. The client of the code just knows it is working with a Collection<IMediaMessage>
and needs to be able to delegate to specific views, for example:
class ImageView : IMediaView
class VideoView : IMediaView
class DocumentView : IMediaView
So we end up needing to right yet "another piece of code" to dispatch the message types to their corresponding views. In Javascript this might be done with a switch
and instanceof
checks since the "compiler" (there is not one) cannot pick the right one at runtime.
Problem: maintaining consistency when adding new concrete types
So, in this example, when adding a new media message type, the developer would have to remember to do two things:
- Update the factory for the new media message type, so it can be serialized to a new concrete type (and make this concrete type)
- Update and create a view to bind the new media message for display
Depending on what else happens, there many be other things that switch on but this is a pretty typical case.
How could the situation be improved ?
The questions then is:
- How do you communicate clearly to a new programmer that this is the process to add a new media message? Package ordering? Documentation?
Is there a way to redesign this better so that it's more obvious without documentation?
I was thinking of something like:
createConcreteAndViewFor(mediaMessage) { return { view: ImageView, concrete: ImageMessage } }
but this then fuses two concepts together, since it is possible to use the concrete type for other things, for example perhaps a generic
Automated tests -- can they help? In these examples, scanning a) the type(s) extending and b) the types available in the media message type enum, one could assert that it is implemented at least once in each of the relevant classes... have the function throw if it cannot handle a new input that was created.
Important remark: This question is about how to solve dispatching and maintaining structures of polymorphic code in dynamic languages -- not about high level overview of dynamic languages!