3

I have a base class called Enemy and various subclasses that are of type Enemy such as BigEnemy, LazerEnemy, AvoidingEnemy etc.

I have a Formation class whose purpose is to create specialized formations of enemies, such as line, grid, pyramid.

I would like Formation to take in as a parameter what type of Subclass of Enemy I want to create.

Formation f = new Formation("LazerEnemy","triangle", 4); // makes a triangle formation of lazer enmies
Formation f = new Formation("BigEnemy","line", 10); // makes a line of big enemies

Currently I was going to do something like pass a string called enemyType (or it could be just an integer and do switch statements), but since I have so many enemy types I was wondering if there was a neater way to pass the type of the object I want to instantiate that doesn't need to use a switch statement.

Perhaps this has something to do with Factory and this question, but I don't quite understand it.

Thanks

Community
  • 1
  • 1
user3772547
  • 165
  • 10
  • Why can't you pass directly a `new LazerEnemy()` or a `new BigEnemy()`, with `Formation` constructor taking a `Enemy` as first parameter? – Tunaki Jun 10 '16 at 19:01
  • You have three options, 1) use conditional brancing switch or if-else, 2) Use reflection and pass in the class e.g. LazerEnemy.class, 3) have different factory methods e.g. Formation.createBigEnemy("line", 10); – bhspencer Jun 10 '16 at 19:04
  • @Tunaki So are you saying, pass in a subclass object as a parameter to Formation like Formation (new LazerEnemy(), "pyramid", 4) and then Formation's constructor will do what? I have to create multiple new Enemy types. Formation(Enemy e, string ftype, int number) { } . I need to create now multiple enemies. Should I be cloneing e? – user3772547 Jun 10 '16 at 19:05
  • Ha, in that case, in you're under Java 8, I would pass a `Supplier`. Each call to `get()` would return a new instance of the enemy. Then you can use `new Formation(BigEnemy::new, ...)` – Tunaki Jun 10 '16 at 19:10

5 Answers5

4

If I understand correctly, the Formation constructor must be able to create multiple instances of a given type of enemy. Instead of passing the type of enemy as a String or as a class, you should simply give the Formation a way to create the Enemies, i.e. a factory, or supplier of enemies:

public clas Formation<E extends Enemy> {

    // we'll store them in a list
    private List<T> enemies;

    public Formation(Supplier<E> enemySupplier, int count) {
        enemies = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            enemies.add(enemySupplier.get());
        }
    }
}

You can then create a Formation of LazerEnemy this way, if LazerEnemy has a no-arg constructor:

Formation<LazerEnemy> f = new Formation<>(LazerEnemy::new, 10);

Suppose the LazerEnemy constructor needs a strength for its lazer, you would use

int strenth = 5;
Formation<LazerEnemy> f = new Formation<>(() -> new LazerEnemy(strength), 10);

So, in short, let the caller decide and specify how the enemies in the formation must be created, instead of forcing the Formation to know how to create all the kinds of enemies.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Yes, that is a good way to do it: it decouples the logic. A `Formation` has the responsibility to create the formation of enemies. It does not have the responsibility to create the enemies; its goal is to arrange them in a proper formation. – Tunaki Jun 10 '16 at 19:16
  • A couple of questions. At the top, you have Formation . I use generics when I use arraylists n such, but what's happening here? What is E? Second.. Is Supplier a class that I have to make? What does it look like? Does the get method just return a new Enemy Object? third, I've seen the double colon operator before, but I don't know what it means. I'm not using Java 8 right now. Haven't seen the arrow before either. – user3772547 Jun 10 '16 at 19:26
  • E is the type of enemies in the formation. If you don't need it, you can make your class non-generic. But if you need to get enemies out of your formation and know what actual type they have, then your type should be generic. It makes sense: if I understand correctly, a Formation is a collection of enemies, just like a list is. E is the type of enemies contained in the formation. [Supplier](https://docs.oracle.com/javase/8/docs/api/java/util/function/Supplier.html) is an interface of the JDK. You could make another one, but Supplier fits well. – JB Nizet Jun 10 '16 at 19:30
  • If you're not using Java 8, then do. It's been out for a long time now, and there's no reason not to use it. You can do that in Java 7 as well, but then you'll have to use another interface than Supplier, and you'll have to use classes (anonymous inner classes, typically) instead of lambda expressions or method references. Or you could use Kotlin, which is compatible with Java 6 and later, and supports lambda expressions as well. – JB Nizet Jun 10 '16 at 19:32
2

You can pass a type to method as an argument.

Formation f = new Formation(LazerEnemy.class, Shape.LINE, 4)

public formation(Class<? extends Enemy> enemyType, Shape shape, int num) {
    Enemy enemy1 = enemyType.newInstance(); 
    ...
}

You'll need a try though

ezPaint
  • 152
  • 1
  • 8
  • 1
    I was typing this up as you posted it! Thanks :). He would also need loop and some way of collecting the Enemies up. He could make a static class factory in the Enemy.class that returns a enemy or enemies. Also he might have to use the Constructor method of newInstance if he wants parameters fed to the enemies. – Mr00Anderson Jun 10 '16 at 19:25
1

There are lots of ways of fixing this. One of those is using Dependency Inversion principle. I.e. the caller of the new Formation() constructor can create the enemies too:

Formation f1 = new Formation(new LazerEnemy(), "triangle", 4);
Formation f2 = new Formation(new BigEnemy(), "line", 10);

If those enemies have long constructors, then you could use a Dependency Injection framework. Google guice is relatively easy to set up.

Tamas Rev
  • 7,008
  • 5
  • 32
  • 49
0

You should solve by creating an EnemyFactory interface, and then specific instances of EnemyFactories for all of your Enemy types. Then use it like

Formation f = new Formation(LazerEnemyFactory,FormationShape, 4);
ControlAltDel
  • 33,923
  • 10
  • 53
  • 80
  • 1
    This should be a comment not an answer. – bhspencer Jun 10 '16 at 19:04
  • 1
    @bhspencer Why? It answers the question, not comments on the way it was asked. – Krzysztof Krasoń Jun 10 '16 at 19:09
  • I agree and put some work into making your answer more descriptive. I know what your trying to tell him, but there are plenty of Java Factories why not show a close to relevant example like... [implementing-factory-design-pattern-in-java](http://howtodoinjava.com/design-patterns/creational/implementing-factory-design-pattern-in-java/) – Mr00Anderson Jun 10 '16 at 19:11
-1

I would probably go with a factory method.

Class A

Class B extends A

Class C extends A

public static A factory(string)
  if(string.equal("b")) return new B
  if(string.equal("c")) return new C

this pseudocode might be a little rough around the edges, but it should get the point across.

Rewbert
  • 347
  • 1
  • 3
  • 11