参考答案

我们无法保证答案的正确性,如果你对参考答案有任何疑问,请联系助教!

书面练习

  • updateQuality() 中最明显的问题是方法过长。它把普通物品、Aged BrieSulfurasBackstage 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.nameItemRuleFactory 中取得对应规则。
    • 调用该规则对象的 update(item)
    • 具体的品质变化、销售期限变化都由对应规则类负责。 这样设计后,GildedRose 不再关心每种物品的细节规则,只负责调度。不同物品的更新逻辑也被拆分到了各自的类中,规则表达更加清晰。
  • 如果继续在 updateQuality() 中添加条件分支,那么实现起来短期看很快,只需要加一个类似 if (items[i].name == "Conjured") 的判断即可。但是这样会让原本已经复杂的函数继续膨胀。每新增一种物品,都要修改同一个函数,容易破坏旧逻辑,可读性和维护性都会越来越差。
  • 从可读性上看,继续加分支会让 updateQuality() 更难理解。读者需要在大量嵌套判断中寻找某一种物品的规则。而引入规则类后,每种物品的逻辑都集中在自己的类中,比如 ConjuredRule 只表达召唤物品的规则,阅读起来更直接。
  • 从可测试性上看,继续加分支时,测试往往只能围绕整个 GildedRose::updateQuality() 进行,定位问题比较麻烦。使用规则类后,可以单独测试 NormalItemRuleConjuredRule 等,每个规则的输入输出都更清晰。
  • 从扩展性上看,继续加分支违反开闭原则,因为新增物品必须修改已有函数。使用规则类或策略对象后,新增 Conjured 只需要增加一个 ConjuredRule,再让工厂能识别它即可。原有普通物品、Aged BrieSulfuras 的规则代码基本不用动,风险更小。
  • 因此,虽然简单场景下直接加条件分支成本最低,但在这个题目中,物品规则已经明显分化,而且未来还可能继续新增类型,所以更推荐引入规则类或策略对象,用多态替代复杂条件判断。
Last Updated:
Contributors: 1-rambo