java - 弹跳形状嵌套在另一个形状中(这段代码有什么问题)

标签 java oop arraylist

我有一个可以在 JPanel 内反弹 Shapes 的应用程序。每当形状撞击一侧时,它们就会向另一个方向反弹。我正在尝试添加一个名为 NestingShape 的新形状,其中包含零个或多个在其内部弹跳的 Shapes,而 NestingShape 在J 面板。 NestingShape 实例的子级可以是简单的 Shapes 或其他 NestingShape 实例。

现在,我在使用 move(width, height) 方法在 NestingShape 中移动 NestingShape 的子级时遇到问题NestingShape 子类。我在 Shape 父类(super class)中开发一种可以找到任何给定形状的父级的方法时也遇到了麻烦。我将复制并粘贴迄今为止为下面的 Shape 父类(super class)和 NestingShape 子类编写的代码以及迄今为止我用来测试代码的测试用例:

形状父类(super class):

注意:parent() 和path() 方法是与此任务最相关的方法,而parent() 方法是我在实现时遇到问题的方法。有很多小细节,例如 fFillcount 等,这些细节与我开发的不同 Shapes 相关,这些可以是被忽略。

package bounce;

import java.awt.Color;
import java.util.List;

/**
 * Abstract superclass to represent the general concept of a Shape. This class
 * defines state common to all special kinds of Shape instances and implements
 * a common movement algorithm. Shape subclasses must override method paint()
 * to handle shape-specific painting.
 * 
 * @author wadfsd
 *
 */
public abstract class Shape {
    // === Constants for default values. ===
    protected static final int DEFAULT_X_POS = 0;

    protected static final int DEFAULT_Y_POS = 0;

    protected static final int DEFAULT_DELTA_X = 5;

    protected static final int DEFAULT_DELTA_Y = 5;

    protected static final int DEFAULT_HEIGHT = 35;

    protected static final int DEFAULT_WIDTH = 25;

    protected static final Color DEFAULT_COLOR = Color.black;

    protected static final String DEFAULT_STRING = "";
    // ===

    // === Instance variables, accessible by subclasses.
    protected int fX;

    protected int fY;

    protected int fDeltaX;

    protected int fDeltaY;

    protected int fWidth;

    protected int fHeight;

    protected boolean fFill;

    protected Color fColor;

    protected int count;

    protected int fState;

    protected int before;

    protected String fString;
    // ===

    /**
     * Creates a Shape object with default values for instance variables.
     */
    public Shape() {
        this(DEFAULT_X_POS, DEFAULT_Y_POS, DEFAULT_DELTA_X, DEFAULT_DELTA_Y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR, DEFAULT_STRING);
    }

    /**
     * Creates a Shape object with a specified x and y position.
     */
    public Shape(int x, int y) {
        this(x, y, DEFAULT_DELTA_X, DEFAULT_DELTA_Y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR, DEFAULT_STRING);
    }

    public Shape(int x, int y, String str) {
        this(x, y, DEFAULT_DELTA_X, DEFAULT_DELTA_Y, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR, str);
    }

    /**
     * Creates a Shape object with specified x, y, and color values.
     */
    public Shape(int x, int y, Color c) {
        this(x, y, DEFAULT_DELTA_X, DEFAULT_DELTA_Y, DEFAULT_WIDTH, DEFAULT_HEIGHT, c, DEFAULT_STRING);
    }

    public Shape(int x, int y, Color c, String str) {
        this(x, y, DEFAULT_DELTA_X, DEFAULT_DELTA_Y, DEFAULT_WIDTH, DEFAULT_HEIGHT, c, str);
    }

    /**
     * Creates a Shape instance with specified x, y, deltaX and deltaY values.
     * The Shape object is created with a default width, height and color.
     */
    public Shape(int x, int y, int deltaX, int deltaY) {
        this(x, y, deltaX, deltaY, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR, DEFAULT_STRING);
    }

