Question

Lets say I have a Enemy class, and the constructor would look something like:

public Enemy(String name, float width, float height, Vector2 position, 
             float speed, int maxHp, int attackDamage, int defense... etc.){}

This looks bad because the constructor has so many parameters, but when I create an Enemy instance I need to specify all of these things. I also want these attributes in the Enemy class, so that I can iterate through a list of them and get/set these parameters. I was thinking maybe subclassing Enemy into EnemyB, EnemyA, while hardcoding their maxHp, and other specific attributes, but then I'd lose access to their hardcoded attributes if I wanted to iterate through a list of Enemy (consisting of EnemyA's, EnemyB's, and EnemyC's).

I'm just trying to learn how to code cleanly. If it makes a difference, I work in Java/C++/C#. Any point in the right direction is appreciated.

Was it helpful?

Solution

The solution is to bundle up the parameters into composite types. Width and Height are conceptually related - they specify the dimensions of the enemy and will usually be needed together. They could be replaced with a Dimensions type, or perhaps a Rectangle type that also includes the position. On the other hand, it might make more sense to group position and speed into a MovementData type, especially if acceleration later enters the picture. From context I assume maxHp, attackDamage, defense, etc also belong together in a Stats type. So, a revised signature might look something like this:

public Enemy(String name, Dimensions dimensions, MovementData movementData, Stats stats)

The fine details of where to draw the lines will depend on the rest of your code and what data is commonly used together.

OTHER TIPS

You might want to take a look at the Builder pattern. From the link (with an examples of the pattern versus alternatives):

[The] Builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters, especially if most of those parameters are optional. Client code is much easier to read and write with builders than with the traditional telescoping constructor pattern, and builders are much safer than JavaBeans.

Using subclasses to preset some values is not desirable. Only subclass when a new type of enemy has different behavior or new attributes.

The factory pattern is usually used to abstract over the exact class used, but it can also be used to provide a templates for object creation:

class EnemyFactory {

    // each of these methods is essentially a template for a kind of enemy

    Enemy enemyA(String name, ...) {
        return new Enemy(name, ..., presetValue, ...);
    }

    Enemy enemyB(String name, ...) {
        return new Enemy(name, ..., otherValue, ...);
    }

    Enemy enemyC(String name, ...) {
        return new EnemySubclass(name, ..., otherValue, ...);
    }

    ...
}

EnemyFactory factory = new EnemyFactory();
Enemy a = factory.enemyA("fred", ...);
Enemy b = factory.enemyB("willy", ...);

I would reserve sub classing to classes that represent object that you might want to independently use, e.g. character class where all characters, not just enemies have name, speed, maxHp, or a class to represent sprites that have a presence on screen with width, height, position.

I don't see anything inherently wrong with a constructor with a lot of input parameters but if you want to split it up a bit then you could have one constructor that sets up most of the parameters and another (overloaded) constructor that can be used to set specific ones and have others set to default values.

Depending on what language you choose to use, some can set default values for the input parameters of your constructor like:

Enemy(float height = 42, float width = 42);

A code example to add to Rory Hunter's answer (in Java):

public class Enemy{
   private String name;
   private float width;
   ...

   public static class Builder{
       private Enemy instance;

       public Builder(){
           this.instance = new Enemy();
       }


       public Builder withName(String name){
           instance.name = name;
           return this;
       }

       ...

       public Enemy build(){
           return instance;
       }
   }
}

Now, you can create new instances of Enemy like this:

Enemy myEnemy = new Enemy.Builder().withName("John").withX(x).build();
Licensed under: CC-BY-SA with attribution
scroll top