【问题标题】:Initialize superclass variables (needed in constructor) in a subclass在子类中初始化超类变量(在构造函数中需要)
【发布时间】:2016-01-05 06:35:55
【问题描述】:

我正在编写一个简单的小行星克隆游戏,使用Swing 来显示图形。我有点关注Derek Banas' tutorials,但决定自己扩展。

最初的想法是游戏中的每个图形元素(即小行星、宇宙飞船和子弹)都扩展了Polygon 类。他们的构造函数看起来像这样:

public class SpaceShip extends Polygon {

    //x and y coordinates
    public static int[] polyXArray = {...};
    public static int[] polyYArray = {...};

    //other class variables
    {...}

    public SpaceShip() {

        super(polyXArray, polyYArray, polyXArray.length);
    }
}

其他图形元素也是类似的。

编辑:这里的关键元素是这两个数组不存储对象的实际坐标,而是它们相对于中心的位置,其坐标为double-type 类变量。因此,数组仅描述对象的 shape,而子类 move() 方法将影响中心的坐标。负责实际绘图的类将调用move() 方法,然后应用仿射变换来移动和旋转形状(根据正确定义的角度参数)。我这样做是为了避免与处理double 算术相关的精度问题。

现在,由于元素共享许多“相等”变量(它们的中心坐标,我需要它来用仿射变换转换它们,它们的速度分量等......)和方法(getter 和 setter,@ 987654330@ 方法等...)我想过让它们成为一个抽象类的扩展——比如说,GameShape——它包含所有这些常用方法和变量。 GameShape 现在将直接扩展 Polygon

public abstract class GameShape extends Polygon {

        //x and y coordinates, still unassigned
        public static int[] polyXArray, polyYArray;

        //other class variables
        {...}

        public GameShape() {

            super(polyXArray, polyYArray, polyXArray.length);
        }
}

然后,当我定义不同的子类以绘制所需的不同形状时,我想将所需的值分配给 polyXArraypolyYArray,但我一直无法找到一种方法去做吧。

我确实希望这些变量是静态的,因为它们是单个类的特定属性,并且我不想在每次实例化新对象时将它们作为参数传递。

我的情况与this question 中描述的情况非常相似,但建议的解决方案似乎不起作用,因为我需要构造函数中的那些变量。有没有办法克服或绕过这个问题?不管过程如何,我的主要目标是拥有一个所有图形元素通用的超类,以避免数十行复制粘贴的代码。

【问题讨论】:

  • 您是说您希望polyXArraypolyYArray 是静态的吗?
  • 是的,它们会保存我的图形点的坐标,所以我认为将它们与它们所指的特定图形相关联是个好主意。我看到在超类中将它们声明为静态可能有点问题和矛盾,但我不知道如何解决这个问题。
  • 我不认为这是一个明智的设计决定。您希望所有实例都具有相同的点。您希望它们都相互独立。 polyXArraypolyYArray 应该是实例变量
  • Polygon 看起来像什么?为什么一定要super(polyXArray, polyYArray, polyXArray.length);
  • @KaMai 我想我开始明白了。对于子类的所有实例,您具有相同的基本坐标。不同实例之间的区别在于,相同的坐标变换方式不同。对吗?

标签: java inheritance constructor subclass superclass


【解决方案1】:

我认为polyXArraypolyYArray 位于Polygon 类中;那是他们所属的地方。因此,拥有重复字段不是一个好主意。另外,去掉 ned 来调用 super 构造函数。我会这样设计类结构:

public class SquareShape extends Polygon {
    private int size;

    public SquareShape(int x, int y, int size) {
        this.size = size;
        int[] xpoints = new int[4]{
                x - size / 2,
                x - size / 2,
                x + size / 2,
                x + size / 2
        };
        int[] ypoints = new int[4]{
                y - size / 2,
                y + size / 2,
                y + size / 2,
                y - size / 2
        };
        setXArray(xpoints);
        setYArray(ypoints);
    }
}

这样,您可以确保所有SquareShape 对象确实具有方形,但您可以自定义您应该能够自定义的东西。像位置和大小一样,它们不应该是静态共享字段。 setXArraysetYArray 应该是位于 Polygon 中的 protected 方法。您不希望外界扰乱各个点。不过,您可以添加公共 getter。

注意

您可能需要考虑使用复杂的Point 类型的单个数组,而不是两个紧密耦合且依赖的数组。我觉得这将大大简化您项目中的许多任务。

