如何创建“抽象领域”?

时间:2010-04-10 13:49:02

标签: java constructor abstract-class

我知道java中不存在抽象字段。我也读过this question,但提出的解决方案并不能解决我的问题。也许没有解决方案,但值得问一下:)

问题

我有一个抽象类,它在构造函数中执行操作,具体取决于其中一个字段的值。 问题是该字段的值将根据子类而改变。 我该怎么做才能对子类重新定义的字段的值进行操作?

如果我只是“覆盖”子类中的字段,则对抽象类中字段的值进行操作。

我愿意接受任何确保在子类实例化期间完成操作的解决方案(即将操作放在构造函数中每个子类调用的方法中不是有效的解决方案,因为有人可能会扩展抽象类而忘记调用方法。)

另外,我不想将字段的值作为构造函数的参数。

有没有解决办法,或者我应该改变我的设计?


编辑:

我的子类实际上是我的主程序使用的一些工具,因此构造函数必须是公共的并且完全接受它们将被调用的参数:

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

(子类是Hand,Pencil和AddObject,它们都扩展了抽象类工具)

这就是为什么我不想改变构造函数。

我即将使用的解决方案是将上述代码略微更改为:

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

并使用抽象的getter来访问该字段。

8 个答案:

答案 0 :(得分:14)

  

另外,我不想给出价值   作为论证的领域   构造

为什么不呢?这是完美的解决方案。使构造函数protected并且不提供默认构造函数,并且子类实现者被强制在其构造函数中提供一个值 - 它可以是公共的并将常量值传递给超类,使得参数对子类的用户不可见。

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);
    }
}

答案 1 :(得分:13)

对于字段的抽象getter / setter怎么样?

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";
    }
}

答案 2 :(得分:2)

使用模板模式怎么样?

public abstract class Template {

    private String field;

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

    abstract String init();
}

通过这种方式,您可以强制所有子类实现init()方法,由于构造函数调用该方法,因此将为您分配字段。

答案 3 :(得分:1)

您不能在构造函数中执行此操作,因为超类将在子类中的任何内容之前初始化。因此,访问特定于子类的值将在超级构造函数中失败 考虑使用工厂方法来创建对象。例如:

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

您在此特定示例中遇到问题,其中MyClass无法进行子类化,但您可以使构造函数受到保护。确保您的基类也具有此代码的公共/受保护构造函数。这只是为了说明你可能需要两步初始化你想做什么。

您可以使用的另一个可能的解决方案是使用Factory类来创建此抽象类的所有变体,并且可以将该字段传递给构造函数。您的工厂将是唯一了解该领域的工厂,工厂的用户可能会忘记它。

编辑:即使没有工厂,您也可以使您的抽象基类需要构造函数中的字段,以便所有子类在实例化时都必须向其传递值。

答案 4 :(得分:1)

如果值是由子类的类型决定的,为什么你需要一个字段呢?您可以使用一个简单的抽象方法来实现为每个子类返回不同的值。

答案 5 :(得分:1)

  

另外,我不想将字段的值作为构造函数的参数。

     

有没有解决办法,或者我应该改变我的设计?

是的,我认为你应该改变你的设计,以便子类将值传递给构造函数。由于在超类构造函数返回之后的之前,对象的子类部分才会被初始化,所以实际上没有其他干净的方法。当然,这样做了:

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

...因为abstractField()的实现不对对象状态进行操作。但是,您不能保证子类不会认为更动态一点是个好主意,让abstractField()返回一个非常量值:

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; }
}

这不符合您的期望,因为子类的初始化器和构造器运行在超类的初始化器和构造器之后。 new Sub2()new Sub3(42)都会打印Abstract field: 0,因为调用valueabstractField()字段尚未初始化。

将值传递给构造函数还有一个额外的好处,即您存储值的字段可以是final

答案 6 :(得分:0)

我认为你需要一个可以对该参数起作用的工厂(也就是“虚拟构造函数”)。

如果用给定的语言很难做到,你可能会错误地思考它。

答案 7 :(得分:0)

如果我理解正确:您希望抽象类的构造函数根据抽象类中的字段执行某些操作,但是由子类设置(希望如此)?

如果我弄错了你可以停止阅读......

但是,如果我做对了,那么你就是在尝试做一些不可能的事情。类的字段以词法顺序实例化(因此,如果声明字段“下方”或“之后”,则构造函数将在调用构造函数之前不实例化)。另外,在对子类做任何事情之前,JVM运行整个超类(这就是为什么子类的构造函数中的“super()”调用需要是构造函数中的第一条指令...因为这仅仅是“建议”关于如何运行超类的构造函数的JVM。)

因此,只有在超类完全实例化之后(并且超类的构造函数已经返回),子类才开始实例化。

这就是你不能拥有抽象字段的原因:抽象类中不存在抽象字段(但只存在于子类中),因此对于超级(抽象)类来说是严格的(!)“禁止” ...因为JVM无法绑定对该字段的任何引用(因为它不存在)。

希望这有帮助。