Zhen Gao
S01401440
Extensible
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pWggSOJV-1633068895352)(./uml.jpg)]
Use Factory Design Pattern to create Balls and Strategies.
Class MovingBall inherited Ball, adding strategy and name properties and fuctions that support moving.
Use Singleton Pattern for Strategies, Factories and BallWorldStore.
Use Observer Pattern to update balls location and change Switch balls strategies.
Use MVC Pattern for the whole Server.
Robust
If a user request a url that does not exist, the controller will redirect him to the home page.
notFound((req, res) -> {
return "<html><body><h3>The request is not found.</h3>" +
"<a href=\"/\">Back to home page.</a></body></html>";
});
degigned classes NullBall and NullStrategy to dealwith illegal input,
the NullBall has no behavior, only to deal with null pointer exception
public class NullBall extends MovingBall {
/**
* Constructor.
*
* @param loc The location of the ball on the canvas
* @param radius The ball radius
* @param vel The ball velocity
* @param color The ball color
* @param strategy
*/
protected NullBall(Point loc, int radius, Point vel, String color, IUpdateStrategy strategy) {
super(loc, radius, vel, color, strategy);
this.setColor("white");
if (!Objects.equals(this.strategy.getName(), "Null")) {
this.strategy = NullStrategy.getOnly();
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
}
@Override
public void update() {
}
}
and the NullStrategy has no behavior too.
public class NullStrategy implements IUpdateStrategy {
static NullStrategy ONLY;
/**
* get the only instance.
*/
public static NullStrategy getOnly() {
if (ONLY == null) {
ONLY = new NullStrategy();
}
return ONLY;
}
/**
* Constructor.
*/
private NullStrategy() {
}
@Override
public String getName() {
return "Null";
}
@Override
public void updateState(Ball context) {
}
}
in BallFactory, when the user want to create a ball of a type does not exist, it will create a NullBall.
default :
ball = new NullBall(loc,radius,vel,"white",fac.make("null"));
In StrategyFactory it is the same.
default :
strategy = NullStrategy.getOnly();
Unit Test
requesting ball type with loadBall creates expected ball
@Test
void testLoad(){
DispatchAdapter dis = new DispatchAdapter();
PropertyChangeListener[] pcls = dis.loadBall("horizontal", "type");
MovingBall ball = (MovingBall) pcls[pcls.length-1];
assertEquals("Moving",ball.getName());
assertEquals("Horizontal",ball.getStrategy().getName());
pcls = dis.loadBall("rotated", "type");
ball = (RotatedBall) pcls[pcls.length-1];
assertEquals("Rotated",ball.getName());
assertEquals("Rotated",ball.getStrategy().getName());
pcls = dis.loadBall("vertical", "type");
ball = (MovingBall) pcls[pcls.length-1];
assertEquals("Moving",ball.getName());
assertEquals("Vertical", ball.getStrategy().getName());
pcls = dis.loadBall("oblique", "type");
ball = (MovingBall) pcls[pcls.length-1];
assertEquals("Moving",ball.getName());
assertEquals("Oblique", ball.getStrategy().getName());
pcls = dis.loadBall("switch", "type");
ball = (SwitchBall) pcls[pcls.length-1];
assertEquals("Switch",ball.getName());
assertEquals("Horizontal", ball.getStrategy().getName());
pcls = dis.loadBall("wrongtype", "type");
ball = (NullBall) pcls[pcls.length-1];
assertEquals("Null",ball.getName());
assertEquals("Null", ball.getStrategy().getName());
}
an update (for each ball type) moves ball to expected location
@Test
void testUpdate(){
BallWorldStore bws = BallWorldStore.getOnly();
BallWorldStore.setCanvasDims(new Point(800,800));
BallFac bf = BallFac.getOnly();
MovingBall ball = (MovingBall) bf.make("horizontal");
Point loc = ball.getLocation();
loc.x += ball.getVelocity().x;
ball.update();
assertEquals(loc, ball.getLocation());
RotatedBall ball1 = (RotatedBall) bf.make("rotated");
loc = ball1.getLocation();
double theta = ball1.getVelocity().x * 0.3;
theta *= (Math.PI / 180);
int oldX = loc.x;
int oldY = loc.y;
double newX = ball1.center.x + (oldX-ball1.center.x)*Math.cos(theta) - (oldY-ball1.center.y)*Math.sin(theta);
double newY = ball1.center.y + (oldX-ball1.center.x)*Math.sin(theta) + (oldY-ball1.center.y)*Math.cos(theta);
loc = new Point((int)newX, (int)newY);
ball1.update();
assertEquals(loc, ball1.getLocation());
ball = (MovingBall) bf.make("vertial");
loc = ball.getLocation();
loc.y += ball.getVelocity().y;
ball.update();
assertEquals(loc, ball.getLocation());
ball = (MovingBall) bf.make("oblique");
loc = ball.getLocation();
loc.x += ball.getVelocity().x;
loc.y += ball.getVelocity().y;
ball.update();
assertEquals(loc, ball.getLocation());
SwitchBall ball2 = (SwitchBall) bf.make("switch");
loc = ball2.getLocation();
loc.x += ball2.getVelocity().x;
ball.update();
assertEquals(loc, ball2.getLocation());
bws.switchStrategy("vertical");
loc = ball2.getLocation();
loc.y += ball2.getVelocity().y;
ball.update();
assertEquals(loc, ball2.getLocation());
bws.switchStrategy("oblique");
loc = ball2.getLocation();
loc.x += ball2.getVelocity().x;
loc.y += ball2.getVelocity().y;
ball.update();
assertEquals(loc, ball2.getLocation());
}
adding a ball to the BallWorld increases the number of balls by 1
@Test
void testAdd(){
BallWorldStore bws = BallWorldStore.getOnly();
int size = bws.getBalls().length;
bws.loadBall("wrongtype","type");
int newsize = bws.getBalls().length;
assertEquals(1, newsize-size);
}
clearing the BallWorld sets the number of balls to 0
@Test
void testRemove(){
BallWorldStore bws = BallWorldStore.getOnly();
for (int i = 0; i < 5; i++) {
bws.loadBall("wrongtype","type");
}
int size = bws.getBalls().length;
assertEquals(5, size);
bws.removeBallsFromStore();
size = bws.getBalls().length;
assertEquals(0, size);
}