Mr. Hell
Mr. Hell

Reputation: 116

How to avoid type-checking of a subclass?

I have this code:

public abstract class Character {
    public abstract void attack(Character victim);
    public abstract void receiveAttack(Attack attack);
}

public class CharacterA extends Character {
    public void attack(Character victim) {
        if (victim.getClass().equals(this.getClass())
            return;

        victim.receiveAttack(new Attack(BASE_DMG));
    }
}

The idea is that a Character can attack and receiveAttacks to/from other Characters, but it cannot receive an attack that comes from a character of its own class (CharacterA cannot attack another CharacterA but could attack CharacterB or receive an attack from a CharacterB).

I know that checking for the type of an object is a bad smell and is usually caused because of a bad design, so the question is how could I change the design so I don't have to check for the object type?

EDIT

The class is not actually named Character, the name is just to simplify the example.

There are no teams. CharacterA can attack any other Character that is not a CharacterA.

FINAL EDIT

Thanks everyone, I solved it by using the Visitor pattern.

Upvotes: 1

Views: 615

Answers (2)

RaffleBuffle
RaffleBuffle

Reputation: 5455

You could achieve this by adding an overloaded attack method to each subclass that's declared to take an instance of the specific subclass, and making this method a no-op, or printing a warning.

For CharacterA the signature would be

public void attack(CharacterA victim)

However, this will only work if your subclass instances are declared with the specific type, not the base Character type.

class Character
{
    public void attack(Character victim)
    {
        System.out.println(getClass() + " attacking " + victim.getClass());
        victim.receiveAttack(new Attack());
    }

    public void receiveAttack(Attack attack) {}
}

class CharacterA extends Character
{
    public void attack(CharacterA victim)
    {
        System.out.println(getClass() + " cannot attack " + victim.getClass());
    }
}

class CharacterB extends Character
{
    public void attack(CharacterB victim)
    {
        System.out.println(getClass() + " cannot attack " + victim.getClass());
    }
}

class Attack
{
}

public class Test
{
    public static void main(String[] args)
    {
        CharacterA a = new CharacterA();
        CharacterB b = new CharacterB();

        a.attack(a);
        a.attack(b);
        b.attack(a);
        b.attack(b);

        // redeclare a & b to have the base type
        Character c = a;
        Character d = b;

        c.attack(c);
        c.attack(d);
        d.attack(c);
        d.attack(d);
    }
}

Output:

    class CharacterA cannot attack class CharacterA
    class CharacterA attacking class CharacterB
    class CharacterB attacking class CharacterA
    class CharacterB cannot attack class CharacterB
    class CharacterA attacking class CharacterA
    class CharacterA attacking class CharacterB
    class CharacterB attacking class CharacterA
    class CharacterB attacking class CharacterB

Upvotes: 0

BlueOxile
BlueOxile

Reputation: 351

A very simple solution/ work-around could be to have only one class, and every Character Object has a String field named characterTeam or something of the like. For characters on the A team, set that field to "A" or "ATeam". For B Team, set that field to "B" or "BTeam". Then compare their characterTeam Strings in the attack method.

Also, I would advise against having a class named Character as this might interfere with the wrapper class for char, also named Character.

Upvotes: 1

Related Questions