2

有一个 Message 超类,还有各种 Message 子类,如 WeddingMessage、GreetingMessage、FarewellMessage、Birthday Message。

Message 超类有一个构造函数:

public Message(String messageType){
        this.messageType = messageType;
}

消息子类都有不同的构造函数,但是它们都调用超类,在那里它们将 messageType 作为参数传递 例如:

public BirthdayMessage( String name, int age){
    super("birthday");
    System.out.println("Happy birthday " + name + "You are " + age " years old");

public FareWellMessage(String name, String message){
    super("farewell");
    System.out.println(message + " " + name);
}

创建的 messageType 由用户传入的参数确定。例如,如果用户插入“birthday John 12”,那么将使用参数 John 和 12 创建 BirthdayMessage。如果用户输入“Farewell Grace take care”,则使用这些参数创建 FarewellMessage 的实例。

而不是一堆 if/else 语句或 switch case,以类似的形式 -

words[] = userinput.slice(' ');
word1 = words[0];
if (word1 == birthday)
     create new BirthdayMessage(parameters here)
if (word1 == wedding)
    create new weddingMessage(parameters here)

ETC

我如何使用反射来确定要创建哪种类型的 Message 类。我目前的想法是使用 File 类来获取包中包含消息子类的所有文件。然后使用反射来获取它们的每个构造函数参数类型,并查看它们是否与用户输入给出的参数匹配。然后用随机参数创建那些匹配类的实例。完成后,子类将使用其 messageType 调用其超类构造函数。然后我可以检查 messageType 变量是否与用户输入匹配。

因此,如果用户输入“birthday john 23”,我会在包中找到所有构造函数,它们以 String 和 int 作为参数并且具有字段 messageType(继承自 Message)。然后我创建该类的一个实例并检查 messageType 是否 == 到用户输入中的第一个单词(在本例中为生日)。如果是,那么我使用用户提供的参数创建该类的实例。

有没有更好的方法通过反射来做到这一点?

4

3 回答 3

1

不是为每种消息类型创建不同的类而不是使用可以存储在某处的不同格式而不是更简单Map<String,String>吗?

我的意思是

Map<String,String> formats = new HashMap<>();
formats.put("birthday","Happy birthday %s. You are %d years old%n");//name, age
formats.put("farewell","%s %s%n");//message, name       

Object[] data = {"Dany", 5};
System.out.printf(formats.get("birthday"),data);

data = new Object[]{"Ferwell Jack.","We will miss you"};
System.out.printf(formats.get("farewell"),data);

如果您不想在每次格式更改后重新编译代码,您可以将它们存储在文件中并在应用程序启动或需要时加载。
简单的方法是java.util.Properties上课。

您可以创建formats.properties包含以下内容的文件

生日=生日快乐 %s。你已经 %d 岁了%n
告别=%s %s%n

和使用它的代码可能看起来像

Properties formatProp = new Properties();
formatProp.load(new FileReader("formats.properties"));//        

Object[] data = {"Dany", 5};
System.out.printf(formatProp.getProperty("birthday"),data);

data = new Object[]{"Ferwell Jack.","We will miss you"};
System.out.printf(formatProp.getProperty("farewell"),data);
于 2015-01-30T23:36:08.167 回答
1

如果您想走这条路(我讨厌反射,但它有它的用途),请确保将其隔离在工厂类中。我建议查看@Annotations 并使用特定注释标记要扫描的类。

比如:(必须承认,写这个例子真的很有趣)

注解:

@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface GreetingcardInstance {
    public String value();
}

您的 2 个消息类和基类

public abstract class Message {
    private String name;
    public Message(String name) {
        this.name = name; // not used, but to keep it in line with your example
    }
}

@GreetingcardInstance("birthday")
public class BirthdayMessage extends Message {
    public BirthdayMessage(Integer i) {
        super("birthday");
        // this line prints if this works.
        System.out.println("Birthdaymessage created: " +i);
    }
}

@GreetingcardInstance("other")
public class OtherMessage extends Message{
    public OtherMessage(Integer i, Integer j) {
        super("other");
    }
}

以及隐藏令人讨厌的反射代码的工厂

public class CardFactory {
    private final Map<String, Class> messageClasses;
    public CardFactory() {
        // I had all my implementations of Message in the package instances
        Reflections reflections = new Reflections("instances");
        Set<Class<?>> greetingCardAnnotations = reflections.getTypesAnnotatedWith(GreetingcardInstance.class);
        Map<String, Class> result = new HashMap<String, Class>();
        for (Class c : greetingCardAnnotations) {
            if (Message.class.isAssignableFrom(c)) {
                GreetingcardInstance annotation = (GreetingcardInstance) c.getAnnotation(GreetingcardInstance.class);
                result.put(annotation.value(), c);
            }
        }
        messageClasses = result;
    }

    public Message createMessage(String messageType, Object[] arguments) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = messageClasses.get(messageType);
        if (clazz == null) {
            throw new IllegalArgumentException("MessageType: " + messageType + " not supported");
        }
        Class[] argumentTypes = new Class[arguments.length];
        for (int i = 0; i < arguments.length; ++i) {
            argumentTypes[i] = arguments[i].getClass();
        }
        Constructor constructor = clazz.getConstructor(argumentTypes);
        return (Message) constructor.newInstance(arguments);
    }
}

