I’ve simplified down the real code into the smallest example that illustrated the point. Excuse the lack of setters/getters, etc.
Imagine we have a couple of web pages that a customer goes through in sequence. The use case here is:-
- User selects which book they want
- User selects if they want it sent via post or email and associated details
- System fulfils the order
This quesiton is focused on the two ways of delivering. This is modelled as follows:
interface DeliveryDetails
{
// Implementations of this have nothing in common other than that they
// fulfil the same logical role.
}
class EmailDeliveryDetails implements DeliveryDetails
{
String emailAddress; // It really has a constructor and getter, I promise.
}
class PostalDeliveryDetails implements DeliveryDetails
{
String streetAddress;
String Country;
}
Now, to represent the information entered by the user when going through the pages, we have this class:
class PurchaseData
{
String title;
DeliveryDetails deliveryDetails;
}
As the user steps through the web pages, information is stored in an instance of PurchaseData. If the user goes back a page, we can show them what they previously entered. After the user has confirmed and it is time to deliver the book, deliveryDetails references an instance of PostalDeliveryDetails or EmailDeliveryDetails.
On conclusion, when the user confirms their information:
// Some code in a factory
if ( purchaseData.deliveryDetails instanceof EmailDelivery )
{
// construct a EmailDeliveryService( purchaseData, SMTP details, etc ... )
}
if ( purchaseData.deliveryDetails instanceof PostalDelivery )
{
// construct a PostalDeliveryService( purchaseData, etc ... )
}
}
It troubles me that Delivery interface has no methods.
This is forced by the differences between email and postal delivery.
I do not think that DeliveryDetails.deliver() is a good method as this would force implementations to get things like the SMTP server address statically. This confuses concerns (plumbing vs. information entered by a user).
If you have to store something of arbitary type, generics can be useful. It is not possible to use generics (PurchaseData<T extends Delivery>) because the delivery type is not known when the PurchaseData instance is created. Anyway, this would not help in the factory.
Is this empty interface ok? Is there a better way to design this code?
To me the (data) differences between
EmailDeliveryDetailsandPostalDeliveryDetailsboil down to addresses. So my first instinct would be to extract that data into a separate Address class. Then you may decide to have a single Address with optional fields for emailAddress and streetAddress, or a class hierarchy with distinct subclasses for email and postal addresses.I would prefer the single class with optional fields, as it is cleaner to use, and to me practicality trumps conceptual "purity".
Update
Based on the comment chain below:
When there is no overlap between the properties (thus the possible states) of specific classes, it is very awkward to try to handle them polymorphically. And if one doesn’t intend to put much functionality into them either, it is even more difficult to handle them in distinct classes inheriting some common interface (as you too noted). OTOH conceptually all of this is some sort of address data, so it can be handled in one class.
Note that most of this is speculation though – it is difficult to reason about your design without more detailed information.
Ah OK, so you aren’t actually going to treat them polymorphically. You only need a common "handle" to access different bits of data to be persisted. And persistence typically doesn’t care about polymorphism and interfaces anyway. In this case having your classes inherit from an empty interface is fine.