After the in-character refactoring of the Gilded Rose kata ( GildedWoes ), a "normie" refactoring would likely involve making a class for each product. However, a rules-based refactoring seems cleaner.
Rules-based has two strong advantages over other implementations: 1) since the requirements are also rules-based, verifying them is straightforward, and 2) the previous implementation's variance with requirements is made obvious.
In the typical products-are-objects implementation, Sulfuras is the "normal" object, since the class does nothing. Adding in a rules-based refactoring makes the normal item the normal object -- i.e. something similar to the following:
class Rules(object):
default_increment = -1
def ruleSellInDecrement(item):
item.sell_in -= 1
def ruleSetIncrement(item):
item.incr = item.default_increment
def ruleSellInDoubles(item):
if item.sell_in < 0:
item.incr = 2 * item.incr
def ruleHandleLowOutOfRangeAsPerPreviousImplementation(item):
if item.quality < 0:
item.incr = 0
def ruleHandleHighOutOfRangeAsPerPreviousImplementation(item):
if item.quality > 50 and item.incr > 0:
item.incr = 0
def ruleNeverGrowNegative(item):
if item.incr < 0 and item.quality + item.incr < 0:
item.quality = 0
item.incr = 0
def ruleNeverGrowOver50(item):
if item.incr > 0 and item.quality + item.incr > 50:
item.quality = 50
item.incr = 0
def ruleAddIncrement(item):
item.quality += item.incr
def ruleBackstageZeroing(item):
pass
rules = [ x for x in locals() if x.startswith('rule') ]
class ItemNormal(Item, Rules):
pass
class ItemConjured(Item, Rules):
default_increment = 2 * ItemNormal.default_increment
class ItemBrie(Item, Rules):
default_increment = 1
def ruleHandleLowOutOfRangeAsPerPreviousImplementation(item):
pass
class ItemBackstage(Item, Rules):
def ruleSetIncrement(item):
item.incr = sum([ item.sell_in >= 0, 0 <= item.sell_in < 5, 0 <= item.sell_in < 10 ])
def ruleHandleLowOutOfRangeAsPerPreviousImplementation(item):
pass
def ruleBackstageZeroing(item):
if item.sell_in < 0:
item.quality = 0
class ItemSulfuras(Item, Rules):
default_increment = 0
def ruleSellInDecrement(item):
pass
class GildedRose(object):
def __init__(self, items):
objmap = { "Sulfuras, Hand of Ragnaros": ItemSulfuras,
"Conjured code": ItemConjured,
"Backstage passes to a TAFKAL80ETC concert": ItemBackstage,
"Aged Brie": ItemBrie }
for item in items:
item.__class__ = objmap[item.name] if item.name in objmap else ItemNormal
self.items = items
def update(self):
for item in self.items:
for rule in item.rules:
getattr(item, rule)()
return self.items