我觉得用一个小例子更容易回答这个问题:
示例问题:您有一个组件根据一些输入进行一些计算。根据客户的不同,以下情况往往会发生变化:
- 输入数据的来源/格式
- 输入数据的预处理
- 根据客户用例计算某些输出的三种不同方法 [A..C]。
- 计算结果的输出格式。
所以工作流程可能看起来有点像这样:
预处理 -> 使用 [A..C] 风格计算 -> 格式化输出 -> 完成。
我会考虑在没有一堆配置意大利面条的情况下处理这个问题的设计是:
- 为每个处理步骤分类一个行为类型。在预处理步骤的示例中,这可能类似于 IInputShaper,负责将特定类型的输入数据转换为内部格式并进行一些预处理。可能这可以再次拆分:IInputFitter、IPreprocess。对于工作流程中的其他步骤,请相应地执行。这为您留下了许多行为类型的合同(这也有助于理解您的问题域。您现在可以轻松查看自由度实际来自哪里,系统有多少不同类型的行为,并且您可以保留核心实现干净。您还可以测试核心代码库,可以记录每个行为契约的要求,并且可以以单元测试方式而不是“体内”测试行为实现
- 根据第 1 步中定义的契约实现每个行为。在行为实现中接受一些重复的代码,而不是过度设计并试图找出这些行为之间的共性。把事情简单化。并对他们每个人进行测试。
结果:具有 0 个配置选项的行为实现集合和具有 0 个配置选项的核心代码库。最后一步是为客户项目选择行为实现,如果有特别之处,可能会编写一个新的实现。
如果你做对了,你将从此过上幸福的生活,而无需更改合同和核心系统。如果您错过了完美的设计,您将进行几次迭代,直到您找出最佳的合约接口设计,从而使您的核心代码库变得稳定。
使用这种方法,您还可以更轻松地估算工作并为新客户编写报价。您可以简单地检查报价阶段,如果您已经拥有所有行为实现,或者是否需要编写一些新的实现。而且,如果您首先跟踪编写它们需要多长时间,您甚至可以很好地猜测所需的工时。