Let's imagine I want to display a list of stock items in a table (using Java). The domain model consists of an abstract base class StockItem from which various other types of stock item derive. StockItem provides a minimal interface (getId() and getDescription()) but beyond that, the subclasses might have significant variations.
If I restrict myself to the methods defined on StockItem, I won't be able to present sufficient detail to the user so this means that some columns will refer to fields that are not applicable to some rows (for example, physical goods are countable and this count should appear in the table whereas service items, which are also expected to appear in the table, are not countable, in which case "N/A" or similar should be shown).
To stick with the example of "Countable", it seems to me that there are a couple of solutions (but bear in mind that Countable won't be the only interface involved).
Make the countable interface part of the base class and force everything to be Countable even when they aren't. Classes for which this is meaningless would need to return some special value or throw an exception or otherwise indicate that they are breaking the contract of a StockItem.
In my iterator, use lots of instanceof checks and cast appropriately. If I introduce a new subclass of StockItem or otherwise change the inheritence tree, I'll have to remember to change this code.
Both of these seem like antipatterns to me and I'd be interested to hear about any more elegent approaches that might be taken. I suspect there is no magic bullet to this but if other languages have features that make this type of thing easier, I'd be interested to hear them also (only out of general interest though, I won't be reimplementing this system any time soon:)
Thanks, Phil
Adaptor pattern
In your StockItem class add a the method:
/**
* Adapt the current instance to the type
* denoted by clazz else return null
* ...
*/
public <T> T adapt(Class<T> clazz){
if( clazz.isInstance(this)){
return (T)this;
}
return null;
}
This will be inherited by all sub-classes and will allow a caller to safely convert type. Don't be put off by the generics, it's just saying I want this method to return an instance that's the same type as the clazz parameter.
Now your table provider can implement something like this:
public String getColumnText(Object cell, int columnIndex) {
if (cell instanceof StockItem) {
StockItem item = (StockItem) cell;
switch(columnIndex) {
case ID:
return item.getID;
case DESCRIPTION:
return item.getDescription;
case COUNT:
Countable countableItem = item.adapt(Countable.class);
return countableItem == null ? "N/A" : countableItem.getCount();
}
}
return "N/A";
}
I'd start with a table model class, something like a bean with one field for each column in your table. An instance of that class can be displayed easily.
Then I'd introduce an interface
public interface TableModelAdapter { // bad name - this is not the adapter pattern...
public TableModel getTableModel();
}
and make StockItem
implement this interface. StockItem
will implement some default behaviour, all subclasses will add something to the fields in the table model:
public class Book {
// ..
@Override
public TableModel getTabelModel {
TableModel model = super.getTableModel(); // the initial one with default entries
model.setTitle(this.title); // like: a book can display a title
return model;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With