我有一个应用程序在JPanel内部反弹Shapes
。每当形状撞到一侧时,它们就会向另一个方向反弹。我正在尝试添加一个名为NestingShape
的新形状,其中包含零个或多个Shapes
在其内部反弹,而NestingShape
在JPanel中反弹。 NestingShape
实例的子项可以是简单Shapes
个实例,也可以是其他NestingShape
个实例。
目前,我在使用NestingShape子类中的NestingShape
方法移动NestingShape
move(width, height)
的子项时遇到问题。我也遇到了在Shape
超类中开发一个可以找到任何给定形状的父类的方法的问题。到目前为止,我将复制并粘贴到目前为止我为Shape
超类和NestingShape
子类编写的代码以及我用来测试代码的测试用例:
Shape
超类:
注意:parent()和path()方法是此任务最相关的方法,而parent()方法是我无法实现的方法。有许多小细节,例如fFill
和count
等与我开发的不同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相关的测试用例,以确保我正在添加形状,因为我尚未开发{{1} parent()
类中的方法。但我想不出实现父方法的方法。
每当我Shape
时,我的测试也会失败,因为我当前的testBasicMovementAndPainting
方法(在NestingShape类中)只会移动第一个move()
中的子项并且不会移动midLevelNest的孩子们。
这是一个很长的阅读,我不确定是否提供了足够的上下文,因为包中有很多其他类我没有包括但是如果有人可以帮助我会非常感激。< / p>
感谢。
答案 0 :(得分:2)
对于“父”问题: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
的额外接口,它将容器功能添加到Shape中,例如
public interface ShapeContainer {
public List<Shape> getChildren();
// .. more?
}
然后你的班级签名看起来像这样
public class NestingShape extends Shape implements ShapeContainer
,Shape
中父字段的类型为ShapeContainer
。