优雅的方式创建了大量的类之一

时间:2013-08-13 18:12:25

标签: java oop design-patterns

对于上下文,我正在尝试按照口袋妖怪的方式制作游戏。你获得,训练和打击怪物。

每种怪物都是一个继承自抽象基类的类(所以它们可以有独特的行为),并且希望整个游戏中会有大量不同的物种。例如:

abstract class Monster {
  int hp;
  void attack();
  //Etc.
}

public class FireBreathingDragon extends Monster {
  static String species_name = "Fire Breathing Dragon";
  //Blah
}

因此,当玩家正在探索时,他们会随机地遇到一个地区的怪物。然后游戏需要从居住在该区域的物种列表中随机创建一个怪物。现在让这些代码可以在区域之间重复使用(并且可以很容易地在代码中的其他位置动态创建怪物)我不想将可能性硬编码到区域中。相反,我想我想要一个工厂的东西,根据需要创造一个特定物种的怪物,如:

public class MonsterFactory {
  Monster createMonster(
    String species_name,
    //Possibly other paramters
  );
}

当你有(可能)数十或数百种不同的Monster类时,问题就是以“漂亮”或“优雅”的方式实现createMonster。当然,您可以使用非常长的if-else if-elseswitch语句,但编写和扩展可怕。有一个很好的方法来做到这一点?如果在添加更多怪物时相对容易扩展也会很好。

或者我应该使用一些完全不同的设计?

免责声明:我的java有点生疏,语法可能不完美,对不起。

6 个答案:

答案 0 :(得分:4)

您可以在Monster

中注册所有List实施类
List<Class<? extends Monster>> monsterTypes = new LinkedList<>();
monsterTypes.add(FireBreathingDragon.class);
// more

这不一定是硬编码的。您可以将其外部化为某些XML,Json或其他文件格式。

然后,工厂实例或类可以从随机索引中的列表中选择怪物类型。然后,您可以使用反射来实例化该类型。

答案 1 :(得分:2)

最简单的解决方案是拥有一个数据驱动的怪物类。这意味着你只有一个类(或一个小数字),这个类可以用于各种具有不同属性和能力的怪物。

您可以拥有一个CSV文件,其中包含每个物种以及物种的所有属性和能力。这样,您可以通过在电子表格中添加一行来添加物种。

答案 2 :(得分:1)

此解决方案使用类工厂不带任何形式的反射。为什么在问题的背景下这是重要的(“最优雅的方式”)?从与另一个贡献者的非常有趣的交流:我引用Sun / Oracle的Reflection API Tutorial“反射是强大的,但不应该不加区别地使用。如果可以在不使用反射的情况下执行操作,那么最好避免使用它。”为了证明这一点,Sun / Oracle的作者采用了Java内部极其技术性的原因。我同意他们,但我的主要原因是长期代码维护和工具。什么是反思的主要替代方案?基于注释的自动代码生成。在这个短暂的空间里我不能做那样的事情,但是我可以产生或多或少的结果代码:

public interface Factory<T> {
    T make();
}
public static class BasicMonster {
}
public static class Monster1 extends BasicMonster {
    public static final Factory<Monster1> getFactory() {
        return new Factory<Monster1>() {
            public Monster1 make() { return new Monster1() ;  }
        };
    }
}
public static class Monster2 extends BasicMonster {
    public static final Factory<Monster2> getFactory() {
        return new Factory<Monster2>() {
            public Monster2 make() { return new Monster2() ;  }
        };
    }
}
List<Factory<? extends BasicMonster>> monsterFactories= new ArrayList<Factory<? extends BasicMonster>>();
{
    monsterFactories.add(Monster1.getFactory());
    monsterFactories.add(Monster2.getFactory());
}
...
BasicMonster newMonster= monsterFactories.get(aRandomOne).make() ;

表单static class用于表示不打算作为内部的类。

即使列表monsterFactories是通过反射初始化的,代码中工厂对象的存在也允许比反射构造函数调用更高级别的静态分析。

答案 3 :(得分:0)

您可以将所有类放在特定的包中,然后扫描该目录以查找类文件,加载它们并跟踪扩展Monster的文件。您甚至可以定义一些自定义注释来帮助管理此操作,例如@IgnoreMonster暂时禁用某些内容而无需更改文件的位置。这类似于例如Hibernate扫描源以查找实体映射。

这是一个例子。所有Monster类都放在包dload.monsters中。首先,这是我在这个例子中使用的基类:

package dload.monsters;

public abstract class Monster {

    public abstract String getName ();

}

然后,MonsterFactory扫描dload.monsters包中的所有类(抱歉它有点草率,我在异常处理方面吝啬):

package dload.monsters;
import java.io.*;
import java.net.*;
import java.util.*;

