Factories in Java without a switch statement
There's a zillion options. For example, you might create a base factory class that also has static methods to manage registered factories (untested code typed here, sorry for errors):
public abstract class CodecFactory {
private final String name;
public CodecFactory (String name) {
this.name = name;
}
public final String getName () {
return name;
}
// Subclasses must implement this.
public abstract Codec newInstance (String filename, String args);
// --- Static factory stuff ---
private static final Map<String,CodecFactory> factories = new HashMap<String,CodecFactory>();
public static void registerFactory (CodecFactory f) {
factories.put(f.getName(), f);
}
public static Codec newInstance (String filename, String codec, String args) {
CodecFactory factory = factories.get(codec);
if (factory != null)
return factory.newInstance(filename, args);
else
throw new IllegalArgumentException("No such codec.");
}
}
Then:
public class QuantumCodecFactory extends CodecFactory {
public QuantumCodecFactory {
super("quantum");
}
@Override public Codec newInstance (String filename, String args) {
return new QuantumCodec(filename, args);
}
}
Of course this means at some point you must:
CodecFactory.registerFactory(new QuantumCodecFactory());
Then usage is:
Codec codec = CodecFactory.newInstance(filename, "quantum", args);
Another option is to use reflection and maintain a Map<String,Class<? extends CodecInterface>>
, using Class.newInstance()
to instantiate. This is convenient to implement because it works on top of Java's Class
, which already supports a factory-style model for instantiating objects. The caveats are, like above the classes must be explicitly registered, and also (unlike above) you can't implicitly enforce constructor parameter types at compile-time (although you could at least abstract it behind some method instead of calling Class.newInstance()
directly from client code).
For example:
public final class CodecFactory {
private static final Map<String,Class<? extends Codec>> classes = new HashMap<String,Class<? extends Codec>>();
public static void registerClass (String name, Class<? extends Codec> clz) {
classes.put(name, clz);
}
public static Codec newInstance (String filename, String codec, String args) {
Class<? extends Codec> clz = classes.get(codec);
if (clz != null)
return clz.getDeclaredConstructor(String.class, String.class).newInstance(filename, args);
else
throw new IllegalArgumentException("No such codec.");
}
}
Where every Codec
is expected to have a constructor that takes (String filename, String args)
. Registration, then, is:
CodecFactory.registerClass("quantum", QuantumCodec.class);
Usage is the same as above:
Codec codec = CodecFactory.newInstance(filename, "quantum", args);
You could even leave out the map and just use Class.forName()
-- this doesn't give you as much flexibility with codec names, but it essentially lets the class loader do all of the work for you and you don't need to explicitly register types ahead of time.
Edit: Re: Question in comments below. You could come up with a system that combined the two above examples to create a reusable, reflection based generic factory derived from CodecFactory
, which still leaves you with the ability to create other more specialized factories, e.g.:
public class GenericCodecFactory extends CodecFactory {
private final String name;
private final Class<? extends Codec> clz;
public GenericCodecFactory (String name, String clzname) {
this.name = name;
this.clz = Class.forName(clzname);
}
public GenericCodecFactory (String name, Class<? extends Codec> clz) {
this.name = name;
this.clz = clz;
}
// parameter type checking provided via calls to this method, reflection
// is abstracted behind it.
@Override public Codec newInstance (String filename, String args) {
return clz.getDeclaredConstructor(String.class, String.class).newInstance(filename, args);
}
}
Then you could use that for whatever:
// you can use specialized factories
ClassFactory.registerFactory(new QuantumCodecFactory());
// you can use the generic factory that requires a class at compile-time
ClassFactory.registerFactory(new GenericCodecFactory("awesome", AwesomeCodec.class));
// you can use the generic factory that doesn't need to have class present at compile-time
ClassFactory.registerFactory(new GenericCodecFactory("ninja", "com.mystuff.codecs.NinjaCodec"));
As you can see, there's a ton of possibilities. Using Class.forName()
in reflection-based factories is nice because the class doesn't need to be present at compile-time; so you can drop in codec classes on the class path and, say, specify a list of class names in a run-time configuration file (then you could have static ClassFactory.registerFactoriesListedInFile(String confgFilename)
or something), or scan a "plugin" directory. You can even construct class names from simpler strings, if you're comfortable with that, e.g.:
public class GenericPackageCodecFactory extends GenericCodecFactory {
public GenericPackageCodecFactory (String name) {
super(name, "com.mystuff." + name + ".Codec");
}
}
You could even use something like that as a fallback in ClassFactory
if the codec name is not found, to get around having to explicitly register types.
The reason reflection keeps popping up, by the way, is that it's very flexible and the Class
interface is essentially an all-encompassing class factory, so it frequently parallels what specific factory architectures are trying to accomplish.
Another option is to use the second example I mentioned above (with the Map<String,Class>
) but make a version of registerFactory
that takes a String
class name instead of a Class
, similar to the generic implementation I just mentioned. That's probably the least amount of code required to avoid having to create instances of CodecFactory
s.
I can't possibly give examples for every combination of things you can do here, so here is a partial list of the tools you have available, which you should use as you see fit. Remember: Factories are a concept; it's up to you to use the tools you have to implement that concept in a clean way that meets your requirements.
- Reflection (
Class<?>
andClass.forName
) - Static initializer blocks (sometimes a good place to register a factory; requires class to be loaded, but
Class.forName
can trigger this). - External configuration files
- Plugin frameworks like http://jpf.sourceforge.net/ or https://code.google.com/p/jspf/ or https://code.google.com/p/jin-plugin/ (good comparison of OSGi, JPF, JSPF can be found here; I've never heard of jin-plugin prior to looking through the answers in the link).
- Maps of registered factories and/or ability to use reflection to generate class names on the fly.
- Don't forget concurrent maps and/or synchronization primitives for multi-threaded support if necessary.
- Lots of other stuff.
Also: Don't go crazy implementing all of these possibilities if you don't have to; think about your requirements and decide on the minimum amount of work you need to do here to meet them. For example, if you need extensible plugins, JSPF alone might be enough to satisfy all of your requirements without you having to do any of this work (I haven't actually checked it out, so I'm not sure). If you don't need that kind of plugin "scanning" behavior, simple implementations like the examples above will do the trick.
Try something like that:
public class CodecFactory {
final private static Map<String, Class<? extends CodecInterface>> codecLibrary;
static {
codecLibrary = new HashMap<String, Class<? extends CodecInterface>>();
codecLibrary.put("codec1", Codec1.class);
//...
}
static CodecInterface create(String filename, String codecid, String args) throws InstantiationException, IllegalAccessException {
Class<? extends CodecInterface> clazz;
clazz = codecLibrary.get(codecid);
CodecInterface codec = clazz.newInstance();
codec.setArgs(args);
codec.setFilename(filename);
return codec;
}
}