9

我刚刚遇到了一个我以前见过的模式,并想得到关于它的意见。有问题的代码涉及这样的接口:

public interface MyCrazyAnalyzer {
    public void setOptions(AnalyzerOptions options);
    public void setText(String text);
    public void initialize();
    public int getOccurances(String query);
}

预期的用法是这样的:

MyCrazyAnalyzer crazy = AnalyzerFactory.getAnalyzer();
crazy.setOptions(true);
crazy.initialize();
Map<String, Integer> results = new HashMap<String, Integer>();
for(String item : items) {
    crazy.setText(item);
    results.put(item, crazy.getOccurances);
}

其中有一些原因。setText(...) 和 getOc​​curances(...) 之所以存在,是因为在对数据进行相同的昂贵分析之后,您可能想要执行多个查询,但这可以重构为结果类。

为什么我认为这很糟糕:实现以接口未明确指示的方式存储状态。我还看到过类似的事情,涉及需要调用“prepareResult”,然后是“getResult”的接口。现在,我可以想到使用其中一些功能的精心设计的代码。Hadoop Mapper 接口扩展了 JobConfigurable 和 Closeable,但我看到了很大的不同,因为它是一个使用用户代码实现这些接口的框架,而不是一个可以有多个实现的服务。我认为与包含必须调用的“关闭”方法相关的任何事情都是合理的,因为没有任何其他合理的方法可以做到这一点。在某些情况下,例如 JDBC,这是抽象泄漏的结果,但在我想到的两段代码中,它

我的问题是:

  1. 每个人都同意这是一个设计不佳的界面吗?
  2. 这是描述的反模式吗?
  3. 这种初始化是否属于接口?
  4. 这对我来说似乎是错误的,因为我偏爱函数式风格和不变性吗?

如果这很常见以至于值得命名,我建议对接口使用“秘密握手”反模式,当接口本身不是有状态的(如集合)时,它会强制您以特定顺序调用多个方法。

4

5 回答 5

18

是的,这是一种反模式:顺序耦合

我将重构为Options- 传递给工厂,并ResultsanalyseText()方法返回。

于 2009-04-27T09:18:07.870 回答
3
  1. 我希望看到 AnalyzerFactory 通过必要的参数并自行构建;否则,它到底在做什么?
  2. 不确定它是否有名字,但它似乎应该:)
  3. 是的,有时在你的接口中使用 setter 并期望类调用它们是很方便的(并且是正确的抽象级别)。我建议这样做需要大量记录该事实。
  4. 不是真的,不。对不变性的偏好当然是一件好事,基于 setter/bean 的设计有时也可能是“正确”的选择,但是您给出的示例太过分了。
于 2009-04-27T08:35:47.503 回答
3

我不确定它是否是一种描述的反模式,但我完全同意这是一个设计不佳的界面。它给错误留下了太多的机会,并且至少违反了一个关键原则:让你的 API 难以被滥用。

除了滥用之外,如果多个线程使用同一个实例,此 API 还可能导致难以调试的错误。

Joshua Bloch实际上对 API 设计进行了出色的演示(36 分 16 秒和 40 分 30 秒),他将此作为设计不佳的 API 的特征之一。

于 2009-04-27T08:36:30.687 回答
0

我看不出这里有什么不好的。setText()准备舞台;之后,您有一个或多个调用getOccurances(). 由于setText()太贵了,我想不出任何其他方法来做到这一点。

getOccurances(text, query)将以巨大的性能成本修复“秘密握手”。您可以尝试缓存文本getOccurances()并仅在文本更改时更新您的内部缓存,但这开始看起来越来越像是对某些 OO 原则的牺牲。如果一条规则没有意义,那就不要应用它。软件开发人员有头脑是有原因的。

于 2009-04-27T09:11:41.270 回答
0

一种可能的解决方案 - 使用 Fluent chaning。这避免了包含需要按特定顺序调用的方法的类。这很像构建器模式,它确保您不会读取仍在填充中间的对象。

于 2017-08-23T23:44:51.303 回答