public class MonsterFactory {

    private static final List<Class<? extends Monster>> monsterClasses = new ArrayList<Class<? extends Monster>>();
    private static final Random random = new Random();

    @SuppressWarnings("unchecked") // <- for monsterClasses.add((Class<? extends Monster>)cls);
    public static void loadMonsters () throws Exception {

        // in this example, Monster is in the same package as the monsters. if
        // that is not the case, replace "." with path relative to Monster.
        File folder = new File(Monster.class.getResource(".").toURI());

        for (File f : folder.listFiles()) {
            if (f.getName().endsWith(".class")) {
                String name = f.getName().split("\\.")[0];
                // replace "dload.monsters." below with package monsters are in
                Class<?> cls = ClassLoader.getSystemClassLoader().loadClass("dload.monsters." + name);
                // if Monster is not in same package as monsters, you can remove
                // cls.equals(Monster.class) check. this check makes sure the loaded
                // class extends Monster, but is not the Monster class itself (since
                // its also in that package).
                if (Monster.class.isAssignableFrom(cls) && !cls.equals(Monster.class)) {
                    System.out.println("Found " + cls.getSimpleName());
                    monsterClasses.add((Class<? extends Monster>)cls);
                }
            }
        }

        // at this point all Class's for monsters are in monsterClasses list.

    }

    public static Monster randomMonster () throws Exception {

        // choose a class at random
        int n = random.nextInt(monsterClasses.size());
        Class<? extends Monster> cls = monsterClasses.get(n);

        // instantiate it
        return cls.newInstance();

    }

}

然后,当你想要使用它时:

public static void main (String[] args) throws Exception {

    // load monsters; only need to do this once at startup
    MonsterFactory.loadMonsters();

    // create 10 random monsters
    for (int n = 0; n < 10; ++ n) {
        Monster m = MonsterFactory.randomMonster();
        System.out.println("name is " + m.getName());
    }

}

请注意,您可以随时查看怪物Class的相关注释。

另一个选项,如果已经加载了类(如果它们从未被使用或显式加载则不会被加载)是使用Instrumentation.getAllLoadedClasses()来获取当前加载的所有类的列表,然后扫描通过所有类寻找可分配给Monster的类。

注意:我觉得有一种更简洁的方法来进行实际扫描,我还没有在JAR中测试过。建议欢迎。

所有这一切,如果怪物的行为可以完全由数据定义,我也支持并推荐上述数据驱动方法。

答案 4 :(得分:0)

你应该看一下Cartesian Product Algorithm。它将生成产品的每个组合,然后您可以随机选择一个。

本质上,算法将获取属性数组并创建不同属性的唯一组合,并将它们添加到数组中。然后,您可以在创建敌人时从阵列中随机选择一个键。这样每个敌人都有随机机会拥有任意数量的属性。

答案 5 :(得分:0)

有一个提供怪物的接口或基类。

我以为我会包含这个wiki-bit,“工厂方法模式是一个面向对象的创建设计模式,用于实现工厂的概念,并处理创建对象(产品)的问题,而不指定确切的类将要创建的对象。“

这使您可以独占使用超类方法或接口,而无需了解接口的特定子类型。这很重要,因为您无法拨打new base_monster();

abstract class base_monster  {
  abstract base_monster factory();
}

/// make sure every monster has a name...
//
abstract class Monster extends base_monster { 
   String name; 
   static int object_counter = 0;

    Monster factory() { 
       name = Integer(object_counter).toString(); 
       object_counter();
       return this; 
    }

    /// this class has a useful setter
    void object_counter( int c ) { object_counter++; out.println( object_counter );    }
}

class Griffon extends Monster {
  Monster factory() { return new Griffon(); }
}


class Harpy extends Monster {
  Harpy() { name = "Grizelda WhuttleThut III"; }
  Harpy factory() { return new Harpy(); }
}


class BlackHarpy  extends Harpy {
  BlackHarpy  factory() { super.factory(); return new BlackHarpy(); }
}


// we assume that each class has a default constructor. But, 
// if the array is filled with monsters of different subclasses we
// would have to use reflection or nasty instanceof switches to be
// able to call a (specific) defined constructor.

ArrayList<Monster> monsters = new ArrayList<Monster>();    

monsters.add( new BlackHarpy() );
for( int I = 0; I < ave_monsters_appearing; I++ )
    monsters.add( new Harpy() );
//
// an array of ten harpies and a boss Harpy.

///
// how can this array of monsters be copied into the other array?
// (we want object copies, not reference copies)
///

ArrayList<Monster> local_monsters = new ArrayList<Monster>();    

/// solution: use the factory method
for( Monster m : monsters ) 
   local_monsters.add( m.factory() ); 


。 希望这能解决没有静态方法的问题。