除了Java中的构造函数重载,还有哪些最佳实践/设计模式?

3

我有一个类,它有多个构造函数。每个构造函数代表不同的使用场景。

public class ABC {
  public ABC(int x) {
  ...
  }
  public ABC(ArrayList<String> Stringarray) {
  ...
  }
  ..many more constructors..
}

在遇到 Java 编译器的相同擦除问题之前,构造函数重载一直是一个很好的解决方案。例如,我想添加另一个构造函数,最终会有相同的擦除问题,因此我选择了暂时使用默认参数来解决问题,如下所示:

public ABC(ArrayList<String> stringArray) {
  …
}
public ABC(ArrayList<Integer> integerArray, boolean… sameErasureFlag) {
  …
}

但我强烈感觉,可能有太多的构造函数并不是这种情况的好设计模式。也许有更好的解决方案或最佳实践设计模式可用于这种情况。我正在查阅建造者模式,但不确定它是否正确/更好。有什么推荐吗?


1
你真的需要提供更多关于实际用例的信息,因为现在看起来你正在尝试创建一个大而全的类,而不是保持你的类简单并让它们只做一件事。你考虑过使用泛型、接口和适当的抽象吗? - Mark Rotteveel
6个回答

10

我有一个类,它有多个构造函数。每个构造函数代表不同的用例。

简单来说,答案是:将每个用例转换为自己独立的类。

如果你有一个类中有多个不相关的字段,而每个“用例”只使用其中的一些字段,则明显表明你的类正在做太多事情

任何类或方法应该只做“一件事”。因此,正如所说的那样:答案就是在这里停下来,而不是向一个类中添加更多东西:问问自己如何有意义地将其拆分。


假设你指的是Bob Martin:一个方法应该只做一件事。类是不同的范围。据我所知,他从来没有建议过一个类只做一件事。恰恰相反。 - jaco0646
我不同意。类的范围仍应该是“一件事”。只是在“更大”的范围内。理想情况下,所有的方法都使用“所有”字段。一旦你能够画出清晰的界限,x/y/z 只被 foo()/bar() 使用,a/b/c 被 horr()/ible() 使用,那么这就是你的类正在做“两件”事情并且应该被拆分的明确指示。 - GhostCat
令人信服的论点,但我认为马丁在他的许多视频课程中相当强调了“一件事”这个观点(其中一些是几年后录制的)。但是有这么多视频,我会很难找到它。 - GhostCat
马丁对SRP的描述在多年来确实发展和成熟了。一方面,我欣赏他对学习和改进的执着。另一方面,我认为SRP周围的许多混乱是他自己造成的。在科学研究中,较新的出版物通常会完善和纠正早期的出版物。因此,我认为最好(也是最简单的)方法是将后来的工作视为取代早期的工作。 - jaco0646
Martin(自2014年以来)的立场是SRP关乎人而非事物。就在上周,他强调了相同观点的这个twitter帖子 - jaco0646
显示剩余3条评论

2

这取决于类对参数的具体操作,我们没有确切的细节,但对于通用类型,您可以做的一件简单的事情是使自己的类成为通用类(在这种情况下可能不需要应用高级设计模式):

public class ABC<T> {
    public ABC(ArrayList<T> stringArray) {
       …
    }
    …
}


ArrayList<Integer> intList = Stream.of(1, 2, 3)
                                   .collect(Collectors.toCollection(ArrayList::new));
ArrayList<String> stringList = Stream.of("a", "b", "c")
                                   .collect(Collectors.toCollection(ArrayList::new));
ABC<Integer> abc1 = new ABC<>(intList);
ABC<String> abc2 = new ABC<>(stringList);

1
但是,如果我想要一个包含不同类型的ArrayList,就无法解决擦除问题。 - drk
@drk 我已经更新了如何使用它,关键在于编译时类型参数可以被编译器用来推断正确的列表类型。当然,在运行时,擦除仍然存在(类型被替换为“Object”)。您还可以创建具有正确类型参数的ABC<T>子类(例如class ABCString extends ABC<String>)。 - M A

2
你的类真的实现了不同的用例,每个用例都由另一个构造函数表示吗?那么这就是答案
如果你只是想为类提供不同的初始化路径,否则它们做相同的事情,无论你用于初始化的数据格式如何,那么拥有多个构造函数可能是合适的选择。
但是当构造函数数量超过一定数量时(有些人说3个,其他人说5个...一些极端分子甚至说1个),你肯定有太多构造函数了,这时你应该考虑使用工厂模式,就像这里所建议的一样。无论你有多少个构造函数,在你考虑引入“虚拟”参数来解决擦除问题时,都应该明确考虑使用工厂模式。工厂模式的优势在于工厂方法的签名不仅仅由参数列表确定,你还可以选择不同的名称(ABC.ofIntList()ABC.ofStringList()等)。

2
我有一种强烈的感觉,可能拥有太多构造函数不是一个好的设计模式。
这要看情况而定。
有些人喜欢使用构造函数,有些人则喜欢其他方法,但我认为你是对的,拥有很多重载构造函数可能会使代码难以阅读和维护。
考虑采用一种替代方案静态工厂方法。最近,很多作者都建议优先考虑静态工厂方法而不是构造函数。
以下是一些偏向于静态工厂方法的原因:

它们提供更好、更清晰和更直观的可读性

你不能给构造函数指定名称。构造函数的名称始终与类名相同;然而,你可能会发现使用命名实际上可以提供更清晰的可读性。

你可以构造并返回子类型

当你使用构造函数时,无法改变所构造对象的类型;然而,工厂方法可以返回从超级类派生的对象,这意味着,你可以根据输入条件决定要构造什么样的确切对象。

1
如果你的类中有太多字段,我建议你的设计不好。有些人可能会建议使用Builder模式。但我认为更好的方法是更尊重面向对象编程,因此最好将您的类分成多个类并使用Decorator模式

0

是的,拥有许多构造函数可能会指出类违反了单一职责原则。 我同意 - 这不足以做出决定。建造者模式可以工作,甚至抽象工厂也可以。 然而 - 考虑使用工厂方法而不是构造函数,正如J.Bloch所建议的那样:

public ABC of(ArrayList<Integer> integerArray, boolean… sameErasureFlag) {

… }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接