Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write subclass constructors without duplicating your code?

I want to call an abstract method, generateId(), in the constructor of an abstract super class, where this abstract method depends on some fields of the respective subclasses. Consider the following pieces of code for clarity:

Abstract class: SuperClass

public abstract class SuperClass {
   protected String id;

   public SuperClass() {
        generateId();
   }

   protected abstract void generateId();
}

Subclass: Sub1

public class Sub1 extends SuperClass {
   private SomeType fieldSub1;

   public Sub1(SomeType fieldSub1) {
      this.fieldSub1 = fieldSub1;
      super();
   }

   protected void generateId() {
      // Some operations that use fieldSub1
   }
}

Subclass: Sub2

public class Sub2 extends SuperClass {
   private SomeOtherType fieldSub2;

   public Sub2(SomeOtherType fieldSub2) {
      this.fieldSub2 = fieldSub2;
      super();
   }

   protected void generateId() {
      // Some operations that use fieldSub2
   }
}

However, the subclass constructors won't work because the super(); must be the first statement in a constructor.

OTOH, if I make super(); the first statement in the constructors of the subclasses, then I won't be able to call generateId() in SuperClass. Because generateId() uses fields in subclasses, where these fields must be initialized before being used.

The only way to "solve" this problem appears to me: Remove the call to generateId() in the superclass. Place a call to generateId() at the end of constructors of each subclass. But this results in code duplication.

So is there any way to solve this problem without duplicating my code? (That is, without calling generateId() at the end of constructors of each subclass?)

like image 915
Utku Avatar asked Jan 05 '16 13:01

Utku


1 Answers

As @GuillaumeDarmont pointed out, using overridable methods in constructs is bad practice.

You want to oblige the superclass id to be initialized by subclasses, so change the constructor:

public abstract class SuperClass {
    protected String id;

    public SuperClass(String id) {
        this.id = id;
    }
}

Also, you might want to change generateId() to be a static method, since you cannot reference this before superclass constructor has been called:

public class Sub1 extends SuperClass {
    private SomeType fieldSub1;

    public Sub1(SomeType fieldSub1) {
        super(generateId(fieldSub1));
        this.fieldSub1 = fieldSub1;
    }

    private static String generateId(SomeType fieldSub1) {
        // Some operations that use fieldSub1
    }
}

EDIT: As SuperClass does not know how to calculate the id, but you want to enforce it has an id, one of your options is the solution above. Another option would be:

public abstract class SuperClass {
    private String id;

    public String getId() {
        if (id == null) { id = generateId(); }
        return id;
    }
    protected abstract String generateId();
}


public class Sub1 extends SuperClass {
    private SomeType fieldSub1;

    public Sub1(SomeType fieldSub1) {
        this.fieldSub1 = fieldSub1;
    }

    @Override protected String generateId() {
        // Some operations that use fieldSub1
    }
}

The difference between both solutions is about when the id will be calculated: at the object initialization time, or the first time the id is requested. That is what @Turing85 was discussing.

like image 56
ericbn Avatar answered Nov 15 '22 00:11

ericbn