Java - Issue with calling abstract method during instantiation in super before child fields have been set

Viewed 61

This is my first time using these forums, so I hope this thread is appropriate and that I don't mess up. This is in Java - I'll try and simplify the example to focus on the main problem. Any help would be appreciated!

Say I have two classes, Parent and Child, where Parent is an abstract class and Child extends Parent. (I'm writing this code on the fly)...

public abstract class Parent {
    // A bunch of fields that have been omitted...
    public Parent() {
        print(getX());
    }

    public abstract int getX();
}
public class Child extends Parent {
    private int x;

    public Child(int x) {
        super();
        this.x = x;
    }

    @Override
    public int getX() {
        return x;
    }
}

I want to instantiate Child, and call an abstract method immediately after instantiation. Obviously, currently there is an issue because the x field hasn't been set when getX() is called in the parent from super(), and therefore will always return 0.

However, I want this to be done in the parent for all children of Parent (if there is a Child2, Child3, etc. that extend Parent in the future). i.e. anything that extends Parent will have their "x" value printed. The work around to this would be to print getX() with every Child that is instantiated, but for some reason, it seems rather off to me to have to do this repeatedly inside every Child class, as opposed to having this captured in the Parent.

Any suggestions as to how I should approach this (calling an abstract method during instantiation)? Design pattern or design changes? Use the repeated call in the Child? Thanks :)

1 Answers

As akuzminykh correctly stated, if the x field is part of all classes derived from Parent, then the field is a Parent field. However sometimes this is not the case, and then you would want the child fields to be set, before the parent is completely living.

This is a common problem in instantiation order, and as you stated correctly:

[...] there is an issue because the x field hasn't been set when getX() is called in the parent from super() [...]

Due to this, that kind of call cannot be made inside a parent constructor. There are some common approaches to this, most involving some kind of init() method being called, after the instance has been completely constructed.

One pattern doing this (without the use of heavy weight dependency injection) is the factory pattern. See https://www.tutorialspoint.com/design_pattern/factory_pattern.htm for detailed information.

The Factory pattern is designed in such a way, that an instance is not only created by a constructor, but ultimately returned by a factory, which is given values that are used for instantiation. That factory can guarantee the call of an init-method, after the instance is completed.

abstract class Parent {
    // Notice the protected modifier!
    protected Parent() {
        // field initializations of Parent.
    }

    protected void init() {
        // I replaced print with System.out.println here ot make it compile.
        System.out.println(getX());
    }

    public abstract int getX();
}

abstract class ParentFactory<T extends Parent> {
    public abstract T instance();
}

public class Child extends Parent {
    private int x;

    protected Child(int x) {
        super();
        this.x = x;
    }

    @Override
    public int getX() {
        return x;
    }
}

class ChildFactory extends ParentFactory<Child> {

    int x = 0;

    public void setX(int x) {
        this.x = x;
    }

    @Override
    public Child instance() {
        Child instance = new Child(x);
        instance.init();
        return instance;
    }
}

@Test
public void test() {
    ChildFactory factory = new ChildFactory();
    factory.setX(7);
    Child child = factory.instance();
    System.out.println("And child has x: " + child.x);
}

Notice that this kind of factory (a stateful factory) is very similar to a Builder from the Builder pattern. It is different only in that it is usually reused, rather than created on the spot. You can vary this pattern in that there is also an explicit method in ChildFactory which takes explicit parameters over the state-fields of ChildFactory:

public Child instance(int x) {
    Child instance = new Child(x);
    instance.init();
    return instance;
}
Related