feat: Implemented the Table Inheritance pattern (#3105)

* sample classes and tests

* sample classes and tests

* read me addition

* fix violations

* fix quality (coverage)

* fix quality (coverage) #2

* resolved comments
This commit is contained in:
HabibaMekay
2025-01-06 19:03:27 +02:00
committed by GitHub
parent eb7a0dff55
commit c5a6862887
10 changed files with 745 additions and 0 deletions
@@ -0,0 +1,65 @@
package com.iluwatar.table.inheritance;
import java.util.logging.Logger;
/**
* The main entry point of the application demonstrating the use of vehicles.
*
* <p>The Table Inheritance pattern models a class hierarchy in a relational database by creating
* separate tables for each class in the hierarchy. These tables share a common primary key, which in
* subclass tables also serves as a foreign key referencing the primary key of the base class table.
* This linkage maintains relationships and effectively represents the inheritance structure. This
* pattern enables the organization of complex data models, particularly when subclasses have unique
* properties that must be stored in distinct tables.
*/
public class App {
/**
* Manages the storage and retrieval of Vehicle objects, including Cars and Trucks.
*
* <p>This example demonstrates the **Table Inheritance** pattern, where each vehicle type
* (Car and Truck) is stored in its own separate table. The `VehicleDatabase` simulates
* a simple database that manages these entities, with each subclass (Car and Truck)
* being stored in its respective table.
*
* <p>The `VehicleDatabase` contains the following tables:
* - `vehicleTable`: Stores all vehicle objects, including both `Car` and `Truck` objects.
* - `carTable`: Stores only `Car` objects, with fields specific to cars.
* - `truckTable`: Stores only `Truck` objects, with fields specific to trucks.
*
* <p>The example demonstrates:
* 1. Saving instances of `Car` and `Truck` to their respective tables in the database.
* 2. Retrieving vehicles (both cars and trucks) from the appropriate table based on their ID.
* 3. Printing all vehicles stored in the database.
* 4. Showing how to retrieve specific types of vehicles (`Car` or `Truck`) by their IDs.
*
* <p>In the **Table Inheritance** pattern, each subclass has its own table, making it easier
* to manage specific attributes of each subclass.
*
* @param args command-line arguments
*/
public static void main(String[] args) {
final Logger logger = Logger.getLogger(App.class.getName());
VehicleDatabase database = new VehicleDatabase();
Car car = new Car(2020, "Toyota", "Corolla", 4, 1);
Truck truck = new Truck(2018, "Ford", "F-150", 60, 2);
database.saveVehicle(car);
database.saveVehicle(truck);
database.printAllVehicles();
Vehicle vehicle = database.getVehicle(car.getId());
Car retrievedCar = database.getCar(car.getId());
Truck retrievedTruck = database.getTruck(truck.getId());
logger.info(String.format("Retrieved Vehicle: %s", vehicle));
logger.info(String.format("Retrieved Car: %s", retrievedCar));
logger.info(String.format("Retrieved Truck: %s", retrievedTruck));
}
}
@@ -0,0 +1,50 @@
package com.iluwatar.table.inheritance;
import lombok.Getter;
/**
* Represents a car with a specific number of doors.
*/
@Getter
public class Car extends Vehicle {
private int numDoors;
/**
* Constructs a Car object.
*
* @param year the manufacturing year
* @param make the make of the car
* @param model the model of the car
* @param numDoors the number of doors
* @param id the unique identifier for the car
*/
public Car(int year, String make, String model, int numDoors, int id) {
super(year, make, model, id);
if (numDoors <= 0) {
throw new IllegalArgumentException("Number of doors must be positive.");
}
this.numDoors = numDoors;
}
/**
* Sets the number of doors for the car.
*
* @param doors the number of doors
*/
public void setNumDoors(int doors) {
if (doors <= 0) {
throw new IllegalArgumentException("Number of doors must be positive.");
}
this.numDoors = doors;
}
@Override
public String toString() {
return "Car{"
+ "id=" + getId()
+ ", make='" + getMake() + '\''
+ ", model='" + getModel() + '\''
+ ", year=" + getYear()
+ ", numberOfDoors=" + getNumDoors()
+ '}';
}
}
@@ -0,0 +1,57 @@
package com.iluwatar.table.inheritance;
import lombok.Getter;
/**
* Represents a truck, a type of vehicle with a specific load capacity.
*/
@Getter
public class Truck extends Vehicle {
private double loadCapacity;
/**
* Constructs a Truck object with the given parameters.
*
* @param year the year of manufacture
* @param make the make of the truck
* @param model the model of the truck
* @param loadCapacity the load capacity of the truck
* @param id the unique ID of the truck
*/
public Truck(int year, String make, String model, double loadCapacity, int id) {
super(year, make, model, id);
if (loadCapacity <= 0) {
throw new IllegalArgumentException("Load capacity must be positive.");
}
this.loadCapacity = loadCapacity;
}
/**
* Sets the load capacity of the truck.
*
* @param capacity the new load capacity
*/
public void setLoadCapacity(double capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("Load capacity must be positive.");
}
this.loadCapacity = capacity;
}
/**
* Returns a string representation of the truck.
*
* @return a string with the truck's details
*/
@Override
public String toString() {
return "Truck{"
+ "id=" + getId()
+ ", make='" + getMake() + '\''
+ ", model='" + getModel() + '\''
+ ", year=" + getYear()
+ ", payloadCapacity=" + getLoadCapacity()
+ '}';
}
}
@@ -0,0 +1,48 @@
package com.iluwatar.table.inheritance;
import lombok.Getter;
import lombok.Setter;
/**
* Represents a generic vehicle with basic attributes like make, model, year, and ID.
*/
@Setter
@Getter
public class Vehicle {
private String make;
private String model;
private int year;
private int id;
/**
* Constructs a Vehicle object with the given parameters.
*
* @param year the year of manufacture
* @param make the make of the vehicle
* @param model the model of the vehicle
* @param id the unique ID of the vehicle
*/
public Vehicle(int year, String make, String model, int id) {
this.make = make;
this.model = model;
this.year = year;
this.id = id;
}
/**
* Returns a string representation of the vehicle.
*
* @return a string with the vehicle's details
*/
@Override
public String toString() {
return "Vehicle{"
+ "id=" + id
+ ", make='" + make + '\''
+ ", model='" + model + '\''
+ ", year=" + year
+ '}';
}
}
@@ -0,0 +1,73 @@
package com.iluwatar.table.inheritance;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
/**
* Manages the storage and retrieval of Vehicle objects, including Cars and Trucks.
*/
public class VehicleDatabase {
final Logger logger = Logger.getLogger(VehicleDatabase.class.getName());
private Map<Integer, Vehicle> vehicleTable = new HashMap<>();
private Map<Integer, Car> carTable = new HashMap<>();
private Map<Integer, Truck> truckTable = new HashMap<>();
/**
* Saves a vehicle to the database. If the vehicle is a Car or Truck, it is added to the respective table.
*
* @param vehicle the vehicle to save
*/
public void saveVehicle(Vehicle vehicle) {
vehicleTable.put(vehicle.getId(), vehicle);
if (vehicle instanceof Car) {
carTable.put(vehicle.getId(), (Car) vehicle);
} else if (vehicle instanceof Truck) {
truckTable.put(vehicle.getId(), (Truck) vehicle);
}
}
/**
* Retrieves a vehicle by its ID.
*
* @param id the ID of the vehicle
* @return the vehicle with the given ID, or null if not found
*/
public Vehicle getVehicle(int id) {
return vehicleTable.get(id);
}
/**
* Retrieves a car by its ID.
*
* @param id the ID of the car
* @return the car with the given ID, or null if not found
*/
public Car getCar(int id) {
return carTable.get(id);
}
/**
* Retrieves a truck by its ID.
*
* @param id the ID of the truck
* @return the truck with the given ID, or null if not found
*/
public Truck getTruck(int id) {
return truckTable.get(id);
}
/**
* Prints all vehicles in the database.
*/
public void printAllVehicles() {
for (Vehicle vehicle : vehicleTable.values()) {
logger.info(vehicle.toString());
}
}
}
@@ -0,0 +1,50 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.iluwatar.table.inheritance.App;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Logger;
import org.junit.jupiter.api.Test;
/**
* Tests if the main method runs without throwing exceptions and prints expected output.
*/
class AppTest {
@Test
void testAppMainMethod() {
ByteArrayOutputStream outContent = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(outContent);
System.setOut(printStream);
Logger logger = Logger.getLogger(App.class.getName());
Handler handler = new ConsoleHandler() {
@Override
public void publish(java.util.logging.LogRecord recordObj) {
printStream.println(getFormatter().format(recordObj));
}
};
handler.setLevel(java.util.logging.Level.ALL);
logger.addHandler(handler);
App.main(new String[]{});
String output = outContent.toString();
assertTrue(output.contains("Retrieved Vehicle:"));
assertTrue(output.contains("Toyota")); // Car make
assertTrue(output.contains("Ford")); // Truck make
assertTrue(output.contains("Retrieved Car:"));
assertTrue(output.contains("Retrieved Truck:"));
}
}
@@ -0,0 +1,169 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.iluwatar.table.inheritance.Car;
import com.iluwatar.table.inheritance.Truck;
import com.iluwatar.table.inheritance.Vehicle;
import com.iluwatar.table.inheritance.VehicleDatabase;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Unit tests for the {@link VehicleDatabase} class.
* Tests saving, retrieving, and printing vehicles of different types.
*/
class VehicleDatabaseTest {
private VehicleDatabase vehicleDatabase;
/**
* Sets up a new instance of {@link VehicleDatabase} before each test.
*/
@BeforeEach
public void setUp() {
vehicleDatabase = new VehicleDatabase();
}
/**
* Tests saving a {@link Car} to the database and retrieving it.
*/
@Test
void testSaveAndRetrieveCar() {
Car car = new Car(2020, "Toyota", "Corolla", 4, 1);
vehicleDatabase.saveVehicle(car);
Vehicle retrievedVehicle = vehicleDatabase.getVehicle(car.getId());
assertNotNull(retrievedVehicle);
assertEquals(car.getId(), retrievedVehicle.getId());
assertEquals(car.getMake(), retrievedVehicle.getMake());
assertEquals(car.getModel(), retrievedVehicle.getModel());
assertEquals(car.getYear(), retrievedVehicle.getYear());
Car retrievedCar = vehicleDatabase.getCar(car.getId());
assertNotNull(retrievedCar);
assertEquals(car.getNumDoors(), retrievedCar.getNumDoors());
}
/**
* Tests saving a {@link Truck} to the database and retrieving it.
*/
@Test
void testSaveAndRetrieveTruck() {
Truck truck = new Truck(2018, "Ford", "F-150", 60, 2);
vehicleDatabase.saveVehicle(truck);
Vehicle retrievedVehicle = vehicleDatabase.getVehicle(truck.getId());
assertNotNull(retrievedVehicle);
assertEquals(truck.getId(), retrievedVehicle.getId());
assertEquals(truck.getMake(), retrievedVehicle.getMake());
assertEquals(truck.getModel(), retrievedVehicle.getModel());
assertEquals(truck.getYear(), retrievedVehicle.getYear());
Truck retrievedTruck = vehicleDatabase.getTruck(truck.getId());
assertNotNull(retrievedTruck);
assertEquals(truck.getLoadCapacity(), retrievedTruck.getLoadCapacity());
}
/**
* Tests saving multiple vehicles to the database and printing them.
*/
@Test
void testPrintAllVehicles() {
Car car = new Car(2020, "Toyota", "Corolla", 4, 1);
Truck truck = new Truck(2018, "Ford", "F-150", 60, 2);
vehicleDatabase.saveVehicle(car);
vehicleDatabase.saveVehicle(truck);
vehicleDatabase.printAllVehicles();
Vehicle retrievedCar = vehicleDatabase.getVehicle(car.getId());
Vehicle retrievedTruck = vehicleDatabase.getVehicle(truck.getId());
assertNotNull(retrievedCar);
assertNotNull(retrievedTruck);
}
/**
* Tests the constructor of {@link Car} with valid values.
*/
@Test
void testCarConstructor() {
Car car = new Car(2020, "Toyota", "Corolla", 4, 1);
assertEquals(2020, car.getYear());
assertEquals("Toyota", car.getMake());
assertEquals("Corolla", car.getModel());
assertEquals(4, car.getNumDoors());
assertEquals(1, car.getId()); // Assuming the ID is auto-generated in the constructor
}
/**
* Tests the constructor of {@link Car} with invalid number of doors (negative value).
*/
@Test
void testCarConstructorWithInvalidNumDoors() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new Car(2020, "Toyota", "Corolla", -4, 1);
});
assertEquals("Number of doors must be positive.", exception.getMessage());
}
/**
* Tests the constructor of {@link Car} with zero doors.
*/
@Test
void testCarConstructorWithZeroDoors() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new Car(2020, "Toyota", "Corolla", 0, 1);
});
assertEquals("Number of doors must be positive.", exception.getMessage());
}
/**
* Tests the constructor of {@link Truck} with invalid load capacity (negative value).
*/
@Test
void testTruckConstructorWithInvalidLoadCapacity() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new Truck(2018, "Ford", "F-150", -60, 2);
});
assertEquals("Load capacity must be positive.", exception.getMessage());
}
/**
* Tests the constructor of {@link Truck} with zero load capacity.
*/
@Test
void testTruckConstructorWithZeroLoadCapacity() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new Truck(2018, "Ford", "F-150", 0, 2);
});
assertEquals("Load capacity must be positive.", exception.getMessage());
}
/**
* Tests setting invalid number of doors in {@link Car} using setter (negative value).
*/
@Test
void testSetInvalidNumDoors() {
Car car = new Car(2020, "Toyota", "Corolla", 4, 1);
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
car.setNumDoors(-2);
});
assertEquals("Number of doors must be positive.", exception.getMessage());
}
/**
* Tests setting invalid load capacity in {@link Truck} using setter (negative value).
*/
@Test
void testSetInvalidLoadCapacity() {
Truck truck = new Truck(2018, "Ford", "F-150", 60, 2);
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
truck.setLoadCapacity(-10);
});
assertEquals("Load capacity must be positive.", exception.getMessage());
}
}