从字符串中启动子类对象

时间:2015-08-19 03:40:01

标签: java design-patterns

考虑一个多人格斗游戏,客户通过向服务器发送包含客户刚刚选择的战斗机名称的字符串来选择战斗机。

考虑这个"战士"服务器中的类结构:

Fighter
   Foo extends Fighter
   Bar extends Fighter
   Qux extends Fighter

例如,客户端发送了此"Bar"字符串,如何根据此字符串启动子类?

我这样做的方式是enum,如此:

public enum Champion {
    Foo {
        @Override
        public Fighter getNew() {
            return new Foo();
        }
    },

    Bar {
        @Override
        public Fighter getNew() {
            return new Bar();
        }
    },

    Qux {
        @Override
        public Fighter getNew() {
            return new Qux();
        }
    };

    public abstract Fighter getNew();

    public static boolean contains(String fighter) {
        for (Champion c : Champion.values()) {
            if (c.name().equals(fighter)) {
                return true;
            }   
        }
        return false;
    }
}

然后我得到一个像这样的对象:

public Fighter getFighter(String fighterName) {
    if(Champion.contains(fighterName)) {
        return Champion.valueOf(fighterName).getNew();
    } else { 
       return null; 
    } // For example sake only :)
}

这是处理这次遭遇的正确方法吗?有没有更好的办法?我记得有一种已知的模式来处理这种遭遇,但不记得究竟是哪种 如果您有任何想法,请告诉我们!

4 个答案:

答案 0 :(得分:3)

使用工厂,解决方案可能如下所示:

interface FighterFactory {
    Fighter create();
}

在立面中保留Map<String, FighterFactory>变量。每个Fighter类型都必须在此地图中注册其工厂。

private Map<String, FighterFactory> fighters;

public void register(String name, FighterFactory factory) {
  fighters.put(name, factory);
}

在每个Fighter类型中嵌入内部类Factory,例如

class Foo extends Fighter {

  //code for Foo

  public static class Factory implements FighterFactory {
    public Foo create() {
      return new Foo();
    }
  }
}

// and in your Facade

register("Foo", new Foo.Factory());

要实例化一个新的战斗机,请进入战斗机地图并对其结果执行创建。

FighterFactory factory = fighters.get(nameFromClient);
// null handling, if nameFromClient is not a Fighter type;
return factory.create();

这种设计比反思有一些优势:

  • 很安全
  • create方法可以做的不仅仅是构造函数

并通过单一工厂方法(一种方法处理所有战斗机类型):

  • 如果create方法变得更复杂(例如创建实例的参数更多),那么对代码的更改位于Fighter类而不是工厂方法。

以及enum方法:

  • 使用的唯一Enum功能是迭代其值。保持值的映射将使用Map查找替换Iterating
  • 如何建造一个位于战斗机附近的战斗机而不是冠军的信息
  • 您甚至可以确定FighterFactories是顶级课程,这是枚举内部类工厂无法实现的。

答案 1 :(得分:1)

如果java 8是一个选项,您可以使用MapString, Supplier<Fighter>>

Map<String, Supplier<Fighter>> factories = new HashMap<>();
factories.put("foo", Foo::new);
factories.put("bar", Bar::new);
factories.put("qux", Qux::new);

然后,您可以让一个供应商使用给定的字符串,并要求它创建专门的Fighter实例:

String s = "bar";
Supplier<Fighter> supplier = factories.get(s);
if (supplier != null) {
    Fighter fighter = supplier.get();
    System.out.println(fighter.getClass().getSimpleName()); // Bar
} else {
    // Handle illegal string
}

答案 2 :(得分:0)

我从最简单的方法开始。

public Fighter getFighter(String fighterName) {
  if("Foo".equals(fighterName))
     return new Foo();
  if("Bar".equals(fighterName))
     return new Bar();
  ...
  return null;
}

然后我可能会将一些逻辑移到子类中。

public Fighter getFighter(String fighterName) {
 Fighter result = null;
  if((result = Foo.createFromString(fighterName))!=null)
     return result;
  if((result = Bar.createFromString(fighterName))!=null)
     return result;
  ...
  return null;
}

您可以从这里获得很多指示,您可以将构造界面粘贴到MapList。例如。

public registerFighterType(IFighterConstructor constructor) {
  fighterTypes.add(constructor);
}

public Fighter getFighter(String fighterName) {
  for(IFighterConstructor constructor : fighterTypes) {
    Fighter result = constructor.createFromString(fighterName);
    if(result!=null) return result;
  }
  return null;
} 

答案 3 :(得分:0)

尝试

MyClass c = Class.forName("MyClass").newInstance();

看起来很丑陋,可能需要在那里某处施放,但它应该有用。