jxk22
jxk22

Reputation: 31

Multiple Generics in a method

My main question is if I can bind a generic object two 2 types if the it is extended by e.g. 4 types. I do not really know how to ask this question without an example.

So i created a little example of a simple game in which Warriors or Rangers can equip different type of Weapons(OneHanded, TwoHanded, Melee, Ranged). Every Weapon has two attributes. So the e.g. the Weapon type Dagger extends Weapon implements OneHanded, Melee.

Ranger class (can use two-handed, ranged Weapons):

    private Weapon weapon;

    public <T extends TwoHanded & Ranged> void equip(T weapon) {
        this.weapon = (Weapon) weapon;
    }

Warrior class (can use one-handed, two-handed, melee, ranged weapons):

    private Weapon weapon;
    public <T extends OneHanded & Melee & Ranged> void equip(T weapon) { //probably have to do this differently
            this.weapon = (Weapon) weapon;
    }

Bow and Dagger class:

public class Bow extends Weapon implements TwoHanded, Ranged {}
public class Dagger extends Weapon implements OneHanded, Melee {}

public void equipTest() {
    ranger.equip(bow);    //works fine
    warrior.equip(dagger); //does not work
}

The main problem here is (I think) that I do not know how to implement it that a warrior can equip different weapons with different attributes(e.g. bow(ranged, two-handed) or also dagger(melee, one-handed)) whereas the ranger has only one possibility. How can I workaround this problem?

Upvotes: 2

Views: 124

Answers (2)

jrook
jrook

Reputation: 3519

The other answer directly addresses the issue in the question. As you can see, using this design, the best option for you is to end up with a bunch of equipXXX() methods.

An alternative would be to use the decorator pattern.

Create abstract Weapon and WeaponDecorator to allow maximum flexibility for later adding new weapon types.

public abstract class Weapon {
...
}

public abstract class WeaponDecorator extends Weapon {
    Weapon _weapon;
    WeaponDecorator(Weapon weapon) {this._weapon = weapon;}
}

Convert various weapon types to act as weapon decorators:

public class OneHanded extends WeaponDecorator {
    OneHanded(Weapon weapon) {
        super(weapon);
    }
}

public class Melee extends WeaponDecorator {
    Melee(Weapon weapon) {
        super(weapon);
    }
}

and remove all generics from the Warrior class too:

public class Warrior  {
    private Weapon weapon;
    public void equip(Weapon weapon) {
        this.weapon = weapon;
    }
}

Now you can simply do:

Weapon w = new OneHanded(new Melee(new Dagger()));
Warrior warrior = new Warrior();
warrior.equip(w);

Here is a full example with code and more explanation.

EDIT:

If you choose this solution, the responsibility of checking the validity of selected weapon for a selected hero should also be addressed in run time. For example, this can be added to Ranger's equip method:

if(weapon.isMelee()) //error (unacceptable)

But as the set of rules grow complex, you might want to use other patterns such as the command pattern. Still, all of this will be delegated to run time. This is the price that you pay for acquiring more flexibility. Of course, you can also try to acquire some compile-time safety by creating a hierarchy of decorators (similar to what java.io library does). However, this could make the application overly complicated very fast.

In the end, if there are only a few of this combined types (TwoHanded + Melee, OneHanded + Ranged, etc) it makes sense to go with the other answer and just have a few more equip methods and have the safety of compile time type checking.

Upvotes: 0

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147154

The code does not compile because Dagger does not implement Ranged.

I think you mean Melee or Ranged. This could be written as overloads.

<T extends Melee & OneHanded> void equip(T weapon) {
<T extends Ranged & OneHanded> void equip(T weapon) {

Note change of order in order for the overload to have distinct erasures. However, it is much better to have distinct names rather than overload.

(Also, I'd use a layer of indirection instead of losing the type information with a base type.)

Upvotes: 1

Related Questions