Java中没有switch语句的工厂

Posted

技术标签:

【中文标题】Java中没有switch语句的工厂【英文标题】:Factories in Java without a switch statement 【发布时间】:2014-04-14 09:18:33 【问题描述】:

我正在尝试构建一个工厂对象,但无法找到用 Java 实现它的好方法。

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

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

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

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

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

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

static Dictionary<String, ClassReference>;

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

    ClassReference clas-s-reference;

    clas-s-reference = codeclibrary(codecid);

    return clas-s-reference.instanceOf(args);

ID 的字典很简单,但我不知道 ClassReference 应该是什么。类引用应该允许我创建所需类的实例,如上例所示。

从网上看,类方法和 instanceOf 似乎正朝着正确的方向前进,但我还没有找到将两者结合在一起的任何东西。作为一个额外的复杂因素,正在创建的对象的构造函数将具有参数。

任何关于我应该看什么的提示将不胜感激。

提前致谢。

解决方案

感谢大家的建议。我最终从您的所有建议中汲取了一些零碎的信息,并提出了以下建议,这似乎可以按我的意愿工作。

请注意,我省略了大部分健全性\错误检查代码以展示重要部分。

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;
    

【问题讨论】:

您的not understanding of what ClassReference should be 有何不同,具体取决于您将其作为开关还是字典查找? 听起来你想看看reflection,尤其是Retrieving Class Objects。 Switch 语句意味着我根本不需要担心 ClassReference,因为我可以明确地说如果您请求创建类 FooBar,创建类 Foobar 的实例并返回它。字典意味着我有一个方便的查找,但我需要存储一些东西,然后我可以用它来创建类。 关于反射,我一直认为它是一种在运行时检查类\环境结构或动态修改\创建类的方法。不过,我会阅读该链接,因为它似乎与我的搜索所带的位置有些相似。 【参考方案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();
    

【讨论】:

【参考方案2】:

有无数种选择。例如,您可以创建一个基工厂类,该类还具有管理注册工厂的静态方法(此处键入未经测试的代码,如有错误请见谅):

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&lt;String,Class&lt;? extends CodecInterface&gt;&gt;,使用Class.newInstance() 进行实例化。这实现起来很方便,因为它工作在 Java 的 Class 之上,它已经支持用于实例化对象的工厂样式模型。需要注意的是,就像上面的类必须显式注册一样,并且(与上面不同)您不能在编译时隐式强制执行构造函数参数类型(尽管您至少可以将它抽象到某个方法后面而不是直接调用 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()——这并没有为您提供编解码器名称的灵活性,但它本质上是让类加载器为您完成所有工作,而您不需要需要提前显式注册类型。


编辑:回复:下面的 cmets 中的问题。您可以想出一个系统,将上述两个示例结合起来,创建一个源自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 中使用类似的东西作为后备,以避免必须显式注册类型。

顺便说一句,反射不断出现的原因是它非常灵活,Class 接口本质上是一个包罗万象的类工厂,因此它经常与特定工厂架构试图完成的工作相平行。

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

我不可能为您在此处可以执行的所有操作组合提供示例,因此这里列出了您可用的工具的部分列表,您应该根据需要使用这些工具。记住:工厂是一个概念;您可以自行决定使用您必须使用的工具,以一种符合您要求的干净方式来实现该概念。

反射(Class&lt;?&gt;Class.forName) 静态初始化程序块(有时是注册工厂的好地方;需要加载类,但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 就足以满足您的所有要求,而无需您进行任何这些工作(我还没有实际检查过,所以我不确定)。如果您不需要那种插件“扫描”行为,那么像上面示例这样的简单实现就可以解决问题。

【讨论】:

你已经读懂了我的想法,注册机制是我转向字典来存储课程信息的原因之一。虽然在实现编解码器时有必要扩展工厂类,但据我了解,这意味着我需要为我创建的每个编解码器类创建一个工厂类?我怀疑有必要允许将参数正确传递给编解码器构造函数,但只是想我会确认。 @user3404036 你可以想出一个不需要的系统。例如,如果你结合我上面给出的两个例子,你可以扩展一个CodecFactory,比如使用反射的GenericCodecFactory。只要您提供了一个带有正确参数的newInstance(),您至少可以将反射限制在一次,并且在调用newInstance() 时进行编译时类型检查。查看我的编辑(大约 10 分钟后)。【参考方案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;
    

【讨论】:

这非常接近我前进的方向(或在卡住之前尝试前进)。但是,无论如何使用构造函数传递参数?使用上述实现,我将不得不公开 setArgs 和 setFilename (后者不太重要),这是不可取的。我可以考虑几种可能限制范围的方法(例如使用包级别等),但几乎所有方法都需要显着更改我的设计或使事情更难管理。也许我只是在迂腐:)。 @user3404036 你可以像这样调用特定的构造函数:CodecInterface codec = clazz.getDeclaredConstructor(String.class, String.class).newInstance(args, filename);

以上是关于Java中没有switch语句的工厂的主要内容,如果未能解决你的问题,请参考以下文章

switch 语句是不是适用于工厂方法? C#

三步实现自动注册工厂替代switch语句(c++)

三步实现自动注册工厂替代switch语句(c++)

switch语句用法规则

PHP丨PHP基础知识之条件语SWITCH判断「理论篇」

Java中为啥我写switch语句,在case后加break就错误,不加就正确,很困惑,