views:

202

answers:

7

I know abstract fields do not exist in java. I also read this question but the solutions proposed won't solve my problem. Maybe there is no solution, but it's worth asking :)

Problem

I have an abstract class that does an operation in the constructor depending on the value of one of its fields. The problem is that the value of this field will change depending on the subclass. How can I do so that the operation is done on the value of the field redefined by the subclass ?

If I just "override" the field in the subclass the operation is done on the value of the field in the abstract class.

I'm open to any solution that would ensure that the operation will be done during the instantiation of the subclass (ie putting the operation in a method called by each subclass in the constructor is not a valid solution, because someone might extend the abstract class and forget to call the method).

Also, I don't want to give the value of the field as an argument of the constructor.

Is there any solution to do that, or should I just change my design ?


Edit:

My subclasses are actually some tools used by my main program, so the constructor has to be public and take exactly the arguments with which they will be called:

tools[0]=new Hand(this);
tools[1]=new Pencil(this);
tools[2]=new AddObject(this);

(the subclasses are Hand, Pencil and AddObject that all extend the abstract class Tool)

That's why I don't want to change the constructor.

The solution I'm about to use is to slightly change the above code to:

tools[0]=new Hand(this);
tools[0].init();
tools[1]=new Pencil(this);
tools[1].init();
tools[2]=new AddObject(this);
tools[2].init();

and use an abstract getter to acces the field.

A: 

I think you need a factory (aka "virtual constructor") that can act on that parameter.

If it's hard to do in a given language, you're probably thinking about it incorrectly.

duffymo
+3  A: 

How about abstract getter/setter for field?

abstract class AbstractSuper {
    public AbstractSuper() {
        if (getFldName().equals("abc")) {
            //....
        }
    }

    abstract public void setFldName();
    abstract public String getFldName();
}

class Sub extends AbstractSuper {
    @Override
    public void setFldName() {
        ///....
    }

    @Override
    public String getFldName() {
        return "def";
    }
}
Crozin
Subclass fields are initialized after the super constructor has executed, so this will not work as intended.
meriton
That actually works if you don't use a field but put the value directly as the return value of the "getter" as in the code example given (so that's no longer a proper getter...)But thanks, that's better than nothing, if no other answer come I'll use that !
Jules Olléon
Isn't that because it's a constant then? The compiler is probably doing something smart with it. Anybody know?
SB
Using @Overrdie annotations can be hazardous to your app's compilability
Daniel
I'll actually use a 2-step initialization as suggested by SB, along with an abstract getter as suggested by Crozin. That way the issue raised by meriton (fields are initialized after the super constructor has executed) is solved. Thanks all !
Jules Olléon
+1  A: 

You can't do this in the constructor since the super class is going to be initialized before anything in the subclass. So accessing values that are specific to your subclass will fail in your super constructor.
Consider using a factory method to create your object. For instance:

private MyClass() { super() }
private void init() { 
    // do something with the field
}
public static MyClass create() {
    MyClass result = new MyClass();
    result.init();
    return result;
}

You have an issue in this particular sample where MyClass can't be subclassed, but you could make the constructor protected. Make sure your base class has a public / protected constructor also for this code. It's just meant to illustrate you probably need two step initialization for what you want to do.

Another potential solution you could use is using a Factory class that creates all variants of this abstract class and you could pass the field into the constructor. Your Factory would be the only one that knows about the field and users of the Factory could be oblivious to it.

EDIT: Even without the factory, you could make your abstract base class require the field in the the constructor so all subclasses have to pass in a value to it when instantiated.

SB
I don't want to add arguments to the constructor, but I'll actually use a 2-step initialization as you suggest, along with an abstract getter as suggested by Crozin. Thanks !
Jules Olléon
+6  A: 

Also, I don't want to give the value of the field as an argument of the constructor.

Why not? It's the perfect solution. Make the constructor protected and offer no default constructor, and subclass implementers are forced to supply a value in their constructors - which can be public and pass a constant value to the superclass, making the parameter invisible to users of the subclasses.

public abstract class Tool{
    protected int id;
    protected Main main;
    protected Tool(int id, Main main)
    {
        this.id = id;
        this.main = main;
    }
}

public class Pencil{
    public static final int PENCIL_ID = 2;
    public Pencil(Main main)
    {
        super(PENCIL_ID, main);
    }
}
Michael Borgwardt
My subclasses are actually some kind of modules used by my main program, so the constructor has to be public and take exactly the arguments with which they will be called (as declared in the superclass). So and I don't see how I could use your solution in that case.
Jules Olléon
In the base class have a protected constructor which takes the parameter, and in the subclass have whatever public constructors you want, just pass the desired parameter to the superclass constructor: super(MY_CONSTANT) +1 to Michael's suggestion
fish
Using this solution, is there any way I could force the subclasses to implement the public constructor "public XXXX(Main main)" ?
Jules Olléon
No, there's no way to control subclass constructors.
Michael Borgwardt
+1  A: 

If the value is determined by the type of subclass, why do you need a field at all? You can have a simple abstract method which is implemented to return a different value for each subclass.

Peter Lawrey
+1  A: 

Also, I don't want to give the value of the field as an argument of the constructor.

Is there any solution to do that, or should I just change my design ?

Yes, I think you should change your design so that the subclass passes the value to the constructor. Since the subclass portion of your object isn't initialized until after the superclass constructor has returned, there's really no other clean way of doing it. Sure, this'd work:

class Super {
    protected abstract int abstractField();
    protected Super() { System.out.println("Abstract field: " + abstractField); }
}
class Sub { 
    protected int abstractField(){ return 1337; }
}

... since the implementation of abstractField() doesn't operate on object state. However, you can't guarantee that subclasses won't think it's a great idea to be a little more dynamic, and let abstractField() returns a non-constant value:

class Sub2 {
    private int value = 5; 
    protected int abstractField(){ return value; }
    public void setValue(int v){ value = v; }
}
class Sub3 {
    private final int value; 
    public Sub3(int v){ value = v; }
    protected int abstractField(){ return value; }
}

This does not do what you'd expect it to, since the initializers and constructors of subclasses run after those of the superclass. Both new Sub2() and new Sub3(42) would print Abstract field: 0 since the value fields haven't been initialized when abstractField() is called.

Passing the value to the constructor also has the added benefit that the field you store the value in can be final.

gustafc
+1  A: 

How about using the Template pattern?

public abstract class Template {

    private String field;

    public void Template() {
        field = init();
    }

    abstract String init();
}

In this way, you force all subclasses to implement the init() method, which, since it being called by the constructor, will assign the field for you.

matsev