首页 > 解决方案 > 在 OOP 编程中,在其方法中引用子接口是正确的方法吗?

问题描述

我想知道在默认方法中使用接口(或抽象类)的具体实现是否是一种好习惯?例如:

interface Foo {

    String bar();

    default Foo withPrefix(String prefix) {
        return new PrefixFooImpl(prefix, this).bar();
    }
}

// proxy class for any Foo, adding a prefix to every bar() call
class PrefixFooImpl implements Foo {

    private String prefix;
    private Foo foo;

    // constructor with Foo and prefix parameter

    public String bar() {
        return prefix + " " + foo.bar();
    }
}

它的用法如下:而不是使用

new PrefixFooImpl("prefix", new OtherFoo());

一个人可以简单地做

new OtherFoo().withPrefix("prefix");

它确实有效,并且使用方便,但默认接口方法是否应该包含对其实现的引用?这不是不必要的耦合还是不好的做法?如果是这样,还有另一种方法可以正确地做到这一点吗?

标签: javaoopmethodsinterfacedefault

解决方案


接口的重点是允许实现更改而不影响调用者。

在您的示例中,调用者withPrefix()可能不会假设它返回PrefixFooImpl,他们只能假设Foo

即使在supercat 的示例中,AsImmutableEnumerable()也不会“泄露”实现细节,这意味着更改不会影响调用者。

当然,所有这一切都是假设如果某个调用者决定将结果withPrefix()转换为特定实现,无论是它PrefixFooImpl还是其他东西,当发生更改时他们不会抱怨。

另一方面,当然,实际上是允许实现改变。

再看这个Foo例子,实现总是可以 override withPrefix(),所以只要不使用默认方法扩展接口,一切都很好。

如果在已有实现(不会覆盖新方法)之后使用默认方法扩展接口,则需要注意默认方法以确保调用者期望的对象仍然表现出任何人想要的行为 Foo实例化实现(例如,您不能从 PostgreSQL 客户端的实现中返回 MySQL 客户端)。

在您的示例中,调用者期望的对象代理原始对象。其他实现可能只返回原始对象。这些都是有效的行为。

所以,虽然做这些事情本身并不违反接口提供的抽象,但实际上为了语法方便而严格这样做会导致更复杂的代码和更复杂的调试。

如果我们Foo再次举这个例子,调用withPrefix()10 次将创建一个包含 11 个对象的链。除了对 GC 的影响之外,想象一个复杂的实现PrefixFooImpl- 与外界对话的东西(例如DnsResolvingFooProxy,将主机名解析bar为 IP 地址)。

或者想象两种方法,asIpAddress()and resolveLoadBalance(),每个都指不同的实现

总之,这并不违反抽象,但如果您确实使用它,请务必谨慎行事。


推荐阅读