3

I have a class which has multiple constructors. Each represent different use cases.

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

Constructor overloading was the clean solution so far until I encountered same erasure issues from java compiler. For example, I want to add another constructor which end up having same erasure, so I just chose to include a default parameter to work around for now like below:

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

But I have a strong feeling probably having so many constructors is not a good design pattern for this use case.. Maybe there is a better solution or best practice design pattern that is used for such a scenario. I am looking up on the builder pattern, but not sure if that is right/better one. Any recommendations?

Mark Rotteveel
  • 82,132
  • 136
  • 114
  • 158
drk
  • 153
  • 1
  • 13
  • 1
    You really need to provide more information about the actual use case, because right now it looks like you're trying to create a god class instead of keeping your class simple and have them do only one thing. Have you considered generics, using an interface and appropriate abstractions? – Mark Rotteveel Mar 19 '21 at 09:34

6 Answers6

7

I have a class which has multiple constructors. Each represent different use cases.

Then the simple answer is: turn each use case into its own distinct class.

The idea that you have multiple unrelated fields in a class, and each "use case" only uses some of them is a clear indication that your class is doing too many things.

Any class or method should do "one thing" only. So, as said: the answer is to stop right there, and instead of adding more things into one class: ask yourself how you could meaningfully rip it apart.

GhostCat
  • 127,190
  • 21
  • 146
  • 218
  • Assuming you're referring to Bob Martin: a method should do one thing only. A class is a different [scope](https://stackoverflow.com/a/57085753/1371329). To my knowledge he has never suggested that a class should do one thing. Quite the opposite. – jaco0646 Mar 19 '21 at 13:12
  • I disagree. The scope of a class should still be "one thing". Just on a "larger" scope. Ideally, all your methods use "all" your fields. As soon as you can draw clear lines and x/y/z gets only used by foo()/bar() and a/b/c get used by horr()/ible() then that is a clear indication that your class is doing "two" things and should be ripped apart. – GhostCat Mar 19 '21 at 13:35
  • To be clear, are you attributing this perspective to Bob Martin, or is this your own personal view? – jaco0646 Mar 19 '21 at 14:10
  • Martin is a strong supporter of the single responsibility principle. You think that only applies to methods? – GhostCat Mar 20 '21 at 07:44
  • Martin created the SRP; but it is separate from and unrelated to "do one thing". This is the (common) confusion addressed in the link in my first comment above. – jaco0646 Mar 20 '21 at 13:16
  • Convincing arguments, but I think that Martin emphasizes the "one thing" idea quite a bit in one of his many video lessons (some of them were recorded years later) But there are so many of them that I would have a hard time finding it. – GhostCat Mar 21 '21 at 08:21
  • Martin's descriptions of the SRP have certainly evolved and matured over the years. On one hand, I appreciate his dedication to learning and improvement. On the other, I think a lot of the confusion around the SRP is of his own making. It is common in scientific studies for newer publications to refine and correct older ones. For this reason I think the best (and simplest) approach is to consider later work as superseding the earlier. – jaco0646 Mar 22 '21 at 13:40
  • Martin's current stance (since at least 2014) is that the SRP is about people (not things). Just last week he endorsed this [twitter thread](https://twitter.com/macerub/status/1371147795873288199) with the same perspective. – jaco0646 Mar 22 '21 at 13:40
2

It depends on what exactly the class is doing with the parameters, we don't have the exact details, but one simple thing you can do for generic types is to make your own class generic (and maybe no need to apply a fancy design pattern in that case):

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);
M A
  • 65,721
  • 13
  • 123
  • 159
  • 1
    but this wont help me with the erasure issue if I want to have an ArrayList of diiferent types as given in the question. – drk Mar 19 '21 at 18:45
  • @drk I've updated on how to use it, the point is that at compile time the type argument can be used by the compiler to infer the correct list type. Of course at runtime, erasure is still there (the type is replaced by `Object`). You can also create subclasses of `ABC` with the correct type argument (e.g. `class ABCString extends ABC`). – M A Mar 22 '21 at 07:55
2

Is your class really implementing different use cases, each represented by another constructor? Then this is the answer!

If you just want to provide different initialisation paths for your class that otherwise does the same things, no matter of the format of the data you used for initialisation, then having multiple constructors might be the proper thing.

But when exceeding a certain number of constructors (some says 3, others 5 … some extremists say even 1), you have definitely too many of them, and then you should consider the factory pattern, as suggested here. This should be considered definitely – no matter how many constructors you have – when you think about the introduction of 'dummy' parameters to resolve erasure issues. The advantage of the factory pattern is that the factory method's signature is not determined by the parameters list only, you can also choose a different name (ABC.ofIntList(), ABC.ofStringList() and so on …).

tquadrat
  • 1,653
  • 12
  • 16
2

I have a strong feeling probably having so many constructors is not a good design pattern..

It depends.

Some people prefer constructors, some - other approaches, but I think you are right, that having a lot of overloaded constructors might be cumbersome to read and maintain.

One alternative to consider is the Static Factory Methods. Lately, a lot of authors suggest favouring static factory methods over constructors.

There are few reasons why you might want to favour them:

They give you better, cleaner and more intuitive readability

You can’t give names to constructors. The constructor name is always the same as the class name; however, you might find useful to use naming, as they will provide way clearer readability.

You can construct and return subtypes

When you use constructor, you can’t vary the type of the constructed object; however, factory method can return objects derived from a super class, which means, that you can decide on what exactly to construct, depending on the input criteria.

Giorgi Tsiklauri
  • 6,699
  • 7
  • 29
  • 54
1

If you have too many fields in your class I would suggest your design is bad. Some may advice using Builder Pattern. But I think it is better to show more respect to OOP, so I think is better to break your class into multiple classes and use Decorator Pattern.

Cristian
  • 177
  • 5
0

Yeah, having many constructors can point out that class breaks Single Responsibility principle. I agree - it's not enough info to make a decision. Builder pattern can work or even abstract factory. However - consider using factory method instead of constructors as adviced J.Bloch:

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

… }