在子类中初始化超类变量(构造函数中需要)

Initialize superclass variables (needed in constructor) in a subclass

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

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

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类型class -多变的。因此,数组仅描述对象的 shape,而 subclass move() 方法将影响中心的坐标。负责实际绘图的 class 将调用 move() 方法,然后应用仿射变换来移动和旋转形状(根据正确定义的角度参数)。我这样做是为了避免与处理 double 算术相关的精度问题。

现在,由于元素共享很多 "equal" 变量(它们的中心坐标,我需要用仿射变换来转换它们,它们的速度分量等...)和方法(吸气剂和 setter,move() 方法等...)我想过让它们成为抽象 class 的扩展 - 比如 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);
        }
}

然后,当我定义不同的 subclasses 时,我想将所需的值分配给 polyXArraypolyYArray,以便绘制我需要的不同形状,但我还没找到办法。

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

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

编辑:

如 VGR 在评论中所述,这将无法编译。所以,我们将不得不稍微改变一下实现,即我们将使用 HAVE 关系而不是 IS 关系:-)

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

其次,在这里使用模板方法设计模式。您的 class 看起来像这样:

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

每次扩展 class 都必须覆盖此方法,您可以在每个方法覆盖中为每个 classes 实例化数组。

此外,关于 IS-HAVE 关系问题的一个词 - 你在你的例子中提出的是一个 IS 关系,其中 GameShape 对象是一个 Polygon,因此需要调用超级构造函数和它的问题。在我的解决方案中,这是由 HAVE 关系替换的,其中 GameShape 对象内部有一个 Polygon 对象,使用 getPolygon() 方法访问。这让你有很多额外的灵活性:-)

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

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

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

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

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

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

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

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

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 应该是 protected 方法驻留在 Polygon 中。您不希望外界干扰各个点。不过,您可以添加 public 吸气剂。

注意

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

如果你真的想在你的构造函数中初始化东西,只需调用空 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}; }
}