我的问题与这里发布的问题几乎完全相同:带有最终未初始化字段的抽象类,我喜欢这个解决方案。然而,我的问题有点复杂,因为抽象类有多个不同类型的最终字段。例如,我有四个int
、两个int[]
和两个double
。强制子类初始化这些变量的最佳方式是什么?
我考虑过的选项:
- 将所有字段转换为字符串并通过
Map
- 有一个非常长的超类构造函数
- 创建一个充当包装器并封装所有值的助手类,然后将该类的实例传递给基类
第一个选项不是很优雅,而且似乎有点复杂,尤其是对于数组。第二个选项非常乏味,而第三个选项似乎我做得太过火了
有"正确"的方法吗?或者,如果没有,提出的三个选项中哪一个会是最优雅的?
我会选择第二个,"拥有一个非常长的超类构造函数。"如果我们遵循您提到的问题中详细介绍的方法,超类构造函数是protected
,不意味着由类层次结构或包外部的任何东西调用。我的感觉一直是,一旦某个东西没有超出这个界限——也就是说,它不是"API"的一部分——那么它看起来是什么样子就无关紧要了。让它有八个不同类型的参数,甚至更多。是的,它在包中是可见的,但从原始解决方案中可以清楚地看出,该构造函数不应该由子类以外的任何东西调用。这是非public
可见性的另一个动机。
当然,当涉及到public
的东西时,你做一些更清洁的事情的本能是正确的。你问这个问题说明你有正确的直觉。
这里有另一种选择,假设您可以控制所有涉及的类:抽象出超类中的字段,并在子类中声明它们,有点像。。。
abstract class SuperClass {
abstract int[] getFooArray(); // not public!
abstract int getBar();
}
然后在每个子类中定义字段,覆盖返回它们的方法。
是的,它会涉及一些代码复制,但最终它可能会比一个无法读取的长构造函数更干净,而且你复制的代码并不多——一个字段,一个返回该字段的单行方法。
然而,我的问题有点复杂,因为抽象类具有多个不同类型的最终字段。
我不确定我是否理解您的场景中增加的复杂性,但我将您的问题解释为:我不想在我的抽象构造函数上有太多争论。一种可能的方法是为具体子类使用的抽象类提供一个Builder。然后,生成器被"传递"到抽象构造函数,用于设置最终字段。
当我需要一个在构造函数中使用许多不同参数的不可变对象(所有成员都是最终成员)时,我通常使用Builder模式。
在这种情况下,您可以使构建器相互子类化,这样您仍然可以保持扩展的能力。
例如,您可以看到不可变集合生成器的Guava API。或者,如果您不需要不变性,这里有一个同样来自同一库的CacheBuilder示例:
Cache<Key, Graph> graphs = CacheBuilder.newBuilder()
.concurrencyLevel(4)
.weakKeys()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
正如您所看到的,使用构造器取代了在构造器中传递6个参数的需要,并使代码更加可读/可用。
如果你仍然不想使用构造函数,我会选择选项(3),因为这将避免维护一个很长的构造函数的一些麻烦