    public Shape(int x, int y, int deltaX, int deltaY, String str) {
        this(x, y, deltaX, deltaY, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_COLOR, str);
    }

    /**
     * Creates a Shape instance with specified x, y, deltaX, deltaY and color values.
     * The Shape object is created with a default width and height.
     */
    public Shape(int x, int y, int deltaX, int deltaY, Color c) {
        this(x, y, deltaX, deltaY, DEFAULT_WIDTH, DEFAULT_HEIGHT, c, DEFAULT_STRING);
    }

    public Shape(int x, int y, int deltaX, int deltaY, Color c, String str) {
        this(x, y, deltaX, deltaY, DEFAULT_WIDTH, DEFAULT_HEIGHT, c, str);
    }

    /**
     * Creates a Shape instance with specified x, y, deltaX, deltaY, width and
     * height values. The Shape object is created with a default color.
     */
    public Shape(int x, int y, int deltaX, int deltaY, int width, int height) {
        this(x, y, deltaX, deltaY, width, height, DEFAULT_COLOR, DEFAULT_STRING);
    }

    public Shape(int x, int y, int deltaX, int deltaY, int width, int height, String str) {
        this(x, y, deltaX, deltaY, width, height, DEFAULT_COLOR, str);
    }

    public Shape(int x, int y, int deltaX, int deltaY, int width, int height, Color c) {
        this(x, y, deltaX, deltaY, width, height, c, DEFAULT_STRING);
    }

    /**
     * Creates a Shape instance with specified x, y, deltaX, deltaY, width,
     * height and color values.
     */
    public Shape(int x, int y, int deltaX, int deltaY, int width, int height, Color c, String str) {
        fX = x;
        fY = y;
        fDeltaX = deltaX;
        fDeltaY = deltaY;
        fWidth = width;
        fHeight = height;
        fFill = false;
        fColor = c;
        count = 0;
        fState = 1;
        before = 0;
        fString = str;
    }

    /**
     * Moves this Shape object within the specified bounds. On hitting a 
     * boundary the Shape instance bounces off and back into the two- 
     * dimensional world and logs whether a vertical or horizontal wall
     * was hit for the DynamicRectangleShape.
     * @param width width of two-dimensional world.
     * @param height height of two-dimensional world.
     */
    public void move(int width, int height) {
        int nextX = fX + fDeltaX;
        int nextY = fY + fDeltaY;

        if (nextY <= 0) {
            nextY = 0;
            fDeltaY = -fDeltaY;
            fFill = false;
            count++;
        } else if (nextY + fHeight >= height) {
            nextY = height - fHeight;
            fDeltaY = -fDeltaY;
            fFill = false;
            count++;
        }

        // When Shape hits a corner the vertical wall fFill value overrides the horizontal
        if (nextX <= 0) {
            nextX = 0;
            fDeltaX = -fDeltaX;
            fFill = true;
            count++;
        } else if (nextX + fWidth >= width) {
            nextX = width - fWidth;
            fDeltaX = -fDeltaX;
            fFill = true;
            count++;
        }

        fX = nextX;
        fY = nextY;
    }

    public void text(Painter painter, String str) {
        painter.drawCentredText(str, fX, fY, fWidth, fHeight);
    }

    /**
     * Returns the NestingShape that contains the Shape that method parent
     * is called on. If the callee object is not a child within a
     * NestingShape instance this method returns null.
     */
    public NestingShape parent() {
        // Related to NestingShape
    }

    /**
     * Returns an ordered list of Shape objects. The first item within the
     * list is the root NestingShape of the containment hierarchy. The last
     * item within the list is the callee object (hence this method always
     * returns a list with at least one item). Any intermediate items are
     * NestingShapes that connect the root NestingShape to the callee Shape.
     * E.g. given:
     * 
     *   NestingShape root = new NestingShape();
     *   NestingShape intermediate = new NestingShape();
     *   Shape oval = new OvalShape();
     *   root.add(intermediate);
     *   intermediate.add(oval);
     *   
     * a call to oval.path() yields: [root,intermediate,oval] 
     */
    public List<Shape> path() {
        // Related to NestingShape
    }

