Jim
Jim

Reputation: 19552

Combination of factory and builder pattern?

I have code such as this:

public class ProcessorBuilder {  
   enum Type {  
       X,  
       Y,
   }  

   private static HashMap<Type, Processor> processors = new HashMap<>();  
   public static void buildProcessors(int arg1, String arg2, CustomObject arg3) {  
      processors.put(Type.X, new ProcessorImpl1(arg1, arg2));  
      processors.put(Type.Y, new ProcessorImpl2(arg3));    
   }  

public Processor getProcessor(Type type) {  
   return processors.get(type);  
}  

Where I have the interface:

public interface Processor {  
   public ArrayList<String> process(ArrayList<CustomObject> input);  
}

Seems that the code is not that clean. I feel that I need somekind of combination of factory and builder pattern.
What am I doing wrong here or how can I improve this?

Upvotes: 2

Views: 1387

Answers (1)

Slimu
Slimu

Reputation: 2371

You can refactor the Processor interface to something like this:

public interface Processor {
    List<String> process(List<?> input);
}

This will allow it to process a list of any type of element and return a list of Strings. Usually it's recommended to return an interface instead of a specific class, especially if you don't use any custom methods which the class may have.

Here is an example of how an implementing class may look:

public class ProcessorImpl1 implements Processor {

public ProcessorImpl1(Integer arg1) {
}

@Override
public List<String> process(List<?> input) {
    return Collections.emptyList();
}
}

The builder class may look like this:

public class ProcessorBuilder {
enum Type {
    X,
    Y,
}

private static final Map<Type, Processor> processors = new EnumMap<>(Type.class);

public static <T extends Processor> void registerProcessor(Type type, Class<? extends T> processorClass,
                                                              Class[] paramsClass, Object... params)
        throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {

        final Constructor<? extends T> constructor = processorClass.getConstructor(paramsClass);
        final Processor processor = constructor.newInstance(params);
        processors.put(type, processor);
}

public static Processor getProcessor(Type type) {
    return processors.get(type);
}

public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
    registerProcessor(Type.X, ProcessorImpl1.class, new Class[]{Integer.class}, 1);
    registerProcessor(Type.Y, ProcessorImpl2.class, new Class[]{Integer.class, Integer.class}, 1, 3);
    Processor p1 = getProcessor(Type.X);
    p1.process(null);
    Processor p2 = getProcessor(Type.Y);
    p2.process(null);
}
}

I've updated the processor map to use an EnumMap since this kind of map has a better performance than HashMap since the keys are enum values. You should always use EnumMap or EnumSet if you use enums as keys (in EnumMap) or values (in EnumSet).

The registerProcessor method is used for constructing new instances of classes which implement the Processor interface using reflection. I will explain each line from it:

final Constructor<? extends T> constructor = processorClass.getConstructor(paramsClass);

The processorClass parameter indicates the class of the object we wish to create. We use it to retrieve a constructor for the class which has the same parameter types (and order) as the elements in the paramsClass array (use Boxed Primitives instead of primites.

final Processor processor = constructor.newInstance(params);

We create a new instance of the required class using the params vararg. The number and type of the parameters must be the same as those expected by the above constructor or else the newInstance call will fail.

After this, we just put the newly created parameter in the map.

Depending on what you wish to do, it may be required to synchronize the registerProcessor and getProcessor methods. If you know for sure that only from your code you register the Processors, you may wish to make the registerParameters package private or make it a static init block.

Here is how a registering call looks:

    registerProcessor(Type.X, ProcessorImpl1.class, new Class[]{Integer.class}, 1);
    registerProcessor(Type.Y, ProcessorImpl2.class, new Class[]{Integer.class, Integer.class}, 1, 3);

Upvotes: 1

Related Questions