mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-14 10:58:42 +00:00
feature: Completed component design pattern implementation, testing and respective README (#2153)
* update the explanation in README.md * #556 update initial files * added README to component design pattern * Rearrange the file * Finalize the directory * Add test sample * Update the title for README.md * Update the title for README.md * Update the title for README.md * Update the title for README.md * Finish the component design pattern * Updated comments and docstrings for component DP, added basic tests for App and GameObject java classes. Slight modifications to pom.xml to reflect the test suite introduction. * updated comments/docstrings for all classes - wrote v1 of README.md for component design pattern. This still requires the class diagram sketch. * Update the UML and linked with the README.md * Update the README.md * Remove the additional update method and rearrange the file based on the CheckStyle plugin * Changed the structure based on the code smells feedback from PR * Documentation update - uml * Documentation update - grammar * Updated readme to reflect the use of the LOGGER instead of the system output prints. * Correct the constant name * Uses Lombok to remove getter/setter boilerplate * Rename the constant name * Branch out from master and finish all the review changes * Correct the CheckStyle warning Co-authored-by: Samman Palihapitiya Gamage <u7287889@anu.edu.au> Co-authored-by: SammanPali <110753804+SammanPali@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package com.iluwatar.component;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* The component design pattern is a common game design structure. This pattern is often
|
||||
* used to reduce duplication of code as well as to improve maintainability.
|
||||
* In this implementation, component design pattern has been used to provide two game
|
||||
* objects with varying component interfaces (features). As opposed to copying and
|
||||
* pasting same code for the two game objects, the component interfaces allow game
|
||||
* objects to inherit these components from the component classes.
|
||||
*
|
||||
* <p>The implementation has decoupled graphic, physics and input components from
|
||||
* the player and NPC objects. As a result, it avoids the creation of monolithic java classes.
|
||||
*
|
||||
* <p>The below example in this App class demonstrates the use of the component interfaces
|
||||
* for separate objects (player & NPC) and updating of these components as per the
|
||||
* implementations in GameObject class and the component classes.
|
||||
*/
|
||||
@Slf4j
|
||||
public final class App {
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args args command line args.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
final var player = GameObject.createPlayer();
|
||||
final var npc = GameObject.createNpc();
|
||||
|
||||
|
||||
LOGGER.info("Player Update:");
|
||||
player.update(KeyEvent.KEY_LOCATION_LEFT);
|
||||
LOGGER.info("NPC Update:");
|
||||
npc.demoUpdate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.iluwatar.component;
|
||||
|
||||
import com.iluwatar.component.component.graphiccomponent.GraphicComponent;
|
||||
import com.iluwatar.component.component.graphiccomponent.ObjectGraphicComponent;
|
||||
import com.iluwatar.component.component.inputcomponent.DemoInputComponent;
|
||||
import com.iluwatar.component.component.inputcomponent.InputComponent;
|
||||
import com.iluwatar.component.component.inputcomponent.PlayerInputComponent;
|
||||
import com.iluwatar.component.component.physiccomponent.ObjectPhysicComponent;
|
||||
import com.iluwatar.component.component.physiccomponent.PhysicComponent;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* The GameObject class has three component class instances that allow
|
||||
* the creation of different game objects based on the game design requirements.
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class GameObject {
|
||||
private final InputComponent inputComponent;
|
||||
private final PhysicComponent physicComponent;
|
||||
private final GraphicComponent graphicComponent;
|
||||
|
||||
private final String name;
|
||||
private int velocity = 0;
|
||||
private int coordinate = 0;
|
||||
|
||||
/**
|
||||
* Creates a player game object.
|
||||
*
|
||||
* @return player object
|
||||
*/
|
||||
public static GameObject createPlayer() {
|
||||
return new GameObject(new PlayerInputComponent(),
|
||||
new ObjectPhysicComponent(),
|
||||
new ObjectGraphicComponent(),
|
||||
"player");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a NPC game object.
|
||||
*
|
||||
* @return npc object
|
||||
*/
|
||||
public static GameObject createNpc() {
|
||||
return new GameObject(
|
||||
new DemoInputComponent(),
|
||||
new ObjectPhysicComponent(),
|
||||
new ObjectGraphicComponent(),
|
||||
"npc");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the three components of the NPC object used in the demo in App.java
|
||||
* note that this is simply a duplicate of update() without the key event for
|
||||
* demonstration purposes.
|
||||
*
|
||||
* <p>This method is usually used in games if the player becomes inactive.
|
||||
*/
|
||||
public void demoUpdate() {
|
||||
inputComponent.update(this, 0);
|
||||
physicComponent.update(this);
|
||||
graphicComponent.update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the three components for objects based on key events.
|
||||
*
|
||||
* @param e key event from the player.
|
||||
*/
|
||||
public void update(int e) {
|
||||
inputComponent.update(this, e);
|
||||
physicComponent.update(this);
|
||||
graphicComponent.update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the velocity based on the acceleration of the GameObject.
|
||||
*
|
||||
* @param acceleration the acceleration of the GameObject
|
||||
*/
|
||||
public void updateVelocity(int acceleration) {
|
||||
this.velocity += acceleration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the c based on the current velocity.
|
||||
*/
|
||||
public void updateCoordinate() {
|
||||
this.coordinate += this.velocity;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.iluwatar.component.component.graphiccomponent;
|
||||
|
||||
import com.iluwatar.component.GameObject;
|
||||
|
||||
/**
|
||||
* Generic GraphicComponent interface.
|
||||
*/
|
||||
public interface GraphicComponent {
|
||||
void update(GameObject gameObject);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.iluwatar.component.component.graphiccomponent;
|
||||
|
||||
import com.iluwatar.component.GameObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* ObjectGraphicComponent class mimics the graphic component of the Game Object.
|
||||
*/
|
||||
@Slf4j
|
||||
public class ObjectGraphicComponent implements GraphicComponent {
|
||||
|
||||
/**
|
||||
* The method updates the graphics based on the velocity of gameObject.
|
||||
*
|
||||
* @param gameObject the gameObject instance
|
||||
*/
|
||||
@Override
|
||||
public void update(GameObject gameObject) {
|
||||
LOGGER.info(gameObject.getName() + "'s current velocity: " + gameObject.getVelocity());
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.iluwatar.component.component.inputcomponent;
|
||||
|
||||
import com.iluwatar.component.GameObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Take this component class to control player or the NPC for demo mode.
|
||||
* and implemented the InputComponent interface.
|
||||
*
|
||||
* <p>Essentially, the demo mode is utilised during a game if the user become inactive.
|
||||
* Please see: http://gameprogrammingpatterns.com/component.html
|
||||
*/
|
||||
@Slf4j
|
||||
public class DemoInputComponent implements InputComponent {
|
||||
private static final int WALK_ACCELERATION = 2;
|
||||
|
||||
/**
|
||||
* Redundant method in the demo mode.
|
||||
*
|
||||
* @param gameObject the gameObject instance
|
||||
* @param e key event instance
|
||||
*/
|
||||
@Override
|
||||
public void update(GameObject gameObject, int e) {
|
||||
gameObject.updateVelocity(WALK_ACCELERATION);
|
||||
LOGGER.info(gameObject.getName() + " has moved right.");
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.iluwatar.component.component.inputcomponent;
|
||||
|
||||
import com.iluwatar.component.GameObject;
|
||||
|
||||
/**
|
||||
* Generic InputComponent interface.
|
||||
*/
|
||||
public interface InputComponent {
|
||||
void update(GameObject gameObject, int e);
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.iluwatar.component.component.inputcomponent;
|
||||
|
||||
import com.iluwatar.component.GameObject;
|
||||
import java.awt.event.KeyEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* PlayerInputComponent is used to handle user key event inputs,
|
||||
* and thus it implements the InputComponent interface.
|
||||
*/
|
||||
@Slf4j
|
||||
public class PlayerInputComponent implements InputComponent {
|
||||
private static final int WALK_ACCELERATION = 1;
|
||||
|
||||
/**
|
||||
* The update method to change the velocity based on the input key event.
|
||||
*
|
||||
* @param gameObject the gameObject instance
|
||||
* @param e key event instance
|
||||
*/
|
||||
@Override
|
||||
public void update(GameObject gameObject, int e) {
|
||||
switch (e) {
|
||||
case KeyEvent.KEY_LOCATION_LEFT:
|
||||
gameObject.updateVelocity(-WALK_ACCELERATION);
|
||||
LOGGER.info(gameObject.getName() + " has moved left.");
|
||||
break;
|
||||
case KeyEvent.KEY_LOCATION_RIGHT:
|
||||
gameObject.updateVelocity(WALK_ACCELERATION);
|
||||
LOGGER.info(gameObject.getName() + " has moved right.");
|
||||
break;
|
||||
default:
|
||||
LOGGER.info(gameObject.getName() + "'s velocity is unchanged due to the invalid input");
|
||||
gameObject.updateVelocity(0);
|
||||
break; // incorrect input
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.iluwatar.component.component.physiccomponent;
|
||||
|
||||
import com.iluwatar.component.GameObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Take this component class to update the x coordinate for the Game Object instance.
|
||||
*/
|
||||
@Slf4j
|
||||
public class ObjectPhysicComponent implements PhysicComponent {
|
||||
|
||||
/**
|
||||
* The method update the horizontal (X-axis) coordinate based on the velocity of gameObject.
|
||||
*
|
||||
* @param gameObject the gameObject instance
|
||||
*/
|
||||
@Override
|
||||
public void update(GameObject gameObject) {
|
||||
gameObject.updateCoordinate();
|
||||
LOGGER.info(gameObject.getName() + "'s coordinate has been changed.");
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.iluwatar.component.component.physiccomponent;
|
||||
|
||||
import com.iluwatar.component.GameObject;
|
||||
|
||||
/**
|
||||
* Generic PhysicComponent interface.
|
||||
*/
|
||||
public interface PhysicComponent {
|
||||
void update(GameObject gameObject);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.iluwatar.component;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests App class : src/main/java/com/iluwatar/component/App.java
|
||||
* General execution test of the application.
|
||||
*/
|
||||
class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteComponentWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.iluwatar.component;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import java.awt.event.KeyEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Tests GameObject class.
|
||||
* src/main/java/com/iluwatar/component/GameObject.java
|
||||
*/
|
||||
@Slf4j
|
||||
class GameObjectTest {
|
||||
GameObject playerTest;
|
||||
GameObject npcTest;
|
||||
@BeforeEach
|
||||
public void initEach() {
|
||||
//creates player & npc objects for testing
|
||||
//note that velocity and coordinates are initialised to 0 in GameObject.java
|
||||
playerTest = GameObject.createPlayer();
|
||||
npcTest = GameObject.createNpc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the create methods - createPlayer() and createNPC().
|
||||
*/
|
||||
@Test
|
||||
void objectTest(){
|
||||
LOGGER.info("objectTest:");
|
||||
assertEquals("player",playerTest.getName());
|
||||
assertEquals("npc",npcTest.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the input component with varying key event inputs.
|
||||
* Targets the player game object.
|
||||
*/
|
||||
@Test
|
||||
void eventInputTest(){
|
||||
LOGGER.info("eventInputTest:");
|
||||
playerTest.update(KeyEvent.KEY_LOCATION_LEFT);
|
||||
assertEquals(-1, playerTest.getVelocity());
|
||||
assertEquals(-1, playerTest.getCoordinate());
|
||||
|
||||
playerTest.update(KeyEvent.KEY_LOCATION_RIGHT);
|
||||
playerTest.update(KeyEvent.KEY_LOCATION_RIGHT);
|
||||
assertEquals(1, playerTest.getVelocity());
|
||||
assertEquals(0, playerTest.getCoordinate());
|
||||
|
||||
LOGGER.info(Integer.toString(playerTest.getCoordinate()));
|
||||
LOGGER.info(Integer.toString(playerTest.getVelocity()));
|
||||
|
||||
GameObject p2 = GameObject.createPlayer();
|
||||
p2.update(KeyEvent.KEY_LOCATION_LEFT);
|
||||
//in the case of an unknown, object stats are set to default
|
||||
p2.update(KeyEvent.KEY_LOCATION_UNKNOWN);
|
||||
assertEquals(-1, p2.getVelocity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the demo component interface.
|
||||
*/
|
||||
@Test
|
||||
void npcDemoTest(){
|
||||
LOGGER.info("npcDemoTest:");
|
||||
npcTest.demoUpdate();
|
||||
assertEquals(2, npcTest.getVelocity());
|
||||
assertEquals(2, npcTest.getCoordinate());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user