清洁代码 - 避免使用基于通用数据类型构建的集合的显式类型转换

时间:2018-05-18 04:14:50

标签: java generics refactoring reusability

我正在使用Map从电子表格中读取行并按以下方式存储其内容:

public class DocumentRow {

    private Map<Column, DocumentCell<?>> rowContents = new HashMap<>();

    private int rowNum;

    public DocumentCell<?> getValue(Column column) {
        return rowContents.get(column);
    }

    public void setValue(Column column, DocumentCell<?> value) {
        rowContents.put(column, value);
    }

    public DigitalDocument toDomainObject() {
        DomainObject domainObject = new DomainObject();
        domainObject.setTextValue((String) rowContents.get(TEXT_VALUE).getValue());
        domainObject.setNumericValue((int) rowContents.get(NUMERIC_VALUE).getValue());
        domainObject.setDateValue((LocalDate) rowContents.get(DATE_VALUE).getValue());
        return domainObject;
    }
}

public class DocumentCell<T> {
    private T value;
}

public enum Column {
    TEXT_VALUE("Text_Column_Name", DataType.STRING),
    NUMERIC_VALUE("Numeric_Column_Name", DataType.NUMBER),
    DATE_VALUE("Date_Column_Name", DataType.DATE);
}

(为了简洁,我已经省略了一些明显的课程)

行值提供为:

row.setValue(column, new DocumentCell<>(getDateCellValue(spreadSheetCell)));

有没有办法使这个更干净,以便我在构建域对象时不需要这些未经检查的强制转换?或者更好的设计方法?

1 个答案:

答案 0 :(得分:1)

枚举的问题在于您无法使用泛型类型。您可以在How to implement enum with generics?中查看更多相关信息。

首先,我们需要使用泛型类型创建一个“类似枚举”的类。

class Column<T> {
    public static final Column<String> TEXT_VALUE = new Column<>("Text_Column_Name", String.class);
    public static final Column<Number> NUMERIC_VALUE = new Column<>("Numeric_Column_Name", Number.class);
    public static final Column<Date> DATE_VALUE = new Column<>("Date_Column_Name", Date.class);

    String name;
    Class<T> clazz;

    private Column(String name, Class<T> clazz){
        this.name = name;
        this.clazz = clazz;
    }
}

这样,我们可以确保在地图中插入值以使Column的类型与方法匹配:

public <U> void setValue(Column<U> column, DocumentCell<U> value) {
    rowContents.put(column, value);
}

示例:

DocumentRow row = new DocumentRow();
row.setValue(Column.TEXT_VALUE, new DocumentCell<String>("asdf"));
row.setValue(Column.TEXT_VALUE, new DocumentCell<Integer>(4)); //Don't compile, can't set an `Integer` document cell into a `Column.TEXT_VALUE`

现在,我们确信为Column.TEXT_VALUE插入的值将保留String,并且对于每个Column常量都是相同的。

由于我们在插入过程中保证了类型,因此我们可能会变脏并将DocumentCell<?>从地图转换为相同类型的Column

public <U> U getValue(Column<U> column) {
    @SuppressWarnings("unchecked")
    DocumentCell<U> doc = (DocumentCell<U>) rowContents.get(column);
    return doc.getValue(column);
}

结果的一个小例子:

String s = row.getValue(Column.TEXT_VALUE);
Integer i = row.getValue(Column.TEXT_VALUE); //DON'T COMPILE : `row.getValue` will return a value of the type define by `Column`, here a `String`

完整的使用示例:

DocumentRow row = new DocumentRow();
row.setValue(Column.TEXT_VALUE, new DocumentCell<>("asdf"));
row.setValue(Column.NUMERIC_VALUE, new DocumentCell<>(4));
row.setValue(Column.DATE_VALUE, new DocumentCell<>(new Date()));

String s = row.getValue(Column.TEXT_VALUE);
Number i = row.getValue(Column.NUMERIC_VALUE);
Date d = row.getValue(Column.DATE_VALUE);

请注意,在上一个示例中,我没有提供DocumentCell的类型,编译器知道它之前会使用相同的Column参数。

您可以在this ideone项目中找到完整的代码。

当然,我们可以删除“类似枚举”的部分并根据需要初始化Column实例。我们需要做的就是将构造函数设置为可见(至少不是私有),我们可以创建新的“列类型映射”

Column<LocalDateTime> colDate = new Column<>("A new Date", LocalDateTime.class);
row.setValue(colDate , new DocumentCell<>(LocalDateTime.now()));