Чистый код - предотвращение явных приведений типов с коллекциями, построенными на общих типах данных - PullRequest
0 голосов
/ 18 мая 2018

Я использую карту для чтения строк из электронной таблицы и сохранения ее содержимого следующим образом:

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 голосов
/ 18 мая 2018

Проблема с enum в том, что вы не можете использовать универсальные типы.Вы можете увидеть больше об этом в Как реализовать enum с помощью дженериков? .

Во-первых, нам нужно создать «enum-like» класс с константами, используя универсальные типы.

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, что и раньше.

И вы можетенайти полный код этого проекта ideone .


Конечно, мы можем отбросить "enum-like" часть aи инициализировал экземпляр Column по необходимости.Все, что нам нужно сделать, это сделать конструктор видимым (по крайней мере, не закрытым), и мы можем создать новые «сопоставления типов столбцов»

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