17

Constructors can be overloaded as any other method as well and I am aware of that fact. Due to a task I decided to use an abstract superclass with multiple constructors:

Abstract Superclass:

protected ListSortierer()
{
  this( null, null );
}

protected ListSortierer( List<E> li )
{
  this( li, null );
}

protected ListSortierer( Comparator<E> comp )
{
  this( null, comp );     
}

protected ListSortierer( List<E> li, Comparator<E> com )
{
  this.original = Optional.ofNullable( li );
  this.comp = Optional.ofNullable( com );
}

To access each of these constructors I needed multiple constructors in the subclass as well.

BubbleSort.java:

public ListBubbleSort()
{
  super();
}

public ListBubbleSort( List<E> li )
{
  super( li );
}

public ListBubbleSort( Comparator<E> com )
{
  super( com );
}

public ListBubbleSort( List<E> li, Comparator<E> com )
{
  super( li, com );
}

In this case every constructor of the subclass calls the constructor of the superclass immediately.It came to my mind that I could refer to the own constructor again and pass null values:

public ListBubbleSort()
{
  this( null, null );
}

public ListBubbleSort( List<E> li )
{
   this( li, null );
}

public ListBubbleSort( Comparator<E> com )
{
   this( null, com );
}

public ListBubbleSort( List<E> li, Comparator<E> com )
{
   super( li, com );
}

Doing so would allow me to omit 3 of the overloaded constructors in the abstract superclass but would enforce that every subclass follows the same pattern.

