在构造函数中调用可覆盖的方法很糟糕。有例外吗?

时间:2014-11-30 19:07:59

标签: java constructor override abstract-class convention

我想知道是否存在调用public的情况,或者在这种情况下,特别是抽象类的构造函数中受保护的方法可以正常,或者至少可以原谅,只要有足够的文档意图。

我的实际问题涉及一个抽象类IdentifiedConnection,如下所示:

public abstract class IdentifiedConnection extends Connection {

    private UUID uuid;

    public IdentifiedConnection(String serverAddress, int port) throws IOException {
        super(serverAddress, port);
        this.uuid = this.aquireIdentifier();
    }

    public UUID getIdentifier() {
        return this.uuid;
    }

    protected abstract UUID aquireIdentifier() throws IOException;

}

我的目的是从服务器获取UUID标识符。如果服务器使用有效的UUID响应,我们在该类的字段中设置该UUID(名为uuid)。如果服务器以非UUID消息响应,我们假设服务器由于某种原因无法访问并抛出IOException

一般来说,我理解从构造函数调用一个可覆盖的方法是不好的,因为它使类容易出现各种或多或少难以检测的错误。然而,在这种情况下,我觉得这样做实际上并不是太糟糕(只要我嘲笑它的地狱)。

你有什么想法?这仍然是一个非常糟糕的主意吗?如果您认为这是不好的话,您建议采用哪种替代方法?


我已经将Connection类的代码留在了这个问题之外,因为我认为这是无关紧要的。

2 个答案:

答案 0 :(得分:2)

我建议你在这种情况下使用工厂方法。

编写一个名为

的静态方法
public static IdentifiedConnection openConnection(String serverAddress, int port)
        throws IOException {
    ...
}

这样可以更好地控制创建,并避免泄漏对未初始化对象的引用的潜在问题。


另一种方法是将Supplier<UUID>作为构造函数的参数,如下所示:

abstract class IdentifiedConnection extends Connection {

    private UUID uuid;

    public IdentifiedConnection(String serverAddress,
                                int port,
                                Supplier<UUID> uuidSupplier) throws IOException {
        super(serverAddress, port);
        this.uuid = uuidSupplier.get();
    }
}

并在子类中将其用作

class SomeConnection extends IdentifiedConnection {

    public SomeConnection(String serverAddress, int port) throws IOException {
        super(serverAddress, port, SomeConnection::createUUID);
    }

    public static UUID createUUID() {
        return ...;
    }

}

答案 1 :(得分:0)

如果这些方法的文档明确声明它们将从基类构造函数调用,那么使用基类构造函数调用虚拟或抽象方法是非常合理和合理的。这种调用在例如以下情况下非常有用。子类需要影响要存储在final基类字段中的值,或者基类不变量的建立需要将新构造的对象暴露给外部代码。不幸的是,在派生类构造函数之前运行的派生类方法没有很好的方法来访问任何派生类构造函数参数。类的每一层都可以定义一个构造函数 - 参数类,每个类都派生自下一个较低层的构造函数 - 参数类型,然后让基类将该对象传递给虚方法这是在基础构造函数中调用的,但这有点笨拙。尽管如此,使用虚拟init(ConstructorParams)方法可以使派生类在基类构造函数返回之前初始化自己 - 这是非常困难的。