HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

More German overengineering™ - Class mappings and factories

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
factoriesmoreoverengineeringandmappingsclassgerman

Problem

Goals:

So the plan was simple: Provide a factory to instantiate implementations of a certain interface (ModelConverter), depending on what model-class you want to convert.

The approach is relatively straightforward:

statically map model-classes to ModelConverter implementations

use that mapping to obtain the implementation class
use reflection to get a constructor and invoke it
return the instance


If any of these steps fail, fail hard, because the application relies on the ModelConverter instances.

My code currently looks as follows (note that I am tied to java 6 so please don't recommend multi-catch):

```
public final class ModelConverterFactory {

private enum ClassMapping {
DEFAULT(Object.class, AbstractConverter.class), RIGHT(Right.class,
RightConverter.class);

private Class modelClass;
private Class converterClass;

private ClassMapping(final Class modelClass, final Class clazz) {
if (modelClass == null || clazz == null) {
throw new BrokenCodeException(
"Cannot create a mapping without modelClass and converterClass");
}

this.modelClass = modelClass;
this.converterClass = clazz;
}

public static ClassMapping fromModelClass(final Class modelClass) {
for (ClassMapping mapping : ClassMapping.values()) {
if (mapping.modelClass.equals(modelClass)) {
return mapping;
}
}
return DEFAULT;
}

public Class getConverterClass() {
return this.converterClass;
}

public Class getModelClass() {
return this.modelClass;
}
}

public static Collection> supportedModelClasses() {
Collection> supported = new HashSet>();
for (ClassMapping mapping : ClassMapping.values()) {
if (mapping != ClassMapping.DEFAULT) {
supporte

Solution

Things will be greatly simplified if ClassMapping didn't map a class to a class, but a class to an instance.

public final class ModelConverterFactory {

    private enum ClassMapping {
        DEFAULT(Object.class, new AbstractConverter()), RIGHT(Right.class, new RightConverter());

        private Class modelClass;
        private ModelConverter converter;

        private  ClassMapping(final Class modelClass, final ModelConverter modelConverter) {
            this.modelClass = modelClass; // got rid of null checks, we control all invocations, none pass null.
            this.converter = modelConverter;
        }

        public static ClassMapping fromModelClass(final Class modelClass) {
            for (ClassMapping mapping : ClassMapping.values()) {
                if (mapping.modelClass.equals(modelClass)) { 
                    return mapping;
                }
            }
            return DEFAULT;
        }

        public ModelConverter getConverter() {
            return this.converter;
        }

        public Class getModelClass() {
            return this.modelClass;
        }
    }

    public static Collection> supportedModelClasses() {
        Collection> supported = new HashSet>();
        for (ClassMapping mapping : ClassMapping.values()) {
            if (mapping != ClassMapping.DEFAULT) {
                supported.add(mapping.getModelClass());
            }
        }
        return supported;
    }

    public static  ModelConverter getConverterFor(final Class clazz) {
        return (ModelConverter) ClassMapping.fromModelClass(clazz).getConverter(); // safe cast : we know all entries will match
    }
}


This gets rid of that nasty introspection bit. Of course this means ModelConverter implementations ought to be reentrant.

I would also be inclined to replace the enum with a Map. You say you use an enum to disallow runtime changing of mappings, but with proper encapsulation that can be perfectly achieved with a Map as well.

Code Snippets

public final class ModelConverterFactory {

    private enum ClassMapping {
        DEFAULT(Object.class, new AbstractConverter()), RIGHT(Right.class, new RightConverter());

        private Class<?> modelClass;
        private ModelConverter<?> converter;

        private <T> ClassMapping(final Class<T> modelClass, final ModelConverter<T> modelConverter) {
            this.modelClass = modelClass; // got rid of null checks, we control all invocations, none pass null.
            this.converter = modelConverter;
        }

        public static ClassMapping fromModelClass(final Class<?> modelClass) {
            for (ClassMapping mapping : ClassMapping.values()) {
                if (mapping.modelClass.equals(modelClass)) { 
                    return mapping;
                }
            }
            return DEFAULT;
        }

        public ModelConverter<?> getConverter() {
            return this.converter;
        }

        public Class<?> getModelClass() {
            return this.modelClass;
        }
    }

    public static Collection<Class<?>> supportedModelClasses() {
        Collection<Class<?>> supported = new HashSet<Class<?>>();
        for (ClassMapping mapping : ClassMapping.values()) {
            if (mapping != ClassMapping.DEFAULT) {
                supported.add(mapping.getModelClass());
            }
        }
        return supported;
    }

    public static <T> ModelConverter<T> getConverterFor(final Class<T> clazz) {
        return (ModelConverter<T>) ClassMapping.fromModelClass(clazz).getConverter(); // safe cast : we know all entries will match
    }
}

Context

StackExchange Code Review Q#57768, answer score: 6

Revisions (0)

No revisions yet.