【问题标题】:Creating a factory method in Java that doesn't rely on if-else在 Java 中创建不依赖 if-else 的工厂方法
【发布时间】:2011-03-26 22:56:24
【问题描述】:

目前我有一个方法可以作为基于给定字符串的工厂。 例如:

public Animal createAnimal(String action)
{
    if (action.equals("Meow"))
    {
        return new Cat();
    }
    else if (action.equals("Woof"))
    {
        return new Dog();
    }

    ...
    etc.
}

我想要做的是在类列表增长时避免整个 if-else 问题。 我想我需要有两种方法,一种将字符串注册到类,另一种根据操作的字符串返回类。

在 Java 中有什么好的方法可以做到这一点?

【问题讨论】:

  • 使用 HashMap :)
  • 好的,但是值应该是多少?它可能是等效的对象。但是假设该方法被多次调用,每次调用都需要一个新对象。我需要在返回对象后重新实例化它吗?
  • 您可以拥有一个 HashMap,其中 Callable 返回一个由 String 指示的类型的对象。然后在工厂构造函数中填充地图。
  • Map 已经足够好了。不要去建筑疯狂的家伙。
  • 使用枚举代替您的操作字符串。

标签: java string factory


【解决方案1】:

你所做的可能是最好的方法,直到可以打开字符串。 (2019 年编辑: 可以打开字符串 - 使用它。)

您可以创建工厂对象和从字符串到这些对象的映射。但这在当前的 Java 中确实有点冗长。

private interface AnimalFactory {
    Animal create();
}
private static final Map<String,AnimalFactory> factoryMap =
    Collections.unmodifiableMap(new HashMap<String,AnimalFactory>() {{
        put("Meow", new AnimalFactory() { public Animal create() { return new Cat(); }});
        put("Woof", new AnimalFactory() { public Animal create() { return new Dog(); }});
    }});

public Animal createAnimal(String action) {
    AnimalFactory factory = factoryMap.get(action);
    if (factory == null) {
        throw new EhException();
    }
    return factory.create();
}

在最初编写此答案时,针对 JDK7 的功能可能会使代码如下所示。事实证明,lambda 出现在 Java SE 8 中,据我所知,没有地图文字的计划。 (2016 年编辑

private interface AnimalFactory {
    Animal create();
}
private static final Map<String,AnimalFactory> factoryMap = {
    "Meow" : { -> new Cat() },
    "Woof" : { -> new Dog() },
};

public Animal createAnimal(String action) {
    AnimalFactory factory = factoryMap.get(action);
    if (factory == null) {
        throw EhException();
    }
    return factory.create();
}

2019 年编辑:目前看起来像这样。

import java.util.function.*;
import static java.util.Map.entry;

private static final Map<String,Supplier<Animal>> factoryMap = Map.of(
    "Meow", Cat::new, // Alternatively: () -> new Cat()
    "Woof", Dog::new // Note: No extra comma like arrays.
);

// For more than 10, use Map.ofEntries and Map.entry.
private static final Map<String,Supplier<Animal>> factoryMap2 = Map.ofEntries(
    entry("Meow", Cat::new),
    ...
    entry("Woof", Dog::new) // Note: No extra comma.
);

public Animal createAnimal(String action) {
    Supplier<Animal> factory = factoryMap.get(action);
    if (factory == null) {
        throw EhException();
    }
    return factory.get();
}

如果要添加参数,则需要将Supplier 切换为Factory(并且get 变为apply,这在上下文中也没有意义)。对于两个参数BiFunction。超过两个参数,您又要重新尝试使其可读。

【讨论】:

  • 只使用 Callable 而不是新的 AnimalFactory 接口有什么问题?
  • Callable 抛出。它也相当非主格。
  • 啊,双括号初始化...(您还有 2 个错字:`facotryMap`)
  • "在 JDK7 中,这可能看起来像..." 你应该为 Java 8 更新这个。; )
  • 第二个例子在 Java 8 中也不能编译,所以我建议你删除它。以为我错过了一些花哨的新功能!仅仅因为没有人发表评论并不意味着人们没有看到它 - 它在我为将字符串映射到类所做的谷歌搜索中名列前茅。
【解决方案2】:

使用此解决方案不需要地图。无论如何,地图基本上只是执行 if/else 语句的不同方式。利用一点点反射,只需几行代码就可以解决所有问题。

public static Animal createAnimal(String action)
{
     Animal a = (Animal)Class.forName(action).newInstance();
     return a;
}

