【问题标题】:How to write subclass constructors without duplicating your code?如何在不复制代码的情况下编写子类构造函数?
【发布时间】:2016-04-09 07:52:06
【问题描述】:

我想在一个抽象超类的构造函数中调用一个抽象方法generateId(),这个抽象方法依赖于各个子类的一些字段。为了清楚起见,请考虑以下代码:

抽象类:SuperClass

public abstract class SuperClass {
   protected String id;

   public SuperClass() {
        generateId();
   }

   protected abstract void generateId();
}

子类: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
   }
}

子类: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
   }
}

但是,子类构造函数不起作用,因为super(); 必须是构造函数中的第一条语句。

OTOH,如果我在子类的构造函数中将super(); 设为第一条语句,那么我将无法在SuperClass 中调用generateId()。因为generateId()使用子类中的字段,这些字段在使用前必须先初始化。

在我看来,“解决”这个问题的唯一方法是:删除超类中对generateId() 的调用。在 each 子类的构造函数末尾调用generateId()。但这会导致代码重复。

那么有什么方法可以在不复制我的代码的情况下解决这个问题? (也就是说,没有在each子类的构造函数末尾调用generateId()?)

【问题讨论】:

  • 如果你让SomeTypeSomeOtherType 有一个共同的父级,然后将ParentType 传递给SuperClass 构造函数呢?
  • @Arc676 我无法想象它。你能发布一个包含代码的答案吗?
  • 您可能想检查这个问题:stackoverflow.com/questions/3404301/…。简单地说:不要在构造函数中使用可覆盖的方法。
  • 您能解释一下您的具体问题是什么吗?我想这可能是XY-Problem
  • @Utku 为什么必须在初始化时生成 id?为什么第一次使用id时不能及时计算id?

标签: java oop inheritance


【解决方案1】:

您可以尝试使用实例或静态初始化块。

初始化顺序:

  1. 源代码顺序中的所有静态项。
  2. 所有成员变量。
  3. 构造函数被调用。

例如:

class InitBlocksDemo {

    private String name ;

    InitBlocksDemo(int x) {
        System.out.println("In 1 argument constructor, name = " + this.name);
    }

    InitBlocksDemo() {
        name = "prasad";
        System.out.println("In no argument constructor, name = " + this.name);

    }

    /* First static initialization block */
    static {
        System.out.println("In first static init block ");
    }

    /* First instance initialization block  */
    {
        System.out.println("In first instance init block, name = " + this.name);
    }

    /* Second instance initialization block */
    {
        System.out.println("In second instance init block, name = " + this.name);
    }

    /* Second static initialization block  */
    static {
        System.out.println("In second static int block ");
    }

    public static void main(String args[]) {
        new InitBlocksDemo();
        new InitBlocksDemo();
        new InitBlocksDemo(7);
    }

}

输出:

In first static init block
In second static int block
In first instance init block, name = null
In second instance init block, name = null
In no argument constructor, name = prasad
In first instance init block, name = null
In second instance init block, name = null
In no argument constructor, name = prasad
In first instance init block, name = null
In second instance init block, name = null
In 1 argument constructor, name = null

示例来源:link

【讨论】:

  • 我强烈反对这种方法,因为它牺牲了代码的可读性。
  • @Turing85 这只是作者的另一种选择。也许这将是两害相权取其轻。
【解决方案2】:

你可以让SuperClass 有一个ParentType 变量,然后在子类中转换类型。很抱歉我在 cmets 中所说的话,术语有点不对劲。您不能在子类中更改超类变量的类型。但是你可以投射。

我刚刚意识到您不能传递超类并将其转换为子类。你只能走另一条路,就像"all dogs are animals but not all animals are dogs"

无论如何,我想到了另一种可能的解决方案。首先是变量:

public class ParentType { //stuff }

public class SomeType extends ParentType { //more stuff }

public class SomeOtherType extends ParentType { //even more stuff }

您可以使用来自this post 的想法。顺便说一句,我在这里有点冒险进入未知领域,我以前没有这样做过。如果有人发现此错误,请发表评论或编辑。

让你的类通用:

public abstract class SuperClass<Type extends ParentType> {
    protected Type field;
}

public class Sub1<SomeType> {
    //now field is of type SomeType
}

public class Sub2<SomeOtherType> {
    //now field is of type SomeOtherType
}

【讨论】:

  • 感谢您的贡献,但 AFAIK,在 oop 语义中 这个解决方案不正确吗?也就是说:SuperClass 实际上不应该是一个泛型类。
  • 我不太确定。正如我所说,我以前从未尝试过这个,所以这都是理论上的。我认为这不可能,我的谷歌搜索给了我这个解决方案的链接,所以我认为它可能适用。如果我有时间,我可能会抽空测试一下,但SuperClass 总是有一些功能禁止它成为abstract
【解决方案3】:

正如@GuillaumeDarmont 指出的那样,在构造中使用可覆盖的方法是不好的做法。

你想让超类id被子类初始化,所以改变构造函数:

public abstract class SuperClass {
    protected String id;

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

此外,您可能希望将generateId() 更改为静态方法,因为在调用超类构造函数之前您无法引用this

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

编辑:由于SuperClass 不知道如何计算id,但您想强制它有一个ID,您的选择之一就是上面的解决方案。另一种选择是:

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

两种解决方案的区别在于何时计算 id:在对象初始化时,或第一次请求 id 时。这就是@Turing85 所讨论的内容。

【讨论】:

  • 您确定 java 接受您发布的最后一个构造吗?
  • @ericbn 这是一个解决方案,但这在语义上有多正确?即:给SuperClass的构造函数的参数实际上不是输入,而是可以用手头的可用数据计算出来的。那么这样写是不是让它看起来像是需要作为输入获得的东西?
猜你喜欢
  • 2016-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-13
相关资源
最近更新 更多