显示异构项列表时采用什么设计方法

时间:2011-05-20 11:36:12

标签: java oop design-patterns

让我们想象一下,我想在表格中显示一个库存商品列表(使用Java)。域模型由一个抽象基类StockItem组成,其中包含各种其他类型的库存项。 StockItem提供了一个最小的接口(getId()和getDescription())但除此之外,子类可能有很大的变化。

如果我将自己限制在StockItem上定义的方法,我将无法向用户提供足够的详细信息,因此这意味着某些列将引用不适用于某些行的字段(例如,实体商品)是可数的,这个计数应出现在表格中,而服务项目,也预计会出现在表格中,是不可数的,在这种情况下,应显示“N / A”或类似情况)。

坚持使用“Countable”的例子,在我看来有几个解决方案(但请记住,Countable不会是唯一涉及的接口)。

  1. 使可数接口成为基类的一部分,并强制所有内容都是可数的,即使它们不是。这是无意义的类需要返回一些特殊值或抛出异常或以其他方式表明它们违反了StockItem的合同。

  2. 在我的迭代器中,使用大量的instanceof检查并适当地进行转换。如果我引入StockItem的新子类或以其他方式更改继承树,我将不得不记得更改此代码。

  3. 这两个看起来像是反模式对我来说,我有兴趣听到可能采取的更多优雅方法。我怀疑这没有灵丹妙药,但如果其他语言具有使这种类型的东西更容易的功能,我也有兴趣听到它们(虽然只是出于普遍的兴趣,我不会在任何时候重新实现这个系统很快:)

    谢谢, 菲尔

5 个答案:

答案 0 :(得分:3)

适配器模式

在您的StockItem类中添加方法:

/**
 * 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;
}

这将由所有子类继承,并允许调用者安全地转换类型。 不要被泛型推迟,只是说我希望这个方法返回一个与clazz参数类型相同的实例。

现在你的表提供者可以实现这样的东西:

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";
}

答案 1 :(得分:1)

我从一个表模型类开始,就像一个bean,表中的每一列都有一个字段。可以轻松显示该类的实例。

然后我会介绍一个界面

public interface TableModelAdapter {   // bad name - this is not the adapter pattern...
  public TableModel getTableModel();
}

并使StockItem实现此接口。 StockItem将实现一些默认行为,所有子类都将向表模型中的字段添加内容:

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;
   }
}

答案 2 :(得分:1)

作为一个非常简单的解决方案,您可以添加一个方法:

/**
* Get key/value for all fields in the right order.
*/
public SortedPairRepresentation getPairRepresentation(){
  // For example one might use a SortedSet<Pair<String,String,>> pairs
  // that sort on the key (lexicographically or in a custom order 
  // s.th. IDs and description are always first). Or you just rely
  // on StockItem and its Subclasses to return them in the correct order.
  // It's up to you.
}

我承认这不是表POV中最优雅的版本,但至少它是一个对您的域对象有意义的方法,并且对域对象的其他客户端没有副作用。

如果你不强制执行排序顺序甚至可能更清楚,但是当你必须显示大量元素时,这可能非常方便,因为以排序的方式创建它们,确保在表格中显示它们可以通过简单地完成 每对代表一次。

答案 3 :(得分:1)

我可能会选择某种工厂,可以为特定类型创建渲染器/组件。一个简单的例子,让我们假装一些东西是可数的,而其他东西有一个年龄限制:

class OrderItem {
    long getId(){ ... }
    String getDescription(){ ... }
}
interface Countable {
    int getCount();
}
interface AgeLimited{
    int getMinimumAge();
}

具有默认实现的简单基本渲染器将如下所示(假装您只想将OrderItem呈现为字符串;在真实应用中,您可能会返回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泛型时具有类型安全性,并且非常适合将项类型/渲染器关联与渲染器和域对象分离。

答案 4 :(得分:0)

确实非常有趣的问题。

我对这一个没有直接答案(可能没有),但是让我告诉你我将如何接近它。

我会考虑使用一个表组件,它会根据它显示的内容进行调整(增加/减少可见列)。我会有一个渲染器,编辑器和放大器。扩展StockItem的每个类的列,以及获取它们的getter方法,它们将成为IStockItem接口的一部分。这样渲染器&amp;编辑类将负责表示已经制作的对象。

渲染器和编辑器将负责确保表的字段相应地表示。由于我们希望某些列不适用于渲染器,渲染器和渲染器的特定子类库存项目。编辑器可以假设超出类定义的任何字段都是空白的而且不可编辑。

这种方法可能有点痛苦,特别是在每行的表格中交换渲染器,我认为。