    /**
     * Method to be implemented by concrete subclasses to handle subclass
     * specific painting.
     * @param painter the Painter object used for drawing.
     */
    public abstract void paint(Painter painter);

    /**
     * Returns this Shape object's x position.
     */
    public int x() {
        return fX;
    }

    /**
     * Returns this Shape object's y position.
     */
    public int y() {
        return fY;
    }

    /**
     * Returns this Shape object's speed and direction.
     */
    public int deltaX() {
        return fDeltaX;
    }

    /**
     * Returns this Shape object's speed and direction.
     */
    public int deltaY() {
        return fDeltaY;
    }

    /**
     * Returns this Shape's width.
     */
    public int width() {
        return fWidth;
    }

    /**
     * Returns this Shape's height.
     */
    public int height() {
        return fHeight;
    }

    /**
     * Returns a String whose value is the fully qualified name of this class 
     * of object. E.g., when called on a RectangleShape instance, this method 
     * will return "bounce.RectangleShape".
     */
    public String toString() {
        return getClass().getName();
    }
}

NestingShape 子类:

注意:使用 move() 方法时遇到问题

package bounce;

import java.util.ArrayList;
import java.util.List;

public class NestingShape extends Shape {
    private List<Shape> nest = new ArrayList<Shape>();

    /**
     * Creates a NestingShape object with default values for state.
     */
    public NestingShape() {
        super();
    }

    /**
     * Creates a NestingShape object with specified location values, default values for other
     * state items.
     */
    public NestingShape(int x, int y) {
        super(x,y);
    }

    /**
     * Creates a NestingShape with specified values for location, velocity and direction.
     * Non-specified state items take on default values.
     */
    public NestingShape(int x, int y, int deltaX, int deltaY) {
        super(x,y,deltaX,deltaY);
    }

    /**
     * Creates a NestingShape with specified values for location, velocity, direction, width, and
     * height.
     */
    public NestingShape(int x, int y, int deltaX, int deltaY, int width, int height) {
        super(x,y,deltaX,deltaY,width,height);
    }

    /**
     * Moves a NestingShape object (including its children) with the bounds specified by arguments
     * width and height.
     */
    public void move(int width, int height) {   
        int nextX = fX + fDeltaX;
        int nextY = fY + fDeltaY;

        if (nextY <= 0) {
            nextY = 0;
            fDeltaY = -fDeltaY;
            fFill = false;
            count++;
        } else if (nextY + fHeight >= height) {
            nextY = height - fHeight;
            fDeltaY = -fDeltaY;
            fFill = false;
            count++;
        }

        if (nextX <= 0) {
            nextX = 0;
            fDeltaX = -fDeltaX;
            fFill = true;
            count++;
        } else if (nextX + fWidth >= width) {
            nextX = width - fWidth;
            fDeltaX = -fDeltaX;
            fFill = true;
            count++;
        }

        fX = nextX;
        fY = nextY;

        // Move children
        for (int i = 0; i < shapeCount(); i++) {
            Shape shape = shapeAt(i);

            int nextXChild = shape.fX + shape.fDeltaX;
            int nextYChild = shape.fY + shape.fDeltaY;

            if (nextYChild <= 0) {
                nextYChild = 0;
                shape.fDeltaY = -shape.fDeltaY;
            } else if (nextYChild + shape.fHeight >= fHeight) {
                nextYChild = fHeight - shape.fHeight;
                shape.fDeltaY = -shape.fDeltaY;
            }

            if (nextXChild <= 0) {
                nextXChild = 0;
                shape.fDeltaX = -shape.fDeltaX;
            } else if (nextXChild + fWidth >= width) {
                nextXChild = fWidth - shape.fWidth;
                shape.fDeltaX = -shape.fDeltaX;
            }

            shape.fX = nextXChild;
            shape.fY = nextYChild;
        }
    }

