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

6
我正在编写一个简单的类似“小行星”的游戏,使用Swing来显示图形。我有点遵循Derek Banas的教程,但决定自己扩展一下。
最初的想法是游戏中的每个图形元素(即小行星、飞船和子弹)都会扩展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类变量。因此,这些数组仅描述了对象的形状,而子类move()方法将影响中心的坐标。负责实际绘制的类将调用move()方法,然后应用仿射变换来移动和旋转形状(根据适当定义的角度参数)。我这样做是为了避免处理double算术相关的精度问题。

现在,由于这些元素共享许多“相等”的变量(它们的中心坐标,我需要这些变量才能使用仿射变换将它们平移,它们的速度分量等等...)和方法(获取器和设置器,move()方法等等...),所以我考虑让它们成为抽象类的扩展 - 比如说,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中描述的情况非常相似,但提出的解决方案似乎不起作用,因为我需要这些变量在构造函数中。是否有一种方法可以克服或避开这个问题?无论采取何种程序,我的主要目标是拥有一个所有图形元素共同的超类,以避免复制粘贴代码的几十行。


3
我认为那不是明智的设计决定。你不要让所有实例都具有相同的点,而是希望它们之间互相独立。polyXArraypolyYArray应该是实例变量。 - Michael
你明白吧,将数组引用作为参数传递的代价并不比传递其他引用更高,也可能不比传递原始值更昂贵。 - John Bollinger
@Michael,你打算使用子类的实例变量吗? - Ka Mai
1
Polygon长什么样子?为什么你必须写super(polyXArray, polyYArray, polyXArray.length);这一行代码呢? - Michael
1
@KaMai 我想我开始明白了。您对于子类的所有实例都有相同的基本坐标。不同实例之间的区别在于相同的坐标被不同地转换。是这样吗? - Paul Boddington
显示剩余10条评论
6个回答

2
您有一对数组,描述了特定类型游戏对象的形状。如果不同的游戏对象可以有不同的形状,那么它们不能共享单个数组对,因为这将是所有游戏对象共同超类的静态属性的情况。同种类型的不同对象可以共享相同的数组对(假设那些不需要每个对象基于个别情况进行修改),这可能对应于这些数组作为具体游戏对象类的静态字段。在这种情况下,如果您希望超类能够访问给定游戏对象的正确形状数据,则必须告诉它这些形状数据。
有两种主要方法可以实现: 1. 您可以将适当的形状数组传递到超类的构造函数中。您说您不想这样做,但我不明白为什么。 2. 您可以在超类上定义访问器方法,子类应该重写以提供正确的形状数据(这称为模板方法模式)。

1
数组字段不能是 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};
    }
}

1
我认为 BoringRectangle 不会编译 :) - ZhongYu
1
@bayou.io 我已经将辅助方法改为静态方法了。感谢您没有对其进行负评! - Paul Boddington
1
我从不给负评。在调用超类构造函数之前访问this似乎是完全不可能的,除非超类有意提供了某些后门,例如在构造函数中调用抽象方法!我找到了另一个解决方法 - ZhongYu

1

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

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

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


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

0
我认为polyXArraypolyYArray应该放在Polygon类中,因为它们属于那里。因此,在声明重复字段时这不是个好主意。另外,可以摆脱调用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类型的单个数组,而不是两个紧密耦合和依赖的数组。我觉得这将极大地简化您项目中的许多任务。


-1

编辑:

正如VGR在评论中所述,这段代码无法编译。因此,我们需要稍微改变实现方式,即使用HAVE关系而不是IS关系 :-)

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

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

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()方法访问。这使您可以拥有很多额外的灵活性 :-)

3
无法编译。在构造函数中调用super(…)必须是第一条语句。 - VGR
1
没错,我通过一点关系交换纠正了我的答案。 - Kelevandos
这很有效,并且确实符合我的目标:我的对象除了具有形状外,还具有许多其他属性,因此HAVE关系对我最为合适。 - Ka Mai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接