2011-05-18 19 views
1

我有一个应用程序在JPanel内弹跳Shapes。无论何时形状碰到一侧,它们都会在另一个方向上反弹。我正在尝试添加一个名为NestingShape的新形状,其中包含零个或更多Shapes,它在其内弹跳,而NestingShape在JPanel中弹跳。 NestingShape实例的子项可以是简单的Shapes或其他NestingShape实例。嵌套在另一个形状中的弹跳形状(此代码有什么问题)

现在我无法在NestingShape子类中将NestingShapeNestingShape的孩子与move(width, height)方法一起移动。我在开发Shape超类的方法时也遇到了问题,它可以找到任何给定形状的父类。我会复制和粘贴我来了这么远低于该Shape超和NestingShape子和我使用的测试至今代码的测试用例代码:

Shape超:

注意: 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以确保我正确添加形状,因为我尚未开发Shape类中的parent()方法。但我想不出一种实现父方法的方法。

每当我testBasicMovementAndPainting,我也得到一个失败的测试,因为我现在的move()方法(在NestingShape类)仅能将孩子在第一NestingShape和不移动midLevelNest的孩子。

这是一个很长的阅读,我不确定是否提供了足够的上下文,因为包中有很多其他类,我没有包括,但如果有人可以帮助,我会非常感激它。

谢谢。

回答

2

对于“父”的问题:一个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这增加容器功能到形状的额外接口,像

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

那么你的类签名是这样的

public class NestingShape extends Shape implements ShapeContainer 

和类型Shape中的父字段将为ShapeContainer

+0

但是这个类是用于'NestingShape',而不是'NestedShape',所以父类()方法在Shape类中更合适吗?分配细节说我应该把它放在'Shape'类中。 – Jigglypuff 2011-05-18 05:11:00

+0

我如何设置父级与“NestingShape”对象相同,以便testAdd中的assertSame调用通过? – Jigglypuff 2011-05-18 05:29:10

+0

@JigglyPuff - 对不起,没注意,一个'NestingShape'是其他形状的容器。我会适应我的答案! – 2011-05-18 05:37:52

相关问题