diff --git a/.all-contributorsrc b/.all-contributorsrc
index 4f780072c499..449bb9b3ccdd 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -3312,6 +3312,24 @@
"contributions": [
"translation"
]
+ },
+ {
+ "login": "HabibaMekay",
+ "name": "HabibaMekay",
+ "avatar_url": "https://avatars.githubusercontent.com/u/133516736?v=4",
+ "profile": "https://github.com/HabibaMekay",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "Ahmed-Taha-981",
+ "name": "Ahmed-Taha-981",
+ "avatar_url": "https://avatars.githubusercontent.com/u/122402269?v=4",
+ "profile": "https://github.com/Ahmed-Taha-981",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 6,
diff --git a/.github/stale.yml b/.github/stale.yml
deleted file mode 100644
index 992d8cb25e94..000000000000
--- a/.github/stale.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-# Configuration for probot-stale - https://github.com/probot/stale
-
-# Number of days of inactivity before an Issue or Pull Request becomes stale
-daysUntilStale: 60
-
-# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
-# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
-daysUntilClose: false
-
-# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
-onlyLabels: []
-
-# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
-exemptLabels:
- - "info: help wanted"
-
-# Set to true to ignore issues in a project (defaults to false)
-exemptProjects: false
-
-# Set to true to ignore issues in a milestone (defaults to false)
-exemptMilestones: false
-
-# Set to true to ignore issues with an assignee (defaults to false)
-exemptAssignees: false
-
-# Label to use when marking as stale
-staleLabel: "status: stale"
-
-# Comment to post when marking as stale. Set to `false` to disable
-markComment: >
- This issue has been automatically marked as stale because it has not had
- recent activity. The issue will be unassigned if no further activity occurs. Thank you
- for your contributions.
-
-# Comment to post when removing the stale label.
-# unmarkComment: >
-# Your comment here.
-
-# Comment to post when closing a stale Issue or Pull Request.
-# closeComment: >
-# Your comment here.
-
-# Limit the number of actions per hour, from 1-30. Default is 30
-limitPerRun: 30
-
-# Limit to only `issues` or `pulls`
-# only: issues
-
-# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
-pulls:
- daysUntilStale: 30
- daysUntilClose: 45
- markComment: >
- This pull request has been automatically marked as stale because it has not had
- recent activity. It will be closed if no further activity occurs.
- closeComment: >
- Closed due to inactivity. Thank you for your contributions.
-
-# issues:
-# exemptLabels:
-# - confirmed
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 000000000000..cbfaa87d9b71
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,19 @@
+name: 'Comment on stale issues and PRs'
+on:
+ schedule:
+ - cron: '30 1 * * *'
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v9
+ with:
+ stale-issue-message: 'This issue is stale because it has been open 60 days with no activity.'
+ stale-pr-message: 'This PR is stale because it has been open 60 days with no activity.'
+ close-issue-message: 'This issue was closed because it has been stalled for too long with no activity.'
+ close-pr-message: 'This PR was closed because it has been stalled for too long with no activity.'
+ days-before-issue-stale: 60
+ days-before-pr-stale: 60
+ days-before-issue-close: -1
+ days-before-pr-close: -1
diff --git a/README.md b/README.md
index 009984d7c43c..a1eb6d660b6b 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[![All Contributors](https://img.shields.io/badge/all_contributors-362-orange.svg?style=flat-square)](#contributors-)
+[![All Contributors](https://img.shields.io/badge/all_contributors-365-orange.svg?style=flat-square)](#contributors-)
@@ -544,6 +544,8 @@ This project is licensed under the terms of the MIT license.
diff --git a/money/README.md b/money/README.md
new file mode 100644
index 000000000000..ca64d68fe882
--- /dev/null
+++ b/money/README.md
@@ -0,0 +1,168 @@
+---
+title: "Money Pattern in Java: Encapsulating Monetary Values with Currency Consistency"
+shortTitle: Money
+description: "Learn how the Money design pattern in Java ensures currency safety, precision handling, and maintainable financial operations. Explore examples, applicability, and benefits of the pattern."
+category: Behavioral
+language: en
+tag:
+ - Encapsulation
+ - Precision handling
+ - Currency safety
+ - Value Object
+ - Financial operations
+ - Currency
+ - Financial
+ - Immutable
+ - Value Object
+---
+
+## Also known as
+
+* Monetary Value Object
+
+## Intent of Money Design Pattern
+
+The Money design pattern provides a robust way to encapsulate monetary values and their associated currencies. It ensures precise calculations, currency consistency, and maintainability of financial logic in Java applications.
+
+## Detailed Explanation of Money Pattern with Real-World Examples
+
+### Real-world example
+
+> Imagine an e-commerce platform where customers shop in their local currencies. The platform needs to calculate order totals, taxes, and discounts accurately while handling multiple currencies seamlessly.
+
+In this example:
+- Each monetary value (like a product price or tax amount) is encapsulated in a `Money` object.
+- The `Money` class ensures that only values in the same currency are combined and supports safe currency conversion for global operations.
+
+### In plain words
+
+> The Money pattern encapsulates both an amount and its currency, ensuring financial operations are precise, consistent, and maintainable.
+
+### Wikipedia says
+
+> "The Money design pattern encapsulates a monetary value and its currency, allowing for safe arithmetic operations and conversions while preserving accuracy and consistency in financial calculations."
+
+## Programmatic Example of Money Pattern in Java
+
+### Money Class
+
+```java
+
+/**
+ * Represents a monetary value with an associated currency.
+ * Provides operations for basic arithmetic (addition, subtraction, multiplication),
+ * as well as currency conversion while ensuring proper rounding.
+ */
+@Getter
+public class Money {
+ private @Getter double amount;
+ private @Getter String currency;
+
+ public Money(double amnt, String curr) {
+ this.amount = amnt;
+ this.currency = curr;
+ }
+
+ private double roundToTwoDecimals(double value) {
+ return Math.round(value * 100.0) / 100.0;
+ }
+
+ public void addMoney(Money moneyToBeAdded) throws CannotAddTwoCurrienciesException {
+ if (!moneyToBeAdded.getCurrency().equals(this.currency)) {
+ throw new CannotAddTwoCurrienciesException("You are trying to add two different currencies");
+ }
+ this.amount = roundToTwoDecimals(this.amount + moneyToBeAdded.getAmount());
+ }
+
+ public void subtractMoney(Money moneyToBeSubtracted) throws CannotSubtractException {
+ if (!moneyToBeSubtracted.getCurrency().equals(this.currency)) {
+ throw new CannotSubtractException("You are trying to subtract two different currencies");
+ } else if (moneyToBeSubtracted.getAmount() > this.amount) {
+ throw new CannotSubtractException("The amount you are trying to subtract is larger than the amount you have");
+ }
+ this.amount = roundToTwoDecimals(this.amount - moneyToBeSubtracted.getAmount());
+ }
+
+ public void multiply(int factor) {
+ if (factor < 0) {
+ throw new IllegalArgumentException("Factor must be non-negative");
+ }
+ this.amount = roundToTwoDecimals(this.amount * factor);
+ }
+
+ public void exchangeCurrency(String currencyToChangeTo, double exchangeRate) {
+ if (exchangeRate < 0) {
+ throw new IllegalArgumentException("Exchange rate must be non-negative");
+ }
+ this.amount = roundToTwoDecimals(this.amount * exchangeRate);
+ this.currency = currencyToChangeTo;
+ }
+}
+
+## When to Use the Money Pattern
+
+The Money pattern should be used in scenarios where:
+
+1. **Currency-safe arithmetic operations**
+ To ensure that arithmetic operations like addition, subtraction, and multiplication are performed only between amounts in the same currency, preventing inconsistencies or errors in calculations.
+
+2. **Accurate rounding for financial calculations**
+ Precise rounding to two decimal places is critical to maintain accuracy and consistency in financial systems.
+
+3. **Consistent currency conversion**
+ When handling international transactions or displaying monetary values in different currencies, the Money pattern facilitates easy and reliable conversion using exchange rates.
+
+4. **Encapsulation of monetary logic**
+ By encapsulating all monetary operations within a dedicated class, the Money pattern improves maintainability and reduces the likelihood of errors.
+
+5. **Preventing errors in financial operations**
+ Strict validation ensures that operations like subtraction or multiplication are only performed when conditions are met, safeguarding against misuse or logical errors.
+
+6. **Handling diverse scenarios in financial systems**
+ Useful in complex systems like e-commerce, banking, and payroll applications where precise and consistent monetary value handling is crucial.
+
+---
+## Benefits and Trade-offs of Money Pattern
+
+### Benefits
+1. **Precision and Accuracy**
+ The Money pattern ensures precise handling of monetary values, reducing the risk of rounding errors.
+
+2. **Encapsulation of Business Logic**
+ By encapsulating monetary operations, the pattern enhances maintainability and reduces redundancy in financial systems.
+
+3. **Currency Safety**
+ It ensures operations are performed only between amounts of the same currency, avoiding logical errors.
+
+4. **Improved Readability**
+ By abstracting monetary logic into a dedicated class, the code becomes easier to read and maintain.
+
+5. **Ease of Extension**
+ Adding new operations, handling different currencies, or incorporating additional business rules is straightforward.
+
+### Trade-offs
+1. **Increased Complexity**
+ Introducing a dedicated `Money` class can add some overhead, especially for small or simple projects.
+
+2. **Potential for Misuse**
+ Without proper validation and handling, incorrect usage of the Money pattern may introduce subtle bugs.
+
+3. **Performance Overhead**
+ Precision and encapsulation might slightly affect performance in systems with extremely high transaction volumes.
+
+---
+
+## Related Design Patterns
+
+1. **Value Object**
+ Money is a classic example of the Value Object pattern, where objects are immutable and define equality based on their value.
+ Link:https://martinfowler.com/bliki/ValueObject.html
+2. **Factory Method**
+ Factories can be employed to handle creation logic, such as applying default exchange rates or rounding rules.
+ Link:https://www.geeksforgeeks.org/factory-method-for-designing-pattern/
+---
+
+## References and Credits
+
+- [Patterns of Enterprise Application Architecture](https://martinfowler.com/eaaCatalog/money.html) by Martin Fowler
+- [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
diff --git a/money/pom.xml b/money/pom.xml
new file mode 100644
index 000000000000..0129bab9501c
--- /dev/null
+++ b/money/pom.xml
@@ -0,0 +1,49 @@
+
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+
+ money
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
+
\ No newline at end of file
diff --git a/money/src/main/java/com/iluwatar/App.java b/money/src/main/java/com/iluwatar/App.java
new file mode 100644
index 000000000000..4030aa776159
--- /dev/null
+++ b/money/src/main/java/com/iluwatar/App.java
@@ -0,0 +1,65 @@
+package com.iluwatar;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+/**
+ * The `App` class demonstrates the functionality of the {@link Money} class, which encapsulates
+ * monetary values and their associated currencies. It showcases operations like addition,
+ * subtraction, multiplication, and currency conversion, while ensuring validation and immutability.
+ *
+ * Through this example, the handling of invalid operations (e.g., mismatched currencies or
+ * invalid inputs) is demonstrated using custom exceptions. Logging is used for transparency.
+ *
+ *
This highlights the practical application of object-oriented principles such as encapsulation
+ * and validation in a financial context.
+ */
+public class App {
+
+ // Initialize the logger
+ private static final Logger logger = Logger.getLogger(App.class.getName());
+ /**
+ * Program entry point.
+ *
+ * @param args command line args
+ */
+ public static void main(String[] args) {
+ // Create instances of Money
+ Money usdAmount1 = new Money(50.00, "USD");
+ Money usdAmount2 = new Money(20.00, "USD");
+
+ // Demonstrate addition
+ try {
+ usdAmount1.addMoney(usdAmount2);
+ logger.log(Level.INFO, "Sum in USD: {0}", usdAmount1.getAmount());
+ } catch (CannotAddTwoCurrienciesException e) {
+ logger.log(Level.SEVERE, "Error adding money: {0}", e.getMessage());
+ }
+
+ // Demonstrate subtraction
+ try {
+ usdAmount1.subtractMoney(usdAmount2);
+ logger.log(Level.INFO, "Difference in USD: {0}", usdAmount1.getAmount());
+ } catch (CannotSubtractException e) {
+ logger.log(Level.SEVERE, "Error subtracting money: {0}", e.getMessage());
+ }
+
+ // Demonstrate multiplication
+ try {
+ usdAmount1.multiply(2);
+ logger.log(Level.INFO, "Multiplied Amount in USD: {0}", usdAmount1.getAmount());
+ } catch (IllegalArgumentException e) {
+ logger.log(Level.SEVERE, "Error multiplying money: {0}", e.getMessage());
+ }
+
+ // Demonstrate currency conversion
+ try {
+ double exchangeRateUsdToEur = 0.85; // Example exchange rate
+ usdAmount1.exchangeCurrency("EUR", exchangeRateUsdToEur);
+ logger.log(Level.INFO, "USD converted to EUR: {0} {1}", new Object[]{usdAmount1.getAmount(), usdAmount1.getCurrency()});
+ } catch (IllegalArgumentException e) {
+ logger.log(Level.SEVERE, "Error converting currency: {0}", e.getMessage());
+ }
+
+ }
+}
+
diff --git a/money/src/main/java/com/iluwatar/CannotAddTwoCurrienciesException.java b/money/src/main/java/com/iluwatar/CannotAddTwoCurrienciesException.java
new file mode 100644
index 000000000000..587c8917ecef
--- /dev/null
+++ b/money/src/main/java/com/iluwatar/CannotAddTwoCurrienciesException.java
@@ -0,0 +1,14 @@
+package com.iluwatar;
+/**
+ * An exception for when the user tries to add two diffrent currencies.
+ */
+public class CannotAddTwoCurrienciesException extends Exception {
+ /**
+ * Constructs an exception with the specified message.
+ *
+ * @param message the message shown in the terminal (as a String).
+ */
+ public CannotAddTwoCurrienciesException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/money/src/main/java/com/iluwatar/CannotSubtractException.java b/money/src/main/java/com/iluwatar/CannotSubtractException.java
new file mode 100644
index 000000000000..881b458c0481
--- /dev/null
+++ b/money/src/main/java/com/iluwatar/CannotSubtractException.java
@@ -0,0 +1,15 @@
+package com.iluwatar;
+/**
+ * An exception for when the user tries to subtract two diffrent currencies or subtract an amount he doesn't have.
+ */
+public class CannotSubtractException extends Exception {
+ /**
+ * Constructs an exception with the specified message.
+ *
+ * @param message the message shown in the terminal (as a String).
+ */
+ public CannotSubtractException(String message) {
+ super(message);
+ }
+
+}
\ No newline at end of file
diff --git a/money/src/main/java/com/iluwatar/Money.java b/money/src/main/java/com/iluwatar/Money.java
new file mode 100644
index 000000000000..e0afe4c7cbb9
--- /dev/null
+++ b/money/src/main/java/com/iluwatar/Money.java
@@ -0,0 +1,91 @@
+package com.iluwatar;
+
+import lombok.Getter;
+
+/**
+ * Represents a monetary value with an associated currency.
+ * Provides operations for basic arithmetic (addition, subtraction, multiplication),
+ * as well as currency conversion while ensuring proper rounding.
+ */
+@Getter
+public class Money {
+ private @Getter double amount;
+ private @Getter String currency;
+
+ /**
+ * Constructs a Money object with the specified amount and currency.
+ *
+ * @param amnt the amount of money (as a double).
+ * @param curr the currency code (e.g., "USD", "EUR").
+ */
+ public Money(double amnt, String curr) {
+ this.amount = amnt;
+ this.currency = curr;
+ }
+
+ /**
+ * Rounds the given value to two decimal places.
+ *
+ * @param value the value to round.
+ * @return the rounded value, up to two decimal places.
+ */
+ private double roundToTwoDecimals(double value) {
+ return Math.round(value * 100.0) / 100.0;
+ }
+
+ /**
+ * Adds another Money object to the current instance.
+ *
+ * @param moneyToBeAdded the Money object to add.
+ * @throws CannotAddTwoCurrienciesException if the currencies do not match.
+ */
+ public void addMoney(Money moneyToBeAdded) throws CannotAddTwoCurrienciesException {
+ if (!moneyToBeAdded.getCurrency().equals(this.currency)) {
+ throw new CannotAddTwoCurrienciesException("You are trying to add two different currencies");
+ }
+ this.amount = roundToTwoDecimals(this.amount + moneyToBeAdded.getAmount());
+ }
+
+ /**
+ * Subtracts another Money object from the current instance.
+ *
+ * @param moneyToBeSubtracted the Money object to subtract.
+ * @throws CannotSubtractException if the currencies do not match or if the amount to subtract is larger than the current amount.
+ */
+ public void subtractMoney(Money moneyToBeSubtracted) throws CannotSubtractException {
+ if (!moneyToBeSubtracted.getCurrency().equals(this.currency)) {
+ throw new CannotSubtractException("You are trying to subtract two different currencies");
+ } else if (moneyToBeSubtracted.getAmount() > this.amount) {
+ throw new CannotSubtractException("The amount you are trying to subtract is larger than the amount you have");
+ }
+ this.amount = roundToTwoDecimals(this.amount - moneyToBeSubtracted.getAmount());
+ }
+
+ /**
+ * Multiplies the current amount of money by a factor.
+ *
+ * @param factor the factor to multiply by.
+ * @throws IllegalArgumentException if the factor is negative.
+ */
+ public void multiply(int factor) {
+ if (factor < 0) {
+ throw new IllegalArgumentException("Factor must be non-negative");
+ }
+ this.amount = roundToTwoDecimals(this.amount * factor);
+ }
+
+ /**
+ * Converts the current amount of money to another currency using the provided exchange rate.
+ *
+ * @param currencyToChangeTo the new currency to convert to.
+ * @param exchangeRate the exchange rate to convert from the current currency to the new currency.
+ * @throws IllegalArgumentException if the exchange rate is negative.
+ */
+ public void exchangeCurrency(String currencyToChangeTo, double exchangeRate) {
+ if (exchangeRate < 0) {
+ throw new IllegalArgumentException("Exchange rate must be non-negative");
+ }
+ this.amount = roundToTwoDecimals(this.amount * exchangeRate);
+ this.currency = currencyToChangeTo;
+ }
+}
diff --git a/money/src/test/java/com/iluwater/money/MoneyTest.java b/money/src/test/java/com/iluwater/money/MoneyTest.java
new file mode 100644
index 000000000000..94d93359b0ad
--- /dev/null
+++ b/money/src/test/java/com/iluwater/money/MoneyTest.java
@@ -0,0 +1,125 @@
+package com.iluwater.money;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import com.iluwatar.CannotAddTwoCurrienciesException;
+import com.iluwatar.CannotSubtractException;
+import com.iluwatar.Money;
+import com.iluwatar.App;
+
+
+ class MoneyTest {
+
+ @Test
+ void testConstructor() {
+ // Test the constructor
+ Money money = new Money(100.00, "USD");
+ assertEquals(100.00, money.getAmount());
+ assertEquals("USD", money.getCurrency());
+ }
+
+ @Test
+ void testAddMoney_SameCurrency() throws CannotAddTwoCurrienciesException {
+ // Test adding two Money objects with the same currency
+ Money money1 = new Money(100.00, "USD");
+ Money money2 = new Money(50.25, "USD");
+
+ money1.addMoney(money2);
+
+ assertEquals(150.25, money1.getAmount(), "Amount after addition should be 150.25");
+ }
+
+ @Test
+ void testAddMoney_DifferentCurrency() {
+ // Test adding two Money objects with different currencies
+ Money money1 = new Money(100.00, "USD");
+ Money money2 = new Money(50.25, "EUR");
+
+ assertThrows(CannotAddTwoCurrienciesException.class, () -> {
+ money1.addMoney(money2);
+ });
+ }
+
+ @Test
+ void testSubtractMoney_SameCurrency() throws CannotSubtractException {
+ // Test subtracting two Money objects with the same currency
+ Money money1 = new Money(100.00, "USD");
+ Money money2 = new Money(50.25, "USD");
+
+ money1.subtractMoney(money2);
+
+ assertEquals(49.75, money1.getAmount(), "Amount after subtraction should be 49.75");
+ }
+
+ @Test
+ void testSubtractMoney_DifferentCurrency() {
+ // Test subtracting two Money objects with different currencies
+ Money money1 = new Money(100.00, "USD");
+ Money money2 = new Money(50.25, "EUR");
+
+ assertThrows(CannotSubtractException.class, () -> {
+ money1.subtractMoney(money2);
+ });
+ }
+
+ @Test
+ void testSubtractMoney_AmountTooLarge() {
+ // Test subtracting an amount larger than the current amount
+ Money money1 = new Money(50.00, "USD");
+ Money money2 = new Money(60.00, "USD");
+
+ assertThrows(CannotSubtractException.class, () -> {
+ money1.subtractMoney(money2);
+ });
+ }
+
+ @Test
+ void testMultiply() {
+ // Test multiplying the money amount by a factor
+ Money money = new Money(100.00, "USD");
+
+ money.multiply(3);
+
+ assertEquals(300.00, money.getAmount(), "Amount after multiplication should be 300.00");
+ }
+
+ @Test
+ void testMultiply_NegativeFactor() {
+ // Test multiplying by a negative factor
+ Money money = new Money(100.00, "USD");
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ money.multiply(-2);
+ });
+ }
+
+ @Test
+ void testExchangeCurrency() {
+ // Test converting currency using an exchange rate
+ Money money = new Money(100.00, "USD");
+
+ money.exchangeCurrency("EUR", 0.85);
+
+ assertEquals("EUR", money.getCurrency(), "Currency after conversion should be EUR");
+ assertEquals(85.00, money.getAmount(), "Amount after conversion should be 85.00");
+ }
+
+ @Test
+ void testExchangeCurrency_NegativeExchangeRate() {
+ // Test converting currency with a negative exchange rate
+ Money money = new Money(100.00, "USD");
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ money.exchangeCurrency("EUR", -0.85);
+ });
+ }
+
+
+ @Test
+ void testAppExecution() {
+ assertDoesNotThrow(() -> {
+ App.main(new String[]{});
+ }, "App execution should not throw any exceptions");
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 97128f9f3cc2..fc1e6e80f9fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -218,7 +218,9 @@
function-composition
microservices-distributed-tracing
microservices-idempotent-consumer
- templateview
+ templateview
+ money
+ table-inheritance
diff --git a/table-inheritance/README.md b/table-inheritance/README.md
new file mode 100644
index 000000000000..3e3ad4f53bf9
--- /dev/null
+++ b/table-inheritance/README.md
@@ -0,0 +1,201 @@
+---
+title: "Table Inheritance Pattern in Java: Modeling Hierarchical Data in Relational Databases"
+shortTitle: Table Inheritance
+description: "Explore the Table Inheritance pattern in Java with real-world examples, database schema, and tutorials. Learn how to model class hierarchies elegantly in relational databases."
+category: Data Access Pattern, Structural Pattern
+language: en
+tag:
+- Decoupling
+- Inheritance
+- Polymorphism
+- Object Mapping
+- Persistence
+- Data Transformation
+---
+
+## Also Known As
+- Class Table Inheritance
+---
+
+## Intent of Table Inheritance Pattern
+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.
+
+---
+
+## Detailed Explanation of Table Inheritance Pattern with Real-World Examples
+
+### Real-World Example
+Consider a **Vehicle Management System** with a `Vehicle` superclass and subclasses like `Car` and `Truck`.
+
+- The **Vehicle Table** stores attributes common to all vehicles, such as `make`, `model`, and `year`. Its primary key (`id`) uniquely identifies each vehicle.
+- The **Car Table** and **Truck Table** store attributes specific to their respective types, such as `numberOfDoors` for cars and `payloadCapacity` for trucks.
+- The `id` column in the **Car Table** and **Truck Table** serves as both the primary key for those tables and a foreign key referencing the `id` in the **Vehicle Table**.
+
+This setup ensures each subclass entry corresponds to a base class entry, maintaining the inheritance relationship while keeping subclass-specific data in their own tables.
+
+### In Plain Words
+In table inheritance, each class in the hierarchy is represented by a separate table, which
+allows for a clear distinction between shared attributes (stored in the base class table) and
+specific attributes (stored in subclass tables).
+
+### Martin Fowler Says
+
+Relational databases don't support inheritance, which creates a mismatch when mapping objects.
+To fix this, Table Inheritance uses a separate table for each class in the hierarchy while maintaining
+relationships through foreign keys, making it easier to link the classes together in the database.
+
+For more detailed information, refer to Martin Fowler's article on [Class Table Inheritance](https://martinfowler.com/eaaCatalog/classTableInheritance.html).
+
+
+## Programmatic Example of Table Inheritance Pattern in Java
+
+
+The `Vehicle` class will be the superclass, and we will have `Car` and `Truck` as subclasses that extend
+`Vehicle`. The `Vehicle` class will store common attributes, while `Car` and `Truck` will store
+attributes specific to those subclasses.
+
+### Key Aspects of the Pattern:
+
+1. **Superclass (`Vehicle`)**:
+ The `Vehicle` class stores attributes shared by all vehicle types, such as:
+ - `make`: The manufacturer of the vehicle.
+ - `model`: The model of the vehicle.
+ - `year`: The year the vehicle was manufactured.
+ - `id`: A unique identifier for the vehicle.
+
+ These attributes are stored in the **`Vehicle` table** in the database.
+
+2. **Subclass (`Car` and `Truck`)**:
+ Each subclass (`Car` and `Truck`) stores attributes specific to that vehicle type:
+ - `Car`: Has an additional attribute `numberOfDoors` representing the number of doors the car has.
+ - `Truck`: Has an additional attribute `payloadCapacity` representing the payload capacity of the truck.
+
+ These subclass-specific attributes are stored in the **`Car` and `Truck` tables**.
+
+3. **Foreign Key Relationship**:
+ Each subclass (`Car` and `Truck`) contains the `id` field which acts as a **foreign key** that
+references the primary key (`id`) of the superclass (`Vehicle`). This foreign key ensures the
+relationship between the common attributes in the `Vehicle` table and the specific attributes in the
+subclass tables (`Car` and `Truck`).
+
+
+```java
+/**
+ * Superclass
+ * Represents a generic vehicle with basic attributes like make, model, year, and ID.
+ */
+public class Vehicle {
+ private String make;
+ private String model;
+ private int year;
+ private int id;
+
+ // Constructor, getters, and setters...
+}
+
+/**
+ * Represents a car, which is a subclass of Vehicle.
+ */
+public class Car extends Vehicle {
+ private int numberOfDoors;
+
+ // Constructor, getters, and setters...
+}
+
+/**
+ * Represents a truck, which is a subclass of Vehicle.
+ */
+public class Truck extends Vehicle {
+ private int payloadCapacity;
+
+ // Constructor, getters, and setters...
+}
+```
+
+
+
+## Table Inheritance Pattern Class Diagram
+
+
+
+
+
+
+
+
+
+## Table Inheritance Pattern Database Schema
+
+### Vehicle Table
+| Column | Description |
+|--------|-------------------------------------|
+| id | Primary key |
+| make | The make of the vehicle |
+| model | The model of the vehicle |
+| year | The manufacturing year of the vehicle |
+
+### Car Table
+| Column | Description |
+|------------------|-------------------------------------|
+| id | Foreign key referencing `Vehicle(id)` |
+| numberOfDoors | Number of doors in the car |
+
+### Truck Table
+| Column | Description |
+|-------------------|-------------------------------------|
+| id | Foreign key referencing `Vehicle(id)` |
+| payloadCapacity | Payload capacity of the truck |
+
+---
+
+## When to Use the Table Inheritance Pattern in Java
+
+- When your application requires a clear mapping of an object-oriented class hierarchy to relational tables.
+- When subclasses have unique attributes that do not fit into a single base table.
+- When scalability and normalization of data are important considerations.
+- When you need to separate concerns and organize data in a way that each subclass has its own
+table but maintains relationships with the superclass.
+
+## Table Inheritance Pattern Java Tutorials
+
+- [Software Patterns Lexicon: Class Table Inheritance](https://softwarepatternslexicon.com/patterns-sql/4/4/2/)
+- [Martin Fowler: Class Table Inheritance](http://thierryroussel.free.fr/java/books/martinfowler/www.martinfowler.com/isa/classTableInheritance.html)
+
+---
+
+## Real-World Applications of Table Inheritance Pattern in Java
+
+- **Vehicle Management System**: Used to store different types of vehicles like Car and Truck in separate tables but maintain a relationship through a common superclass `Vehicle`.
+- **E-Commerce Platforms**: Where different product types, such as Clothing, Electronics, and Furniture, are stored in separate tables with shared attributes in a superclass `Product`.
+
+## Benefits and Trade-offs of Table Inheritance Pattern
+
+### Benefits
+
+- **Clear Structure**: Each class has its own table, making the data model easier to maintain and understand.
+- **Scalability**: Each subclass can be extended independently without affecting the other tables, making the system more scalable.
+- **Data Normalization**: Helps avoid data redundancy and keeps the schema normalized.
+
+### Trade-offs
+
+- **Multiple Joins**: Retrieving data that spans multiple subclasses may require joining multiple tables, which could lead to performance issues.
+- **Increased Complexity**: Managing relationships between tables and maintaining integrity can become more complex.
+- **Potential for Sparse Tables**: Subclasses with fewer attributes may end up with tables that have many null fields.
+
+## Related Java Design Patterns
+
+- **Single Table Inheritance** – A strategy where a single table is used to store all classes in an
+inheritance hierarchy. It stores all attributes of the class and its subclasses in one table.
+- **Singleton Pattern** – Used when a class needs to have only one instance.
+
+
+## References and Credits
+
+- **Martin Fowler** - [*Patterns of Enterprise Application Architecture*](https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420)
+- **Java Persistence with Hibernate** - [Link to book](https://www.amazon.com/Java-Persistence-Hibernate-Christian-Bauer/dp/193239469X)
+- **Object-Relational Mapping on Wikipedia** - [Link to article](https://en.wikipedia.org/wiki/Object-relational_mapping)
diff --git a/table-inheritance/pom.xml b/table-inheritance/pom.xml
new file mode 100644
index 000000000000..9a886307ffe7
--- /dev/null
+++ b/table-inheritance/pom.xml
@@ -0,0 +1,31 @@
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+
+ table-inheritance
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.7.0
+ test
+
+
+ org.projectlombok
+ lombok
+ 1.18.24
+ provided
+
+
+
+
+
+
\ No newline at end of file
diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/App.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/App.java
new file mode 100644
index 000000000000..4f848fb83e61
--- /dev/null
+++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/App.java
@@ -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.
+ *
+ * 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.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
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));
+
+ }
+}
diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Car.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Car.java
new file mode 100644
index 000000000000..b7332bf2a6b0
--- /dev/null
+++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Car.java
@@ -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()
+ + '}';
+ }
+}
diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Truck.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Truck.java
new file mode 100644
index 000000000000..5d093c688c9e
--- /dev/null
+++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Truck.java
@@ -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()
+ + '}';
+ }
+}
+
diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Vehicle.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Vehicle.java
new file mode 100644
index 000000000000..1333f6a166ee
--- /dev/null
+++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Vehicle.java
@@ -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
+ + '}';
+ }
+}
diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/VehicleDatabase.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/VehicleDatabase.java
new file mode 100644
index 000000000000..403112c32aa8
--- /dev/null
+++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/VehicleDatabase.java
@@ -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 vehicleTable = new HashMap<>();
+ private Map carTable = new HashMap<>();
+ private Map 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());
+ }
+ }
+}
+
diff --git a/table-inheritance/src/test/java/AppTest.java b/table-inheritance/src/test/java/AppTest.java
new file mode 100644
index 000000000000..9d4e74ebdd5b
--- /dev/null
+++ b/table-inheritance/src/test/java/AppTest.java
@@ -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:"));
+ }
+}
+
+
+
+
diff --git a/table-inheritance/src/test/java/VehicleDatabaseTest.java b/table-inheritance/src/test/java/VehicleDatabaseTest.java
new file mode 100644
index 000000000000..33f7335372f5
--- /dev/null
+++ b/table-inheritance/src/test/java/VehicleDatabaseTest.java
@@ -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());
+ }
+}
+
+