Monster Buff/Debuff System Design Below are a collection of classes that compose my buff/debuff system. The largest flaw in my design that I couldn't overcome is the base class Effect having an enum filled with the different types of buffs and debuffs. If there is a workaround that could clean up that part of the code I'd be really grateful. Otherwise, I'm simply looking for suggestions on potential bugs, mishaps, inefficient methods, etc.

The base class for Effects

public abstract class Effect {

    public static enum Type { NIL, ATTACKBUFF, ATTACKBREAK, DEFENSEBUFF, DEFENSEBREAK }

    private Effect.Type type;

    public Effect(Effect.Type type) {

        this.type = type;

    }

    public abstract void apply();
    public abstract void expire();
    public abstract void update();

    public static Effect newEffect(Effect.Type type, Monster monster, int duration) {

        Effect effect;

        switch(type) {
        default:
        case NIL:
            effect = new NilEffect();
            break;
        case ATTACKBUFF:
            effect = new AttackBuff(monster, duration);
            break;
        case ATTACKBREAK:
            effect = new AttackBreak(monster, duration);
            break;
        case DEFENSEBUFF:
            effect = new DefenseBuff(monster, duration);
            break;
        case DEFENSEBREAK:
            effect = new DefenseBreak(monster, duration);
            break;
        }

        return effect;

    }

    public Effect.Type getType() {

        return type;

    }

}


A class for specific StatModifiers

abstract class StatModifier extends Effect {

    protected final Monster monster;
    protected final Stat originalStat;
    protected final Stat.Type statType;
    protected final double modifier;

    private int duration;

    public StatModifier(Monster monster, Effect.Type effectType, Stat.Type statType, double modifier, int duration) {

        super(effectType);

        this.monster = monster;
        this.statType = statType;
        originalStat = monster.getStat(statType);
        this.modifier = modifier;
        this.duration = duration;

    }

    @Override
    public void apply() {

        final int augmentedBaseStatValue = (int) Math.ceil(originalStat.getAugmentedStat() * modifier);
        final Stat stat = new Stat(augmentedBaseStatValue, 0, 0);
        monster.setStat(statType, stat);

    }

    @Override
    public void expire() {

        monster.setStat(statType, originalStat);

    }

    @Override
    public void update() {

        duration--;

        if(duration &lt;= 0) {

            expire();

        }

    }

}


An example of a StatModifier class

public final class AttackBuff extends StatModifier {

    public AttackBuff(Monster monster, int duration) {

        super(monster, Effect.Type.ATTACKBUFF, Stat.Type.ATTACK, 1.5 ,duration);

    }

}


The Monster class (partial)

public class Monster {

    private final Map&lt;Effect.Type, Effect&gt; effects;

    public Monster() {

        effects = new HashMap&lt;&gt;();

    }

    public void addEffect(Effect.Type type, int duration) {

        if(effects.containsKey(type)) {

            effects.get(type).expire();
            effects.remove(type);

        }

        Effect effect = Effect.newEffect(type, this, duration);
        effect.apply();
        effects.put(effect.getType(), effect);

    }

    public void updateEffects() {

        effects.values()
               .parallelStream()
               .forEach(effect -&gt; 

                   effect.update()

               );

    }

}
Visibility modifiers and names

enum types are by default public static. You can choose to remove them.

Type is a very broad name for your enum, so you can consider just calling it EffectType.

switch and return

Instead of assigning the result to effect, you should return directly from each case, i.e.:

switch(type) {
case ATTACKBUFF:
    return new AttackBuff(monster, duration);
case ATTACKBREAK:
    return new AttackBreak(monster, duration);
case DEFENSEBUFF:
    return new DefenseBuff(monster, duration);
case DEFENSEBREAK:
    return new DefenseBreak(monster, duration);
// shifted default case as the last, it's more 'conventional'
default:
// case NIL:
    return new NilEffect();
}


You can also choose to place this as method for EffectType instead, but from an OOP approach, this depends if an EffectType is supposed to 'know' an Effect (and Monster) or not. Your approach is still fine.

Adding and updating effects

Since you are on Java 8, you can consider return-ing null from Map.computeIfPresent() to expire the type and remove the entry in one go:

public void addEffect(Effect.Type type, int duration) {
    effects.computeIfPresent(type, (k, effect) -&gt; { effect.expire(); return null; });
    Effect effect = Effect.newEffect(type, this, duration);
    effect.apply();
    effects.put(effect.getType(), effect);
}


Do you really need to invoke parallelStream() when updating effects? Since the maximum number of elements you can have in your Map is bounded by the number of EffectType values (BTW, you may want to switch to an EnumMap), I think the overhead of doing a concurrent access on your Map is not required. Also the forEach() operation can be done using a method reference as well, in this case:

public void updateEffects() {
    effects.values().stream().forEach(Effect::update);
}