From fea63d032cc46a68881043a9690c3fddfb3be826 Mon Sep 17 00:00:00 2001 From: Ahmed-Taha-981 <122402269+Ahmed-Taha-981@users.noreply.github.com> Date: Thu, 5 Dec 2024 00:57:22 +0200 Subject: [PATCH 1/6] Money pattern the implementation of the money patter and the tests along with the read me file --- money/README.md | 167 ++++++++++++++++++ money/pom.xml | 53 ++++++ .../CannotAddTwoCurrienciesException.java | 14 ++ .../com/iluwatar/CannotSubtractException.java | 15 ++ money/src/main/java/com/iluwatar/Money.java | 91 ++++++++++ .../java/com/iluwater/money/MoneyTest.java | 115 ++++++++++++ pom.xml | 1 + 7 files changed, 456 insertions(+) create mode 100644 money/README.md create mode 100644 money/pom.xml create mode 100644 money/src/main/java/com/iluwatar/CannotAddTwoCurrienciesException.java create mode 100644 money/src/main/java/com/iluwatar/CannotSubtractException.java create mode 100644 money/src/main/java/com/iluwatar/Money.java create mode 100644 money/src/test/java/com/iluwater/money/MoneyTest.java diff --git a/money/README.md b/money/README.md new file mode 100644 index 000000000000..f797c08b702e --- /dev/null +++ b/money/README.md @@ -0,0 +1,167 @@ +--- +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: Finance +language: en +tag: + - Encapsulation + - Precision handling + - Currency safety + - Value Object + - Financial operations +--- + +## Also known as + +* 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 +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; + + 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. + +2. **Factory Method** + Factories can be employed to handle creation logic, such as applying default exchange rates or rounding rules. + +--- + +## 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..ce7d0741fe10 --- /dev/null +++ b/money/pom.xml @@ -0,0 +1,53 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + money + + + 17 + 17 + UTF-8 + + + + org.junit.jupiter + junit-jupiter-api + test + + + + \ No newline at end of file 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..78235370c4ed --- /dev/null +++ b/money/src/test/java/com/iluwater/money/MoneyTest.java @@ -0,0 +1,115 @@ +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; + + + 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); + }); + } +} diff --git a/pom.xml b/pom.xml index 959643f79fcf..101fc0763dcd 100644 --- a/pom.xml +++ b/pom.xml @@ -218,6 +218,7 @@ function-composition microservices-distributed-tracing microservices-idempotent-consumer + money From b525449bf3f501d2011536cd2e68da3310491399 Mon Sep 17 00:00:00 2001 From: Ahmed-Taha-981 <122402269+Ahmed-Taha-981@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:35:45 +0200 Subject: [PATCH 2/6] Update money/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ilkka Seppälä --- money/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/money/README.md b/money/README.md index f797c08b702e..b126c02ecebb 100644 --- a/money/README.md +++ b/money/README.md @@ -2,7 +2,7 @@ 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: Finance +category: Behavioral language: en tag: - Encapsulation From 6ff45361884e88229b85bc75814ad5b6d69a9ac1 Mon Sep 17 00:00:00 2001 From: Ahmed-Taha-981 <122402269+Ahmed-Taha-981@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:43:45 +0200 Subject: [PATCH 3/6] added App file anf modified README file and pom.xml file --- money/src/main/java/com/iluwatar/App.java | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 money/src/main/java/com/iluwatar/App.java 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..1f82820ea236 --- /dev/null +++ b/money/src/main/java/com/iluwatar/App.java @@ -0,0 +1,51 @@ +package com.iluwatar; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class App { + + // Initialize the logger + private static final Logger logger = Logger.getLogger(App.class.getName()); + + 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()); + } + + } +} + From 3af647cc42cd7326caabcc03f8ddb1460536a5d7 Mon Sep 17 00:00:00 2001 From: Ahmed-Taha-981 <122402269+Ahmed-Taha-981@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:11:45 +0200 Subject: [PATCH 4/6] modified README and pom.xml --- money/README.md | 13 +++++++------ money/pom.xml | 6 +----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/money/README.md b/money/README.md index b126c02ecebb..ca64d68fe882 100644 --- a/money/README.md +++ b/money/README.md @@ -10,11 +10,15 @@ tag: - Currency safety - Value Object - Financial operations + - Currency + - Financial + - Immutable + - Value Object --- ## Also known as -* Value Object +* Monetary Value Object ## Intent of Money Design Pattern @@ -43,9 +47,6 @@ In this example: ### Money Class ```java -package com.iluwatar; - -import lombok.Getter; /** * Represents a monetary value with an associated currency. @@ -155,10 +156,10 @@ The Money pattern should be used in scenarios where: 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 diff --git a/money/pom.xml b/money/pom.xml index ce7d0741fe10..0129bab9501c 100644 --- a/money/pom.xml +++ b/money/pom.xml @@ -37,11 +37,7 @@ money - - 17 - 17 - UTF-8 - + org.junit.jupiter From d8d07a4b14fc1e3f19e3d04283ef38933e40c6cd Mon Sep 17 00:00:00 2001 From: Ahmed-Taha-981 <122402269+Ahmed-Taha-981@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:31:36 +0200 Subject: [PATCH 5/6] added comments --- money/src/main/java/com/iluwatar/App.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/money/src/main/java/com/iluwatar/App.java b/money/src/main/java/com/iluwatar/App.java index 1f82820ea236..4030aa776159 100644 --- a/money/src/main/java/com/iluwatar/App.java +++ b/money/src/main/java/com/iluwatar/App.java @@ -2,12 +2,26 @@ 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"); From 6119a0e858183064762a31ffd15d9fa3baf46eaf Mon Sep 17 00:00:00 2001 From: Ahmed-Taha-981 <122402269+Ahmed-Taha-981@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:12:34 +0200 Subject: [PATCH 6/6] Added a test for App.java --- money/src/test/java/com/iluwater/money/MoneyTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/money/src/test/java/com/iluwater/money/MoneyTest.java b/money/src/test/java/com/iluwater/money/MoneyTest.java index 78235370c4ed..94d93359b0ad 100644 --- a/money/src/test/java/com/iluwater/money/MoneyTest.java +++ b/money/src/test/java/com/iluwater/money/MoneyTest.java @@ -5,6 +5,7 @@ import com.iluwatar.CannotAddTwoCurrienciesException; import com.iluwatar.CannotSubtractException; import com.iluwatar.Money; +import com.iluwatar.App; class MoneyTest { @@ -112,4 +113,13 @@ void testExchangeCurrency_NegativeExchangeRate() { money.exchangeCurrency("EUR", -0.85); }); } + + + @Test + void testAppExecution() { + assertDoesNotThrow(() -> { + App.main(new String[]{}); + }, "App execution should not throw any exceptions"); + } + }