没有switch语句的Java工厂

我正在尝试构建一个工厂对象,但是在使用Java编写一个很好的方法时遇到了麻烦。

我正在编写的应用程序用于处理各种格式的文件,因此有一个CodecInterface适用于所有用于读写文件的类。 我们假设它定义了以下方法。 这些文件中的每一个都具有唯一的人指定ID字符串,用于id’编码器\解码器。

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

工厂类将有一个create方法,用于创建这些编解码器类的实例。 我想方法签名看起来像这样。

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

filename是要读/写的文件的名称,codecid是指示要使用的编解码器的唯一ID。 args参数是传递给正在生成的解码器/编码器对象的参数字符串。 返回的应该是所请求的编解码器对象的实例。

我见过的所有Factory示例通常在create方法中都有一个switch语句,它创建一个依赖于ID的对象实例。 我想避免这样做,因为它似乎不是’正确’的方式,它也意味着除非你修改create方法,否则列表或多或少是固定的。 理想情况下,我想使用像字典(由编解码器ID索引)的东西,其中包含可用于创建我想要的编解码器类的实例的东西(我将称之为神秘类ClassReference)。 再次使用一些准java代码,这就是我所想的创建方法的主体。

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

ID的字典很容易,但我无法弄清楚ClassReference应该是什么。 Class Reference应该允许我创建所需类的实例,如上例所示。

从在线浏览,类方法和instanceOf似乎朝着正确的方向前进,但我没有找到任何将两者放在一起的东西。 作为一个额外的复杂性,正在创建的对象的构造函数将具有参数。

任何有关我应该看的内容的提示都将不胜感激。

提前致谢。

谢谢大家的意见。 我最终从你的所有建议中得到了点点滴滴,并提出了以下似乎按我的意愿工作的内容。

请注意,我已经省略了很多sanity \ error检查代码来显示重要的位。

 import java.lang.reflect.Constructor; import java.util.HashMap; public class CodecFactory { private static HashMap<String, Class> codecs; static { codecs = new HashMap<String, Class>(); //Register built-in codecs here register("codecA", CodecA.class); register("codecB", CodecB.class); register("codecC", CodecC.class); } public static void register(String id, Class codec) { Class 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 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; } } 

试试这样的事情:

 public class CodecFactory { final private static Map> codecLibrary; static { codecLibrary = new HashMap>(); codecLibrary.put("codec1", Codec1.class); //... } static CodecInterface create(String filename, String codecid, String args) throws InstantiationException, IllegalAccessException { Class clazz; clazz = codecLibrary.get(codecid); CodecInterface codec = clazz.newInstance(); codec.setArgs(args); codec.setFilename(filename); return codec; } } 

有很多选择。 例如,您可以创建一个基础工厂类,该类也具有静态方法来管理已注册的工厂(此处输入未经测试的代码,抱歉错误):

 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 factories = new HashMap(); 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."); } } 

然后:

 public class QuantumCodecFactory extends CodecFactory { public QuantumCodecFactory { super("quantum"); } @Override public Codec newInstance (String filename, String args) { return new QuantumCodec(filename, args); } } 

当然这意味着在某些时候你必须:

 CodecFactory.registerFactory(new QuantumCodecFactory()); 

那么用法是:

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

另一种选择是使用reflection并维护一个Map> Map> ,使用Class.newInstance()实例化。 这很容易实现,因为它在Java的Class ,它已经支持用于实例化对象的工厂样式模型。 注意事项,如上所述必须显式注册类,并且(不同于上面)您不能在编译时隐式强制执行构造函数参数类型(尽管您至少可以在某些方法后面抽象而不是调用Class.newInstance()直接来自客户端代码)。

例如:

 public final class CodecFactory { private static final Map> classes = new HashMap>(); public static void registerClass (String name, Class clz) { classes.put(name, clz); } public static Codec newInstance (String filename, String codec, String args) { Class clz = classes.get(codec); if (clz != null) return clz.getDeclaredConstructor(String.class, String.class).newInstance(filename, args); else throw new IllegalArgumentException("No such codec."); } } 

期望每个Codec都有一个构造函数(String filename, String args) 。 然后,注册是:

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

用法与上述相同:

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

您甚至可以省略地图并使用Class.forName() – 这并不能为您提供编解码器名称的灵活性,但它实际上允许类加载器为您完成所有工作而您不需要明确地提前注册类型。


编辑:Re:下面的评论中的问题。 您可以想出一个系统,它结合上面两个示例来创建一个从CodecFactory派生的可重用,基于reflection的通用工厂,这仍然使您能够创建其他更专业的工厂,例如:

 public class GenericCodecFactory extends CodecFactory { private final String name; private final Class clz; public GenericCodecFactory (String name, String clzname) { this.name = name; this.clz = Class.forName(clzname); } public GenericCodecFactory (String name, Class 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); } } 

然后你可以用它来做任何事情:

 // 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")); 

如您所见,有很多可能性。 在基于reflection的工厂中使用Class.forName()很好,因为该类不需要在编译时出现; 所以你可以在类路径上放入编解码器类,比如在运行时配置文件中指定类名列表(然后你可以使用静态ClassFactory.registerFactoriesListedInFile(String confgFilename)或者其他东西),或扫描一个“插件” “ 目录。 你甚至可以用更简单的字符串构造类名,如果你对它感到满意,例如:

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

如果找不到编解码器名称,您甚至可以使用类似的东西作为ClassFactory的后备,以避免必须显式注册类型。

顺便说一下,reflection不断涌现的原因在于它非常灵活,而且Class接口本质上是一个包罗万象的类工厂,所以它经常与特定工厂架构试图完成的工作相媲美。

另一种选择是使用我上面提到的第二个例子(使用Map ),但是创建一个registerFactory版本,它采用String类名而不是Class ,类似于我刚才提到的generics实现。 这可能是避免必须创建CodecFactory实例所需的最少代码量。

我不可能举例说明你可以在这里做的各种事情的组合,所以这里有你可用的工具的部分清单,你应该根据自己的需要使用它们。 记住: 工厂是一个概念; 您可以使用所需的工具以清洁的方式实现该概念,以满足您的要求。

  • reflection( ClassClass.forName
  • 静态初始化程序块(有时是注册工厂的好地方;需要加载类,但Class.forName可以触发它)。
  • 外部配置文件
  • 插件框架,如http://jpf.sourceforge.net/或https://code.google.com/p/jspf/或https://code.google.com/p/jin-plugin/(OSGi的良好比较) ,JPF,JSPF可以在这里找到;我在查看链接中的答案之前从未听说过jin-plugin)。
  • 已注册工厂的地图和/或使用reflection来动态生成类名的能力。
  • 如有必要,请不要忘记并发映射和/或同步原语以获得multithreading支持。
  • 很多其他的东西。

另外:如果你不需要,不要疯狂地实施所有这些可能性; 考虑您的要求并决定您在这里完成的最少工作量。 例如,如果你需要可扩展的插件,单独的JSPF可能足以满足你的所有要求,而你不必做任何这项工作(我实际上没有检查过,所以我不确定)。 如果您不需要这种插件“扫描”行为,那么像上面的示例这样的简单实现就可以解决问题。