    /**
     * Paints a NestingShape object by drawing a rectangle around the edge of its bounding box.
     * The NestingShape object's children are then painted.
     */
    public void paint(Painter painter) {
        painter.drawRect(fX,fY,fWidth,fHeight);
        painter.translate(fX,fY);
        for (int i = 0; i < shapeCount(); i++) {
            Shape shape = shapeAt(i);
            shape.paint(painter);
        }
        painter.translate(0,0);
    }

    /**
     * Attempts to add a Shape to a NestingShape object. If successful, a two-way link is
     * established between the NestingShape and the newly added Shape. Note that this method
     * has package visibility - for reasons that will become apparent in Bounce III.
     * @param shape the shape to be added.
     * @throws IllegalArgumentException if an attempt is made to add a Shape to a NestingShape
     * instance where the Shape argument is already a child within a NestingShape instance. An
     * IllegalArgumentException is also thrown when an attempt is made to add a Shape that will
     * not fit within the bounds of the proposed NestingShape object.
     */
    void add(Shape shape) throws IllegalArgumentException {
        if (contains(shape)) {
            throw new IllegalArgumentException();
        } else if (shape.fWidth > fWidth || shape.fHeight > fHeight) {
            throw new IllegalArgumentException();
        } else {
            nest.add(shape);
        }
    }

    /**
     * Removes a particular Shape from a NestingShape instance. Once removed, the two-way link
     * between the NestingShape and its former child is destroyed. This method has no effect if
     * the Shape specified to remove is not a child of the NestingShape. Note that this method
     * has package visibility - for reasons that will become apparent in Bounce III.
     * @param shape the shape to be removed.
     */
    void remove(Shape shape) {
        int index = indexOf(shape);
        nest.remove(index);
    }

    /**
     * Returns the Shape at a specified position within a NestingShape. If the position specified
     * is less than zero or greater than the number of children stored in the NestingShape less
     * one this method throws an IndexOutOfBoundsException.
     * @param index the specified index position.
     */
    public Shape shapeAt(int index) throws IndexOutOfBoundsException {
        if (index < 0 || index >= shapeCount()) {
            throw new IndexOutOfBoundsException();
        } else {
            Shape shape = nest.get(index);
            return shape;
        }
    }

    /**
     * Returns the number of children contained within a NestingShape object. Note this method is
     * not recursive - it simply returns the number of children at the top level within the callee
     * NestingShape object.
     */
    public int shapeCount() {
        int number = nest.size();
        return number;
    }

    /**
     * Returns the index of a specified child within a NestingShape object. If the Shape specified
     * is not actually a child of the NestingShape this method returns -1; otherwise the value
     * returned is in the range 0 .. shapeCount() - 1.
     * @param shape the shape whose index position within the NestingShape is requested.
     */
    public int indexOf(Shape shape) {
        int index = nest.indexOf(shape);
        return index;
    }

    /**
     * Returns true if the shape argument is a child of the NestingShape object on which this method
     * is called, false otherwise.
     */
    public boolean contains(Shape shape) {
        boolean child = nest.contains(shape);
        return child;
    }
}

TestNestingShape 测试用例:

package bounce;

import java.util.List;

import junit.framework.TestCase;

/**
 * Class to test class NestingShape according to its specification.
 */
public class TestNestingShape extends TestCase {

    private NestingShape topLevelNest;
    private NestingShape midLevelNest;
    private NestingShape bottomLevelNest;
    private Shape simpleShape;

    public TestNestingShape(String name) {
        super(name);
    }

