Stackable Power-Ups: Shield, Speed & Double Damage
Trying to model combinations with inheritance (`FireSword`, `FirePoisonSword`, `DoubleFireCritSword`, `FireShieldHero`, `FireShieldHasteHero`) causes a $2^N$ combinatorial subclass explosion. Furthermore, static inheritance cannot stack duplicate buffs or attach/strip enchantments dynamically mid-battle.
With N independent buffs, there are 2^N possible active combinations. Supporting just 8 buffs would require writing and maintaining 256 distinct subclasses.
Attach additional responsibilities to an object dynamically by wrapping it inside a decorator class that implements the same interface.
We define a unified Character interface. BaseHero implements the core stats, while BuffDecorator also implements Character and holds a std::unique_ptr<Character> inner_. Each concrete decorator (FireShieldBuff, HasteBuff, DoubleDamageBuff) overrides specific methods to augment or multiply values before delegating to inner_. Because decorators wrap Character pointers, decorators can wrap other decorators to arbitrary depth, eliminating subclass explosion.
Day 1 of building our RPG Game Engine. We have a basic Weapon and Hero combat prototype where a sword deals flat base damage and a hero possesses fixed baseline attributes.
Game designers want players to stack arbitrary enchantments (Fire, Lifesteal, Critical Strike, Poison) onto any weapon and hero buffs (Fire Shield, Haste Boots, Double Damage Potion) at runtime, even stacking two Fire runes on the same blade.
Trying to model combinations with inheritance (FireSword, FirePoisonSword, DoubleFireCritSword, FireShieldHero, FireShieldHasteHero) causes a 2^N combinatorial subclass explosion. Furthermore, static inheritance cannot stack duplicate buffs or attach/strip enchantments dynamically mid-battle.
With Decorator (WeaponDecorator / BuffDecorator), any enchantment or buff wraps any Weapon or Character dynamically at runtime to arbitrary depth. Next in Sprint 2 (C2 Β· Composite), players need an inventory bag (where pouches hold items or smaller pouches) and a party/raid hierarchy that calculates weight and damage uniformly.
The product ticket
In our 2D action RPG, players equip heroes with baseline gear and venture into dungeons. For the upcoming expansion, game designers want heroes to pick up temporary power-ups (buffs) during combat: a Fire Shield (+15 defense), Haste Boots (+20 speed), and a Double Damage Potion (2x attack power).
- β’
Support stacking multiple distinct buffs simultaneously (e.g.
FireShield+HasteBoots+DoubleDamagePotion). - β’
Support stacking duplicate buffs (e.g. two
FireShieldpickups must grant +30 total defense). - β’
Allow power-ups to be added and stripped dynamically at runtime without rebuilding the hero or resetting baseline stats.
- β’
Query character stats uniformly via
attackPower(),defense(),speed(), and inspect active effects viadescribe().
Next sprint, designers plan to introduce 5 additional buffs: Frost Aura, Invisibility Cloak, Vampiric Drain, Berserk Rage, and Thorns. We must support new buffs without modifying existing hero classes or generating combinatorial subclass explosion.
Step 1 β The naive code & why it breaks
The initial instinct is to create subclasses for buff combinations: FireShieldHero, HasteHero, FireShieldHasteHero, and FireShieldHasteDoubleDamageHero.
#include <print>
#include <string>
// Naive design: subclassing for every buff combination causes a 2^N explosion!
class Hero {
protected:
std::string name_;
int attack_{50};
int defense_{20};
int speed_{10};
public:
explicit Hero(std::string name) : name_(std::move(name)) {}
virtual ~Hero() = default;
virtual int attackPower() const { return attack_; }
virtual int defense() const { return defense_; }
virtual int speed() const { return speed_; }
virtual std::string describe() const { return name_; }
};
class FireShieldHero : public Hero {
public:
using Hero::Hero;
int defense() const override { return Hero::defense() + 15; }
std::string describe() const override { return Hero::describe() + " + Fire Shield"; }
};
class HasteHero : public Hero {
public:
using Hero::Hero;
int speed() const override { return Hero::speed() + 20; }
std::string describe() const override { return Hero::describe() + " + Haste Boots"; }
};
// Combinatorial nightmare begins: what if the player picks up BOTH?
class FireShieldHasteHero : public Hero {
public:
using Hero::Hero;
int defense() const override { return Hero::defense() + 15; }
int speed() const override { return Hero::speed() + 20; }
std::string describe() const override {
return Hero::describe() + " + Fire Shield + Haste Boots";
}
};
// What if they also drink a Double Damage potion? Another class!
class FireShieldHasteDoubleDamageHero : public Hero {
public:
using Hero::Hero;
int attackPower() const override { return Hero::attackPower() * 2; }
int defense() const override { return Hero::defense() + 15; }
int speed() const override { return Hero::speed() + 20; }
std::string describe() const override {
return Hero::describe() + " + Fire Shield + Haste Boots + Double Damage";
}
};
int main() {
Hero base{"Valeros the Fighter"};
std::println("[Base] {} -> Atk: {}, Def: {}, Spd: {}",
base.describe(), base.attackPower(), base.defense(), base.speed());
FireShieldHasteHero buffed{"Valeros the Fighter"};
std::println("[Buffed] {} -> Atk: {}, Def: {}, Spd: {}",
buffed.describe(), buffed.attackPower(), buffed.defense(), buffed.speed());
FireShieldHasteDoubleDamageHero superBuffed{"Valeros the Fighter"};
std::println("[Super] {} -> Atk: {}, Def: {}, Spd: {}",
superBuffed.describe(), superBuffed.attackPower(),
superBuffed.defense(), superBuffed.speed());
}With N independent buffs, there are 2^N possible active combinations. Supporting just 8 buffs would require writing and maintaining 256 distinct subclasses.
Inheritance binds types at compile time. A hero cannot equip two FireShield buffs because an object cannot inherit from the same class twice without messy ad-hoc types like DoubleFireShieldHero.
In C++, an object's dynamic type cannot morph at runtime. When a hero drinks a potion mid-battle, you cannot mutate a BaseHero into a DoubleDamageHero without copying state into a new object.
Step 2 β The fix: Decorator Pattern
Attach additional responsibilities to an object dynamically by wrapping it inside a decorator class that implements the same interface.
We define a unified Character interface. BaseHero implements the core stats, while BuffDecorator also implements Character and holds a std::unique_ptr<Character> inner_. Each concrete decorator (FireShieldBuff, HasteBuff, DoubleDamageBuff) overrides specific methods to augment or multiply values before delegating to inner_. Because decorators wrap Character pointers, decorators can wrap other decorators to arbitrary depth, eliminating subclass explosion.
Character β pure virtual interface declaring attackPower(), defense(), speed(), and describe().
BaseHero β base implementation holding core hero stats (baseAttack_, baseDefense_, baseSpeed_).
BuffDecorator β implements Character, holds std::unique_ptr<Character> inner_, and delegates calls by default.
FireShieldBuff, HasteBuff, DoubleDamageBuff β override specific stat methods to add bonuses or multipliers.
Unlock Full UML Diagram & C++23 Solution
The interactive UML architecture diagrams, refactored C++23 code, real-world extensions, and interview Q&A are part of Brute2Best Pro. All 7 foundation chapters are 100% free.
First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.