Ka Mai
Ka Mai

Reputation: 71

Initialize superclass variables (needed in constructor) in a subclass

I'm writing a simple Asteroids-clone game, using Swing to display the graphics. I'm kind of following Derek Banas' tutorials, but decided to expand on my own.

The original idea is that every graphic element in the game (i.e. the Asteroids, the Spaceship and the Bullets) extends the Polygon class. Their constructor would look something like this:

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

And it will be similar for the other graphic elements.

EDIT: The key element here is that the two arrays don't store the actual coordinates of the objects, but their position relative to their centre, whose coordinates are double-type class-variable. Thus, the arrays describe just the shape of the object, while the subclass move() method will affect the centre's coordinates. The class responsible for the actual drawing will call the move() method and then apply an affine transform to move and rotate the shape (according to a properly defined angle parameter). I'm doing this to avoid precision problems related to dealing with double arithmetic.

Now, since the elements share a lot of "equal" variables (their centre coordinates, which I need in order to translate them with an affine transform, their speed components etc...) and methods (getters and setters, move() methods, etc...) I thought about making them be the extension of an abstract class - say, GameShape - which holds all these common methods and variables. GameShape would now be the one extending Polygon directly:

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

Then, I'd want to assign the desired value to polyXArray and polyYArray when I define the different subclasses in order to draw the different shapes I need, but I haven't been able to find a way to do it.

I do want those variable to be static because they are specific properties of the single classes, and I wouldn't want to pass them as a parameter every time I instantiate a new object.

My situation is very similar to the one described in this question, but the proposed solution doesn't seem to work, since I need those very variables in the constructor. Is there a way to get over - or around - this problem? Regardless of the procedure, my main aim is to have a superclass common to all the graphic elements, in order to avoid tens of lines of copy-pasted code.

Upvotes: 6

Views: 4410

Answers (6)

Michael
Michael

Reputation: 2773

I would think that polyXArray and polyYArray reside in the Polygon class; that's where they belong. Therefore it's not a good idea to have duplicate fields. Also, get rid of the ned to call the super constructor. I would design the class structure like this:

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

This way, you can ensure that all SquareShape objects do indeed have a square shape, but you can customize things you should be able to customize. Like position and size, which should not be static shared fields. setXArray and setYArray should be protected methods residing in Polygon. You don't want the outside world messing with the individual points. You can add public getters, although.

NOTE

You may want to consider using a single array of a complex Point type, rather then two tightly coupled and dependent arrays. I feel like this will greatly simplify a lot of tasks in your project.

Upvotes: 0

Paul Boddington
Paul Boddington

Reputation: 37655

The array fields cannot be static because different shapes have different coordinates. Also you don't need these arrays in the specific subclasses because they are already in Polygon or GameShape.

Here is more-or-less how I would write GameShape (although I agree with @Michael that you don't need to pass both polyXArray and polyXArray.length to the constructor).

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

The trouble is that super must be the first line of a constructor, but you can do it using private methods to build the arrays:

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

Upvotes: 1

PaoloC
PaoloC

Reputation: 4125

If you really do want to initialize things in your constructor, just call the empty super(); and then loop against abstract getPolyXArray() and getPolyYArray() to feed 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}; }
}

Upvotes: 1

John Bollinger
John Bollinger

Reputation: 181714

You have pairs of arrays that describe the shapes of specific kinds of game objects. If different game objects can have different shapes, then they cannot all share a single pair of arrays, as would be the case if they were static properties of a common superclass of all the game object classes. Different objects of the same kind can share the same pair of arrays (supposing that those don't need to be modified on a per-object basis), which could correspond to those arrays being static fields of the concrete game object classes. In that case, however, if you want a superclass of those classes to be able to access the correct shape data for a given game object, then it has to be told what those shape data are.

There are two main ways you could do that:

  1. You could pass the appropriate shape arrays to the superclass's constructor. You say you don't want to do this, but I don't understand why.

  2. You could define accessor methods on the superclass that the subclasses are supposed to override to provide the correct shape data (this is called the Template Method pattern).

Upvotes: 2

ursa
ursa

Reputation: 4611

The solution from this question will work if your classes will NOT extend shape, but provide shapes via accessor + private static field.

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

This also helps to escape shapes duplication.

Upvotes: 1

Kelevandos
Kelevandos

Reputation: 7082

EDIT:

As VGR stated in the comments, this will not compile. So, we will have to change the implementation a little, namely, we will use the HAVE relationship instead of IS relationship :-)

First of all, do not make the poly array fields static. If you do, they will be the same for all of the subclasses as well, so what is the point?

Secondly, use a template method design pattern here. Your class will look something like this:

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

Every extending class will have to override this method and you can instantiate the arrays for each of the classes in each of the method overrides.

Also, a word about the IS-HAVE relationship problem - what you presented in your example is a IS relationship, in which the GameShape object IS a Polygon, hence the need to call the super constructor and the problem with that. In my solution this is replaces by a HAVE relationship, in which the GameShape object HAS a Polygon object inside, accessed with a getPolygon() method. This allows you to have lots of additional flexibility :-)

Upvotes: -1

Related Questions