Я бы, вероятно, выбрал какую-то фабрику, которая может создавать средства визуализации / компоненты для определенных типов. Простой пример, давайте представим, что некоторые вещи исчисляются, а другие имеют возрастное ограничение:
class OrderItem {
long getId(){ ... }
String getDescription(){ ... }
}
interface Countable {
int getCount();
}
interface AgeLimited{
int getMinimumAge();
}
Простой базовый рендер с реализацией по умолчанию выглядел бы так (притворяясь, что вы просто хотите визуализировать OrderItem
s как строки; в реальном приложении вы, вероятно, вернете JComponent или что-то еще):
abstract class Renderer<T extends OrderItem> {
private final Class<T> type;
protected Renderer(Class<T> type){
this.type = type;
}
Class<T> getType(){ return type; }
abstract String render(T orderItem);
}
class DefaultRenderer<T extends OrderItem> extends Renderer<T> {
public DefaultRenderer(Class<T> type){ super(type); }
String render(T orderItem){
return orderItem.getId() + " - " + orderItem.getDescription();
}
}
Немного многословно - спасибо, нерафинированные дженерики! - но продлить это легко:
class CountableRenderer<T extends OrderItem & Countable> extends DefaultRenderer<T> {
public CountableRenderer(Class<T> type){ super(type); }
String render(T countableOrderItem){
return String.format("%s (%d pcs)",
super.render(countableOrderItem),
countableOrderItem.getCount());
}
}
class AgeLimitedRenderer<T extends OrderItem & AgeLimited> extends DefaultRenderer<T> {
public AgeLimitedRenderer(Class<T> type){ super(type); }
String render(T ageLimitedOrderItem){
return String.format("%s (age limit: %d)",
super.render(ageLimitedOrderItem),
ageLimitedOrderItem.getMinimumAge());
}
}
Теперь нам нужен какой-то реестр, где мы связываем средства визуализации и типы, которые они отображают:
class RendererRegistry {
private final Map<Class<?>, Renderer<?>> renderers = new HashMap<Class<?>, Renderer<?>>();
<T extends OrderItem> void register(Class<? extends T> componentType, Renderer<T> renderer) {
renderers.put(componentType, renderer);
}
String renderItem(OrderItem item) {
Renderer<?> renderer = renderers.get(item.getClass());
return doRender(renderer, item);
}
private <T extends OrderItem> String doRender(Renderer<T> renderer, OrderItem item) {
return renderer.render(renderer.getType().cast(item));
}
}
Теперь, учитывая некоторые классы домена ...
class Film extends OrderItem implements AgeLimited {
public int getMinimumAge(){return 0;}
}
class AdultFilm extends Film {
public int getMinimumAge(){return 18;}
}
/**
* Non-alcoholic beer, doesn't need an age limit ;)
*/
class Beer extends OrderItem implements Countable {
public int getCount(){return 6;}
}
Мы можем создать реестр следующим образом:
RendererRegistry registry = new RendererRegistry();
registry.register(OrderItem.class, new DefaultRenderer<OrderItem>(OrderItem.class));
Renderer<Film> filmRenderer = new AgeLimitedRenderer<Film>(Film.class);
registry.register(Film.class, filmRenderer);
registry.register(AdultFilm.class, filmRenderer);
registry.register(Beer.class, new CountableRenderer<Beer>(Beer.class));
System.out.println(registry.renderItem(new OrderItem()));
System.out.println(registry.renderItem(new Beer()));
System.out.println(registry.renderItem(new Film()));
System.out.println(registry.renderItem(new AdultFilm()));
Есть много улучшений, которые могли бы пойти сюда (например, вывести подходящие средства визуализации для незарегистрированных классов, объявить средства визуализации с аннотациями к классам домена, некоторую достойную обработку ошибок и т. Д.), Но эта основная идея хорошо сработала для меня в прошлое. Он довольно гибкий и такой же безопасный для типов, как и при работе с обобщениями Java, и он довольно хорош при отделении ассоциаций типа / рендерера элементов от объектов рендерера и домена.