mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-08-05 14:23:52 +00:00
deps: Refactor dependencies (#3224)
* remove spring dep move junit, logging, mockito under dep mgmt * upgrade anti-corruption-layer deps * async method invocation * balking, bloc * bridge to bytecode * caching * callback - cqrs * component - health check * hexagonal - metadata mapping * rest of the patterns * remove checkstyle, take spotless into use
This commit is contained in:
@@ -30,76 +30,77 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* <p>The idea behind the <b>Spatial Partition</b> design pattern is to enable efficient location
|
||||
* of objects by storing them in a data structure that is organised by their positions. This is
|
||||
* The idea behind the <b>Spatial Partition</b> design pattern is to enable efficient location of
|
||||
* objects by storing them in a data structure that is organised by their positions. This is
|
||||
* especially useful in the gaming world, where one may need to look up all the objects within a
|
||||
* certain boundary, or near a certain other object, repeatedly. The data structure can be used to
|
||||
* store moving and static objects, though in order to keep track of the moving objects, their
|
||||
* positions will have to be reset each time they move. This would mean having to create a new
|
||||
* instance of the data structure each frame, which would use up additional memory, and so this
|
||||
* pattern should only be used if one does not mind trading memory for speed and the number of
|
||||
* objects to keep track of is large to justify the use of the extra space.</p>
|
||||
* objects to keep track of is large to justify the use of the extra space.
|
||||
*
|
||||
* <p>In our example, we use <b>{@link QuadTree} data structure</b> which divides into 4 (quad)
|
||||
* sub-sections when the number of objects added to it exceeds a certain number (int field
|
||||
* capacity). There is also a
|
||||
* <b>{@link Rect}</b> class to define the boundary of the quadtree. We use an abstract class
|
||||
* <b>{@link Point}</b>
|
||||
* with x and y coordinate fields and also an id field so that it can easily be put and looked up in
|
||||
* the hashmap. This class has abstract methods to define how the object moves (move()), when to
|
||||
* check for collision with any object (touches(obj)) and how to handle collision
|
||||
* (handleCollision(obj)), and will be extended by any object whose position has to be kept track of
|
||||
* in the quadtree. The <b>{@link SpatialPartitionGeneric}</b> abstract class has 2 fields - a
|
||||
* hashmap containing all objects (we use hashmap for faster lookups, insertion and deletion)
|
||||
* and a quadtree, and contains an abstract method which defines how to handle interactions between
|
||||
* objects using the quadtree.</p>
|
||||
* capacity). There is also a <b>{@link Rect}</b> class to define the boundary of the quadtree. We
|
||||
* use an abstract class <b>{@link Point}</b> with x and y coordinate fields and also an id field so
|
||||
* that it can easily be put and looked up in the hashmap. This class has abstract methods to define
|
||||
* how the object moves (move()), when to check for collision with any object (touches(obj)) and how
|
||||
* to handle collision (handleCollision(obj)), and will be extended by any object whose position has
|
||||
* to be kept track of in the quadtree. The <b>{@link SpatialPartitionGeneric}</b> abstract class
|
||||
* has 2 fields - a hashmap containing all objects (we use hashmap for faster lookups, insertion and
|
||||
* deletion) and a quadtree, and contains an abstract method which defines how to handle
|
||||
* interactions between objects using the quadtree.
|
||||
*
|
||||
* <p>Using the quadtree data structure will reduce the time complexity of finding the objects
|
||||
* within a certain range from <b>O(n^2) to O(nlogn)</b>, increasing the speed of computations
|
||||
* immensely in case of large number of objects, which will have a positive effect on the rendering
|
||||
* speed of the game.</p>
|
||||
* speed of the game.
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
public class App {
|
||||
|
||||
static void noSpatialPartition(int numOfMovements, Map<Integer, Bubble> bubbles) {
|
||||
//all bubbles have to be checked for collision for all bubbles
|
||||
// all bubbles have to be checked for collision for all bubbles
|
||||
var bubblesToCheck = bubbles.values();
|
||||
|
||||
//will run numOfMovement times or till all bubbles have popped
|
||||
// will run numOfMovement times or till all bubbles have popped
|
||||
while (numOfMovements > 0 && !bubbles.isEmpty()) {
|
||||
bubbles.forEach((i, bubble) -> {
|
||||
// bubble moves, new position gets updated
|
||||
// and collisions are checked with all bubbles in bubblesToCheck
|
||||
bubble.move();
|
||||
bubbles.replace(i, bubble);
|
||||
bubble.handleCollision(bubblesToCheck, bubbles);
|
||||
});
|
||||
bubbles.forEach(
|
||||
(i, bubble) -> {
|
||||
// bubble moves, new position gets updated
|
||||
// and collisions are checked with all bubbles in bubblesToCheck
|
||||
bubble.move();
|
||||
bubbles.replace(i, bubble);
|
||||
bubble.handleCollision(bubblesToCheck, bubbles);
|
||||
});
|
||||
numOfMovements--;
|
||||
}
|
||||
//bubbles not popped
|
||||
// bubbles not popped
|
||||
bubbles.keySet().forEach(key -> LOGGER.info("Bubble {} not popped", key));
|
||||
}
|
||||
|
||||
static void withSpatialPartition(
|
||||
int height, int width, int numOfMovements, Map<Integer, Bubble> bubbles) {
|
||||
//creating quadtree
|
||||
// creating quadtree
|
||||
var rect = new Rect(width / 2D, height / 2D, width, height);
|
||||
var quadTree = new QuadTree(rect, 4);
|
||||
|
||||
//will run numOfMovement times or till all bubbles have popped
|
||||
// will run numOfMovement times or till all bubbles have popped
|
||||
while (numOfMovements > 0 && !bubbles.isEmpty()) {
|
||||
//quadtree updated each time
|
||||
// quadtree updated each time
|
||||
bubbles.values().forEach(quadTree::insert);
|
||||
bubbles.forEach((i, bubble) -> {
|
||||
//bubble moves, new position gets updated, quadtree used to reduce computations
|
||||
bubble.move();
|
||||
bubbles.replace(i, bubble);
|
||||
var sp = new SpatialPartitionBubbles(bubbles, quadTree);
|
||||
sp.handleCollisionsUsingQt(bubble);
|
||||
});
|
||||
bubbles.forEach(
|
||||
(i, bubble) -> {
|
||||
// bubble moves, new position gets updated, quadtree used to reduce computations
|
||||
bubble.move();
|
||||
bubbles.replace(i, bubble);
|
||||
var sp = new SpatialPartitionBubbles(bubbles, quadTree);
|
||||
sp.handleCollisionsUsingQt(bubble);
|
||||
});
|
||||
numOfMovements--;
|
||||
}
|
||||
//bubbles not popped
|
||||
// bubbles not popped
|
||||
bubbles.keySet().forEach(key -> LOGGER.info("Bubble {} not popped", key));
|
||||
}
|
||||
|
||||
@@ -108,7 +109,6 @@ public class App {
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
|
||||
public static void main(String[] args) {
|
||||
var bubbles1 = new ConcurrentHashMap<Integer, Bubble>();
|
||||
var bubbles2 = new ConcurrentHashMap<Integer, Bubble>();
|
||||
@@ -117,8 +117,8 @@ public class App {
|
||||
var b = new Bubble(rand.nextInt(300), rand.nextInt(300), i, rand.nextInt(2) + 1);
|
||||
bubbles1.put(i, b);
|
||||
bubbles2.put(i, b);
|
||||
LOGGER.info("Bubble {} with radius {} added at ({},{})",
|
||||
i, b.radius, b.coordinateX, b.coordinateY);
|
||||
LOGGER.info(
|
||||
"Bubble {} with radius {} added at ({},{})", i, b.radius, b.coordinateX, b.coordinateY);
|
||||
}
|
||||
|
||||
var start1 = System.currentTimeMillis();
|
||||
|
||||
@@ -33,7 +33,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* Bubble class extends Point. In this example, we create several bubbles in the field, let them
|
||||
* move and keep track of which ones have popped and which ones remain.
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
public class Bubble extends Point<Bubble> {
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
@@ -46,15 +45,15 @@ public class Bubble extends Point<Bubble> {
|
||||
}
|
||||
|
||||
void move() {
|
||||
//moves by 1 unit in either direction
|
||||
// moves by 1 unit in either direction
|
||||
this.coordinateX += RANDOM.nextInt(3) - 1;
|
||||
this.coordinateY += RANDOM.nextInt(3) - 1;
|
||||
}
|
||||
|
||||
boolean touches(Bubble b) {
|
||||
//distance between them is greater than sum of radii (both sides of equation squared)
|
||||
// distance between them is greater than sum of radii (both sides of equation squared)
|
||||
return (this.coordinateX - b.coordinateX) * (this.coordinateX - b.coordinateX)
|
||||
+ (this.coordinateY - b.coordinateY) * (this.coordinateY - b.coordinateY)
|
||||
+ (this.coordinateY - b.coordinateY) * (this.coordinateY - b.coordinateY)
|
||||
<= (this.radius + b.radius) * (this.radius + b.radius);
|
||||
}
|
||||
|
||||
@@ -64,12 +63,12 @@ public class Bubble extends Point<Bubble> {
|
||||
}
|
||||
|
||||
void handleCollision(Collection<? extends Point> toCheck, Map<Integer, Bubble> allBubbles) {
|
||||
var toBePopped = false; //if any other bubble collides with it, made true
|
||||
var toBePopped = false; // if any other bubble collides with it, made true
|
||||
for (var point : toCheck) {
|
||||
var otherId = point.id;
|
||||
if (allBubbles.get(otherId) != null //the bubble hasn't been popped yet
|
||||
&& this.id != otherId //the two bubbles are not the same
|
||||
&& this.touches(allBubbles.get(otherId))) { //the bubbles touch
|
||||
if (allBubbles.get(otherId) != null // the bubble hasn't been popped yet
|
||||
&& this.id != otherId // the two bubbles are not the same
|
||||
&& this.touches(allBubbles.get(otherId))) { // the bubbles touch
|
||||
allBubbles.get(otherId).pop(allBubbles);
|
||||
toBePopped = true;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import java.util.Map;
|
||||
*
|
||||
* @param <T> T will be type subclass
|
||||
*/
|
||||
|
||||
public abstract class Point<T> {
|
||||
|
||||
public int coordinateX;
|
||||
@@ -46,9 +45,7 @@ public abstract class Point<T> {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* defines how the object moves.
|
||||
*/
|
||||
/** defines how the object moves. */
|
||||
abstract void move();
|
||||
|
||||
/**
|
||||
@@ -63,7 +60,7 @@ public abstract class Point<T> {
|
||||
* handling interactions/collisions with other objects.
|
||||
*
|
||||
* @param toCheck contains the objects which need to be checked
|
||||
* @param all contains hashtable of all points on field at this time
|
||||
* @param all contains hashtable of all points on field at this time
|
||||
*/
|
||||
abstract void handleCollision(Collection<? extends Point> toCheck, Map<Integer, T> all);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import java.util.Map;
|
||||
* insert(Point) and query(range) methods to insert a new object and find the objects within a
|
||||
* certain (rectangular) range respectively.
|
||||
*/
|
||||
|
||||
public class QuadTree {
|
||||
Rect boundary;
|
||||
int capacity;
|
||||
@@ -93,13 +92,9 @@ public class QuadTree {
|
||||
}
|
||||
|
||||
Collection<Point> query(Rect r, Collection<Point> relevantPoints) {
|
||||
//could also be a circle instead of a rectangle
|
||||
// could also be a circle instead of a rectangle
|
||||
if (this.boundary.intersects(r)) {
|
||||
this.points
|
||||
.values()
|
||||
.stream()
|
||||
.filter(r::contains)
|
||||
.forEach(relevantPoints::add);
|
||||
this.points.values().stream().filter(r::contains).forEach(relevantPoints::add);
|
||||
if (this.divided) {
|
||||
this.northwest.query(r, relevantPoints);
|
||||
this.northeast.query(r, relevantPoints);
|
||||
|
||||
@@ -28,14 +28,13 @@ package com.iluwatar.spatialpartition;
|
||||
* The Rect class helps in defining the boundary of the quadtree and is also used to define the
|
||||
* range within which objects need to be found in our example.
|
||||
*/
|
||||
|
||||
public class Rect {
|
||||
double coordinateX;
|
||||
double coordinateY;
|
||||
double width;
|
||||
double height;
|
||||
|
||||
//(x,y) - centre of rectangle
|
||||
// (x,y) - centre of rectangle
|
||||
|
||||
Rect(double x, double y, double width, double height) {
|
||||
this.coordinateX = x;
|
||||
@@ -58,4 +57,3 @@ public class Rect {
|
||||
|| this.coordinateY - this.height / 2 >= other.coordinateY + other.height / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -31,7 +31,6 @@ import java.util.Map;
|
||||
* This class extends the generic SpatialPartition abstract class and is used in our example to keep
|
||||
* track of all the bubbles that collide, pop and stay un-popped.
|
||||
*/
|
||||
|
||||
public class SpatialPartitionBubbles extends SpatialPartitionGeneric<Bubble> {
|
||||
|
||||
private final Map<Integer, Bubble> bubbles;
|
||||
@@ -48,7 +47,7 @@ public class SpatialPartitionBubbles extends SpatialPartitionGeneric<Bubble> {
|
||||
var rect = new Rect(b.coordinateX, b.coordinateY, 2D * b.radius, 2D * b.radius);
|
||||
var quadTreeQueryResult = new ArrayList<Point>();
|
||||
this.bubblesQuadTree.query(rect, quadTreeQueryResult);
|
||||
//handling these collisions
|
||||
// handling these collisions
|
||||
b.handleCollision(quadTreeQueryResult, this.bubbles);
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -32,7 +32,6 @@ import java.util.Map;
|
||||
*
|
||||
* @param <T> T will be type of object (that extends Point)
|
||||
*/
|
||||
|
||||
public abstract class SpatialPartitionGeneric<T> {
|
||||
|
||||
Map<Integer, T> playerPositions;
|
||||
|
||||
@@ -33,10 +33,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Testing methods in Bubble class.
|
||||
*/
|
||||
|
||||
/** Testing methods in Bubble class. */
|
||||
class BubbleTest {
|
||||
|
||||
@Test
|
||||
@@ -45,7 +42,7 @@ class BubbleTest {
|
||||
var initialX = b.coordinateX;
|
||||
var initialY = b.coordinateY;
|
||||
b.move();
|
||||
//change in x and y < |2|
|
||||
// change in x and y < |2|
|
||||
assertTrue(b.coordinateX - initialX < 2 && b.coordinateX - initialX > -2);
|
||||
assertTrue(b.coordinateY - initialY < 2 && b.coordinateY - initialY > -2);
|
||||
}
|
||||
@@ -55,7 +52,7 @@ class BubbleTest {
|
||||
var b1 = new Bubble(0, 0, 1, 2);
|
||||
var b2 = new Bubble(1, 1, 2, 1);
|
||||
var b3 = new Bubble(10, 10, 3, 1);
|
||||
//b1 touches b2 but not b3
|
||||
// b1 touches b2 but not b3
|
||||
assertTrue(b1.touches(b2));
|
||||
assertFalse(b1.touches(b3));
|
||||
}
|
||||
@@ -68,7 +65,7 @@ class BubbleTest {
|
||||
bubbles.put(1, b1);
|
||||
bubbles.put(2, b2);
|
||||
b1.pop(bubbles);
|
||||
//after popping, bubble no longer in hashMap containing all bubbles
|
||||
// after popping, bubble no longer in hashMap containing all bubbles
|
||||
assertNull(bubbles.get(1));
|
||||
assertNotNull(bubbles.get(2));
|
||||
}
|
||||
@@ -86,7 +83,7 @@ class BubbleTest {
|
||||
bubblesToCheck.add(b2);
|
||||
bubblesToCheck.add(b3);
|
||||
b1.handleCollision(bubblesToCheck, bubbles);
|
||||
//b1 touches b2 and not b3, so b1, b2 will be popped
|
||||
// b1 touches b2 and not b3, so b1, b2 will be popped
|
||||
assertNull(bubbles.get(1));
|
||||
assertNull(bubbles.get(2));
|
||||
assertNotNull(bubbles.get(3));
|
||||
|
||||
@@ -33,10 +33,7 @@ import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Testing QuadTree class.
|
||||
*/
|
||||
|
||||
/** Testing QuadTree class. */
|
||||
class QuadTreeTest {
|
||||
|
||||
@Test
|
||||
@@ -47,22 +44,21 @@ class QuadTreeTest {
|
||||
var p = new Bubble(rand.nextInt(300), rand.nextInt(300), i, rand.nextInt(2) + 1);
|
||||
points.add(p);
|
||||
}
|
||||
var field = new Rect(150, 150, 300, 300); //size of field
|
||||
var queryRange = new Rect(70, 130, 100, 100); //result = all points lying in this rectangle
|
||||
//points found in the query range using quadtree and normal method is same
|
||||
var field = new Rect(150, 150, 300, 300); // size of field
|
||||
var queryRange = new Rect(70, 130, 100, 100); // result = all points lying in this rectangle
|
||||
// points found in the query range using quadtree and normal method is same
|
||||
var points1 = QuadTreeTest.quadTreeTest(points, field, queryRange);
|
||||
var points2 = QuadTreeTest.verify(points, queryRange);
|
||||
assertEquals(points1, points2);
|
||||
}
|
||||
|
||||
static Hashtable<Integer, Point> quadTreeTest(Collection<Point> points, Rect field, Rect queryRange) {
|
||||
//creating quadtree and inserting all points
|
||||
static Hashtable<Integer, Point> quadTreeTest(
|
||||
Collection<Point> points, Rect field, Rect queryRange) {
|
||||
// creating quadtree and inserting all points
|
||||
var qTree = new QuadTree(queryRange, 4);
|
||||
points.forEach(qTree::insert);
|
||||
|
||||
return qTree
|
||||
.query(field, new ArrayList<>())
|
||||
.stream()
|
||||
return qTree.query(field, new ArrayList<>()).stream()
|
||||
.collect(Collectors.toMap(p -> p.id, p -> p, (a, b) -> b, Hashtable::new));
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Testing Rect class.
|
||||
*/
|
||||
|
||||
/** Testing Rect class. */
|
||||
class RectTest {
|
||||
|
||||
@Test
|
||||
@@ -40,7 +37,7 @@ class RectTest {
|
||||
var r = new Rect(10, 10, 20, 20);
|
||||
var b1 = new Bubble(2, 2, 1, 1);
|
||||
var b2 = new Bubble(30, 30, 2, 1);
|
||||
//r contains b1 and not b2
|
||||
// r contains b1 and not b2
|
||||
assertTrue(r.contains(b1));
|
||||
assertFalse(r.contains(b2));
|
||||
}
|
||||
@@ -50,7 +47,7 @@ class RectTest {
|
||||
var r1 = new Rect(10, 10, 20, 20);
|
||||
var r2 = new Rect(15, 15, 20, 20);
|
||||
var r3 = new Rect(50, 50, 20, 20);
|
||||
//r1 intersects r2 and not r3
|
||||
// r1 intersects r2 and not r3
|
||||
assertTrue(r1.intersects(r2));
|
||||
assertFalse(r1.intersects(r3));
|
||||
}
|
||||
|
||||
+2
-5
@@ -30,10 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import java.util.HashMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Testing SpatialPartition_Bubbles class.
|
||||
*/
|
||||
|
||||
/** Testing SpatialPartition_Bubbles class. */
|
||||
class SpatialPartitionBubblesTest {
|
||||
|
||||
@Test
|
||||
@@ -55,7 +52,7 @@ class SpatialPartitionBubblesTest {
|
||||
qt.insert(b4);
|
||||
var sp = new SpatialPartitionBubbles(bubbles, qt);
|
||||
sp.handleCollisionsUsingQt(b1);
|
||||
//b1 touches b3 and b4 but not b2 - so b1,b3,b4 get popped
|
||||
// b1 touches b3 and b4 but not b2 - so b1,b3,b4 get popped
|
||||
assertNull(bubbles.get(1));
|
||||
assertNotNull(bubbles.get(2));
|
||||
assertNull(bubbles.get(3));
|
||||
|
||||
Reference in New Issue
Block a user