lundi 25 septembre 2017

Good design for converting a String to different type in Java

I have a class Data that stores a single piece of data in form of a String, it also stores type to which this data should be converted, the type is stored as an enum constant (to allow only for specyfic types). Data objects that describe one item are stored in DataItem class. Intention is that the Data object corresponds to a field in a table and DataItem represents a full row. It is also important to mention that Data objects are created from DataTemplate class which specifies where to look for this kind of data and its type (so the type of each Data should be known at compile time).

I want this program to be very flexible when it comes to database choice so method "save" comes from Storage interface that allows to use any type of storage (file/RDB/Document database...) after implementing it.

I wonder about a good way of converting those String values from Data objects to the appropriate types so I can save them to database. An easy way would be to use something like this:

public void save(DataItem dataItem) {
    for (Data data : dataItem) {
        if (data.getType() == DataType.BOOLEAN) {
            // Convert to String to boolean and save
        }
        else if (data.getType() == DataType.DOUBLE) {
            // Convert to String to double and save
        }
        ...
    }
}

But it's not a very good design as I would have to repeat this code for each implemenation of save. It also violates open/closed principle, because if I add some new types I would have to modify all the save methods.

I have also tried using generics or reflection but none of those implementations was satisfactory.

One generic solution that I came up with would require user to to use one of the provided enum constants but then instead of storing enum constant, Data class would store Class instance of corresponding type. That way I stil control what types can be used and get compile time errors if wrong class is picked. This would allow me to implement converter method that works this way.

public <T> T convert(Data data, Class<T> clazz) {
    if (data.getType() == Boolean.class) {
        // Convert String to Boolean
        return (T) 
    }
    else if (data.getType() == Double.class) {
            // Convert to String to Double
            return (T)
    }
    ...
}

Then I could also use similar pattern and store converting methods in DataType enum alongside allowed data types. Using abstract method that every type would have to specify. Something like:

public enum DataType {
    BOOLEAN(Boolean.class){
        @Override
        public <T> T convert(Data data, Class<T> clazz) {
            return clazz.cast(Boolean.parseBoolean(data.getContent()));
        }
    },
    DOUBLE(Double.class){
        @Override
        public <T> T convert(Data data, Class<T> clazz) {
            return clazz.cast(Double.parseDouble(data.getContent()));
        }
    },
    ...;
    ...
    public abstract <T> T convert(Data data, Class<T> clazz);
}

In that case I would just have to modify the DataType enum when adding a new type, provided that underlying storage has a method accepting all of the allowed types.

So finally, my questions: 1. Is there a better way to do this? 2. If no, which design should I choose?

Aucun commentaire:

Enregistrer un commentaire