参考答案
我们无法保证答案的正确性,如果你对参考答案有任何疑问,请联系助教!
书面练习
updateQuality()中最明显的问题是方法过长。它把普通物品、Aged Brie、Sulfuras、Backstage passes的所有更新规则都塞进了一个函数里,导致函数职责过多,阅读时很难看出每种物品自己的规则。- 第二个问题是重复代码较多。例如“品质不能小于 0”、“品质不能超过 50”、“Sulfuras 不更新”等规则在多个地方反复判断。重复代码会增加修改成本,一旦规则变化,很容易漏改。
- 针对这些坏味道,可以采用的重构思路是:先提取小函数,把“增加品质”“降低品质”“降低 sellIn”“判断上限下限”等公共逻辑封装起来;然后进一步把不同物品的更新规则拆分到不同的规则类中,用多态或策略对象替代大量条件分支。
- 引入“规则类”或“策略对象”,让每种物品对应一个更新规则。
- 可以设计一个统一的规则接口,例如:
ItemRule:抽象规则接口,负责定义update(Item& item)。NormalItemRule:普通物品规则,负责普通物品的品质和销售期限更新。AgedBrieRule:负责Aged Brie的更新规则,品质随时间增加,过期后增加更快,但不超过 50。SulfurasRule:负责Sulfuras的更新规则,既不降低sellIn,也不改变quality。BackstagePassRule:负责Backstage passes的更新规则,根据剩余天数增加品质,过期后品质归 0。ItemRuleFactory:根据Item.name选择对应的规则对象。GildedRose:仍然保留原来的接口,只负责遍历items,为每个Item找到对应规则并执行更新。
- 每天更新时的调用流程可以是:
- 外部代码仍然调用
GildedRose::updateQuality()。 updateQuality()遍历items中的每个物品。- 对每个物品,根据
item.name从ItemRuleFactory中取得对应规则。 - 调用该规则对象的
update(item)。 - 具体的品质变化、销售期限变化都由对应规则类负责。 这样设计后,
GildedRose不再关心每种物品的细节规则,只负责调度。不同物品的更新逻辑也被拆分到了各自的类中,规则表达更加清晰。
- 外部代码仍然调用
- 如果继续在
updateQuality()中添加条件分支,那么实现起来短期看很快,只需要加一个类似if (items[i].name == "Conjured")的判断即可。但是这样会让原本已经复杂的函数继续膨胀。每新增一种物品,都要修改同一个函数,容易破坏旧逻辑,可读性和维护性都会越来越差。 - 从可读性上看,继续加分支会让
updateQuality()更难理解。读者需要在大量嵌套判断中寻找某一种物品的规则。而引入规则类后,每种物品的逻辑都集中在自己的类中,比如ConjuredRule只表达召唤物品的规则,阅读起来更直接。 - 从可测试性上看,继续加分支时,测试往往只能围绕整个
GildedRose::updateQuality()进行,定位问题比较麻烦。使用规则类后,可以单独测试NormalItemRule、ConjuredRule等,每个规则的输入输出都更清晰。 - 从扩展性上看,继续加分支违反开闭原则,因为新增物品必须修改已有函数。使用规则类或策略对象后,新增 Conjured 只需要增加一个
ConjuredRule,再让工厂能识别它即可。原有普通物品、Aged Brie、Sulfuras的规则代码基本不用动,风险更小。 - 因此,虽然简单场景下直接加条件分支成本最低,但在这个题目中,物品规则已经明显分化,而且未来还可能继续新增类型,所以更推荐引入规则类或策略对象,用多态替代复杂条件判断。