My question is: What is the the better approach in case of consistency?Handle missing values in the abstract superclass or in the subclass? Does it make a difference regarding instantiation or is it just a matter of opinion?

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
L.Spillner
  • 1,772
  • 10
  • 19
  • 2
    Of course, this is a subjective opinion. From the perspective of clean code and not having unnecessary duplication, it would be to only have one constructor in the superclass. You'll need to have multiple constructors in the subclass in any case, and the superclass cannot be constructed by client code directly. – Erwin Bolwidt May 03 '18 at 07:11
  • 1
    IMO, I would go with the stricter one in the superclass (requiring two arguments) and the subclass can either follow the same or can provide one arg-constructor that delegates by passing null for the rest – Thiyagu May 03 '18 at 07:12
  • 3
    My advice is using Builder pattern which construct the `ListBubbleSort` instance for you. – John May 03 '18 at 07:13
  • 1
    For sure good practice will be make code properly formatted regarding java convention `{` should be on the same line as method name is. Except this, nice post, but I think its too broad, will be better to put the question to the [code review](https://codereview.stackexchange.com/), if you will do, then please delete post there to prevent cross domain posting. – xxxvodnikxxx May 03 '18 at 07:13
  • 2
    @xxxvodnikxxx you might be right that this question would also fit in [codereview.se] but I clearly wanted to know if there are any technical effects regarding instantiation. And regarding the conventions. This is not only the style of my company but also the so called [Allman convention](https://stackoverflow.com/a/4452315/8181750) which is not uncommen in java. – L.Spillner May 03 '18 at 07:22
  • 4
    Beware of using the phrase "best practice" : http://www.satisfice.com/blog/archives/27, http://innovationexcellence.com/blog/2013/03/09/there-are-no-best-practices/, https://www.forbes.com/sites/mikemyatt/2012/08/15/best-practices-arent – Stephen C May 03 '18 at 07:25
  • 1
    just a side note, what are you going to do with a null list? So I would enforce a non-null List to begin with... – Eugene May 03 '18 at 08:58
  • 1
    @Eugene. Bascially if there is no list the concrete algorithm implemented in the subclass is fed with an empty list and therefore the result will also be an empty list. – L.Spillner May 03 '18 at 09:04
  • 1
    So it turns out that neither of your arguments are required... in such a case a builder pattern is what you are after – Eugene May 03 '18 at 09:08
  • 1
    Is there anything speaking against using only the 2-params ctor and then letting the calling code provide a `new List()` (or `null` for a `Comparator`) if that is what is needed there? Calling the constructor that way should still fit in one line, too. – Raphael Schmitz May 03 '18 at 09:20
  • 1
    @R.Schmitz but this would not make it a such an upvoted question :) – Eugene May 03 '18 at 09:45
  • 2
    @StephenC *Strongly* disagree. At any rate the two articles you posted seem to largely attack a straw man. – Konrad Rudolph May 03 '18 at 09:50
  • 1
    This question belongs on https://codereview.stackexchange.com/ – jaco0646 May 03 '18 at 13:43

5 Answers5

15

What is a better approach in case of consistency?

  1. Make all child constructors private.
  2. Introduce static factory methods.

    ListBubbleSort.withList(List<E> list)
    ListBubbleSort.withComparator(Comparator<E> comparator)
    
  3. Make a call to a proper super constructor. Don't pass any nulls.

    public static <E> ListBubbleSort withList(List<E> list) {
        return new ListBubbleSort(list);
    }
    
    private ListBubbleSort(List<E>) {
        super(list);
    }
    
    protected ListSortierer(List<E>) {
        // initialise only the list field
        this.origin = list;
    }
    
  4. Don't use Optional as a field.

    this.original = Optional.ofNullable(li);

  5. Consider the Builder Pattern if you have 3+ parameters.

Handle missing values in the abstract superclass or in the subclass?

A constructor is supposed to provide initial values. You aren't passing initial values, you are just indicating their absence.

By default, null is the initial value for reference types. So, there is no need to reassign a field if the value for it hasn't been given.

Does it make a difference regarding instantiation or is it just a matter of opinion?

Readability, maintenance.


I would recommend reading Effective Java by Joshua Bloch:

Creating and Destroying Objects

  • Item 1: Consider static factory methods instead of constructors
  • Item 2: Consider a builder when faced with many constructor parameters
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
8

Regarding the question itself: I think that both options aren't ideal.

You strive to write as few code as possible. You are careful about adding overloads just because they seem convenient. Actually, you should do the reverse: think very hard what your your real use cases are, and only support those.

And in your case, the whole point of the exercise seems to be to allow sorting with different implementations. And in that sense, you should rather look into the strategy pattern for example.

In other words: your first thought is always prefer composition over inheritance. And when your design leads you to such questions, the better answer might be to step back from your current design and find a way to enable different sorting as some sort of "service" - instead of backing the list itself together into a sorting thing.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
2

Doing so would allow me to omit 3 of the overloaded constructors in the abstract superclass but would enforce that every subclass follows the same pattern.

Enforcing a contract is achieved via abstract methods or interfaces. You cannot be sure that every subclass will have these constructors, or at least you cannot be sure that every subclass will add these constructors properly.

So, considering Encapsulation, these constructors are better off in the superclass.

isah
  • 5,221
  • 3
  • 26
  • 36
2

Doing so would allow me to omit 3 of the overloaded constructors in the abstract superclass but would enforce that every subclass follows the same pattern.

A subclass doesn't have any constraint to invoke a specific constructor of the parent class. It may invoke any of them while this invoke one of them.
So you could not achieve such a requirement.

About the solution using a static factory method, in some cases it is acceptable and even fine but that it not miracle and has also some limitations.
For example : it doesn't allow to switch to another implementations.
For JDK classes, I agree that it is almost never an issue but for custom class we should use it with cautious.
Besides, nothing prevents a subclass from extending the parent class and to not pass by a factory to create instances of this subclass.
So the quoted requirement is not ensured either.

My question is: What is the the better approach in case of consistency?Handle missing values in the abstract superclass or in the subclass? Does it make a difference regarding instantiation or is it just a matter of opinion?

It is not a question of opinion. It matters.
If the subclass delegates to the parent class and that the checks are always the same, it seems interesting to let the parent class to perform the checks.
But if you don't have guarantee that the parent class be never modified about the checks, the subclasses may create some inconsistency instances if the parent class changes its checks.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • 1
    The last block of your answer has a really strong point that I haven't really considered yet. Thank you for the insight! – L.Spillner May 03 '18 at 12:58
1

You can add a final addAll, to be called in the child constructors as optional extra. For the comparator the semantics differ, it (almost) must be overloaded when optional.

private final Optional<Comparator<E>> comparatorOption;

public final void addAll(List<E> li) {
    ...;
}

protected ListSortierer() {
    comparatorOption = Optional.empty();     
}

protected ListSortierer(Comparator<E> comp) {
    comparatorOption = Optional.of(comp);     
}

Avoiding null parameters seems better.

Prescribing all subclasses to have the same constructors by having many constructors is not bad if you want to. The advantage is that the API for all classes behave the same. It is however unnecessary boiler plate code, that a builder pattern could prevent. In the original old java many constructors were the way to go, but nowadays one sees many builders in APIs.

For a list sorter a fluent API using some kind of builder might be an idea.

So the final answer: more a contextual style decision.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138