您需要将参数从“Woof”和“Meow”更改为“Cat”和“Dog”,但这应该很容易做到。这避免了在某些映射中使用类名对字符串进行任何“注册”,并使您的代码可重复用于您将来可能添加的任何 Animal。

【讨论】:

  • 一般来说,应该避免使用 Class.newInstance()(因为它的异常处理能力很差)。
  • 我在一次采访中被问到这个问题,他们拒绝让我使用反射。我如上所述选择了 HashMap,但想要一个不需要更改源的更动态的解决方案。无法提出解决方案。
【解决方案3】:

如果你没有使用字符串,你可以为动作使用枚举类型,并定义一个抽象工厂方法。

...
public enum Action {
    MEOW {
        @Override
        public Animal getAnimal() {
            return new Cat();
        }
    },

    WOOF {
        @Override
        public Animal getAnimal() {
            return new Dog();
        }
    };

    public abstract Animal getAnimal();
}

然后您可以执行以下操作:

...
Action action = Action.MEOW;
Animal animal = action.getAnimal();
...

这有点时髦,但确实有效。这样,如果你没有为每个动作定义 getAnimal() ,编译器就会发牢骚,而且你不能传入一个不存在的动作。

【讨论】:

  • 很酷的解决方案,我得试试这个。
【解决方案4】:

使用扫描注释!

第 1 步。 创建如下注释:

package animal;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface AniMake {
    String action();
}

请注意,RetentionPolicy 是运行时的,我们将通过反射访问它。

第 2 步。(可选)创建一个通用超类:

package animal;

public abstract class Animal {

    public abstract String greet();

}

第 3 步。 使用新注释创建子类:

package animal;

@AniMake(action="Meow")
public class Cat extends Animal {

    @Override
    public String greet() {
        return "=^meow^=";
    }

}
////////////////////////////////////////////
package animal;

@AniMake(action="Woof")
public class Dog extends Animal {

    @Override
    public String greet() {
        return "*WOOF!*";
    }

}

第 4 步。 创建工厂:

package animal;

import java.util.Set;

import org.reflections.Reflections;

public class AnimalFactory {

    public Animal createAnimal(String action) throws InstantiationException, IllegalAccessException {
        Animal animal = null;
        Reflections reflections = new Reflections("animal");
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(AniMake.class);

        for (Class<?> clazz : annotated) {
            AniMake annoMake = clazz.getAnnotation(AniMake.class);
            if (action.equals(annoMake.action())) {
                animal = (Animal) clazz.newInstance();
            }
        }

        return animal;
    }

