在 OOP 编程中,在其方法中引用子接口是正确的方法吗?

Is it a correct way in OOP programming to have a reference to interface children in its methods?

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

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");

它确实有效,而且使用起来很方便,但是默认接口方法是否应该包含对其实现的引用?这不是不必要的耦合还是不好的做法?如果是这样,还有其他正确的方法吗?

这有时是一个很好的模式,如果语言在实现 ListEnumerable 等接口时支持默认接口实现,那么它们包含更多的接口是有意义的方法,其中大部分将使用通用默认实现来实现。

作为一个简单的例子,有接口ImmutableEnumerable和classImmutableArrayList是有意义的,它们都实现Enumerable并保证重复枚举将always and forevermore 产生相同的对象序列。 Enumerable 接口然后可以包含一个 AsImmutableEnumerable 方法,其默认实现将调用 ImmutableArrayList 的构造函数,该构造函数从 Enumerable 构建私有的 ArrayList。然而,ImmutableArrayList 或任何其他不可变集合中的 AsImmutableEnumerable 的实现覆盖了该定义,以便简单地 return 本身。

如果代码拥有一个 Enumerable 集合并想要为其拍摄快照,它可以调用 AsImmutableEnumerable。如果集合是可变的,该方法会将集合的内容复制到新的不可变对象中,但如果集合已经是不可变的,则无需复制其内容。

接口的要点是允许在不影响调用者的情况下更改实现。

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

即使在 中,AsImmutableEnumerable() 也不会 "leak" 实现细节,这意味着更改不会影响调用者。

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

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

再次以 Foo 为例,一个实现总是可以重写 withPrefix(),因此只要不使用默认方法扩展接口,一切都很好。

如果使用默认方法扩展接口 after Foo 已经有现有的实现(不会覆盖新方法),那么默认方法需要注意确保调用者期望的对象仍然表现出实例化实现的人所期望的行为(例如,您不能 return 来自 PostgreSQL 客户端实现的 MySQL 客户端)。

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

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

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

或者设想两种方法,asIpAddress()resolveLoadBalance(),每种方法都引用 不同的实现方式

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