diff --git a/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/AmountBasedDiscountPolicy.java b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/AmountBasedDiscountPolicy.java new file mode 100644 index 0000000000..db673d5d50 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/AmountBasedDiscountPolicy.java @@ -0,0 +1,15 @@ +package com.baeldung.ddd.order.doubledispatch; + +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; + +public class AmountBasedDiscountPolicy implements DiscountPolicy { + @Override + public double discount(Order order) { + if (order.totalCost() + .isGreaterThan(Money.of(CurrencyUnit.USD, 500.00))) { + return 0.10; + } else + return 0; + } +} diff --git a/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/DiscountPolicy.java b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/DiscountPolicy.java new file mode 100644 index 0000000000..7e3c5765c6 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/DiscountPolicy.java @@ -0,0 +1,5 @@ +package com.baeldung.ddd.order.doubledispatch; + +public interface DiscountPolicy { + double discount(Order order); +} diff --git a/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/FlatDiscountPolicy.java b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/FlatDiscountPolicy.java new file mode 100644 index 0000000000..ac7d49fdeb --- /dev/null +++ b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/FlatDiscountPolicy.java @@ -0,0 +1,8 @@ +package com.baeldung.ddd.order.doubledispatch; + +public class FlatDiscountPolicy implements DiscountPolicy { + @Override + public double discount(Order order) { + return 0.01; + } +} diff --git a/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/Order.java b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/Order.java new file mode 100644 index 0000000000..c2f763e14c --- /dev/null +++ b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/Order.java @@ -0,0 +1,29 @@ +package com.baeldung.ddd.order.doubledispatch; + +import java.math.RoundingMode; +import java.util.List; + +import org.joda.money.Money; + +import com.baeldung.ddd.order.OrderLine; +import com.baeldung.ddd.order.doubledispatch.visitor.OrderVisitor; +import com.baeldung.ddd.order.doubledispatch.visitor.Visitable; + +public class Order extends com.baeldung.ddd.order.Order implements Visitable { + public Order(List orderLines) { + super(orderLines); + } + + public Money totalCost(SpecialDiscountPolicy discountPolicy) { + return totalCost().multipliedBy(1 - applyDiscountPolicy(discountPolicy), RoundingMode.HALF_UP); + } + + protected double applyDiscountPolicy(SpecialDiscountPolicy discountPolicy) { + return discountPolicy.discount(this); + } + + @Override + public void accept(OrderVisitor visitor) { + visitor.visit(this); + } +} diff --git a/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/SpecialDiscountPolicy.java b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/SpecialDiscountPolicy.java new file mode 100644 index 0000000000..866e0e63c2 --- /dev/null +++ b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/SpecialDiscountPolicy.java @@ -0,0 +1,5 @@ +package com.baeldung.ddd.order.doubledispatch; + +public interface SpecialDiscountPolicy extends DiscountPolicy { + double discount(SpecialOrder order); +} diff --git a/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/SpecialOrder.java b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/SpecialOrder.java new file mode 100644 index 0000000000..1fe629e0ef --- /dev/null +++ b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/SpecialOrder.java @@ -0,0 +1,36 @@ +package com.baeldung.ddd.order.doubledispatch; + +import java.util.List; + +import com.baeldung.ddd.order.OrderLine; +import com.baeldung.ddd.order.doubledispatch.visitor.OrderVisitor; + +public class SpecialOrder extends Order { + + private boolean eligibleForExtraDiscount; + + public SpecialOrder(List orderLines) { + super(orderLines); + this.eligibleForExtraDiscount = false; + } + + public SpecialOrder(List orderLines, boolean eligibleForSpecialDiscount) { + super(orderLines); + this.eligibleForExtraDiscount = eligibleForSpecialDiscount; + } + + public boolean isEligibleForExtraDiscount() { + return eligibleForExtraDiscount; + } + + @Override + protected double applyDiscountPolicy(SpecialDiscountPolicy discountPolicy) { + return discountPolicy.discount(this); + } + + @Override + public void accept(OrderVisitor visitor) { + visitor.visit(this); + } + +} diff --git a/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/visitor/HtmlOrderViewCreator.java b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/visitor/HtmlOrderViewCreator.java new file mode 100644 index 0000000000..ea23cdaa4b --- /dev/null +++ b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/visitor/HtmlOrderViewCreator.java @@ -0,0 +1,24 @@ +package com.baeldung.ddd.order.doubledispatch.visitor; + +import com.baeldung.ddd.order.doubledispatch.Order; +import com.baeldung.ddd.order.doubledispatch.SpecialOrder; + +public class HtmlOrderViewCreator implements OrderVisitor { + + private String html; + + public String getHtml() { + return html; + } + + @Override + public void visit(Order order) { + html = String.format("

Regular order total cost: %s

", order.totalCost()); + } + + @Override + public void visit(SpecialOrder order) { + html = String.format("

Special Order

total cost: %s

", order.totalCost()); + } + +} diff --git a/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/visitor/OrderVisitor.java b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/visitor/OrderVisitor.java new file mode 100644 index 0000000000..00f0740f6e --- /dev/null +++ b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/visitor/OrderVisitor.java @@ -0,0 +1,9 @@ +package com.baeldung.ddd.order.doubledispatch.visitor; + +import com.baeldung.ddd.order.doubledispatch.Order; +import com.baeldung.ddd.order.doubledispatch.SpecialOrder; + +public interface OrderVisitor { + void visit(Order order); + void visit(SpecialOrder order); +} diff --git a/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/visitor/Visitable.java b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/visitor/Visitable.java new file mode 100644 index 0000000000..1628718d9b --- /dev/null +++ b/ddd/src/main/java/com/baeldung/ddd/order/doubledispatch/visitor/Visitable.java @@ -0,0 +1,5 @@ +package com.baeldung.ddd.order.doubledispatch.visitor; + +public interface Visitable { + void accept(V visitor); +} diff --git a/ddd/src/main/java/com/baeldung/ddd/order/jpa/JpaOrder.java b/ddd/src/main/java/com/baeldung/ddd/order/jpa/JpaOrder.java index ed11b0dca4..81ae3bbd19 100644 --- a/ddd/src/main/java/com/baeldung/ddd/order/jpa/JpaOrder.java +++ b/ddd/src/main/java/com/baeldung/ddd/order/jpa/JpaOrder.java @@ -92,7 +92,7 @@ class JpaOrder { } void removeLineItem(int line) { - JpaOrderLine removedLine = orderLines.remove(line); + orderLines.remove(line); } void setCurrencyUnit(String currencyUnit) { diff --git a/ddd/src/main/java/com/baeldung/ddd/order/jpa/JpaProduct.java b/ddd/src/main/java/com/baeldung/ddd/order/jpa/JpaProduct.java index 61e67fa12a..1d2ae5230a 100644 --- a/ddd/src/main/java/com/baeldung/ddd/order/jpa/JpaProduct.java +++ b/ddd/src/main/java/com/baeldung/ddd/order/jpa/JpaProduct.java @@ -15,7 +15,7 @@ class JpaProduct { public JpaProduct(BigDecimal price, String currencyUnit) { super(); this.price = price; - currencyUnit = currencyUnit; + this.currencyUnit = currencyUnit; } @Override diff --git a/ddd/src/test/java/com/baeldung/ddd/order/OrderFixtureUtils.java b/ddd/src/test/java/com/baeldung/ddd/order/OrderFixtureUtils.java new file mode 100644 index 0000000000..2b0e0b1997 --- /dev/null +++ b/ddd/src/test/java/com/baeldung/ddd/order/OrderFixtureUtils.java @@ -0,0 +1,17 @@ +package com.baeldung.ddd.order; + +import java.util.Arrays; +import java.util.List; + +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; + +public class OrderFixtureUtils { + public static List anyOrderLines() { + return Arrays.asList(new OrderLine(new Product(Money.of(CurrencyUnit.USD, 100)), 1)); + } + + public static List orderLineItemsWorthNDollars(int totalCost) { + return Arrays.asList(new OrderLine(new Product(Money.of(CurrencyUnit.USD, totalCost)), 1)); + } +} diff --git a/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/DoubleDispatchDiscountPolicyUnitTest.java b/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/DoubleDispatchDiscountPolicyUnitTest.java new file mode 100644 index 0000000000..0ec73415d3 --- /dev/null +++ b/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/DoubleDispatchDiscountPolicyUnitTest.java @@ -0,0 +1,77 @@ +package com.baeldung.ddd.order.doubledispatch; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.baeldung.ddd.order.OrderFixtureUtils; + +public class DoubleDispatchDiscountPolicyUnitTest { + // @formatter:off + @DisplayName( + "given regular order with items worth $100 total, " + + "when apply 10% discount policy, " + + "then cost after discount is $90" + ) + // @formatter:on + @Test + void test() throws Exception { + // given + Order order = new Order(OrderFixtureUtils.orderLineItemsWorthNDollars(100)); + SpecialDiscountPolicy discountPolicy = new SpecialDiscountPolicy() { + + @Override + public double discount(Order order) { + return 0.10; + } + + @Override + public double discount(SpecialOrder order) { + return 0; + } + }; + + // when + Money totalCostAfterDiscount = order.totalCost(discountPolicy); + + // then + assertThat(totalCostAfterDiscount).isEqualTo(Money.of(CurrencyUnit.USD, 90)); + } + + // @formatter:off + @DisplayName( + "given special order eligible for extra discount with items worth $100 total, " + + "when apply 20% discount policy for extra discount orders, " + + "then cost after discount is $80" + ) + // @formatter:on + @Test + void test1() throws Exception { + // given + boolean eligibleForExtraDiscount = true; + Order order = new SpecialOrder(OrderFixtureUtils.orderLineItemsWorthNDollars(100), eligibleForExtraDiscount); + SpecialDiscountPolicy discountPolicy = new SpecialDiscountPolicy() { + + @Override + public double discount(Order order) { + return 0; + } + + @Override + public double discount(SpecialOrder order) { + if (order.isEligibleForExtraDiscount()) + return 0.20; + return 0.10; + } + }; + + // when + Money totalCostAfterDiscount = order.totalCost(discountPolicy); + + // then + assertThat(totalCostAfterDiscount).isEqualTo(Money.of(CurrencyUnit.USD, 80.00)); + } +} diff --git a/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/HtmlOrderViewCreatorUnitTest.java b/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/HtmlOrderViewCreatorUnitTest.java new file mode 100644 index 0000000000..e360c1c76a --- /dev/null +++ b/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/HtmlOrderViewCreatorUnitTest.java @@ -0,0 +1,43 @@ +package com.baeldung.ddd.order.doubledispatch; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.baeldung.ddd.order.doubledispatch.Order; +import com.baeldung.ddd.order.OrderFixtureUtils; +import com.baeldung.ddd.order.OrderLine; +import com.baeldung.ddd.order.doubledispatch.visitor.HtmlOrderViewCreator; + +public class HtmlOrderViewCreatorUnitTest { + // @formatter:off + @DisplayName( + "given collection of regular and special orders, " + + "when create HTML view using visitor for each order, " + + "then the dedicated view is created for each order" + ) + // @formatter:on + @Test + void test() throws Exception { + // given + List anyOrderLines = OrderFixtureUtils.anyOrderLines(); + List orders = Arrays.asList(new Order(anyOrderLines), new SpecialOrder(anyOrderLines)); + HtmlOrderViewCreator htmlOrderViewCreator = new HtmlOrderViewCreator(); + + // when + orders.get(0) + .accept(htmlOrderViewCreator); + String regularOrderHtml = htmlOrderViewCreator.getHtml(); + orders.get(1) + .accept(htmlOrderViewCreator); + String specialOrderHtml = htmlOrderViewCreator.getHtml(); + + // then + assertThat(regularOrderHtml).containsPattern("

Regular order total cost: .*

"); + assertThat(specialOrderHtml).containsPattern("

Special Order

total cost: .*

"); + } +} diff --git a/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/MethodOverloadExampleUnitTest.java b/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/MethodOverloadExampleUnitTest.java new file mode 100644 index 0000000000..3d135e9dbe --- /dev/null +++ b/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/MethodOverloadExampleUnitTest.java @@ -0,0 +1,50 @@ +package com.baeldung.ddd.order.doubledispatch; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.baeldung.ddd.order.doubledispatch.Order; +import com.baeldung.ddd.order.OrderFixtureUtils; +import com.baeldung.ddd.order.OrderLine; +import com.baeldung.ddd.order.doubledispatch.SpecialDiscountPolicy; +import com.baeldung.ddd.order.doubledispatch.SpecialOrder; + +public class MethodOverloadExampleUnitTest { +// @formatter:off +@DisplayName( + "given discount policy accepting special orders, " + + "when apply the policy on special order declared as regular order, " + + "then regular discount method is used" + ) +// @formatter:on + @Test + void test() throws Exception { + // given + SpecialDiscountPolicy specialPolicy = new SpecialDiscountPolicy() { + @Override + public double discount(Order order) { + return 0.01; + } + + @Override + public double discount(SpecialOrder order) { + return 0.10; + } + }; + Order specialOrder = new SpecialOrder(anyOrderLines()); + + // when + double discount = specialPolicy.discount(specialOrder); + + // then + assertThat(discount).isEqualTo(0.01); + } + + private List anyOrderLines() { + return OrderFixtureUtils.anyOrderLines(); + } +} diff --git a/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/SingleDispatchDiscountPolicyUnitTest.java b/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/SingleDispatchDiscountPolicyUnitTest.java new file mode 100644 index 0000000000..82e074d028 --- /dev/null +++ b/ddd/src/test/java/com/baeldung/ddd/order/doubledispatch/SingleDispatchDiscountPolicyUnitTest.java @@ -0,0 +1,37 @@ +package com.baeldung.ddd.order.doubledispatch; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.baeldung.ddd.order.OrderFixtureUtils; + +public class SingleDispatchDiscountPolicyUnitTest { + // @formatter:off + @DisplayName( + "given two discount policies, " + + "when use these policies, " + + "then single dispatch chooses the implementation based on runtime type" + ) + // @formatter:on + @Test + void test() throws Exception { + // given + DiscountPolicy flatPolicy = new FlatDiscountPolicy(); + DiscountPolicy amountPolicy = new AmountBasedDiscountPolicy(); + Order orderWorth501Dollars = orderWorthNDollars(501); + + // when + double flatDiscount = flatPolicy.discount(orderWorth501Dollars); + double amountDiscount = amountPolicy.discount(orderWorth501Dollars); + + // then + assertThat(flatDiscount).isEqualTo(0.01); + assertThat(amountDiscount).isEqualTo(0.1); + } + + private Order orderWorthNDollars(int totalCost) { + return new Order(OrderFixtureUtils.orderLineItemsWorthNDollars(totalCost)); + } +}