Plants in Java without a switch statement

I am trying to create a factory object, but it has problems developing a good way to do this in Java.

The application that I am writing is used to process files in various formats, so there is a CodecInterface that applies to all classes that are used to read and write files. Suppose he defines the following methods. Each of these files has a unique identification line, designated by a person, which is used to identify the encoder / decoder.

String read(); void write(String data); String getID(); 

The create method will be created in the factory class, which is designed to create instances of these codec classes. I assume that the signature of the method will look something like this.

 static CodecInterface CodecFactory.create(String filename, String codecid, String args); 

The file name is the name of the file to read / write, and the codec is a unique identifier that indicates which codec to use. The args parameter is a string of arguments passed to the decoder / encoder generated object. The return of this should be an instance of the requested codec object.

All factory examples that I saw, as a rule, have a switch statement inside the create method, which creates an instance of the object, depending on the identifier. I want to avoid this, since this does not seem to be the “right” way, and also means that the list is more or less fixed unless you change the creation method. Ideally, I would like to use something like a dictionary (with a codec ID index) that contains something that can be used to instantiate the codec classes that I want (I will call this secret class ClassReference). Using some quasi-java code again, this is what I thought of as the body for the create method.

 static Dictionary<String, ClassReference>; static CodecInterface CodecFactory.create(String filename, String codecid, String args); { ClassReference classreference; classreference = codeclibrary(codecid); return classreference.instanceOf(args); } 

The identifier dictionary is simple enough, but I can’t understand what ClassReference is. The class reference should allow me to instantiate the required class, as in the above example.

From the look on the Internet, the class method and instanceOf seem to be going in the right direction, but I have not found anything that puts them together. As an additional complication, constructors for created objects will have arguments.

Any advice on what I should look at would be greatly appreciated.

Thanks in advance.

Decision

Thank you all for your advice. I ended up buying all your offers and came up with the following, which seems to work the way I wanted.

Please note that I skipped most of the health check / error check code to show important bits.

 import java.lang.reflect.Constructor; import java.util.HashMap; public class CodecFactory { private static HashMap<String, Class<? extends CodecInterface>> codecs; static { codecs = new HashMap<String, Class<? extends CodecInterface>>(); //Register built-in codecs here register("codecA", CodecA.class); register("codecB", CodecB.class); register("codecC", CodecC.class); } public static void register(String id, Class<? extends CodecInterface> codec) { Class<? extends CodecInterface> existing; existing = codecs.get(id); if(existing == null) { codecs.put(id, codec); } else { //Duplicate ID error handling } } public static CodecInterface create(String codecid, String filename, String mode, String arguments) { Class<? extends CodecInterface> codecclass; CodecInterface codec; Constructor constructor; codec = null; codecclass = codecs.get(codecid); if(codecclass != null) { try { constructor = codecclass.getDeclaredConstructor(String.class, String.class, String.class, String.class); codec = (CodecInterface)(constructor.newInstance(codecid, filename, mode, arguments)); } catch(Exception e) { //Error handling for constructor/instantiation } } return codec; } } 
+8
source share
3 answers

Try something like this:

 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; } } 
+3
source

There are a million options. For example, you can create a factory base class that also has static methods for managing registered factories (unverified code printed here, sorry for the 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 that at some point you should:

 CodecFactory.registerFactory(new QuantumCodecFactory()); 

Then use:

 Codec codec = CodecFactory.newInstance(filename, "quantum", args); 

Another option is to use reflection and maintain Map<String,Class<? extends CodecInterface>> Map<String,Class<? extends CodecInterface>> , using Class.newInstance() to instantiate. This is convenient to implement because it runs on top of the Java Class , which already supports a stylish factory model for creating objects. Cautions, as above, classes must be explicitly registered, and also (unlike above) you cannot implicitly use constructor parameter types at compile time (although you could at least abstract it by some method instead of directly calling Class.newInstance() from client code).

For instance:

 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 each Codec must have a constructor that accepts (String filename, String args) . Thus, registration:

 CodecFactory.registerClass("quantum", QuantumCodec.class); 

Usage is the same as above:

 Codec codec = CodecFactory.newInstance(filename, "quantum", args); 

You can even leave the card and simply use Class.forName() - this does not give you such flexibility with codec names, but essentially allows the class loader to do all the work for you, and you do not need to explicitly register the types ahead of time.


Edit: Re: Question in the comments below. You could come up with a system that combines the two above examples to create a universal factory based on reflection factory derived from CodecFactory , which still leaves you the opportunity to create other more specialized factories, for example:

 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 can use this for anything:

 // 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 are many possibilities. Using Class.forName() in reflection-based factories is good because the class should not be present at compile time; therefore, you can put codec classes in the class path and, say, specify a list of class names in the runtime configuration file (then you can have a static ClassFactory.registerFactoriesListedInFile(String confgFilename) or something else) or scan the plugin directory. You can even create class names from simpler strings if you like it, for example:

 public class GenericPackageCodecFactory extends GenericCodecFactory { public GenericPackageCodecFactory (String name) { super(name, "com.mystuff." + name + ".Codec"); } } 

You can even use something like this as a backup in ClassFactory if the codec name is not found, to get ClassFactory to explicitly register types.

The difference seems to continue to arise because it is very flexible and the Class interface is essentially an all-encompassing factory class, so it is often parallel to what specific factory architectures are trying to implement.

Another option is to use the second example mentioned above (with Map<String,Class> ), but make a version of registerFactory that accepts the class name String instead of Class , similar to the general implementation I just mentioned. This is probably the smallest amount of code required to not create instances of CodecFactory s.

I cannot give examples for each combination of things that you can do here, so here is a partial list of the tools available to you that you should use as you wish. Remember: Factories are a concept; You need to use the tools that you must implement in this concept, according to your requirements.

  • Reflection ( Class<?> And Class.forName )
  • Static initializer blocks (sometimes this is a good place to register a factory; requires the class to be loaded, but Class.forName can call this).
  • External configuration files
  • Plugins like http://jpf.sourceforge.net/ or https://code.google.com/p/jspf/ or https://code.google.com/p/jin-plugin/ (good OSGi comparison , JPF, JSPF can be found here ; I have never heard of jin-plugin before looking at the answers in the link).
  • Maps of registered factories and / or the ability to use reflection to generate class names on the fly.
  • Remember, if necessary, parallel cards and / or synchronization primitives for multi-threaded support.
  • Lots of other things.

Also: do not go crazy, performing all these possibilities, if you do not need it; think about your requirements and decide on the minimum amount of work that you must do here to satisfy them. For example, if you need extensible plugins, one JSPF may be enough to satisfy all your requirements without having to do any of these jobs (I have not actually tested it, so I'm not sure). If you don’t need this “scan” plugin behavior, simple implementations like the examples above will do the trick.

+10
source

You can also use enum as enum below:

 interface CodecInterface { } class CodecA implements CodecInterface { } class CodecB implements CodecInterface { } class CodecC implements CodecInterface { } enum CodecType { codecA { public CodecInterface create() { return new CodecA(); } }, codecB { public CodecInterface create() { return new CodecB(); } }, codecC { public CodecInterface create() { return new CodecC(); } }; public CodecInterface create() { return null; } } class CodecFactory { public CodecInterface newInstance(CodecType codecType) { return codecType.create(); } } 
0
source

All Articles