diff --git a/separated-interface/README.md b/separated-interface/README.md
new file mode 100644
index 000000000..11ad4a978
--- /dev/null
+++ b/separated-interface/README.md
@@ -0,0 +1,119 @@
+---
+layout: pattern
+title: Separated Interface
+folder: separated-interface
+permalink: /patterns/separated-interface/
+categories: Architectural
+tags:
+ - Decoupling
+---
+
+
+## Intent
+Separate the interface definition and implementation in different packages. This allows the client to be completly unaware of the implementation.
+
+## Explanation
+
+Real world example
+
+> An Invoice generator may be created with ability to use different Tax calculators that may be added in the invoice depending upon type of purchase, region etc.
+
+In plain words
+
+> Separated interface pattern encourages to keep the implementations of an interface decoupled from the client and its definition, so the client is not dependent on the implementation.
+
+A client code may abstract some specific functionality to an interface, and define the definition of the interface as an SPI. Another package may implement this interface definition with a concrete logic, which will be injected into the client code at runtime (with a third class, injecting the implementation in the client) or at compile time (using Plugin pattern with some configurable file).
+
+**Programmatic Example**
+
+**Client** An Invoice generator class accepts the cost of the product and calculates the total amount payable inclusive of tax
+
+```java
+public class InvoiceGenerator {
+
+ private final TaxCalculator taxCalculator;
+
+ private final double amount;
+
+ public InvoiceGenerator(double amount, TaxCalculator taxCalculator) {
+ this.amount = amount;
+ this.taxCalculator = taxCalculator;
+ }
+
+ public double getAmountWithTax() {
+ return amount + taxCalculator.calculate(amount);
+ }
+
+}
+```
+The tax calculation logic is delegated to the ```TaxCalculator``` interface
+
+```java
+
+public interface TaxCalculator {
+
+ double calculate(double amount);
+
+}
+
+```
+
+**Implementation package**
+In another package (which the client is completely unaware of) there exist multiple implementations of the ```TaxCalculator``` interface
+```ForeignTax``` which levies 60% tax for international products.
+```java
+public class ForeignTax implements TaxCalculator {
+
+ public static final double TAX_PERCENTAGE = 60;
+
+ @Override
+ public double calculate(double amount) {
+ return amount * TAX_PERCENTAGE / 100.0;
+ }
+
+}
+```
+
+```DomesticTax``` which levies 20% tax for international products.
+```java
+public class DomesticTax implements TaxCalculator {
+
+ public static final double TAX_PERCENTAGE = 20;
+
+ @Override
+ public double calculate(double amount) {
+ return amount * TAX_PERCENTAGE / 100.0;
+ }
+
+}
+```
+
+These both implementations are instantiated and injected in the client class by the ```App.java``` class
+
+```java
+ var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, new ForeignTax());
+
+ LOGGER.info("Foreign Tax applied: {}", "" + internationalProductInvoice.getAmountWithTax());
+
+ var domesticProductInvoice = new InvoiceGenerator(PRODUCT_COST, new DomesticTax());
+
+ LOGGER.info("Domestic Tax applied: {}", "" + domesticProductInvoice.getAmountWithTax());
+```
+
+## Class diagram
+
+
+## Applicability
+Use the Separated interface pattern when
+
+* You are developing a framework package, and your framework needs to call some application code through interfaces.
+* You have separate packages implementing the functionalities which may be plugged in your client code at runtime or compile-time.
+* Your code resides in a layer that is not allowed to call the interface implementation layer by rule. For example, a domain layer needs to call a data mapper.
+
+## Tutorial
+
+* [Separated Interface Tutorial](https://www.youtube.com/watch?v=d3k-hOA7k2Y)
+
+## Credits
+
+* [Martin Fowler](https://www.martinfowler.com/eaaCatalog/separatedInterface.html)
diff --git a/separated-interface/etc/class_diagram.png b/separated-interface/etc/class_diagram.png
new file mode 100644
index 000000000..a0edbac8d
Binary files /dev/null and b/separated-interface/etc/class_diagram.png differ
diff --git a/separated-interface/etc/separated-interface.urm.puml b/separated-interface/etc/separated-interface.urm.puml
new file mode 100644
index 000000000..a406acd40
--- /dev/null
+++ b/separated-interface/etc/separated-interface.urm.puml
@@ -0,0 +1,36 @@
+@startuml
+package com.iluwatar.separatedinterface {
+ class App {
+ - LOGGER : Logger {static}
+ + PRODUCT_COST : double {static}
+ + App()
+ + main(args : String[]) {static}
+ }
+}
+package com.iluwatar.separatedinterface.taxes {
+ class DomesticTax {
+ + TAX_PERCENTAGE : double {static}
+ + DomesticTax()
+ + calculate(amount : double) : double
+ }
+ class ForeignTax {
+ + TAX_PERCENTAGE : double {static}
+ + ForeignTax()
+ + calculate(amount : double) : double
+ }
+}
+package com.iluwatar.separatedinterface.invoice {
+ class InvoiceGenerator {
+ - amount : double
+ - taxCalculator : TaxCalculator
+ + InvoiceGenerator(amount : double, taxCalculator : TaxCalculator)
+ + getAmountWithTax() : double
+ }
+ interface TaxCalculator {
+ + calculate(double) : double {abstract}
+ }
+}
+InvoiceGenerator --> "-taxCalculator" TaxCalculator
+DomesticTax ..|> TaxCalculator
+ForeignTax ..|> TaxCalculator
+@enduml
\ No newline at end of file
diff --git a/separated-interface/pom.xml b/separated-interface/pom.xml
new file mode 100644
index 000000000..da7584a93
--- /dev/null
+++ b/separated-interface/pom.xml
@@ -0,0 +1,71 @@
+
+
+
The Strategy pattern (also known as the policy pattern) is a software design pattern that + * enables an algorithm's behavior to be selected at runtime.
+ * + *Before Java 8 the Strategies needed to be separate classes forcing the developer + * to write lots of boilerplate code. With modern Java it is easy to pass behavior with method + * references and lambdas making the code shorter and more readable.
+ * + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public static final double PRODUCT_COST = 50.0; + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + //Create the invoice generator with product cost as 50 and foreign product tax + var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, new ForeignTax()); + LOGGER.info("Foreign Tax applied: {}", "" + internationalProductInvoice.getAmountWithTax()); + + //Create the invoice generator with product cost as 50 and domestic product tax + var domesticProductInvoice = new InvoiceGenerator(PRODUCT_COST, new DomesticTax()); + LOGGER.info("Domestic Tax applied: {}", "" + domesticProductInvoice.getAmountWithTax()); + } +} diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java new file mode 100644 index 000000000..f73ee32d8 --- /dev/null +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java @@ -0,0 +1,28 @@ +package com.iluwatar.separatedinterface.invoice; + +/** + * InvoiceGenerator class generates an invoice, accepting the product cost and calculating the total + * price payable inclusive tax (calculated by {@link TaxCalculator}) + */ +public class InvoiceGenerator { + + /** + * The TaxCalculator interface to calculate the payable tax. + */ + private final TaxCalculator taxCalculator; + + /** + * The base product amount without tax. + */ + private final double amount; + + public InvoiceGenerator(double amount, TaxCalculator taxCalculator) { + this.amount = amount; + this.taxCalculator = taxCalculator; + } + + public double getAmountWithTax() { + return amount + taxCalculator.calculate(amount); + } + +} \ No newline at end of file diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java new file mode 100644 index 000000000..aa4b81a84 --- /dev/null +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java @@ -0,0 +1,30 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.separatedinterface.invoice; + +public interface TaxCalculator { + + double calculate(double amount); + +} diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTax.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTax.java new file mode 100644 index 000000000..ebe0e306e --- /dev/null +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTax.java @@ -0,0 +1,40 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.separatedinterface.taxes; + +import com.iluwatar.separatedinterface.invoice.TaxCalculator; + +/** + * TaxCalculator for Domestic goods with 20% tax. + */ +public class DomesticTax implements TaxCalculator { + + public static final double TAX_PERCENTAGE = 20; + + @Override + public double calculate(double amount) { + return amount * TAX_PERCENTAGE / 100.0; + } + +} diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTax.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTax.java new file mode 100644 index 000000000..583f70d3b --- /dev/null +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTax.java @@ -0,0 +1,40 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.separatedinterface.taxes; + +import com.iluwatar.separatedinterface.invoice.TaxCalculator; + +/** + * TaxCalculator for foreign goods with 60% tax. + */ +public class ForeignTax implements TaxCalculator { + + public static final double TAX_PERCENTAGE = 60; + + @Override + public double calculate(double amount) { + return amount * TAX_PERCENTAGE / 100.0; + } + +} diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java new file mode 100644 index 000000000..4114f9bb7 --- /dev/null +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.separatedinterface; + +import org.junit.jupiter.api.Test; + +import com.iluwatar.separatedinterface.App; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Application test. + */ +class AppTest { + + @Test + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } +} diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java new file mode 100644 index 000000000..669fb1452 --- /dev/null +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java @@ -0,0 +1,25 @@ +package com.iluwatar.separatedinterface.invoice; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; + +public class InvoiceGeneratorTest { + + private InvoiceGenerator target; + + @Test + public void testGenerateTax() { + var productCost = 50.0; + var tax = 10.0; + TaxCalculator taxCalculatorMock = mock(TaxCalculator.class); + doReturn(tax).when(taxCalculatorMock).calculate(productCost); + + target = new InvoiceGenerator(productCost, taxCalculatorMock); + + Assertions.assertEquals(target.getAmountWithTax(), productCost + tax); + verify(taxCalculatorMock, times(1)).calculate(productCost); + } + +} diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxTest.java new file mode 100644 index 000000000..e562586c2 --- /dev/null +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxTest.java @@ -0,0 +1,18 @@ +package com.iluwatar.separatedinterface.taxes; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class DomesticTaxTest { + + private DomesticTax target; + + @Test + public void testTaxCaluclation(){ + target = new DomesticTax(); + + var tax=target.calculate(100.0); + Assertions.assertEquals(tax,20.0); + } + +} diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxTest.java new file mode 100644 index 000000000..86bf39e80 --- /dev/null +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxTest.java @@ -0,0 +1,18 @@ +package com.iluwatar.separatedinterface.taxes; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ForeignTaxTest { + + private ForeignTax target; + + @Test + public void testTaxCaluclation(){ + target = new ForeignTax(); + + var tax=target.calculate(100.0); + Assertions.assertEquals(tax,60.0); + } + +}