    /**
     * Creates a Shape composition hierarchy with the following structure:
     *   NestingShape (topLevelNest)
     *     |
     *     --- NestingShape (midLevelNest)
     *           |
     *           --- NestingShape (bottomLevelNest)
     *           |
     *           --- RectangleShape (simpleShape)
     */
    protected void setUp() throws Exception {
        topLevelNest = new NestingShape(0, 0, 2, 2, 100, 100);
        midLevelNest = new NestingShape(0, 0, 2, 2, 50, 50);
        bottomLevelNest = new NestingShape(5, 5, 2, 2, 10, 10);
        simpleShape = new RectangleShape(1, 1, 1, 1, 5, 5);

        midLevelNest.add(bottomLevelNest);
        midLevelNest.add(simpleShape);
        topLevelNest.add(midLevelNest);
    }

    /**
     * Checks that methods move() and paint() correctly move and paint a 
     * NestingShape's contents.
     */
    public void testBasicMovementAndPainting() {
        Painter painter = new MockPainter();

        topLevelNest.move(500, 500);
        topLevelNest.paint(painter);
        assertEquals("(rectangle 2,2,100,100)(rectangle 2,2,50,50)(rectangle 7,7,10,10)(rectangle 2,2,5,5)", painter.toString());
    }

    /**
     * Checks that method add successfuly adds a valid Shape, supplied as 
     * argument, to a NestingShape instance. 
     */
    public void testAdd() {
        // Check that topLevelNest and midLevelNest mutually reference each other.
        assertSame(topLevelNest, midLevelNest.parent());
        assertTrue(topLevelNest.contains(midLevelNest));

        // Check that midLevelNest and bottomLevelNest mutually reference each other.
        assertSame(midLevelNest, bottomLevelNest.parent());
        assertTrue(midLevelNest.contains(bottomLevelNest));
    }

    /**
     * Check that method add throws an IlegalArgumentException when an attempt 
     * is made to add a Shape to a NestingShape instance where the Shape 
     * argument is already part of some NestingShape instance.
     */
    public void testAddWithArgumentThatIsAChildOfSomeOtherNestingShape() {
        try {
            topLevelNest.add(bottomLevelNest);
            fail();
        } catch(IllegalArgumentException e) {
            // Expected action. Ensure the state of topLevelNest and 
            // bottomLevelNest has not been changed.
            assertFalse(topLevelNest.contains(bottomLevelNest));
            assertSame(midLevelNest, bottomLevelNest.parent());
        }
    }

    /**
     * Check that method add throws an IllegalArgumentException when an attempt
     * is made to add a shape that will not fit within the bounds of the 
     * proposed NestingShape object.
     */
    public void testAddWithOutOfBoundsArgument() {
        Shape rectangle = new RectangleShape(80, 80, 2, 2, 50, 50);

        try {
            topLevelNest.add(rectangle);
            fail();
        } catch(IllegalArgumentException e) {
            // Expected action. Ensure the state of topLevelNest and 
            // rectangle has not been changed.
            assertFalse(topLevelNest.contains(rectangle));
            assertNull(rectangle.parent());
        }
    }

    /**
     * Check that method remove breaks the two-way link between the Shape 
     * object that has been removed and the NestingShape it was once part of.
     */
    public void testRemove() {
        topLevelNest.remove(midLevelNest);
        assertFalse(topLevelNest.contains(midLevelNest));
        assertNull(midLevelNest.parent());
    }

    /**
     * Check that method shapeAt returns the Shape object that is held at a
     * specified position within a NestingShape instance.
     */
    public void testShapeAt() {
        assertSame(midLevelNest, topLevelNest.shapeAt(0));
    }

    /**
     * Check that method shapeAt throws a IndexOutOfBoundsException when called
     * with an invalid index argument.
     */
    public void testShapeAtWithInvalidIndex() {
        try {
            topLevelNest.shapeAt(1);
            fail();
        } catch(IndexOutOfBoundsException e) {
            // Expected action.
        }
    }

    /**
     * Check that method shapeCount returns zero when called on a NestingShape
     * object without children.
     */
    public void testShapeCountOnEmptyParent() {
        assertEquals(0, bottomLevelNest.shapeCount());
    }