【讨论】:

    【解决方案2】:

    数组字段不能为static,因为不同的形状有不同的坐标。此外,您不需要在特定子类中使用这些数组,因为它们已经在 PolygonGameShape 中。

    这或多或少是我将如何编写GameShape(尽管我同意@Michael,您不需要将polyXArraypolyXArray.length 都传递给构造函数)。

    public abstract class GameShape extends Polygon {
    
        // I got rid of the array fields as I think they are in Polygon anyway.
    
        //other class variables
        {...}
    
        // I added arguments to your constructor.
        public GameShape(int[] polyXArray, int[] polyYArray) {
    
            super(polyXArray, polyYArray, polyXArray.length);
        }
    }
    

    麻烦的是super必须是构造函数的第一行,但是你可以使用私有方法来构建数组:

    public final class BoringRectangle extends GameShape {
    
        public BoringRectangle(int left, int right, int top, int bottom) {
            super(xArray(left, right), yArray(top, bottom));
        }
    
        private static int[] xArray(int left, int right) {
            return new int[] {left, right, right, left};
        }
    
        private static int[] yArray(int top, int bottom) {
            return new int[] {bottom, bottom, top, top};
        }
    }
    

    【讨论】:

    • 我不认为 BoringRectangle 编译 :)
    • @bayou.io 我已将辅助方法设为静态。感谢您不投反对票!
    • 我从不投反对票。在调用超级构造函数之前访问this 似乎是绝对不可能的;除非超类故意提供一些后门,例如在构造函数中调用抽象方法!我找到了另一个workaround
    【解决方案3】:

    如果您确实想在构造函数中初始化事物,只需调用空的super();,然后循环抽象getPolyXArray()getPolyYArray() 以提供addPoint

    public abstract class GameShape extends Polygon {
    
        public GameShape() {
            super();
    
            final int length = getPolyXArray().length;
            for (int i = 0; i < length; i++) {
                addPoint(getPolyXArray()[i], getPolyYArray()[i]);
            }
        }
    
        public abstract int[] getPolyXArray();
        public abstract int[] getPolyYArray();
    
        //common stuff...
    }
    
    
    public class Asteroids extends Polygon {
        public int[] getPolyXArray() { return new int[]{1, 2, 3}; }
        public int[] getPolyYArray() { return new int[]{1, 2, 3}; }
    }
    

    【讨论】:

      【解决方案4】:

      您有成对的数组来描述特定类型游戏对象的形状。如果不同的游戏对象可以有不同的形状,那么它们就不能共享一对数组,就像它们是所有游戏对象类的公共超类的静态属性一样。相同类型的不同对象可以共享同一对数组(假设不需要在每个对象的基础上修改),这可能对应于这些数组是具体的游戏对象类。但是,在这种情况下,如果您希望这些类的超类能够访问给定游戏对象的正确形状数据,则必须告诉它这些形状数据是什么。

      您可以通过两种主要方式做到这一点:

      1. 您可以将适当的形状数组传递给超类的构造函数。你说你不想这样做,但我不明白为什么。

      2. 您可以在子类应该覆盖的超类上定义访问器方法以提供正确的形状数据(这称为模板方法模式)。

      【讨论】:

        【解决方案5】:

        this question 的解决方案如果您的类不会扩展形状,而是通过访问器 + 私有静态字段提供形状,则将起作用。

        public abstract class GameObject {
            ...
            public abstract Polygon getShape();
        

        这也有助于避免形状重复。

        【讨论】:

          【解决方案6】:

          编辑:

          正如 cmets 中的 VGR 所述,这不会编译。所以,我们将不得不稍微改变实现,即,我们将使用 HAVE 关系而不是 IS 关系:-)

          首先,不要将多边形数组字段设为静态。如果这样做,它们对于所有子类也将是相同的,那么有什么意义呢?

          其次,在这里使用模板方法设计模式。您的课程将如下所示:

          public abstract class GameShape {
          
                  //x and y coordinates, still unassigned
                  public int[] polyXArray, polyYArray;
          
                  private Polygon polygon;
          
                  //other class variables
                  {...}
          
                  public GameShape() {
                      instantiatePolyArrays();
                      this.polygon = new Polygon(polyXArray, polyYArray, polyXArray.length);
                  }
          
                  protected abstract void instantiatePolyArrays();
          
                  public final Polygon getPolygon(){
                      return this.polygon;
                  }
          }
          

          每个扩展类都必须重写此方法,您可以在每个方法重写中为每个类实例化数组。

          另外,关于 IS-HAVE 关系问题 - 您在示例中介绍的是 IS 关系,其中 GameShape 对象是 Polygon,因此需要调用超级构造函数和问题接着就,随即。在我的解决方案中,这被 HAVE 关系所取代,其中GameShape 对象内部有一个Polygon 对象,可通过 getPolygon() 方法访问。这让你有很多额外的灵活性:-)

          【讨论】:

          • 那不会编译。对super(…) 的调用必须是构造函数中的第一条语句。
          • 没错,通过一点关系交换更正了我的答案。
          • 这很好用,非常符合我的目标:我的对象确实有形状,但也有许多其他属性,所以 HAVE 关系最适合我。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-10-23
          • 2017-01-12
          • 2020-09-27
          相关资源
          最近更新 更多