    /**
     * @param args
     * @throws IllegalAccessException 
     * @throws InstantiationException 
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        AnimalFactory factory = new AnimalFactory();
        Animal dog = factory.createAnimal("Woof");
        System.out.println(dog.greet());
        Animal cat = factory.createAnimal("Meow");
        System.out.println(cat.greet());
    }

}

这个工厂,可以清理一下,例如处理讨厌的检查异常等。
在这个工厂中,我使用了Reflections 库。
我很难做到这一点,即我没有创建一个 maven 项目,我必须手动添加依赖项。
依赖项是:

如果您跳过了第 2 步,则需要更改工厂方法以返回 Object。
从这一点开始,您可以继续添加子类,只要您使用 AniMake(或您想出的任何更好的名称)注释它们,并将它们放在 Reflections 构造函数中定义的包中(在本例中为“动物”),并保持默认的无参数构造函数可见,然后工厂将为您实例化您的类,而无需自行更改。

这是输出:

log4j:WARN No appenders could be found for logger (org.reflections.Reflections).
log4j:WARN Please initialize the log4j system properly.
*WOOF!*
=^meow^=

【讨论】:

  • 过度设计、缓慢且品味相当差(当然,至少最后一个是主观的)。即使最初的问题更大,需要更复杂的解决方案,应用像 Guice 这样更通用的解决方案也会更可取。
  • @Dimitris,你的概要完全没有根据! 1) 单个可重用的 Annotation 类比每次添加类时都必须编辑映射更可取,如果无论如何要更改它,最好将 if else 结构留在工厂中。您可以消除库并自己进行反射,但我不喜欢重新发明轮子。 2)你需要量化慢......像“反射很慢”这样的陈述与“Java很慢”生活在同一个时代。 3) 即使你使用 Guice,你仍然需要以某种方式将类映射到任意关键字并提供工厂。
  • 虽然反射 比虚拟方法调用慢(好吧,试试吧),我指的是类路径扫描,这可能是异常慢(你确实意识到这必须搜索类路径的所有 jar,并解析其中的所有类的部分字节码 - 额​​外如果 jar 甚至不在本地文件系统中,则点...)
  • 具有讽刺意味的是,这个技巧会产生,只是您使用的依赖项,解压缩和解析约 1200 个类的成本。更不用说引入新的、无声的依赖类路径的错误了。现在将所有这些与此处其他一些答案的简单性、可靠性和效率进行比较。嗯,确实有味道! :P
  • 这个答案是开玩笑吗?我知道如果你有 100 种动物,这可能会有用,但除此之外,它似乎代表了人们抱怨 java 和过度工程的一切。
【解决方案5】:

我还没有尝试过,但可以创建一个 Map 并使用“喵”等作为键 和(比如)Cat.class 作为值。

通过接口提供静态实例生成并调用为

Animal classes.get("Meow").getInstance()

【讨论】:

    【解决方案6】:

    我希望检索字符串的 Enum 表示并打开它。

    【讨论】:

    • 您仍然需要使用相同的 if-else somewhere 从输入字符串中获取枚举,不是吗?那么,这样会更好吗?
    • 神圣的 11 年前。我似乎记得我在想 Enum.valueOf() 是否适合,或者查找值的映射等。自从发布此消息以来,就已经开始启用字符串了。
    • 打开字符串是否比 if-else 类型比较更有效?
    【解决方案7】:

    您已经选择了该问题的答案,但这仍然会有所帮助。

    虽然我是 .NET/C# 开发人员,但这确实是一个普遍的 OOP 问题。我遇到过同样的问题,并且我找到了一个很好的解决方案(我认为)使用 IoC 容器

    如果您还没有使用,那可能是一个很好的开始。我不知道 Java 中的 IoC 容器,但我认为一定有一个具有类似功能的容器。

    我拥有的是一个包含对 IoC 容器的引用的工厂,该引用由容器本身解析(在 BootStrapper 中)

    ...
    public AnimalFactory(IContainer container) 
    { 
        _container = container; 
    }
    

    然后,您可以设置您的 IoC 容器以根据键(示例中的声音)解析正确的类型。它将完全抽象您的工厂需要返回的具体类。

    最后,你的工厂方法被缩小到这个:

    ...
    public Createable CreateAnimal(string action) 
    { 
        return _container.Resolve<Createable>(action); 
    } 
    

    This stackoverflow question 说明了与现实世界元素相同的问题,并且经过验证的答案显示了我的解决方案草稿(伪代码)。 后来我写了a blog post with the real pieces of code,那里更清楚了。

    希望这会有所帮助。但在简单的情况下,这可能是矫枉过正。我使用它是因为我需要解决 3 个级别的依赖关系,并且 IoC 容器已经组装了我的所有组件。

    【讨论】:

      【解决方案8】:

      我的想法是以某种方式将字符串映射到函数。这样您就可以将Meow 传递给映射并返回已经映射出来的构造函数。我不确定如何在 Java 中执行此操作,但快速搜索返回了 this SO thread。不过,其他人可能有更好的主意。

      【讨论】:

        【解决方案9】:

        人们对在 Tom Hawtin 的回答中使用 Class.newInstance() 有何看法? 这将避免我们在内存中存储不必要的匿名类? Plus 代码会更干净。

        它看起来像这样:

        private static final Map<String,Class> factoryMap =
            Collections.unmodifiableMap(new HashMap<String,Class>() {{
                put("Meow", Cat.class);
                put("Woof", Dog.class);
        }});
        
        public Animal createAnimal(String action) {
            return (Animal) factoryMap.get(action).newInstance();
        }
        

        【讨论】:

          【解决方案10】:

          现在您可以使用 Java 8 构造函数引用和函数式接口。

          import java.util.HashMap;
          import java.util.Map;
          import java.util.function.Supplier;
          
          public class AnimalFactory {
              static final Map<String, Supplier<Animal>> constructorRefMap = new HashMap<>();
          
              public static void main(String[] args) {
                  register("Meow", Cat::new);
                  register("Woof", Dog::new);
          
                  Animal a = createAnimal("Meow");
                  System.out.println(a.whatAmI());
              }
          
              public static void register(String action, Supplier<Animal> constructorRef) {
                  constructorRefMap.put(action, constructorRef);
              }
          
              public static Animal createAnimal(String action) {
                  return constructorRefMap.get(action).get();
              }
          }
          
          interface Animal {
              public String whatAmI();
          }
          
          class Dog implements Animal {
              @Override
              public String whatAmI() {
                  return "I'm a dog";
              }
          }
          
          class Cat implements Animal {
              @Override
              public String whatAmI() {
                  return "I'm a cat";
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-08-20
            • 2018-08-18
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多