英文原文:How to get rid of helper and utils classes
无论是进行代码 review 还是紧急编码调整,你总会发现:你又搞出了一个帮助类(helper class)。代码运行一切正常,进度又必须跟上,发布任务一个接一个,因此那个帮助类逐渐变成了一个提供了很多静态(static)方法的“怪兽类”(monster class),在它的 utils 包内不受控制地增长。utils 包长久以来就是一个技术争议的荒蛮之地,面向对象设计理念连半步都不敢踏入。“工具类是功能集中,并且逻辑毫不重复(Do not repeat yourself)” 一些开发人员会这样喊道 ,通常就是他们编写了这些工具类。因为所有都是静态的,所以它很快 - 团队里面的另外一些人这样说,也许就是是添加另外一些静态方法的人。它很容易使用,我们使这些代码很简洁 — 你可以在这个空间内听到这样的言论,但这又是另外一个对 KISS 的误解了。
我们会争论到:通常帮助类和工具类都很简单,特别是当我们不能修改新功能的目标类(例如外部依赖库)或我们不能找到使用的目标(不清晰的领域模型,PoC,需求缺失),或者我们只是不想去找它(懒,这也是帮助类的最主要原因)。但是最大的问题在于这很明显不是面向对象的解决方案,并且随着时间的推移(缺少团队沟通,资源重用,快速修复和一些其他的东西)它会导致一些包含无尽静态方法的容器和令人头疼的维护(你想要做到 DRY,但你却是用 10 个方法来提供几乎相同的功能,尽管不是完全一样;你想要快速,但你现在不能方便地添加一个 cache 机制到那个静态类中或者你遇到了并发的麻烦;你想使事情变得简单,但现在你的 IDE 提供了一长列的各种各样的方法,这并不能简化你的工作)。但不要担心,我们会尝试着去解决它。
让我们来重构帮助类
首先,我们需要定义我们的问题:一个只提供静态方法的无状态类(有 Helper 或 Utils 后缀),它没有明确的职责,在项目中也不会被初始化为对象。
接着,我们需要一个几乎明确的方案来解决问题。这几乎就代表了例外和项目特性:最后的决定当然是根据具体的情况来了,任何被称为通用解决方案的基本上都可以忽略。我们最后需要分析一下给出的类,尝试着:
上面的任何方案都可以提供一个更好的模型。然后我们再依据下面的步骤(假设根据下面的步骤进行项目重构):
ProjectUtils
到CarHelper
,EngineHelper
,WheelHelper
等等)。(好,你的代码难道看起来不是更简洁了吗?)如果这些新类只有一个方法,我们需要看一下它的用途。如果我们只有一个调用者,那么恭喜你,那就是我们的目标类了!你可以把方法移到类中,作为 behavior 或私有方法(保持它的 static 标识或者利用内部状态)。这个帮助类就消失了。
我们目前得到的帮助类(但是它确实可以成为你的起点)确定了这些关联方法的一个通用状态。提示:看一下那些方法中的大部分通用参数(例如,所有方法都接收一个Car
对象),这表明,这些方法可能应该作为方法属于Car
类(或者扩展?封装类?)。否则,这些通用的参数应该是一个可以传给构造函数并且被所有(非静态和其他的)方法使用的类的属性,状态。那个属性应该会使你想起类的前缀,方法的归类可以使你想起一系列行为的类(CarValidator
,CarReader
,CarConverter
等等)。那么这个帮助类又可以去掉了。
Helper.calculate (x)
,calculate (x, y)
,calculate (x, z)
,calculate (y, z)
的静态方法我们可以简单地想到如newBuilder () .with (x) .with (y) .calculate ()
。帮助类会提供 behaviours,减少业务方法列表,并且提供更好的扩展性。调用方可以把它当作内部属性来重用或者在需要的时候再初始化。这个帮助类(我们所知的)又可以去掉了。如果帮助类提供的方法确实是供不同的参数使用的(但,在这个时候,都是用于同一对象的),可以考虑使用命令模式(Command pattern):调用方实际上创建必须的命令(处理必须的输入和提供必要的操作),在确定的上下文情况下会有一个调用者进行执行。你也许可以获取到每个静态方法的命令实现,你的代码也从Helper.calculate (x,y)
,calculate (z)
变成了invoker.calculate (new Action (x, y))
。帮助类再见。
如果帮助类提供的方法接收相同的参数,但处理不同的逻辑,可以考虑使用策略模式(Strategy pattern
):每一个静态方法都可以简单地变成一个策略实现,从而消除原来的帮助类(取而代之的是上下文组件)。
Visitor pattern
):你可以根据不同的访问方法得到几个访问者实现,这也许可以替换部分或所有之前存在的静态方法。总结
过程很简单,找到对的实体和合理的目标类或者通过一种采用面向对象设计的标准方法来重构给定的帮助类(但会在代码复杂度上有所增加,值得吗?)。过一下上面提到的场景列表,也许当你尝试理解怎么去实现重构时会有多于一个将会为你提供灵感;特定的限制也许会限制已确定的解决方案;复杂的静态方法和相关的流程也许需要几个重构的步骤,可以一直优化它直到得到可接受的结果。或者你可以选择在某种程度上以代码可读性和简单性的名义来维持原来的帮助类(希望能满足上面至少 5 个步骤)。帮助类并不都是有害的,但绝大多数情况下你并不需要它们。
参考: 如休整脱离帮助类和工具类参考自我们的 JCG 成员 Antonio Di Matteo 重构的建议。
翻译: ImportNew.com - 陈晓舜
译文链接: http://www.importnew.com/11593.html