0

I am relatively new to Java and programming, so I apologize if this question seems stupid. I am creating a battle-game for a Java programming class -- I have a Hero class with some basic methods and a subclass Paladin that extends Hero but with its own unique methods added in. I want to have a Battleground object that passes in ANY Hero class but then check which specific subclass was passed in. How do I determine which of the Hero subclasses were passed in?

public class Hero {

  private String name;
  private int hitPoints;

  public Hero (String name, int hitPoints) {
     this.name = name;
     this.hitPoints = hitPoints;
  }

  public String getName() { return this.name; }

  public int getHitPoints() { return this.hitPoints; }

  public void takeDamage(int amount) { this.hitPoints -= amount; }
}

And here is the Paladin Class

public class Paladin extends Hero {

  public Hero (String name, int hitPoints) {
    super(name, hitPoints);
  }

  public void heal(int amount) {
    this.hitPoints += amount;
  }
}

So in the battleground class, I have a method that attempts (incorrectly) to check if the hero passed in is a Paladin. How would I go about doing this? The if statement is a placeholder psuedo-code just to clarify what I mean.

public class Battleground {

  private Hero player;

  public Battleground (Hero player) {
    this.player = player;
  }

  public void startRound() {
    // HERE!!
    if (player.equals(Paladin)) {
      player.heal();
    }
  }
}
OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
TTT
  • 186
  • 1
  • 11
  • 2
    `player.heal()` won't compile. You need to cast the variable to a `Paladin`. Or add a heal implementation that does nothing in the Hero class (you could even call `takeDamage(-1 * amount)` – OneCricketeer Feb 04 '20 at 22:54
  • 3
    @cricket_007 I disagree with the close vote. There are more than 1 way to differentiate between classes. Instance of is one. Another is having different constructors. A third is having an ENUM or String field called EntityType. – Menelaos Feb 04 '20 at 22:57
  • On the later: Joshua Bloch advises against making your own type system in *Effective Java*. You should use the type system and not try to introduce your own enum/string value based "getType()" system. @MenelaosBakopoulos – markspace Feb 04 '20 at 23:30
  • @markspace indeed. We used to make our own type system a few years back because it was considered faster. I even remember running into it inside the AST Nodes of eclipse. But Java is much faster now a days. – Menelaos Feb 04 '20 at 23:34

4 Answers4

5

Thinking in terms of what your classes are actually modelling, it doesn't make much sense for a battleground to know that a Paladin heals themselves at the start of a round, nor for the battleground to be responsible for making sure the Paladin heals themselves.

A more sensible design would be for the game to inform the hero that the round has started, and let the particular Hero subclass control what that kind of hero does when the round starts. For example:

public class Hero {
    // ...

    public void onRoundStart() {
        // do nothing
    }
}
public class Paladin extends Hero {
    // ...

    @Override
    public void onRoundStart() {
        // your heal method takes an int as its argument
        heal(10);
    }
}
public class Battleground {
    // ...

    public void startRound() {
        // let the particular Hero subclass control what happens
        player.onRoundStart();

        // ...
    }
}

This way you don't need any if statements or instanceof checks, but also the code defining a Paladin's behaviour is in the Paladin class where it sensibly belongs. If you want to change the rules for Paladins later, it will be easier to know which class you need to edit.

This kind of refactoring is called "replace conditional with polymorphism".

kaya3
  • 47,440
  • 4
  • 68
  • 97
2

Using Instanceof is Considered a Code Smell Sometimes

Using instanceof can be considered to be a code smell - which means a bad practice. There is an alternative for you to consider.

Add the heal() method to the Hero class, but leave the implementation blank.

Put only an implementation in the Paladin class. Then, even though heal() will be called on all players, it will only do something inside Paladins.

However... if you still need to detect the class type...

Ways to Detect the class

There are multiple ways to differentiate between classes. Instance of is one. Another is having different constructors. A third is having an ENUM or String field called EntityType.

In your case, I think instanceof or using a special field make the most sense.

Instanceof

if(player instanceof Paladin)

Using a Special Field

Quick Example Hero

 public class Hero {

      private String name;
      private int hitPoints;
      private int HeroType;

     public Hero (String name, int hitPoints) {
          this.name = name;
          this.hitPoints = hitPoints;
          this.heroType = BASIC_HERO;
      }       

      public static int BASIC_HERO = 0;
      public static int PALADIN_HERO = 1;

... }

Quick Example Paladin

public class Paladin extends Hero {

public Paladin(String name, int hitPoints) {
    super(name, hitPoints);
    this.heroType = PALADIN_HERO;
  }

}

Detecting the Type

You would have a method in both classes called getHeroType().

if(hero.getHeroType == Hero.PALADIN_HERO){

}else if(hero.getHeroType == Hero.BASIC_HERO){

}
Menelaos
  • 23,508
  • 18
  • 90
  • 155
2

If you want, you can use to check the class of the object:

if (player instanceof Paladin)

No question, this will work. If you don't have a lot of "special" behaviour and a limited small amount of cases, that can be a reasonable solution. But assuming that your game will end up with a lot of special handling for each subclass of Hero and probably not only in the startRound() method of your Battlefield class, your code will someday be cluttered with these instanceof checks. Same applies, if you use a specific type field within the Hero class.

In that case a better solution might be to relocate the logic into special classes and try to avoid type checks if possible or at least have a well defined place for them, if necessary.

Update: removed faulty demo implementation

Jan Held
  • 634
  • 4
  • 14
  • While this is generally a good answer, the overloaded methods in `BattleGroundHandlers` are not likely to behave the way you expect. The method to invoke is selected at _compile time_ based on the type of the reference to the Hero. For example, if you wrote `Hero h = new Paladin();` and then `battlegroundHandlers.handleStartRound(h)`, the method that takes `Hero` will be invoked, not the one that takes `Paladin`. – dnault Feb 05 '20 at 01:35
  • 1
    You are right, good point, thank you. That's what happens, if you want to present a fast answer. Anyway, regardless of the concrete solution, the thing I wanted to point out is, to try to avoid cluttering the code with a bunch of `instanceof` checks or similar. – Jan Held Feb 05 '20 at 08:20
1

You can always do player.getClass to get actuall class. As for if statements you can use instanceof operator.

So

if (player instanceof Paladin) {
  ((Paladin)player).heal();
}
Antoniossss
  • 31,590
  • 6
  • 57
  • 99