mirror of
https://github.com/emilybache/GildedRose-Refactoring-Kata.git
synced 2026-05-15 22:31:14 +00:00
feat: refactor GildedRose with Strategy pattern, ItemRecord, and days injection
This commit is contained in:
parent
4db0d990a7
commit
81332e2d28
@ -1,4 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
|
|
||||||
@ -99,40 +101,57 @@ class ConjuredStrategy:
|
|||||||
item.quality = max(0, item.quality - 2)
|
item.quality = max(0, item.quality - 2)
|
||||||
|
|
||||||
|
|
||||||
class GildedRose(object):
|
_STRATEGY_MAP: dict[str, UpdateStrategy] = {
|
||||||
|
"Aged Brie": AgedBrieStrategy(),
|
||||||
|
"Backstage passes to a TAFKAL80ETC concert": BackstagePassStrategy(),
|
||||||
|
"Sulfuras, Hand of Ragnaros": SulfurasStrategy(),
|
||||||
|
"Conjured Mana Cake": ConjuredStrategy(),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, items):
|
|
||||||
|
@dataclass
|
||||||
|
class ItemRecord:
|
||||||
|
"""
|
||||||
|
Internal pairing of an Item with its UpdateStrategy.
|
||||||
|
|
||||||
|
Built once in GildedRose.__init__ so the name-to-strategy lookup
|
||||||
|
happens at construction time, not on every update call. Exposed
|
||||||
|
publicly so tests can inject strategies directly without going
|
||||||
|
through the name-lookup dict.
|
||||||
|
"""
|
||||||
|
|
||||||
|
item: Item
|
||||||
|
strategy: UpdateStrategy
|
||||||
|
|
||||||
|
|
||||||
|
class GildedRose:
|
||||||
|
"""
|
||||||
|
Inventory manager for the Gilded Rose inn.
|
||||||
|
|
||||||
|
Accepts a list of Item objects and updates their quality and sell_in
|
||||||
|
values according to each item's type-specific strategy. The Item
|
||||||
|
class and the items list are left untouched per the goblin's terms.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
rose = GildedRose(items)
|
||||||
|
rose.update_quality() # advance one day
|
||||||
|
rose.update_quality(days=7) # advance a full week
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, items: list[Item]) -> None:
|
||||||
self.items = items
|
self.items = items
|
||||||
|
self._records: list[ItemRecord] = [
|
||||||
|
ItemRecord(
|
||||||
|
item=i,
|
||||||
|
strategy=_STRATEGY_MAP.get(i.name, NormalStrategy()),
|
||||||
|
)
|
||||||
|
for i in items
|
||||||
|
]
|
||||||
|
|
||||||
def update_quality(self):
|
def update_quality(self, days: int = 1) -> None:
|
||||||
for item in self.items:
|
"""Advance quality and sell_in for all items by `days` days."""
|
||||||
if item.name != "Aged Brie" and item.name != "Backstage passes to a TAFKAL80ETC concert":
|
for record in self._records:
|
||||||
if item.quality > 0:
|
record.strategy.update(record.item, days)
|
||||||
if item.name != "Sulfuras, Hand of Ragnaros":
|
|
||||||
item.quality = item.quality - 1
|
|
||||||
else:
|
|
||||||
if item.quality < 50:
|
|
||||||
item.quality = item.quality + 1
|
|
||||||
if item.name == "Backstage passes to a TAFKAL80ETC concert":
|
|
||||||
if item.sell_in < 11:
|
|
||||||
if item.quality < 50:
|
|
||||||
item.quality = item.quality + 1
|
|
||||||
if item.sell_in < 6:
|
|
||||||
if item.quality < 50:
|
|
||||||
item.quality = item.quality + 1
|
|
||||||
if item.name != "Sulfuras, Hand of Ragnaros":
|
|
||||||
item.sell_in = item.sell_in - 1
|
|
||||||
if item.sell_in < 0:
|
|
||||||
if item.name != "Aged Brie":
|
|
||||||
if item.name != "Backstage passes to a TAFKAL80ETC concert":
|
|
||||||
if item.quality > 0:
|
|
||||||
if item.name != "Sulfuras, Hand of Ragnaros":
|
|
||||||
item.quality = item.quality - 1
|
|
||||||
else:
|
|
||||||
item.quality = item.quality - item.quality
|
|
||||||
else:
|
|
||||||
if item.quality < 50:
|
|
||||||
item.quality = item.quality + 1
|
|
||||||
|
|
||||||
|
|
||||||
class Item:
|
class Item:
|
||||||
|
|||||||
@ -10,6 +10,7 @@ if str(_EXERCISE_ROOT) not in sys.path:
|
|||||||
from gilded_rose import (
|
from gilded_rose import (
|
||||||
Item, GildedRose, NormalStrategy, AgedBrieStrategy,
|
Item, GildedRose, NormalStrategy, AgedBrieStrategy,
|
||||||
BackstagePassStrategy, SulfurasStrategy, ConjuredStrategy,
|
BackstagePassStrategy, SulfurasStrategy, ConjuredStrategy,
|
||||||
|
ItemRecord,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -207,5 +208,65 @@ class TestConjuredStrategy(unittest.TestCase):
|
|||||||
self.assertEqual(0, item.quality)
|
self.assertEqual(0, item.quality)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGildedRose(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Integration tests for GildedRose.
|
||||||
|
|
||||||
|
Verifies strategy dispatch by item name and that the days parameter
|
||||||
|
is forwarded correctly to each strategy.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_dispatches_normal_strategy_for_unknown_item(self):
|
||||||
|
items = [Item("unknown widget", sell_in=5, quality=10)]
|
||||||
|
GildedRose(items).update_quality()
|
||||||
|
self.assertEqual(9, items[0].quality)
|
||||||
|
self.assertEqual(4, items[0].sell_in)
|
||||||
|
|
||||||
|
def test_dispatches_aged_brie_strategy(self):
|
||||||
|
items = [Item("Aged Brie", sell_in=5, quality=20)]
|
||||||
|
GildedRose(items).update_quality()
|
||||||
|
self.assertEqual(21, items[0].quality)
|
||||||
|
|
||||||
|
def test_dispatches_backstage_pass_strategy(self):
|
||||||
|
items = [Item("Backstage passes to a TAFKAL80ETC concert", sell_in=5, quality=20)]
|
||||||
|
GildedRose(items).update_quality()
|
||||||
|
self.assertEqual(23, items[0].quality)
|
||||||
|
|
||||||
|
def test_dispatches_sulfuras_strategy(self):
|
||||||
|
items = [Item("Sulfuras, Hand of Ragnaros", sell_in=0, quality=80)]
|
||||||
|
GildedRose(items).update_quality()
|
||||||
|
self.assertEqual(80, items[0].quality)
|
||||||
|
self.assertEqual(0, items[0].sell_in)
|
||||||
|
|
||||||
|
def test_dispatches_conjured_strategy(self):
|
||||||
|
items = [Item("Conjured Mana Cake", sell_in=5, quality=20)]
|
||||||
|
GildedRose(items).update_quality()
|
||||||
|
self.assertEqual(18, items[0].quality)
|
||||||
|
|
||||||
|
def test_multi_day_update_via_days_param(self):
|
||||||
|
# sell_in=3, quality=10, days=3
|
||||||
|
# Day1: q=9 si=2 | Day2: q=8 si=1 | Day3: q=7 si=0
|
||||||
|
items = [Item("unknown widget", sell_in=3, quality=10)]
|
||||||
|
GildedRose(items).update_quality(days=3)
|
||||||
|
self.assertEqual(7, items[0].quality)
|
||||||
|
self.assertEqual(0, items[0].sell_in)
|
||||||
|
|
||||||
|
def test_multiple_items_updated_independently(self):
|
||||||
|
items = [
|
||||||
|
Item("Aged Brie", sell_in=5, quality=20),
|
||||||
|
Item("unknown widget", sell_in=5, quality=20),
|
||||||
|
]
|
||||||
|
GildedRose(items).update_quality()
|
||||||
|
self.assertEqual(21, items[0].quality) # Aged Brie goes up
|
||||||
|
self.assertEqual(19, items[1].quality) # normal goes down
|
||||||
|
|
||||||
|
def test_item_record_allows_direct_strategy_injection(self):
|
||||||
|
# ItemRecord lets tests bypass name-lookup and inject a strategy directly
|
||||||
|
item = Item("anything", sell_in=5, quality=20)
|
||||||
|
record = ItemRecord(item=item, strategy=ConjuredStrategy())
|
||||||
|
record.strategy.update(record.item, days=1)
|
||||||
|
self.assertEqual(18, item.quality)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user