驯服“实用功能”类


15

在我们的Java代码库中,我不断看到以下模式:

/**
 This is a stateless utility class
 that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
  public int foo(...) {...}
  public void bar(...) {...}
}

/**
 This class does applied foo-related things.
*/
class FooSomething {
  int DoBusinessWithFoo(FooUtil fooUtil, ...) {
    if (fooUtil.foo(...)) fooUtil.bar(...);
  }
}

困扰我的是FooUtil,由于测试,我必须传递一个无处不在的实例。

  • 我无法将FooUtil方法设为静态,因为我无法模拟它们来测试FooUtil其客户端类和客户端类。
  • 同样,我无法FooUtil在消费地点创建的实例new,因为我将无法对其进行模拟以进行测试。

我想我最好的选择是使用注射(我也这样做),但这会增加自己的麻烦。同样,传递几个util实例会扩大方法参数列表的大小。

有没有办法更好地处理我看不到的问题?

更新:由于实用程序类是无状态的,因此我可以添加一个静态单例INSTANCE成员或一个静态getInstance()方法,同时保留更新该类的基础静态字段以进行测试的能力。看起来也不是超级干净。


4
而且您认为需要模拟所有依赖项以有效进行单元测试的假设是不正确的(尽管我仍在寻找讨论该问题的问题)。
Telastyn

4
为什么这些方法不是它们要操作其数据的类的成员?
2015年

3
如果FooUtility有一组单独的Junit调用静态fns。然后,您可以使用它而无需嘲笑。如果您需要模拟它,那么我怀疑它不仅是一个实用程序,而且还在做一些IO或业务逻辑,并且实际上应该是一个类成员引用,并通过构造函数,init或setter方法初始化。
tgkprog 2015年

2
为Jules评论+1。实用程序类是代码气味。根据语义,应该有一个更好的解决方案。也许FooUtil应该将方法放进去FooSomething,也许FooSomething应该更改/扩展其属性,以便FooUtil不再需要这些方法。请给我们一个更具体的例子,FooSomething以帮助我们为这个问题提供一个很好的答案。
valenterry

13
@valenterry:util类具有代码味道的唯一原因是因为Java没有提供一种具有独立功能的好方法。拥有非特定于类的功能的理由非常充分。
罗伯特·哈维

Answers:


16

首先,让我说针对不同问题有不同的编程方法。对于我的工作,我更喜欢用于组织和维护的强大OO设计,我的回答反映了这一点。功能性方法会有完全不同的答案。

我倾向于发现大多数实用程序类是由于方法应基于的对象不存在而创建的。这几乎总是来自以下两种情况之一:有人在传递集合,或者有人在传递bean(这些通常称为POJO,但我认为最好将一堆setter和getter形容为bean)。

当您有一个要传递的集合时,必须有一些代码希望成为该集合的一部分,并且最终将这些代码散布到整个系统中,并最终被收集到实用程序类中。

我的建议是将您的集合(或bean)与任何其他紧密相关的数据包装到新类中,并将“实用程序”代码放入实际对象中。

您可以扩展集合并在其中添加方法,但是那样会失去对对象状态的控制-建议您完全封装对象,只公开必要的业务逻辑方法(请考虑“不要向对象索要数据,给您的对象一个命令,让他们根据自己的私有数据采取行动”,这是一个重要的面向对象的租户,考虑到它,需要我在这里建议的方法。

此“包装”对保存数据但不能添加代码的任何通用库对象很有用-将代码与它处理的数据一起放入是OO设计中的另一个主要概念。

在很多编码风格中,这种包装概念将是一个坏主意-谁想要在实现一次性脚本或测试时编写整个类?但我认为,对于OO来说,必须封装任何无法向自己添加代码的基本类型/集合。


这个答案真棒。我遇到了这个问题(很明显),有些怀疑地读了这个答案,然后回过头看我的代码,并问“缺少哪些对象应具有这些方法”。瞧,找到了优雅的解决方案,并填补了对象模型的空白。
GreenAsJade


@Deduplicator在40年的编程经验中,我很少(从来没有?)看到过我被过多级别的间接阻止的情况(除了偶尔与我的IDE搏斗而跳到实现而不是接口),但我我们经常(非常经常)看到人们编写可怕的代码而不是创建明确需要的类的情况。虽然我相信太多的间接可能已经咬伤了一些人,我会在适当的OO原则方的错误,如每类做一件事做好,适当遏制,等
比尔ķ

@BillK通常,这是个好主意。但是如果没有转义线,封装实际上会成为障碍。您可以将自由算法与您想要的任何类型相匹配,这是有其美的,尽管在严格的OO语言中,除了OO以外,别无选择。
重复数据删除器

@Deduplicator编写必须在特定业务需求之外编写的“实用程序”功能时,您是正确的。充满功能的实用程序类(例如集合)在OO中很尴尬,因为它们与数据无关。这些是不熟悉OO的人所使用的“逃生舱口”(同样,工具供应商必须将它们用于非常通用的功能)。上面我的建议是在处理业务逻辑时将这些hacks包装在类中,以便您可以将代码与数据重新关联。
比尔K,

1

您可以使用Provider模式,并向您注入FooSomething一个FooUtilProvider对象(可以在构造函数中;仅一次)。

FooUtilProvider只会有一种方法:FooUtils get();。然后,该类将使用FooUtils它提供的任何实例。

现在,与使用依赖注入框架仅相距仅一步之遥,这时您只需要为DI cointainer连接两次即可:用于生产代码和用于测试套件。您只需将FooUtils接口绑定到RealFooUtilsMockedFooUtils,其余的就会自动发生。依赖的每个对象FooUtilsProvider都会获得的正确版本FooUtils

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.