没有switch语句的Java工厂

时间:2014-03-11 00:41:54

标签: java factory

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

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

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

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

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

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

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

static Dictionary<String, ClassReference>;

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<? 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;
    }
}

3 个答案:

答案 0 :(得分:10)

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

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.");
    }

}

然后:

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

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

例如:

Class.newInstance()

预计每个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."); } } 都有一个带Codec的构造函数。然后,注册是:

(String filename, String args)

用法与上述相同:

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

你甚至可以省略地图并使用Codec codec = CodecFactory.newInstance(filename, "quantum", args); - 这对编解码器名称没有多大的灵活性,但它实际上让类加载器为你完成所有的工作而你不要我需要提前明确注册类型。


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

CodecFactory

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

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);
    } 

}

如您所见,有很多可能性。在基于反射的工厂中使用// 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")); 很好,因为类不需要在编译时出现;所以你可以在类路径上放入编解码器类,比如在运行时配置文件中指定一个类名列表(然后你可以有静态Class.forName()或者什么),或扫描一个“插件”目录。你甚至可以用简单的字符串构造类名,如果你对它感到满意,例如:

ClassFactory.registerFactoriesListedInFile(String confgFilename)

如果找不到编解码器名称,您甚至可以使用类似的东西作为public class GenericPackageCodecFactory extends GenericCodecFactory { public GenericPackageCodecFactory (String name) { super(name, "com.mystuff." + name + ".Codec"); } } 中的后备,以避免必须显式注册类型。

顺便说一句,反射不断涌现的原因在于它非常灵活,ClassFactory界面本质上是一个无所不包的类工厂,所以它经常与特定工厂架构试图完成的工作相媲美。 / p>

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

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

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

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

答案 1 :(得分:3)

尝试类似的东西:

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;
    }
}

答案 2 :(得分:1)

您也可以使用enum,如下所示:

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();
    }
}