I have a Composition
object that contains 4 ArrayList
objects, each of which contains objects of a particular subclass of my Abstract
class Role
. The 4 subclasses are Bruiser
, Healer
, Tank
, and DamageDealer
. The point of the program is to analyze two opposing Composition objects as they are being built (players fill their Composition object one Role pick at a time), and based on each of their properties, recommend to the player a particular Hero for a particular Role that would suit the player's Composition.
The values of the Composition
object's properties are incremented accordingly each time a Role
subclass gets added to one of its ArrayList
s, like so (note: "this" corresponds to the Composition
object):
public class Composition {
private ArrayList<Tank> tanks;
private ArrayList<DamageDealer> damageDealers;
private ArrayList<Healer> healers;
private ArrayList<Bruiser> bruisers;
private boolean doubleSupport;
private boolean doubleWarrior;
private boolean heavyDamage;
private boolean lockdownProtection;
private int mobility;
private int waveclear;
private int nuMelee;
private int nuRange;
private int lockdown;
private int burstMitigation;
private int sustainedMitigation;
private int damage;
private int burstDamageRating;
private int sustainedDamageRating;
private int frontlineRating;
private int defensiveRating;
private int offensiveRating;
public Composition() {
this.tanks = new ArrayList<Tank>();
this.damageDealers = new ArrayList<DamageDealer>();
this.healers = new ArrayList<Healer>();
this.bruisers = new ArrayList<Bruiser>();
}
private void addMutualContribution(Role r) {
this.mobility += r.getMobility();
this.defensiveRating += r.getPeeling();
this.waveclear += r.getWaveclear();
this.burstMitigation += r.getBurstMitigaion();
this.sustainedMitigation += r.getSustainedMitigation();
}
public void addBruiserContribution(Bruiser b) {
addMutualContribution(b);
this.offensiveRating += b.getPressure();
this.damage += b.getDamage();
this.frontlineRating += b.getFrontlineRating();
addBruiser(b);
}
public void addDDContribution(DamageDealer d) {
addMutualContribution(d);
this.burstDamageRating += d.getBurstDamageRating();
this.sustainedDamageRating += d.getSustainedDamageRating();
this.damage += d.getDamageOutput();
this.offensiveRating += d.getAggression();
addDamageDealer(d);
}
public void addTankContribution(Tank t) {
addMutualContribution(t);
this.defensiveRating += t.getAntiMeleeEffectiveness();
this.offensiveRating += t.getEngage();
this.frontlineRating += t.getFrontlineRating();
addTank(t);
}
public void addHealerContribution(Healer h) {
addMutualContribution(h);
this.burstMitigation += h.getBurstHealing();
this.sustainedMitigation += h.getSustainedHealing();
if (h.hasLockdownProtection()) {
this.lockdownProtection = true;
}
addHealer(h);
}
private void addTank(Tank t) {
tanks.add(t);
if (tanks.size() + bruisers.size() > 1) {
setDoubleWarrior(true);
}
}
private void addHealer(Healer h) {
healers.add(h);
if (healers.size() == 2) {
setDoubleSupport(true);
}
}
private void addDamageDealer(DamageDealer d) {
damageDealers.add(d);
if (damageDealers.size() + bruisers.size() > 2) {
setHeavyDamage(true);
}
}
private void addBruiser(Bruiser b) {
bruisers.add(b);
if(bruisers.size() + damageDealers.size() > 2) {
setHeavyDamage(true);
}
}
private void setDoubleWarrior(boolean b) {
doubleWarrior = b;
}
private void setDoubleSupport(boolean b) {
doubleSupport = b;
}
private void setHeavyDamage(boolean b) {
heavyDamage = b;
}
}
To me, this implementation feels backwards. Ultimately I should only have the one public method to add a Role
subclass to the composition, then the Composition
should privately adjust its own properties based on which particular subclass is being added, something like this:
public void addRole(CustomEnum roleType, Role r) {
// increment properties inherent to all roles like mobility, waveclear, etc.
// ...
switch (roleType) {
case BRUISER:
addBruiserContribution(r);
break;
case TANK:
addTankContribution(r);
break;
case DAMAGE_DEALER:
addDDContribution(r);
break;
case HEALER:
addHealerContribution(r);
break;
default:
throw new InvalidArgumentException("Wrong roletype provided!");
}
}
This doesn't work without me having to hardcast the Role
abstract class into a particular subclass before passing it to the appropriate add__Contribution()
method, which is a big red-flag. Thus it feels like I missed an opportunity for cleaner polymorphism, but I can't figure out where.
How could I make my code well-formed and polymorphic to suit my needs?
Role Abstract class:
public abstract class Role {
protected String name;
protected int mobility;
protected int waveclear;
protected int peeling;
protected int burstMitigation;
protected int sustainedMitigation;
protected boolean lockdown;
protected boolean aaControl;
protected AttackRange range;
public String getName() {
return name;
}
public int getMobility() {
return mobility;
}
public int getWaveclear() {
return waveclear;
}
public int getPeeling() {
return peeling;
}
public int getBurstMitigaion() {
return burstMitigation;
}
public int getSustainedMitigation() {
return sustainedMitigation;
}
public boolean hasLockdown() {
return lockdown;
}
public boolean hasAAControl() {
return aaControl;
}
public AttackRange getRange() {
return range;
}
}
Bruiser Subclass:
public class Bruiser extends Role {
private int pressure;
private int damage;
private int frontlineRating;
protected Bruiser() {
}
public int getPressure() {
return pressure;
}
public int getDamage() {
return damage;
}
public int getFrontlineRating() {
return frontlineRating;
}
}
Healer subclass:
public class Healer extends Role {
private int burstHealing;
private int sustainedHealing;
private boolean lockdownProtection;
protected Healer() {
}
public int getBurstHealing() {
return burstHealing;
}
public int getSustainedHealing() {
return sustainedHealing;
}
public boolean hasLockdownProtection() {
return lockdownProtection;
}
}
Tank subclass:
public class Tank extends Role {
private int antiMeleeEffectiveness;
private int frontlineRating;
private int engage;
protected Tank() {
}
public int getAntiMeleeEffectiveness() {
return antiMeleeEffectiveness;
}
public int getFrontlineRating() {
return frontlineRating;
}
public int getEngage() {
return engage;
}
}
Damage Dealer subclass:
public class DamageDealer extends Role {
private int burstDamageRating;
private int sustainedDamageRating;
private int aggression;
private int damageOutput;
protected DamageDealer() {
}
public int getBurstDamageRating() {
return burstDamageRating;
}
public int getSustainedDamageRating() {
return sustainedDamageRating;
}
public int getAggression() {
return aggression;
}
public int getDamageOutput() {
return damageOutput;
}
}
Composition Class (minus property "getters"):
updateGroupContribution(Group groupToContributeTo)
method to theRole
class. Then each role can update the group it's being added to by adding to its properties. Adding a role to the composite class is then as easy asaddContribution(Role r) { r.updateGroupContribution(this); }
\$\endgroup\$