您可以使用springgoogle 的库,也可以手动扫描它们,尽管您会发现这很麻烦。在此示例中,我使用了运行良好的 google 库。

在这个特定的实现中,所有类都存在于同一个包中。我不认为这太糟糕,但可能不适合你。

我也没有处理基本类型,在这种情况下,构造函数采用整数,而不是我最初打算的 int。

解析字符串时,只需将参数解析为 String、INteger 等并将它们作为 Object[] 传递,它们将用作构造函数参数。

public static void main(String[] argv) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
    CardFactory cf = new CardFactory();
    System.out.println(cf.toString());

    cf.createMessage("birthday", new Object[] { new Integer(0) });
}

输出:

Birthdaymessage created: 0
于 2015-01-31T01:18:08.503 回答
0

有很多方法可以做你想做的事。一种方法是学习如何使用像Google Guice这样的注入库。从长远来看,您可能会从中获得最大的收益。另一种选择是学习像Clojure这样的语言 编辑最后添加的 Clojure 示例。

如果您想看一个看起来像 Java 的最小示例,以下类的 main 将向您展示如何做到这一点。基本上,它需要一个String->Classnames(字符串)的映射,并将其转换为String->Class(对象)的映射,然后一个超级简单的构建器方法在映射中查找代码字并构造一个新的实例该类并返回它。

主要构建其中两个并打印它们的输出。例如

I am a bar.
I'm a baz!

这是Java程序。如果更改包,则必须更改textConfig变量中的类名。下面是等效的 Clojure 代码。

package foo;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

public abstract class Foo {

    public abstract String something(); // the abstract method

    static class Bar extends Foo {  // one subclass
        @Override public String something() {
            return "I am a bar.";
        }
    }

    static class Baz extends Foo { // another subclass
        @Override public String something() {
            return "I'm a baz!";
        }
    }

    public static Class loadClass(String classname) {        
        try { // wrapper for Class.forName that doesn't throw checked exception
            return Class.forName(classname);
        } catch (ClassNotFoundException ex) { 
            throw new IllegalArgumentException(ex);
        }
    }

    public static Map<String, Class> buildConfig(Map<String, String> textConfig) {
        // turn {codeword, classname} into {codeword, class} entries
        // java 8 voodoo follows...
        return textConfig.entrySet().stream().collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> loadClass(e.getValue())));
    }

    public static Foo construct(Map<String, Class> config, String codeword) {
        try { // lookup codeword and return new instance of class
            return (Foo)config.get(codeword).newInstance();
        }
        catch(InstantiationException | IllegalAccessException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    public static void main(String[] args) {
        // some configuration, you could hardcode this, or even put the
        // data in annoations if you want to be fancy
        Map<String, String> textConfig = new HashMap<>();
        textConfig.put("codeword-bar", "foo.Foo$Bar");
        textConfig.put("codeword-baz", "foo.Foo$Baz");

        // turn your text config into something more immediately useful
        Map<String, Class> config = buildConfig(textConfig);

        // show that it works.
        System.out.println(construct(config, "codeword-bar").something());
        System.out.println(construct(config, "codeword-baz").something());
    }
}

编辑

上面代码的冗长让我发疯。所以,如果你有兴趣,这里是 Clojure 中的等效代码。

它将两个函数放入带有键的映射中,:bar然后:baz查找、调用它们并打印返回值。

user=> (def config {:bar (fn [] "I am a bar.") :baz (fn [] "I'm a bar!")})
#'user/config
user=> (println ((:bar config)))
I am a bar.
nil
user=> (println ((:baz config)))
I'm a bar!
nil
于 2015-01-31T02:03:43.320 回答