    /**
     * Check that method shapeCount returns the number of children held within 
     * a NestingShape instance - where the number of children > 0.
     */
    public void testShapeCountOnNonEmptyParent() {
        assertEquals(2, midLevelNest.shapeCount());
    }

    /**
     * Check that method indexOf returns the index position within a
     * NestingShape instance of a Shape held within the NestingShape. 
     */
    public void testIndexOfWith() {
        assertEquals(0, topLevelNest.indexOf(midLevelNest));
        assertEquals(1, midLevelNest.indexOf(simpleShape));
    }

    /**
     * Check that method indexOf returns -1 when called with an argument that
     * is not part of the NestingShape callee object.
     */
    public void testIndexOfWithNonExistingChild() {
        assertEquals(-1, topLevelNest.indexOf(bottomLevelNest));
    }

    /**
     * Check that Shape's path method correctly returns the path from the root
     * NestingShape object through to the Shape object that path is called on.
     */
    public void testPath() {
        List<Shape> path = simpleShape.path();

        assertEquals(3, path.size());
        assertSame(topLevelNest, path.get(0));
        assertSame(midLevelNest, path.get(1));
        assertSame(simpleShape, path.get(2));
    }

    /**
     * Check that Shape's path method correctly returns a singleton list
     * containing only the callee object when this Shape object has no parent.
     */
    public void testPathOnShapeWithoutParent() {
        List<Shape> path = topLevelNest.path();

        assertEquals(1, path.size());
        assertSame(topLevelNest, path.get(0));
    }
}

使用到目前为止运行测试用例时的代码,我无法测试 testAdd 和 testRemove 相关测试用例以确保我正确添加形状,因为我尚未开发 parent Shape 类中的 () 方法。但我想不出一种实现父方法的方法。

每当我 testBasicMovementAndPainting 时,我也会得到失败的测试,因为我当前的 move() 方法(在 NestingShape 类中)仅移动第一个 中的子级NestingShape 并且不会移动 midLevelNest 的子级。

读起来有点长,我不确定这是否提供了足够的上下文,因为包中还有很多我没有包含的其他类,但如果有人可以提供帮助,我将非常感激。

谢谢。

最佳答案

对于“父”问题:Shape 需要一个额外的 Shape 属性来指向外部嵌套形状(它是容器/父):

private Shape parent = null;

您可以使用构造函数设置它,也可以简单地添加 getter/setter 方法:

public Shape(Shape parent) {
  this.parent = parent;
}

public void setParent(Shape parent) {
  this.parent = parent;
}

public Shape parent() {
  return parent;
}

注意现在的问题是任何形状都可以是其他形状的容器 - 它不限于NestingShape。但是,如果我将父级声明为 NestingShape,那么我们就会遇到丑陋的情况,即 Shape 依赖于它的子类 NestingShape

也许您只需定义一个名为 ShapeContainer 的额外接口(interface),它将容器功能添加到 Shape,例如

public interface ShapeContainer {
  public List<Shape> getChildren();
  // .. more?
}

那么你的类签名将如下所示

public class NestingShape extends Shape implements ShapeContainer

并且 Shape 中父字段的类型将为 ShapeContainer

关于java - 弹跳形状嵌套在另一个形状中(这段代码有什么问题),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6039994/

相关文章:

java - 表达式的类型必须是数组类型,但它解析为字符串

php - 为什么 child 可以覆盖并访问其父私有(private)方法?

java - 添加新项目时列表中的项目会被覆盖

java - 运行时指定的数组/ArrayList 的维数

java - 将 arraylist 与字符串、int、double 一起使用

java - rt.jar 中类 Java API 的 Eclipse 调试问题

java - 如何将字节数组中的数据打印为字符?

java - 在制定规则后,我如何将事实 boolean 值放入槽中,谁可以计算它?

javascript - 如何在javascript中获取真正的取消引用的对象实例

javascript - 如何销毁在此构造函数中创建的对象?