22

是否有特定的设计模式描述了提供非抽象默认实现的场景,该实现使用空的 NO-OP 实现实现接口上的所有或部分方法。这样做的目的是减轻子类实现它们自己可能不需要/使用的方法的负担:

public interface MyInterface {
    public void doThis();
    public void doThat();
    public void done();
}

public class MyClass implements MyInterface {
    public void doThis() {
        // NO-OP
    }
    public void doThat() {
        // NO-OP
    }
    public void done() {
        // Some standard implementation
    }
}

public class MuSubClass extends MyClass {
    public void doThat() {
        // Subclass only cares about doThat()
    }
}

我已经看到这种模式多次使用,包括SAX 框架中的 Java 的 DefaultHandlerMouseAdapter。在某些情况下,此类类被命名为适配器,但我的印象是适配器模式在两个不同的接口之间进行转换。

鉴于在这些情况下,只有一个已声明的接口被转换为该接口的未定义子集——我不清楚这如何符合适配器模式的精神。

此外,鉴于某些方法可能具有实现,并且 NullObject 传统上是单例,我也不太明白这如何遵守NullObject 模式。

4

9 回答 9

5

默认实现没有设计模式。

我通常将DoNothing前缀附加到类的名称。根据它的意图,我也使用BaseDefault(后者被广泛使用)。大概MouseAdapter应该叫DefaultMouseListener

在您关心的情况下,您可以使用简单的DynamicProxy系统地存根接口,您必须只返回一个“不错的”默认值(对象为 null,数字为 0 等)。

顺便说一句,这是一个非常好的问题。

编辑

此外,这既不是Stub也不是Mock:也许它可以与 Stub 混淆,但意图不同。

于 2009-08-11T11:06:08.307 回答
3

您应该遵循不同的设计原则:接口隔离原则

接口隔离原则指出不应强迫客户端实现他们不使用的接口。代替一个胖接口,许多基于方法组的小接口是首选的,每个接口服务一个子模块。

你不应该实施 MORE 你不应该实施 LESS

有关更多详细信息,请查看相关的 SE 问题。

接口隔离原则

接口隔离原则——程序到接口

于 2016-02-18T15:11:11.730 回答
2

我在 spring 中看到过这种设计,他们有一个名为FlowExecutionListenerAdapter的类,它可以节省您实现所有 FlowExecutionListener 操作的时间。

然而,它听起来也像空对象模式。但是我觉得它在适配器世界中更好,纯粹是因为它通过只允许你实现你想要的位来改变接口的行为......但它是一个艰难的。

我确定以前有人问过这个问题吗?

这听起来很相似,不是吗?可能值得一读。

于 2009-08-11T10:42:02.143 回答
2

它也用于 Swing(WindowAdapter,它实现了 WindowListener)。它只是一个方便的适配器,您只需以这种方式定义1-2个方法即可拥有一个有用的windowlistener。这确实是适配器模式的一个实例,也显示了抽象类的强大。它甚至是一个例子来说明为什么多重实现继承有时是有用的。

至于常规的设计模式,在模板方法中你可以定义钩子操作,它可以被覆盖(不像抽象方法,必须被覆盖),但默认行为(通常是 NO-OP)也是有意义的。

于 2009-08-11T12:18:06.443 回答
1

好问题。

我已经开始使用NoOp这个模式的类名前缀。它简短、清晰且不重载(例如Empty[不包含任何内容?]、Null[空对象模式,有什么不同?]、Abstract[它提供了一些实现吗?] 或Base[它提供了一些实现吗?])。

当我有一个第三方 API 时,我可能会编写这种风格的类,该 API 在复杂操作期间为仪器提供“Hooks”。考虑一个库提供的以下两个类:

public class LongRunningActionRunner {
    public void runSomethingLong(DecisionListener cdh) {
        // ...
    }
}

public interface DecisionListener {
    public void beforeFooHook();
    public void afterFooHook();
    public void beforeBarHook();
    public void afterBarHook();
    public void beforeBazHook();
    public void afterBazHook();
}

在这种情况下,您可能会像这样使用这种模式来纠正一个类:

public class NoOpDecisionListener implements DecisionListener {
    @Override public Something beforeFooHook() {}
    @Override public Something afterFooHook() {}
    @Override public Something beforeBarHook() {}
    @Override public Something afterBarHook() {}
    @Override public Something beforeBazHook() {}
    @Override public Something afterBazHook() {}
}
于 2013-01-21T23:04:48.443 回答
1

这种模式在旧版本的 Java 中很普遍。它是接口中默认方法的 Java 7 替代方案

Josh Bloch 称其为骨架实现。虽然骨架实现通常是抽象的,但如果骨架本身就足够了,您不需要强制客户端创建子类。

我同意前面的回答,指出接口隔离原则。对骨架实现的需求可能是代码异味,表明接口太“胖”并且可能试图做不止一项工作。在这种情况下,拆分接口比使用虚拟或 noop 逻辑创建骨架实现更可取。

于 2018-10-09T16:27:29.163 回答
0

你问的是空对象模式吗?

除了您的编辑之外,该MyClass对象只不过是一个默认实现。我认为没有任何特定的设计模式可以描述它。

于 2009-08-11T10:22:31.903 回答
0

对我来说,这似乎最接近特例空对象模式。

您的更新建议类似于模板方法期望您没有调用每个模板方法的单个方法,例如

public void doEverything()
{
  doThis();
  doThat();
  done();
}
于 2009-08-11T10:26:13.613 回答
0

我相信 Martin Fowler 会称之为空对象模式。在他的 Refactoring book[1] 中,Martin 介绍了 null 对象:

多态性的本质是,不是询问对象它是什么类型,然后根据答案调用一些行为,而是调用行为。对象,根据其类型,做正确的事。执行此操作的不太直观的地方之一是字段中有空值。

他后来补充说,“当许多客户想要做同样的事情时,你会受益;他们可以简单地依赖默认的 null 行为。” 他还为需要变体行为的客户端引入了 isNull() 方法。

我同意我有时会看到一个(通常是抽象的)实现,称为适配器。例如,在Android框架中,AnimatorListenerAdapter(源码在这里)被描述为:

这个适配器类提供了来自 Animator.AnimatorListener 的方法的空实现。任何只关心此侦听器方法子集的自定义侦听器都可以简单地继承此适配器类,而不是直接实现接口。

[1] “重构:改进现有代码的设计”,第 9 章,“简化条件表达式”,“引入 Null 对象”。

于 2014-05-04T22:19:20.687 回答