Notes and exercises for learning design patterns
Build a basic character stat system using the Broker Chain pattern. This exercise focuses on the three-part core: Query, Broker, and Modifier.
Implement:
StatQuery — a mutable object with:
character_name: strstat: str (e.g. "attack", "defense")value: int — starts at the base value, modifiers change itStatBroker — an event bus with:
subscribe(handler) — registers a callableunsubscribe(handler) — removes a callablepublish(query) — calls every registered handler with the queryCharacter — holds base stats and a broker reference:
base_attack = 10, base_defense = 5get_attack() -> int — creates a StatQuery, publishes it, returns query.valueget_defense() -> int — same for defenseVerify the bare character with no modifiers:
broker = StatBroker()
hero = Character("hero", broker)
assert hero.get_attack() == 10
assert hero.get_defense() == 5
Add a FlatBonusModifier that adds a fixed integer to one stat of one character:
FlatBonusModifier(broker, character_name="hero", stat="attack", bonus=10)
After registering, hero.get_attack() should return 20.
Multiple FlatBonusModifier instances for the same stat should stack:
FlatBonusModifier(broker, "hero", "attack", 10)
FlatBonusModifier(broker, "hero", "attack", 5)
assert hero.get_attack() == 25
Add a MultiplierModifier that multiplies a stat by a float:
MultiplierModifier(broker, character_name="hero", stat="attack", multiplier=2.0)
Add a remove() method to both modifier classes that unsubscribes them from the broker.
Verify:
sword = FlatBonusModifier(broker, "hero", "attack", 10) # attack = 20
berserk = MultiplierModifier(broker, "hero", "attack", 2.0) # attack = 40
assert hero.get_attack() == 40
berserk.remove()
assert hero.get_attack() == 20 # multiplier gone
sword.remove()
assert hero.get_attack() == 10 # back to base
See exercise3.py.
StatBroker.publish is just a loop: for h in self._handlers: h(query).FlatBonusModifier subscribes self._handle in __init__. _handle checks query.character_name and query.stat before modifying query.value.MultiplierModifier._handle should cast the result back to int: query.value = int(query.value * self._multiplier).