Java generic fields
generics, java
Solution
Well the problem is that you need to retain your inheritance as generic type information.
Basically you would have to do something like:
class PlayerCharacter<P extends PlayerCharacter<P>> {
List<Trait<? super P>> myTraits;
}
class VampirePlayer extends PlayerCharacter<VampirePlayer> {...}
abstract class Trait<P extends PlayerCharacter<P>> {
abstract void apply(P player);
}
class FangLengthTrait extends Trait<VampirePlayer> {...}
It begins to get very clunky, though. You can somewhat improve the situation by approaching from composition:
class Attributes {}
class Base extends Attributes {
int strength;
}
class Vampire extends Base {
int fangLength;
}
class Player<A extends Attributes> {
final A attributes;
final List<Trait<? super A>> traits = new ArrayList<>();
Player(A attributes) {
this.attributes = attributes;
}
void applyTraits() {
for(Trait<? super A> t : traits)
t.apply(this);
}
}
interface Trait<A extends Attributes> {
void apply(Player<? extends A> player);
}
class StrengthTrait implements Trait<Base> {
@Override
public void apply(Player<? extends Base> player) {
player.attributes.strength = 1000;
}
}
class FangLengthTrait implements Trait<Vampire> {
@Override
public void apply(Player<? extends Vampire> player) {
player.attributes.fangLength = 100;
}
}
final class Factory {
private Factory() {}
public static Player<Base> newPlayer() {
return new Player<Base>(new Base());
}
public static Player<Vampire> newVampire() {
return new Player<Vampire>(new Vampire());
}
}
I still find it clunky, personally. If you are mainly just using these `Trait`s to construct objects you might think about using a builder or factory so you don't need to use generics.
Problem
I have a PlayerCharacter class. PlayerCharacter can be extended (for example, VampirePlayerCharacter vs WerewolfPlayerCharacter) I have a Trait class. Trait can be extended (for example, Generation or Gnosis). `PlayerCharacter` has a method, `#withTrait(Trait)`, which adds the Trait to a collection. `PlayerCharacter` has a method, `#applyAllTraits()` which loops through the collection and applies each of them to the character. A `VampirePlayerCharacter` should be able to be given any `Trait` that could apply to a `PlayerCharacter`, as well as any `Trait` that could only apply to a `VampirePlayerCharacter`. So I added a generic type, making `Trait<PC extends PlayerCharacter>` Thus, there can be `BasicTrait<PlayerCharacter>` and `Generation<VampirePlayerCharacter>` My conundrum: If PlayerCharacter's collection of traits is `Collection<Trait<PlayerCharacter>>`, then `VampirePlayerCharacter` can't add a `Trait<VampirePlayerCharacter>` to the collection. If `PlayerCharacter`'s collection of traits is `Collection<Trait<? extends PlayerCharacter>>`, then `VampirePlayerCharacter` can add a `Trait<VampirePlayerCharacter>` to the collection. However, `PlayerCharacter` can no longer loop through the traits, because their type is indeterminate (it could be `Trait<PlayerCharacter>` or a `Trait<VampirePlayerCharacter>` or a `Trait<WerewolfPlayerCharacter>` or...) If `PlayerCharacter`'s collection of traits is `Collection<Trait<? super PlayerCharacter>>`, then `VampirePlayerCharacter` can't add a `Trait<VampirePlayerCharacter>`, because `VampirePlayerCharacter` isn't a supertype of `PlayerCharacter` I'm about a hair's-breadth from saying that more specialized traits just have to use a cast in their apply method and if you set things up inappropriately, they'll explode- but I'm certain that this is not a novel problem, and I just can't wrap my head around the solution. ``` class PlayerCharacter { private int str; List<Trait<?>> traits = new ArrayList<>(); PlayerCharacter withStrength(int str) { this.str = str; return this; } PlayerCharacter withTrait(Trait trait) { this.traits.add(trait); return this; } void applyTraits() { traits.forEach((Trait<?> t) -> t.apply(this)); } } class VampirePlayerCharacter extends PlayerCharacter { private int fangLength; VampirePlayerCharacter withFangLength(int fangLength) { this.fangLength = fangLength; return this; } } abstract class Trait<PC extends PlayerChracter> { void apply(PC pc); } class StrengthTrait extends Trait<PlayerCharacter> { private int str; StrengthTrait(int str) { this.str = str; } void apply(PlayerCharacter pc) { pc.withStrength(str); } } class FangLengthTrait extends Trait<VampirePlayerCharacter> { private int fangLength; FangLengthTrait(int fangLength) { this.fangLength = fangLength; } void apply(VampirePlayerCharacter pc) { pc.withFangLength(fangLength); } } ```