提交 6c4c6097 编写于 作者: A Amit Garg
此差异已折叠。
#
# 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.
#
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
# Some tests need screen access
- name: Install xvfb
run: sudo apt-get install xvfb
# SonarQube scan does not work for forked repositories
# See https://jira.sonarsource.com/browse/MMF-1371
- name: Build with Maven
if: github.ref != 'refs/heads/master'
run: xvfb-run mvn clean verify
- name: Build with Maven and run SonarQube analysis
if: github.ref == 'refs/heads/master'
run: xvfb-run mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
env:
# These two env variables are needed for sonar analysis
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
...@@ -24,11 +24,9 @@ ...@@ -24,11 +24,9 @@
# This workflow will build a Java project with Maven # This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven name: Java PR Builder
on: on:
push:
branches: [ master ]
pull_request: pull_request:
branches: [ master ] branches: [ master ]
...@@ -43,12 +41,17 @@ jobs: ...@@ -43,12 +41,17 @@ jobs:
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 11 java-version: 11
- uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
# Some tests need screen access # Some tests need screen access
- name: Install xvfb - name: Install xvfb
run: sudo apt-get install xvfb run: sudo apt-get install xvfb
# SonarQube scan does not work for forked repositories
# See https://jira.sonarsource.com/browse/MMF-1371
- name: Build with Maven - name: Build with Maven
run: xvfb-run mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar if: github.ref != 'refs/heads/master'
env: run: xvfb-run mvn clean verify
# These two env variables are needed for sonar analysis
GITHUB_TOKEN: ${{ secrets.REPOSITORY_ACCESS_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
此差异已折叠。
...@@ -9,21 +9,182 @@ tags: ...@@ -9,21 +9,182 @@ tags:
--- ---
## Intent ## Intent
Achieve flexibility of untyped languages and keep the type-safety
Use dynamic properties and achieve flexibility of untyped languages while keeping type-safety.
## Explanation
The Abstract Document pattern enables handling additional, non-static properties. This pattern
uses concept of traits to enable type safety and separate properties of different classes into
set of interfaces.
Real world example
> Consider a car that consists of multiple parts. However we don't know if the specific car really has all the parts, or just some of them. Our cars are dynamic and extremely flexible.
In plain words
> Abstract Document pattern allows attaching properties to objects without them knowing about it.
Wikipedia says
> An object-oriented structural design pattern for organizing objects in loosely typed key-value stores and exposing
the data using typed views. The purpose of the pattern is to achieve a high degree of flexibility between components
in a strongly typed language where new properties can be added to the object-tree on the fly, without losing the
support of type-safety. The pattern makes use of traits to separate different properties of a class into different
interfaces.
**Programmatic Example**
Let's first define the base classes `Document` and `AbstractDocument`. They basically make the object hold a property
map and any amount of child objects.
```java
public interface Document {
Void put(String key, Object value);
Object get(String key);
<T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor);
}
public abstract class AbstractDocument implements Document {
private final Map<String, Object> properties;
protected AbstractDocument(Map<String, Object> properties) {
Objects.requireNonNull(properties, "properties map is required");
this.properties = properties;
}
@Override
public Void put(String key, Object value) {
properties.put(key, value);
return null;
}
@Override
public Object get(String key) {
return properties.get(key);
}
@Override
public <T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor) {
return Stream.ofNullable(get(key))
.filter(Objects::nonNull)
.map(el -> (List<Map<String, Object>>) el)
.findAny()
.stream()
.flatMap(Collection::stream)
.map(constructor);
}
...
}
```
Next we define an enum `Property` and a set of interfaces for type, price, model and parts. This allows us to create
static looking interface to our `Car` class.
```java
public enum Property {
PARTS, TYPE, PRICE, MODEL
}
public interface HasType extends Document {
default Optional<String> getType() {
return Optional.ofNullable((String) get(Property.TYPE.toString()));
}
}
public interface HasPrice extends Document {
default Optional<Number> getPrice() {
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
}
}
public interface HasModel extends Document {
default Optional<String> getModel() {
return Optional.ofNullable((String) get(Property.MODEL.toString()));
}
}
public interface HasParts extends Document {
default Stream<Part> getParts() {
return children(Property.PARTS.toString(), Part::new);
}
}
```
Now we are ready to introduce the `Car`.
```java
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
public Car(Map<String, Object> properties) {
super(properties);
}
}
```
And finally here's how we construct and use the `Car` in a full example.
```java
LOGGER.info("Constructing parts and car");
var wheelProperties = Map.of(
Property.TYPE.toString(), "wheel",
Property.MODEL.toString(), "15C",
Property.PRICE.toString(), 100L);
var doorProperties = Map.of(
Property.TYPE.toString(), "door",
Property.MODEL.toString(), "Lambo",
Property.PRICE.toString(), 300L);
var carProperties = Map.of(
Property.MODEL.toString(), "300SL",
Property.PRICE.toString(), 10000L,
Property.PARTS.toString(), List.of(wheelProperties, doorProperties));
var car = new Car(carProperties);
LOGGER.info("Here is our car:");
LOGGER.info("-> model: {}", car.getModel().orElseThrow());
LOGGER.info("-> price: {}", car.getPrice().orElseThrow());
LOGGER.info("-> parts: ");
car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}",
p.getType().orElse(null),
p.getModel().orElse(null),
p.getPrice().orElse(null))
);
// Constructing parts and car
// Here is our car:
// model: 300SL
// price: 10000
// parts:
// wheel/15C/100
// door/Lambo/300
```
## Class diagram ## Class diagram
![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain")
![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain")
## Applicability ## Applicability
Use the Abstract Document Pattern when
* there is a need to add new properties on the fly Use the Abstract Document Pattern when
* you want a flexible way to organize domain in tree like structure
* you want more loosely coupled system
* There is a need to add new properties on the fly
* You want a flexible way to organize domain in tree like structure
* You want more loosely coupled system
## Credits ## Credits
* [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern) * [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern)
* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf) * [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf)
* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://www.amazon.com/gp/product/0470059028/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0470059028&linkId=e3aacaea7017258acf184f9f3283b492)
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>abstract-document</artifactId> <artifactId>abstract-document</artifactId>
<dependencies> <dependencies>
......
...@@ -43,9 +43,11 @@ public class App { ...@@ -43,9 +43,11 @@ public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class); private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/** /**
* Executes the App. * Program entry point.
*
* @param args command line args
*/ */
public App() { public static void main(String[] args) {
LOGGER.info("Constructing parts and car"); LOGGER.info("Constructing parts and car");
var wheelProperties = Map.of( var wheelProperties = Map.of(
...@@ -75,14 +77,4 @@ public class App { ...@@ -75,14 +77,4 @@ public class App {
p.getPrice().orElse(null)) p.getPrice().orElse(null))
); );
} }
/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
new App();
}
} }
...@@ -32,7 +32,6 @@ import java.util.stream.Stream; ...@@ -32,7 +32,6 @@ import java.util.stream.Stream;
*/ */
public interface HasParts extends Document { public interface HasParts extends Document {
default Stream<Part> getParts() { default Stream<Part> getParts() {
return children(Property.PARTS.toString(), Part::new); return children(Property.PARTS.toString(), Part::new);
} }
......
...@@ -32,7 +32,6 @@ import java.util.Optional; ...@@ -32,7 +32,6 @@ import java.util.Optional;
*/ */
public interface HasPrice extends Document { public interface HasPrice extends Document {
default Optional<Number> getPrice() { default Optional<Number> getPrice() {
return Optional.ofNullable((Number) get(Property.PRICE.toString())); return Optional.ofNullable((Number) get(Property.PRICE.toString()));
} }
......
...@@ -32,7 +32,6 @@ import java.util.Optional; ...@@ -32,7 +32,6 @@ import java.util.Optional;
*/ */
public interface HasType extends Document { public interface HasType extends Document {
default Optional<String> getType() { default Optional<String> getType() {
return Optional.ofNullable((String) get(Property.TYPE.toString())); return Optional.ofNullable((String) get(Property.TYPE.toString()));
} }
......
...@@ -40,14 +40,14 @@ public class AbstractDocumentTest { ...@@ -40,14 +40,14 @@ public class AbstractDocumentTest {
private static final String KEY = "key"; private static final String KEY = "key";
private static final String VALUE = "value"; private static final String VALUE = "value";
private class DocumentImplementation extends AbstractDocument { private static class DocumentImplementation extends AbstractDocument {
DocumentImplementation(Map<String, Object> properties) { DocumentImplementation(Map<String, Object> properties) {
super(properties); super(properties);
} }
} }
private DocumentImplementation document = new DocumentImplementation(new HashMap<>()); private final DocumentImplementation document = new DocumentImplementation(new HashMap<>());
@Test @Test
public void shouldPutAndGetValue() { public void shouldPutAndGetValue() {
......
...@@ -25,14 +25,23 @@ package com.iluwatar.abstractdocument; ...@@ -25,14 +25,23 @@ package com.iluwatar.abstractdocument;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Simple App test * Simple App test
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void shouldExecuteAppWithoutException() { void shouldExecuteAppWithoutException() {
App.main(null); assertDoesNotThrow(() -> App.main(null));
} }
} }
...@@ -9,16 +9,19 @@ tags: ...@@ -9,16 +9,19 @@ tags:
--- ---
## Also known as ## Also known as
Kit Kit
## Intent ## Intent
Provide an interface for creating families of related or dependent Provide an interface for creating families of related or dependent
objects without specifying their concrete classes. objects without specifying their concrete classes.
## Explanation ## Explanation
Real world example Real world example
> To create a kingdom we need objects with common theme. Elven kingdom needs an Elven king, Elven castle and Elven army whereas Orcish kingdom needs an Orcish king, Orcish castle and Orcish army. There is a dependency between the objects in the kingdom. > To create a kingdom we need objects with a common theme. Elven kingdom needs an Elven king, Elven castle and Elven army whereas Orcish kingdom needs an Orcish king, Orcish castle and Orcish army. There is a dependency between the objects in the kingdom.
In plain words In plain words
...@@ -30,15 +33,18 @@ Wikipedia says ...@@ -30,15 +33,18 @@ Wikipedia says
**Programmatic Example** **Programmatic Example**
Translating the kingdom example above. First of all we have some interfaces and implementation for the objects in the kingdom Translating the kingdom example above. First of all we have some interfaces and implementation for the objects in the
kingdom.
```java ```java
public interface Castle { public interface Castle {
String getDescription(); String getDescription();
} }
public interface King { public interface King {
String getDescription(); String getDescription();
} }
public interface Army { public interface Army {
String getDescription(); String getDescription();
} }
...@@ -66,7 +72,7 @@ public class ElfArmy implements Army { ...@@ -66,7 +72,7 @@ public class ElfArmy implements Army {
} }
} }
// Orcish implementations similarly... // Orcish implementations similarly -> ...
``` ```
...@@ -112,9 +118,17 @@ var castle = factory.createCastle(); ...@@ -112,9 +118,17 @@ var castle = factory.createCastle();
var king = factory.createKing(); var king = factory.createKing();
var army = factory.createArmy(); var army = factory.createArmy();
castle.getDescription(); // Output: This is the Elven castle! castle.getDescription();
king.getDescription(); // Output: This is the Elven king! king.getDescription();
army.getDescription(); // Output: This is the Elven Army! army.getDescription();
```
Program output:
```java
This is the Elven castle!
This is the Elven king!
This is the Elven Army!
``` ```
Now, we can design a factory for our different kingdom factories. In this example, we created FactoryMaker, responsible for returning an instance of either ElfKingdomFactory or OrcKingdomFactory. Now, we can design a factory for our different kingdom factories. In this example, we created FactoryMaker, responsible for returning an instance of either ElfKingdomFactory or OrcKingdomFactory.
...@@ -156,46 +170,52 @@ public static void main(String[] args) { ...@@ -156,46 +170,52 @@ public static void main(String[] args) {
``` ```
## Class diagram ## Class diagram
![alt text](./etc/abstract-factory.urm.png "Abstract Factory class diagram") ![alt text](./etc/abstract-factory.urm.png "Abstract Factory class diagram")
## Applicability ## Applicability
Use the Abstract Factory pattern when Use the Abstract Factory pattern when
* a system should be independent of how its products are created, composed and represented * The system should be independent of how its products are created, composed and represented
* a system should be configured with one of multiple families of products * The system should be configured with one of multiple families of products
* a family of related product objects is designed to be used together, and you need to enforce this constraint * The family of related product objects is designed to be used together, and you need to enforce this constraint
* you want to provide a class library of products, and you want to reveal just their interfaces, not their implementations * You want to provide a class library of products, and you want to reveal just their interfaces, not their implementations
* the lifetime of the dependency is conceptually shorter than the lifetime of the consumer. * The lifetime of the dependency is conceptually shorter than the lifetime of the consumer.
* you need a run-time value to construct a particular dependency * You need a run-time value to construct a particular dependency
* you want to decide which product to call from a family at runtime. * You want to decide which product to call from a family at runtime.
* you need to supply one or more parameters only known at run-time before you can resolve a dependency. * You need to supply one or more parameters only known at run-time before you can resolve a dependency.
* when you need consistency among products * When you need consistency among products
* you don’t want to change existing code when adding new products or families of products to the program. * You don’t want to change existing code when adding new products or families of products to the program.
## Use Cases: Example use cases
* Selecting to call the appropriate implementation of FileSystemAcmeService or DatabaseAcmeService or NetworkAcmeService at runtime. * Selecting to call to the appropriate implementation of FileSystemAcmeService or DatabaseAcmeService or NetworkAcmeService at runtime.
* Unit test case writing becomes much easier * Unit test case writing becomes much easier
* UI tools for different OS * UI tools for different OS
## Consequences: ## Consequences:
* Dependency injection in java hides the service class dependencies that can lead to runtime errors that would have been caught at compile time. * Dependency injection in java hides the service class dependencies that can lead to runtime errors that would have been caught at compile time.
* While the pattern is great when creating predefined objects, adding the new ones might be challenging. * While the pattern is great when creating predefined objects, adding the new ones might be challenging.
* The code may become more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern. * The code becomes more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern.
## Tutorial ## Tutorial
* [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java)
* [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java)
## Real world examples ## Known uses
* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html) * [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html)
* [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--) * [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--)
* [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--) * [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--)
## Related patterns
[Factory Method](https://java-design-patterns.com/patterns/factory-method/)
[Factory Kit](https://java-design-patterns.com/patterns/factory-kit/)
## Credits ## Credits
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) * [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>abstract-factory</artifactId> <artifactId>abstract-factory</artifactId>
<dependencies> <dependencies>
......
...@@ -36,7 +36,7 @@ import org.junit.jupiter.api.Test; ...@@ -36,7 +36,7 @@ import org.junit.jupiter.api.Test;
*/ */
public class AbstractFactoryTest { public class AbstractFactoryTest {
private App app = new App(); private final App app = new App();
private KingdomFactory elfFactory; private KingdomFactory elfFactory;
private KingdomFactory orcFactory; private KingdomFactory orcFactory;
......
...@@ -25,12 +25,23 @@ package com.iluwatar.abstractfactory; ...@@ -25,12 +25,23 @@ package com.iluwatar.abstractfactory;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Tests that Abstract Factory example runs without errors. * Tests that Abstract Factory example runs without errors.
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -9,12 +9,126 @@ tags: ...@@ -9,12 +9,126 @@ tags:
--- ---
## Intent ## Intent
Allow new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating the troublesome dependency cycles that are inherent to the GOF VISITOR Pattern.
Allow new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating
the troublesome dependency cycles that are inherent to the GoF Visitor Pattern.
## Explanation
Real world example
> We have a hierarchy of modem classes. The modems in this hierarchy need to be visited by an external algorithm based
> on filtering criteria (is it Unix or DOS compatible modem).
In plain words
> Acyclic Visitor allows functions to be added to existing class hierarchies without modifying the hierarchies.
[WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) says
> The Acyclic Visitor pattern allows new functions to be added to existing class hierarchies without affecting those
> hierarchies, and without creating the dependency cycles that are inherent to the GangOfFour VisitorPattern.
**Programmatic Example**
Here's the `Modem` hierarchy.
```java
public abstract class Modem {
public abstract void accept(ModemVisitor modemVisitor);
}
public class Zoom extends Modem {
...
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof ZoomVisitor) {
((ZoomVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem");
}
}
}
public class Hayes extends Modem {
...
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof HayesVisitor) {
((HayesVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem");
}
}
}
```
Next we introduce the `ModemVisitor` hierarchy.
```java
public interface ModemVisitor {
}
public interface HayesVisitor extends ModemVisitor {
void visit(Hayes hayes);
}
public interface ZoomVisitor extends ModemVisitor {
void visit(Zoom zoom);
}
public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {
}
public class ConfigureForDosVisitor implements AllModemVisitor {
...
@Override
public void visit(Hayes hayes) {
LOGGER.info(hayes + " used with Dos configurator.");
}
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Dos configurator.");
}
}
public class ConfigureForUnixVisitor implements ZoomVisitor {
...
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Unix configurator.");
}
}
```
Finally, here are the visitors in action.
```java
var conUnix = new ConfigureForUnixVisitor();
var conDos = new ConfigureForDosVisitor();
var zoom = new Zoom();
var hayes = new Hayes();
hayes.accept(conDos);
zoom.accept(conDos);
hayes.accept(conUnix);
zoom.accept(conUnix);
```
Program output:
```
// Hayes modem used with Dos configurator.
// Zoom modem used with Dos configurator.
// Only HayesVisitor is allowed to visit Hayes modem
// Zoom modem used with Unix configurator.
```
## Class diagram ## Class diagram
![alt text](./etc/acyclic-visitor.png "Acyclic Visitor") ![alt text](./etc/acyclic-visitor.png "Acyclic Visitor")
## Applicability ## Applicability
This pattern can be used: This pattern can be used:
* When you need to add a new function to an existing hierarchy without the need to alter or affect that hierarchy. * When you need to add a new function to an existing hierarchy without the need to alter or affect that hierarchy.
...@@ -24,6 +138,7 @@ This pattern can be used: ...@@ -24,6 +138,7 @@ This pattern can be used:
* When the recompilation, relinking, retesting or redistribution of the derivatives of Element is very expensive. * When the recompilation, relinking, retesting or redistribution of the derivatives of Element is very expensive.
## Consequences ## Consequences
The good: The good:
* No dependency cycles between class hierarchies. * No dependency cycles between class hierarchies.
...@@ -32,11 +147,14 @@ The good: ...@@ -32,11 +147,14 @@ The good:
The bad: The bad:
* Violates the principle of least surprise or Liskov's Substitution principle by showing that it can accept all visitors but actually only being interested in particular visitors. * Violates [Liskov's Substitution Principle](https://java-design-patterns.com/principles/#liskov-substitution-principle) by showing that it can accept all visitors but actually only being interested in particular visitors.
* Parallel hierarchy of visitors has to be created for all members in visitable class hierarchy. * Parallel hierarchy of visitors has to be created for all members in visitable class hierarchy.
## Related patterns ## Related patterns
* [Visitor Pattern](../visitor/)
* [Visitor Pattern](https://java-design-patterns.com/patterns/visitor/)
## Credits ## Credits
* [Acyclic Visitor](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf)
* [Acyclic Visitor by Robert C. Martin](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf)
* [Acyclic Visitor in WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor)
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.2.2" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
<interface id="1" language="java" name="com.iluwatar.acyclicvisitor.ModemVisitor" project="acyclic-visitor"
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ModemVisitor.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="860" y="67"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="2" language="java" name="com.iluwatar.acyclicvisitor.Modem" project="acyclic-visitor"
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="327" y="77"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="com.iluwatar.acyclicvisitor.ConfigureForUnixVisitor" project="acyclic-visitor"
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="124" width="196" x="647" y="225"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.acyclicvisitor.Zoom" project="acyclic-visitor"
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="203" y="305"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="5" language="java" name="com.iluwatar.acyclicvisitor.HayesVisitor" project="acyclic-visitor"
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1019" y="468"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<interface id="6" language="java" name="com.iluwatar.acyclicvisitor.ZoomVisitor" project="acyclic-visitor"
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="758" y="467"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="7" language="java" name="com.iluwatar.acyclicvisitor.Hayes" project="acyclic-visitor"
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="479" y="307"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="8" language="java" name="com.iluwatar.acyclicvisitor.ConfigureForDosVisitor" project="acyclic-visitor"
file="/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="142" width="192" x="883" y="225"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<generalization id="9">
<end type="SOURCE" refId="7"/>
<end type="TARGET" refId="2"/>
</generalization>
<realization id="10">
<end type="SOURCE" refId="8"/>
<end type="TARGET" refId="6"/>
</realization>
<realization id="11">
<end type="SOURCE" refId="8"/>
<end type="TARGET" refId="5"/>
</realization>
<realization id="12">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="6"/>
</realization>
<realization id="13">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="1"/>
</realization>
<realization id="14">
<end type="SOURCE" refId="8"/>
<end type="TARGET" refId="1"/>
</realization>
<generalization id="15">
<end type="SOURCE" refId="4"/>
<end type="TARGET" refId="2"/>
</generalization>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>
\ No newline at end of file
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>acyclic-visitor</artifactId> <artifactId>acyclic-visitor</artifactId>
......
...@@ -25,13 +25,23 @@ package com.iluwatar.acyclicvisitor; ...@@ -25,13 +25,23 @@ package com.iluwatar.acyclicvisitor;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Tests that the Acyclic Visitor example runs without errors. * Tests that the Acyclic Visitor example runs without errors.
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
\ No newline at end of file
...@@ -37,7 +37,7 @@ import uk.org.lidalia.slf4jtest.TestLoggerFactory; ...@@ -37,7 +37,7 @@ import uk.org.lidalia.slf4jtest.TestLoggerFactory;
*/ */
public class ConfigureForDosVisitorTest { public class ConfigureForDosVisitorTest {
private TestLogger logger = TestLoggerFactory.getTestLogger(ConfigureForDosVisitor.class); private final TestLogger logger = TestLoggerFactory.getTestLogger(ConfigureForDosVisitor.class);
@Test @Test
public void testVisitForZoom() { public void testVisitForZoom() {
......
...@@ -12,9 +12,8 @@ tags: ...@@ -12,9 +12,8 @@ tags:
Wrapper Wrapper
## Intent ## Intent
Convert the interface of a class into another interface the clients Convert the interface of a class into another interface the clients expect. Adapter lets classes work together that
expect. Adapter lets classes work together that couldn't otherwise because of couldn't otherwise because of incompatible interfaces.
incompatible interfaces.
## Explanation ## Explanation
...@@ -56,7 +55,7 @@ And captain expects an implementation of `RowingBoat` interface to be able to mo ...@@ -56,7 +55,7 @@ And captain expects an implementation of `RowingBoat` interface to be able to mo
```java ```java
public class Captain { public class Captain {
private RowingBoat rowingBoat; private final RowingBoat rowingBoat;
// default constructor and setter for rowingBoat // default constructor and setter for rowingBoat
public Captain(RowingBoat rowingBoat) { public Captain(RowingBoat rowingBoat) {
this.rowingBoat = rowingBoat; this.rowingBoat = rowingBoat;
...@@ -75,7 +74,7 @@ public class FishingBoatAdapter implements RowingBoat { ...@@ -75,7 +74,7 @@ public class FishingBoatAdapter implements RowingBoat {
private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoatAdapter.class); private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoatAdapter.class);
private FishingBoat boat; private final FishingBoat boat;
public FishingBoatAdapter() { public FishingBoatAdapter() {
boat = new FishingBoat(); boat = new FishingBoat();
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>adapter</artifactId> <artifactId>adapter</artifactId>
<dependencies> <dependencies>
......
...@@ -29,7 +29,7 @@ package com.iluwatar.adapter; ...@@ -29,7 +29,7 @@ package com.iluwatar.adapter;
*/ */
public class FishingBoatAdapter implements RowingBoat { public class FishingBoatAdapter implements RowingBoat {
private FishingBoat boat; private final FishingBoat boat;
public FishingBoatAdapter() { public FishingBoatAdapter() {
boat = new FishingBoat(); boat = new FishingBoat();
......
...@@ -25,12 +25,23 @@ package com.iluwatar.adapter; ...@@ -25,12 +25,23 @@ package com.iluwatar.adapter;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Tests that Adapter example runs without errors. * Tests that Adapter example runs without errors.
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -6,20 +6,97 @@ permalink: /patterns/aggregator-microservices/ ...@@ -6,20 +6,97 @@ permalink: /patterns/aggregator-microservices/
categories: Architectural categories: Architectural
tags: tags:
- Cloud distributed - Cloud distributed
- Decoupling
- Microservices
--- ---
## Intent ## Intent
The user makes a single call to the Aggregator, and the aggregator then calls each relevant microservice and collects The user makes a single call to the aggregator service, and the aggregator then calls each relevant microservice.
the data, apply business logic to it, and further publish is as a REST Endpoint.
More variations of the aggregator are: ## Explanation
- Proxy Microservice Design Pattern: A different microservice is called upon the business need.
- Chained Microservice Design Pattern: In this case each microservice is dependent/ chained to a series Real world example
of other microservices.
> Our web marketplace needs information about products and their current inventory. It makes a call to an aggregator
> service which in turn calls the product information microservice and product inventory microservice returning the
> combined information.
In plain words
> Aggregator Microservice collects pieces of data from various microservices and returns an aggregate for processing.
Stack Overflow says
> Aggregator Microservice invokes multiple services to achieve the functionality required by the application.
**Programmatic Example**
Let's start from the data model. Here's our `Product`.
```java
public class Product {
private String title;
private int productInventories;
// getters and setters ->
...
}
```
Next we can introduce our `Aggregator` microservice. It contains clients `ProductInformationClient` and
`ProductInventoryClient` for calling respective microservices.
```java
@RestController
public class Aggregator {
@Resource
private ProductInformationClient informationClient;
@Resource
private ProductInventoryClient inventoryClient;
@RequestMapping(path = "/product", method = RequestMethod.GET)
public Product getProduct() {
var product = new Product();
var productTitle = informationClient.getProductTitle();
var productInventory = inventoryClient.getProductInventories();
//Fallback to error message
product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed"));
//Fallback to default error inventory
product.setProductInventories(requireNonNullElse(productInventory, -1));
return product;
}
}
```
Here's the essence of information microservice implementation. Inventory microservice is similar, it just returns
inventory counts.
```java
@RestController
public class InformationController {
@RequestMapping(value = "/information", method = RequestMethod.GET)
public String getProductTitle() {
return "The Product Title.";
}
}
```
Now calling our `Aggregator` REST API returns the product information.
```bash
curl http://localhost:50004/product
{"title":"The Product Title.","productInventories":5}
```
## Class diagram ## Class diagram
![alt text](./etc/aggregator-microservice.png "Aggregator Microservice") ![alt text](./aggregator-service/etc/aggregator-service.png "Aggregator Microservice")
## Applicability ## Applicability
...@@ -28,3 +105,5 @@ Use the Aggregator Microservices pattern when you need a unified API for various ...@@ -28,3 +105,5 @@ Use the Aggregator Microservices pattern when you need a unified API for various
## Credits ## Credits
* [Microservice Design Patterns](http://web.archive.org/web/20190705163602/http://blog.arungupta.me/microservice-design-patterns/) * [Microservice Design Patterns](http://web.archive.org/web/20190705163602/http://blog.arungupta.me/microservice-design-patterns/)
* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=8b4e570267bc5fb8b8189917b461dc60)
* [Architectural Patterns: Uncover essential patterns in the most indispensable realm of enterprise architecture](https://www.amazon.com/gp/product/B077T7V8RC/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B077T7V8RC&linkId=c34d204bfe1b277914b420189f09c1a4)
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>aggregator-microservices</artifactId> <artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>aggregator-service</artifactId> <artifactId>aggregator-service</artifactId>
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>aggregator-microservices</artifactId> <artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>aggregator-microservices</artifactId> <artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>inventory-microservice</artifactId> <artifactId>inventory-microservice</artifactId>
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>aggregator-microservices</artifactId> <artifactId>aggregator-microservices</artifactId>
......
...@@ -10,28 +10,37 @@ tags: ...@@ -10,28 +10,37 @@ tags:
--- ---
## Intent ## Intent
Provide a helper service instance on a client and offload common functionality away from a shared resource. Provide a helper service instance on a client and offload common functionality away from a shared resource.
## Explanation ## Explanation
Real world example Real world example
> A remote service has many clients accessing a function it provides. The service is a legacy application and is impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request frequency should be implemented along with latency checks and client-side logging. > A remote service has many clients accessing a function it provides. The service is a legacy application and is
> impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request
> frequency should be implemented along with latency checks and client-side logging.
In plain words In plain words
> Using the ambassador pattern, we can implement less-frequent polling from clients along with latency checks and logging. > With the Ambassador pattern, we can implement less-frequent polling from clients along with latency checks and
> logging.
Microsoft documentation states Microsoft documentation states
> An ambassador service can be thought of as an out-of-process proxy that is co-located with the client. This pattern can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, or other applications that are difficult to modify, in order to extend their networking capabilities. It can also enable a specialized team to implement those features. > An ambassador service can be thought of as an out-of-process proxy which is co-located with the client. This pattern
> can be useful for offloading common client connectivity tasks such as monitoring, logging, routing,
> security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications,
> or other applications that are difficult to modify, in order to extend their networking capabilities. It can also
> enable a specialized team to implement those features.
**Programmatic Example** **Programmatic Example**
With the above example in mind we will imitate the functionality in a simple manner. We have an interface implemented by the remote service as well as the ambassador service: With the above introduction in mind we will imitate the functionality in this example. We have an interface implemented
by the remote service as well as the ambassador service:
```java ```java
interface RemoteServiceInterface { interface RemoteServiceInterface {
long doRemoteFunction(int value) throws Exception; long doRemoteFunction(int value) throws Exception;
} }
``` ```
...@@ -136,7 +145,7 @@ public class Client { ...@@ -136,7 +145,7 @@ public class Client {
} }
``` ```
And here are two clients using the service. Here are two clients using the service.
```java ```java
public class App { public class App {
...@@ -149,13 +158,29 @@ public class App { ...@@ -149,13 +158,29 @@ public class App {
} }
``` ```
Here's the output for running the example:
```java
Time taken (ms): 111
Service result: 120
Time taken (ms): 931
Failed to reach remote: (1)
Time taken (ms): 665
Failed to reach remote: (2)
Time taken (ms): 538
Failed to reach remote: (3)
Service result: -1
```
## Class diagram ## Class diagram
![alt text](./etc/ambassador.urm.png "Ambassador class diagram") ![alt text](./etc/ambassador.urm.png "Ambassador class diagram")
## Applicability ## Applicability
Ambassador is applicable when working with a legacy remote service that cannot
be modified or would be extremely difficult to modify. Connectivity features can Ambassador is applicable when working with a legacy remote service which cannot be modified or would be extremely
be implemented on the client avoiding the need for changes on the remote service. difficult to modify. Connectivity features can be implemented on the client avoiding the need for changes on the remote
service.
* Ambassador provides a local interface for a remote service. * Ambassador provides a local interface for a remote service.
* Ambassador provides logging, circuit breaking, retries and security on the client. * Ambassador provides logging, circuit breaking, retries and security on the client.
...@@ -168,10 +193,14 @@ be implemented on the client avoiding the need for changes on the remote service ...@@ -168,10 +193,14 @@ be implemented on the client avoiding the need for changes on the remote service
* Offload remote service tasks * Offload remote service tasks
* Facilitate network connection * Facilitate network connection
## Real world examples ## Known uses
* [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador) * [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador)
## Related patterns
* [Proxy](https://java-design-patterns.com/patterns/proxy/)
## Credits ## Credits
* [Ambassador pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador) * [Ambassador pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador)
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>ambassador</artifactId> <artifactId>ambassador</artifactId>
......
...@@ -62,7 +62,7 @@ public class RemoteService implements RemoteServiceInterface { ...@@ -62,7 +62,7 @@ public class RemoteService implements RemoteServiceInterface {
* *
* @param value integer value to be multiplied. * @param value integer value to be multiplied.
* @return if waitTime is less than {@link RemoteService#THRESHOLD}, it returns value * 10, * @return if waitTime is less than {@link RemoteService#THRESHOLD}, it returns value * 10,
* otherwise {@link RemoteServiceInterface#FAILURE}. * otherwise {@link RemoteServiceStatus#FAILURE}.
*/ */
@Override @Override
public long doRemoteFunction(int value) { public long doRemoteFunction(int value) {
...@@ -74,6 +74,7 @@ public class RemoteService implements RemoteServiceInterface { ...@@ -74,6 +74,7 @@ public class RemoteService implements RemoteServiceInterface {
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e); LOGGER.error("Thread sleep state interrupted", e);
} }
return waitTime <= THRESHOLD ? value * 10 : FAILURE; return waitTime <= THRESHOLD ? value * 10
: RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue();
} }
} }
...@@ -27,7 +27,6 @@ package com.iluwatar.ambassador; ...@@ -27,7 +27,6 @@ package com.iluwatar.ambassador;
* Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}). * Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}).
*/ */
interface RemoteServiceInterface { interface RemoteServiceInterface {
int FAILURE = -1;
long doRemoteFunction(int value) throws Exception; long doRemoteFunction(int value);
} }
/*
* 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.ambassador;
/**
* Holds information regarding the status of the Remote Service.
*
* <p> This Enum replaces the integer value previously
* stored in {@link RemoteServiceInterface} as SonarCloud was identifying
* it as an issue. All test cases have been checked after changes,
* without failures. </p>
*/
public enum RemoteServiceStatus {
FAILURE(-1)
;
private final long remoteServiceStatusValue;
RemoteServiceStatus(long remoteServiceStatusValue) {
this.remoteServiceStatusValue = remoteServiceStatusValue;
}
public long getRemoteServiceStatusValue() {
return remoteServiceStatusValue;
}
}
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
package com.iluwatar.ambassador; package com.iluwatar.ambassador;
import static com.iluwatar.ambassador.RemoteServiceStatus.FAILURE;
import static java.lang.Thread.sleep; import static java.lang.Thread.sleep;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -58,14 +59,14 @@ public class ServiceAmbassador implements RemoteServiceInterface { ...@@ -58,14 +59,14 @@ public class ServiceAmbassador implements RemoteServiceInterface {
private long safeCall(int value) { private long safeCall(int value) {
var retries = 0; var retries = 0;
var result = (long) FAILURE; var result = FAILURE.getRemoteServiceStatusValue();
for (int i = 0; i < RETRIES; i++) { for (int i = 0; i < RETRIES; i++) {
if (retries >= RETRIES) { if (retries >= RETRIES) {
return FAILURE; return FAILURE.getRemoteServiceStatusValue();
} }
if ((result = checkLatency(value)) == FAILURE) { if ((result = checkLatency(value)) == FAILURE.getRemoteServiceStatusValue()) {
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")"); LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
retries++; retries++;
try { try {
......
...@@ -25,13 +25,23 @@ package com.iluwatar.ambassador; ...@@ -25,13 +25,23 @@ package com.iluwatar.ambassador;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -37,6 +37,6 @@ class ClientTest { ...@@ -37,6 +37,6 @@ class ClientTest {
Client client = new Client(); Client client = new Client();
var result = client.useService(10); var result = client.useService(10);
assertTrue(result == 100 || result == RemoteService.FAILURE); assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
} }
} }
...@@ -37,7 +37,7 @@ class RemoteServiceTest { ...@@ -37,7 +37,7 @@ class RemoteServiceTest {
void testFailedCall() { void testFailedCall() {
var remoteService = new RemoteService(new StaticRandomProvider(0.21)); var remoteService = new RemoteService(new StaticRandomProvider(0.21));
var result = remoteService.doRemoteFunction(10); var result = remoteService.doRemoteFunction(10);
assertEquals(RemoteServiceInterface.FAILURE, result); assertEquals(RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue(), result);
} }
@Test @Test
...@@ -48,7 +48,7 @@ class RemoteServiceTest { ...@@ -48,7 +48,7 @@ class RemoteServiceTest {
} }
private static class StaticRandomProvider implements RandomProvider { private static class StaticRandomProvider implements RandomProvider {
private double value; private final double value;
StaticRandomProvider(double value) { StaticRandomProvider(double value) {
this.value = value; this.value = value;
......
...@@ -35,6 +35,6 @@ class ServiceAmbassadorTest { ...@@ -35,6 +35,6 @@ class ServiceAmbassadorTest {
@Test @Test
void test() { void test() {
long result = new ServiceAmbassador().doRemoteFunction(10); long result = new ServiceAmbassador().doRemoteFunction(10);
assertTrue(result == 100 || result == RemoteServiceInterface.FAILURE); assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
} }
} }
...@@ -12,49 +12,53 @@ tags: ...@@ -12,49 +12,53 @@ tags:
## Intent ## Intent
Aggregate calls to microservices in a single location: the API Gateway. The user makes a single call to the API Gateway, Aggregate calls to microservices in a single location, the API Gateway. The user makes a single call
and the API Gateway then calls each relevant microservice. to the API Gateway, and the API Gateway then calls each relevant microservice.
## Explanation ## Explanation
With the Microservices pattern, a client may need data from multiple different microservices. If the client called each With the Microservices pattern, a client may need data from multiple different microservices. If the
microservice directly, that could contribute to longer load times, since the client would have to make a network request client called each microservice directly, that could contribute to longer load times, since the
for each microservice called. Moreover, having the client call each microservice directly ties the client to that client would have to make a network request for each microservice called. Moreover, having the
microservice - if the internal implementations of the microservices change (for example, if two microservices are client call each microservice directly ties the client to that microservice - if the internal
combined sometime in the future) or if the location (host and port) of a microservice changes, then every client that implementations of the microservices change (for example, if two microservices are combined sometime
in the future) or if the location (host and port) of a microservice changes, then every client that
makes use of those microservices must be updated. makes use of those microservices must be updated.
The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway pattern, an additional The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway
entity (the API Gateway) is placed between the client and the microservices. The job of the API Gateway is to aggregate pattern, an additional entity (the API Gateway) is placed between the client and the microservices.
the calls to the microservices. Rather than the client calling each microservice individually, the client calls the The job of the API Gateway is to aggregate the calls to the microservices. Rather than the client
API Gateway a single time. The API Gateway then calls each of the microservices that the client needs. calling each microservice individually, the client calls the API Gateway a single time. The API
Gateway then calls each of the microservices that the client needs.
Real world example Real world example
> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system the API Gateway makes > We are implementing microservices and API Gateway pattern for an e-commerce site. In this system
calls to the Image and Price microservices. > the API Gateway makes calls to the Image and Price microservices.
In plain words In plain words
> For a system implemented using microservices architecture, API Gateway is the single entry point that aggregates the > For a system implemented using microservices architecture, API Gateway is the single entry point
calls to the individual microservices. > that aggregates the calls to the individual microservices.
Wikipedia says Wikipedia says
> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling and security > API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling
policies, passes requests to the back-end service and then passes the response back to the requester. A gateway often > and security policies, passes requests to the back-end service and then passes the response back
includes a transformation engine to orchestrate and modify the requests and responses on the fly. A gateway can also > to the requester. A gateway often includes a transformation engine to orchestrate and modify the
provide functionality such as collecting analytics data and providing caching. The gateway can provide functionality to > requests and responses on the fly. A gateway can also provide functionality such as collecting
support authentication, authorization, security, audit and regulatory compliance. > analytics data and providing caching. The gateway can provide functionality to support
> authentication, authorization, security, audit and regulatory compliance.
**Programmatic Example** **Programmatic Example**
This implementation shows what the API Gateway pattern could look like for an e-commerce site. The `ApiGateway` makes This implementation shows what the API Gateway pattern could look like for an e-commerce site. The
calls to the Image and Price microservices using the `ImageClientImpl` and `PriceClientImpl` respectively. Customers `ApiGateway` makes calls to the Image and Price microservices using the `ImageClientImpl` and
viewing the site on a desktop device can see both price information and an image of a product, so the `ApiGateway` calls `PriceClientImpl` respectively. Customers viewing the site on a desktop device can see both price
both of the microservices and aggregates the data in the `DesktopProduct` model. However, mobile users only see price information and an image of a product, so the `ApiGateway` calls both of the microservices and
information; they do not see a product image. For mobile users, the `ApiGateway` only retrieves price information, which aggregates the data in the `DesktopProduct` model. However, mobile users only see price information;
it uses to populate the `MobileProduct`. they do not see a product image. For mobile users, the `ApiGateway` only retrieves price
information, which it uses to populate the `MobileProduct`.
Here's the Image microservice implementation. Here's the Image microservice implementation.
...@@ -64,7 +68,6 @@ public interface ImageClient { ...@@ -64,7 +68,6 @@ public interface ImageClient {
} }
public class ImageClientImpl implements ImageClient { public class ImageClientImpl implements ImageClient {
@Override @Override
public String getImagePath() { public String getImagePath() {
var httpClient = HttpClient.newHttpClient(); var httpClient = HttpClient.newHttpClient();
...@@ -114,7 +117,7 @@ public class PriceClientImpl implements PriceClient { ...@@ -114,7 +117,7 @@ public class PriceClientImpl implements PriceClient {
} }
``` ```
And here we can see how API Gateway maps the requests to the microservices. Here we can see how API Gateway maps the requests to the microservices.
```java ```java
public class ApiGateway { public class ApiGateway {
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>api-gateway</artifactId> <artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway-service</artifactId> <artifactId>api-gateway-service</artifactId>
......
...@@ -23,11 +23,16 @@ ...@@ -23,11 +23,16 @@
package com.iluwatar.api.gateway; package com.iluwatar.api.gateway;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers; import java.net.http.HttpResponse.BodyHandlers;
import org.slf4j.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
...@@ -35,6 +40,8 @@ import org.springframework.stereotype.Component; ...@@ -35,6 +40,8 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
public class ImageClientImpl implements ImageClient { public class ImageClientImpl implements ImageClient {
private static final Logger LOGGER = getLogger(ImageClientImpl.class);
/** /**
* Makes a simple HTTP Get request to the Image microservice. * Makes a simple HTTP Get request to the Image microservice.
* *
...@@ -49,12 +56,26 @@ public class ImageClientImpl implements ImageClient { ...@@ -49,12 +56,26 @@ public class ImageClientImpl implements ImageClient {
.build(); .build();
try { try {
LOGGER.info("Sending request to fetch image path");
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
logResponse(httpResponse);
return httpResponse.body(); return httpResponse.body();
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
e.printStackTrace(); LOGGER.error("Failure occurred while getting image path", e);
} }
return null; return null;
} }
private void logResponse(HttpResponse<String> httpResponse) {
if (isSuccessResponse(httpResponse.statusCode())) {
LOGGER.info("Image path received successfully");
} else {
LOGGER.warn("Image path request failed");
}
}
private boolean isSuccessResponse(int responseCode) {
return responseCode >= 200 && responseCode <= 299;
}
} }
...@@ -23,18 +23,26 @@ ...@@ -23,18 +23,26 @@
package com.iluwatar.api.gateway; package com.iluwatar.api.gateway;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers; import java.net.http.HttpResponse.BodyHandlers;
import org.slf4j.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* An adapter to communicate with the Price microservice. * An adapter to communicate with the Price microservice.
*/ */
@Component @Component
public class PriceClientImpl implements PriceClient { public class PriceClientImpl implements PriceClient {
private static final Logger LOGGER = getLogger(PriceClientImpl.class);
/** /**
* Makes a simple HTTP Get request to the Price microservice. * Makes a simple HTTP Get request to the Price microservice.
* *
...@@ -49,12 +57,26 @@ public class PriceClientImpl implements PriceClient { ...@@ -49,12 +57,26 @@ public class PriceClientImpl implements PriceClient {
.build(); .build();
try { try {
LOGGER.info("Sending request to fetch price info");
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
logResponse(httpResponse);
return httpResponse.body(); return httpResponse.body();
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
e.printStackTrace(); LOGGER.error("Failure occurred while getting price info", e);
} }
return null; return null;
} }
private void logResponse(HttpResponse<String> httpResponse) {
if (isSuccessResponse(httpResponse.statusCode())) {
LOGGER.info("Price info received successfully");
} else {
LOGGER.warn("Price info request failed");
}
}
private boolean isSuccessResponse(int responseCode) {
return responseCode >= 200 && responseCode <= 299;
}
} }
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>api-gateway</artifactId> <artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>image-microservice</artifactId> <artifactId>image-microservice</artifactId>
......
...@@ -23,15 +23,20 @@ ...@@ -23,15 +23,20 @@
package com.iluwatar.image.microservice; package com.iluwatar.image.microservice;
import static org.slf4j.LoggerFactory.getLogger;
import org.slf4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/** /**
* Exposes the Image microservice's endpoints. * Exposes the Image microservice's endpoints.
*/ */
@RestController @RestController
public class ImageController { public class ImageController {
private static final Logger LOGGER = getLogger(ImageController.class);
/** /**
* An endpoint for a user to retrieve an image path. * An endpoint for a user to retrieve an image path.
...@@ -40,6 +45,7 @@ public class ImageController { ...@@ -40,6 +45,7 @@ public class ImageController {
*/ */
@RequestMapping(value = "/image-path", method = RequestMethod.GET) @RequestMapping(value = "/image-path", method = RequestMethod.GET)
public String getImagePath() { public String getImagePath() {
LOGGER.info("Successfully found image path");
return "/product-image.png"; return "/product-image.png";
} }
} }
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway</artifactId> <artifactId>api-gateway</artifactId>
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>api-gateway</artifactId> <artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
......
...@@ -23,15 +23,20 @@ ...@@ -23,15 +23,20 @@
package com.iluwatar.price.microservice; package com.iluwatar.price.microservice;
import static org.slf4j.LoggerFactory.getLogger;
import org.slf4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/** /**
* Exposes the Price microservice's endpoints. * Exposes the Price microservice's endpoints.
*/ */
@RestController @RestController
public class PriceController { public class PriceController {
private static final Logger LOGGER = getLogger(PriceController.class);
/** /**
* An endpoint for a user to retrieve a product's price. * An endpoint for a user to retrieve a product's price.
...@@ -40,6 +45,7 @@ public class PriceController { ...@@ -40,6 +45,7 @@ public class PriceController {
*/ */
@RequestMapping(value = "/price", method = RequestMethod.GET) @RequestMapping(value = "/price", method = RequestMethod.GET)
public String getPrice() { public String getPrice() {
LOGGER.info("Successfully found price info");
return "20"; return "20";
} }
} }
...@@ -9,22 +9,137 @@ tags: ...@@ -9,22 +9,137 @@ tags:
--- ---
## Also known as ## Also known as
Given/When/Then Given/When/Then
## Intent ## Intent
The Arrange/Act/Assert (AAA) is a pattern for organizing unit tests.
Arrange/Act/Assert (AAA) is a pattern for organizing unit tests.
It breaks tests down into three clear and distinct steps: It breaks tests down into three clear and distinct steps:
1. Arrange: Perform the setup and initialization required for the test. 1. Arrange: Perform the setup and initialization required for the test.
2. Act: Take action(s) required for the test. 2. Act: Take action(s) required for the test.
3. Assert: Verify the outcome(s) of the test. 3. Assert: Verify the outcome(s) of the test.
## Explanation
This pattern has several significant benefits. It creates a clear separation between a test's
setup, operations, and results. This structure makes the code easier to read and understand. If
you place the steps in order and format your code to separate them, you can scan a test and
quickly comprehend what it does.
It also enforces a certain degree of discipline when you write your tests. You have to think
clearly about the three steps your test will perform. It makes tests more natural to write at
the same time since you already have an outline.
Real world example
> We need to write comprehensive and clear unit test suite for a class.
In plain words
> Arrange/Act/Assert is a testing pattern that organizes tests into three clear steps for easy
> maintenance.
WikiWikiWeb says
> Arrange/Act/Assert is a pattern for arranging and formatting code in UnitTest methods.
**Programmatic Example**
Let's first introduce our `Cash` class to be unit tested.
```java
public class Cash {
private int amount;
Cash(int amount) {
this.amount = amount;
}
void plus(int addend) {
amount += addend;
}
boolean minus(int subtrahend) {
if (amount >= subtrahend) {
amount -= subtrahend;
return true;
} else {
return false;
}
}
int count() {
return amount;
}
}
```
Then we write our unit tests according to Arrange/Act/Assert pattern. Notice the clearly
separated steps for each unit test.
```java
public class CashAAATest {
@Test
public void testPlus() {
//Arrange
var cash = new Cash(3);
//Act
cash.plus(4);
//Assert
assertEquals(7, cash.count());
}
@Test
public void testMinus() {
//Arrange
var cash = new Cash(8);
//Act
var result = cash.minus(5);
//Assert
assertTrue(result);
assertEquals(3, cash.count());
}
@Test
public void testInsufficientMinus() {
//Arrange
var cash = new Cash(1);
//Act
var result = cash.minus(6);
//Assert
assertFalse(result);
assertEquals(1, cash.count());
}
@Test
public void testUpdate() {
//Arrange
var cash = new Cash(5);
//Act
cash.plus(6);
var result = cash.minus(3);
//Assert
assertTrue(result);
assertEquals(8, cash.count());
}
}
```
## Applicability ## Applicability
Use Arrange/Act/Assert pattern when Use Arrange/Act/Assert pattern when
* you need to structure your unit tests so they're easier to read, maintain, and enhance. * You need to structure your unit tests so that they're easier to read, maintain, and enhance.
## Credits ## Credits
* [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx) * [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx)
* [Bill Wake: 3A – Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/) * [Bill Wake: 3A – Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/)
* [Martin Fowler: GivenWhenThen](https://martinfowler.com/bliki/GivenWhenThen.html) * [Martin Fowler: GivenWhenThen](https://martinfowler.com/bliki/GivenWhenThen.html)
* [xUnit Test Patterns: Refactoring Test Code](https://www.amazon.com/gp/product/0131495054/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0131495054&linkId=99701e8f4af2f7e8dd50d720c9b63dbf)
* [Unit Testing Principles, Practices, and Patterns](https://www.amazon.com/gp/product/1617296279/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617296279&linkId=74c75cf22a63c3e4758ae08aa0a0cc35)
* [Test Driven Development: By Example](https://www.amazon.com/gp/product/0321146530/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321146530&linkId=5c63a93d8c1175b84ca5087472ef0e05)
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
......
...@@ -60,7 +60,7 @@ public class CashAAATest { ...@@ -60,7 +60,7 @@ public class CashAAATest {
//Act //Act
cash.plus(4); cash.plus(4);
//Assert //Assert
assertEquals(cash.count(), 7); assertEquals(7, cash.count());
} }
@Test @Test
...@@ -71,7 +71,7 @@ public class CashAAATest { ...@@ -71,7 +71,7 @@ public class CashAAATest {
var result = cash.minus(5); var result = cash.minus(5);
//Assert //Assert
assertTrue(result); assertTrue(result);
assertEquals(cash.count(), 3); assertEquals(3, cash.count());
} }
@Test @Test
...@@ -82,7 +82,7 @@ public class CashAAATest { ...@@ -82,7 +82,7 @@ public class CashAAATest {
var result = cash.minus(6); var result = cash.minus(6);
//Assert //Assert
assertFalse(result); assertFalse(result);
assertEquals(cash.count(), 1); assertEquals(1, cash.count());
} }
@Test @Test
...@@ -94,6 +94,6 @@ public class CashAAATest { ...@@ -94,6 +94,6 @@ public class CashAAATest {
var result = cash.minus(3); var result = cash.minus(3);
//Assert //Assert
assertTrue(result); assertTrue(result);
assertEquals(cash.count(), 8); assertEquals(8, cash.count());
} }
} }
...@@ -44,16 +44,16 @@ public class CashAntiAAATest { ...@@ -44,16 +44,16 @@ public class CashAntiAAATest {
var cash = new Cash(3); var cash = new Cash(3);
//test plus //test plus
cash.plus(4); cash.plus(4);
assertEquals(cash.count(), 7); assertEquals(7, cash.count());
//test minus //test minus
cash = new Cash(8); cash = new Cash(8);
assertTrue(cash.minus(5)); assertTrue(cash.minus(5));
assertEquals(cash.count(), 3); assertEquals(3, cash.count());
assertFalse(cash.minus(6)); assertFalse(cash.minus(6));
assertEquals(cash.count(), 3); assertEquals(3, cash.count());
//test update //test update
cash.plus(5); cash.plus(5);
assertTrue(cash.minus(5)); assertTrue(cash.minus(5));
assertEquals(cash.count(), 3); assertEquals(3, cash.count());
} }
} }
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>async-method-invocation</artifactId> <artifactId>async-method-invocation</artifactId>
<dependencies> <dependencies>
......
...@@ -100,7 +100,7 @@ public class ThreadAsyncExecutor implements AsyncExecutor { ...@@ -100,7 +100,7 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
void setValue(T value) { void setValue(T value) {
this.value = value; this.value = value;
this.state = COMPLETED; this.state = COMPLETED;
this.callback.ifPresent(ac -> ac.onComplete(value, Optional.<Exception>empty())); this.callback.ifPresent(ac -> ac.onComplete(value, Optional.empty()));
synchronized (lock) { synchronized (lock) {
lock.notifyAll(); lock.notifyAll();
} }
......
...@@ -25,12 +25,24 @@ package com.iluwatar.async.method.invocation; ...@@ -25,12 +25,24 @@ package com.iluwatar.async.method.invocation;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void test() throws Exception { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
......
...@@ -24,15 +24,26 @@ ...@@ -24,15 +24,26 @@
package com.iluwatar.balking; package com.iluwatar.balking;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void main() { void shouldExecuteApplicationWithoutException() {
App.main(); assertDoesNotThrow((Executable) App::main);
} }
} }
\ No newline at end of file
...@@ -9,20 +9,26 @@ tags: ...@@ -9,20 +9,26 @@ tags:
--- ---
## Also known as ## Also known as
Handle/Body Handle/Body
## Intent ## Intent
Decouple an abstraction from its implementation so that the two can vary independently. Decouple an abstraction from its implementation so that the two can vary independently.
## Explanation ## Explanation
Real world example Real world example
> Consider you have a weapon with different enchantments and you are supposed to allow mixing different weapons with different enchantments. What would you do? Create multiple copies of each of the weapons for each of the enchantments or would you just create separate enchantment and set it for the weapon as needed? Bridge pattern allows you to do the second. > Consider you have a weapon with different enchantments, and you are supposed to allow mixing
> different weapons with different enchantments. What would you do? Create multiple copies of each
> of the weapons for each of the enchantments or would you just create separate enchantment and set
> it for the weapon as needed? Bridge pattern allows you to do the second.
In Plain Words In Plain Words
> Bridge pattern is about preferring composition over inheritance. Implementation details are pushed from a hierarchy to another object with a separate hierarchy. > Bridge pattern is about preferring composition over inheritance. Implementation details are pushed
> from a hierarchy to another object with a separate hierarchy.
Wikipedia says Wikipedia says
...@@ -30,7 +36,7 @@ Wikipedia says ...@@ -30,7 +36,7 @@ Wikipedia says
**Programmatic Example** **Programmatic Example**
Translating our weapon example from above. Here we have the `Weapon` hierarchy Translating our weapon example from above. Here we have the `Weapon` hierarchy:
```java ```java
public interface Weapon { public interface Weapon {
...@@ -105,7 +111,7 @@ public class Hammer implements Weapon { ...@@ -105,7 +111,7 @@ public class Hammer implements Weapon {
} }
``` ```
And the separate enchantment hierarchy Here's the separate enchantment hierarchy:
```java ```java
public interface Enchantment { public interface Enchantment {
...@@ -151,7 +157,7 @@ public class SoulEatingEnchantment implements Enchantment { ...@@ -151,7 +157,7 @@ public class SoulEatingEnchantment implements Enchantment {
} }
``` ```
And both the hierarchies in action Here are both hierarchies in action:
```java ```java
var enchantedSword = new Sword(new SoulEatingEnchantment()); var enchantedSword = new Sword(new SoulEatingEnchantment());
...@@ -178,18 +184,21 @@ hammer.unwield(); ...@@ -178,18 +184,21 @@ hammer.unwield();
``` ```
## Class diagram ## Class diagram
![alt text](./etc/bridge.urm.png "Bridge class diagram") ![alt text](./etc/bridge.urm.png "Bridge class diagram")
## Applicability ## Applicability
Use the Bridge pattern when Use the Bridge pattern when
* you want to avoid a permanent binding between an abstraction and its implementation. This might be the case, for example, when the implementation must be selected or switched at run-time. * You want to avoid a permanent binding between an abstraction and its implementation. This might be the case, for example, when the implementation must be selected or switched at run-time.
* both the abstractions and their implementations should be extensible by subclassing. In this case, the Bridge pattern lets you combine the different abstractions and implementations and extend them independently * Both the abstractions and their implementations should be extensible by subclassing. In this case, the Bridge pattern lets you combine the different abstractions and implementations and extend them independently.
* changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled. * Changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled.
* you have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies * You have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies.
* you want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien's String class, in which multiple objects can share the same string representation. * You want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien's String class, in which multiple objects can share the same string representation.
## Tutorial ## Tutorial
* [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java) * [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java)
## Credits ## Credits
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>bridge</artifactId> <artifactId>bridge</artifactId>
<dependencies> <dependencies>
......
...@@ -25,12 +25,22 @@ package com.iluwatar.bridge; ...@@ -25,12 +25,22 @@ package com.iluwatar.bridge;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{}); assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -9,36 +9,47 @@ tags: ...@@ -9,36 +9,47 @@ tags:
--- ---
## Intent ## Intent
Separate the construction of a complex object from its
representation so that the same construction process can create different Separate the construction of a complex object from its representation so that the same construction
representations. process can create different representations.
## Explanation ## Explanation
Real world example Real world example
> Imagine a character generator for a role playing game. The easiest option is to let computer create the character for you. But if you want to select the character details like profession, gender, hair color etc. the character generation becomes a step-by-step process that completes when all the selections are ready. > Imagine a character generator for a role-playing game. The easiest option is to let the computer
> create the character for you. If you want to manually select the character details like
> profession, gender, hair color etc. the character generation becomes a step-by-step process that
> completes when all the selections are ready.
In plain words In plain words
> Allows you to create different flavors of an object while avoiding constructor pollution. Useful when there could be several flavors of an object. Or when there are a lot of steps involved in creation of an object. > Allows you to create different flavors of an object while avoiding constructor pollution. Useful
> when there could be several flavors of an object. Or when there are a lot of steps involved in
> creation of an object.
Wikipedia says Wikipedia says
> The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern. > The builder pattern is an object creation software design pattern with the intentions of finding
> a solution to the telescoping constructor anti-pattern.
Having said that let me add a bit about what telescoping constructor anti-pattern is. At one point or the other we have all seen a constructor like below: Having said that let me add a bit about what telescoping constructor anti-pattern is. At one point
or the other, we have all seen a constructor like below:
```java ```java
public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) {
} }
``` ```
As you can see the number of constructor parameters can quickly get out of hand and it might become difficult to understand the arrangement of parameters. Plus this parameter list could keep on growing if you would want to add more options in future. This is called telescoping constructor anti-pattern. As you can see the number of constructor parameters can quickly get out of hand, and it may become
difficult to understand the arrangement of parameters. Plus this parameter list could keep on
growing if you would want to add more options in the future. This is called telescoping constructor
anti-pattern.
**Programmatic Example** **Programmatic Example**
The sane alternative is to use the Builder pattern. First of all we have our hero that we want to create The sane alternative is to use the Builder pattern. First of all we have our hero that we want to
create:
```java ```java
public final class Hero { public final class Hero {
...@@ -60,7 +71,7 @@ public final class Hero { ...@@ -60,7 +71,7 @@ public final class Hero {
} }
``` ```
And then we have the builder Then we have the builder:
```java ```java
public static class Builder { public static class Builder {
...@@ -105,20 +116,22 @@ And then we have the builder ...@@ -105,20 +116,22 @@ And then we have the builder
} }
``` ```
And then it can be used as: Then it can be used as:
```java ```java
var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build();
``` ```
## Class diagram ## Class diagram
![alt text](./etc/builder.urm.png "Builder class diagram") ![alt text](./etc/builder.urm.png "Builder class diagram")
## Applicability ## Applicability
Use the Builder pattern when Use the Builder pattern when
* the algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled * The algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled
* the construction process must allow different representations for the object that's constructed * The construction process must allow different representations for the object that's constructed
## Real world examples ## Real world examples
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>builder</artifactId> <artifactId>builder</artifactId>
<dependencies> <dependencies>
......
...@@ -25,12 +25,23 @@ package com.iluwatar.builder; ...@@ -25,12 +25,23 @@ package com.iluwatar.builder;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>business-delegate</artifactId> <artifactId>business-delegate</artifactId>
<dependencies> <dependencies>
......
...@@ -28,7 +28,7 @@ package com.iluwatar.business.delegate; ...@@ -28,7 +28,7 @@ package com.iluwatar.business.delegate;
*/ */
public class Client { public class Client {
private BusinessDelegate businessDelegate; private final BusinessDelegate businessDelegate;
public Client(BusinessDelegate businessDelegate) { public Client(BusinessDelegate businessDelegate) {
this.businessDelegate = businessDelegate; this.businessDelegate = businessDelegate;
......
...@@ -28,5 +28,5 @@ package com.iluwatar.business.delegate; ...@@ -28,5 +28,5 @@ package com.iluwatar.business.delegate;
*/ */
public enum ServiceType { public enum ServiceType {
EJB, JMS; EJB, JMS
} }
...@@ -27,13 +27,23 @@ import org.junit.jupiter.api.Test; ...@@ -27,13 +27,23 @@ import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Tests that Business Delegate example runs without errors. * Tests that Business Delegate example runs without errors.
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() throws IOException { void shouldExecuteApplicationWithoutException() {
String[] args = {};
App.main(args); assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
......
...@@ -58,12 +58,14 @@ public class App { ...@@ -58,12 +58,14 @@ public class App {
var vm = new VirtualMachine(); var vm = new VirtualMachine();
vm.getWizards()[0] = wizard; vm.getWizards()[0] = wizard;
interpretInstruction("LITERAL 0", vm); String literal = "LITERAL 0";
interpretInstruction("LITERAL 0", vm);
interpretInstruction(literal, vm);
interpretInstruction(literal, vm);
interpretInstruction("GET_HEALTH", vm); interpretInstruction("GET_HEALTH", vm);
interpretInstruction("LITERAL 0", vm); interpretInstruction(literal, vm);
interpretInstruction("GET_AGILITY", vm); interpretInstruction("GET_AGILITY", vm);
interpretInstruction("LITERAL 0", vm); interpretInstruction(literal, vm);
interpretInstruction("GET_WISDOM ", vm); interpretInstruction("GET_WISDOM ", vm);
interpretInstruction("ADD", vm); interpretInstruction("ADD", vm);
interpretInstruction("LITERAL 2", vm); interpretInstruction("LITERAL 2", vm);
......
...@@ -30,9 +30,9 @@ import java.util.Stack; ...@@ -30,9 +30,9 @@ import java.util.Stack;
*/ */
public class VirtualMachine { public class VirtualMachine {
private Stack<Integer> stack = new Stack<>(); private final Stack<Integer> stack = new Stack<>();
private Wizard[] wizards = new Wizard[2]; private final Wizard[] wizards = new Wizard[2];
/** /**
* Constructor. * Constructor.
......
...@@ -25,13 +25,22 @@ package com.iluwatar.bytecode; ...@@ -25,13 +25,22 @@ package com.iluwatar.bytecode;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{}); assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -104,7 +104,7 @@ public class VirtualMachineTest { ...@@ -104,7 +104,7 @@ public class VirtualMachineTest {
bytecode[2] = LITERAL.getIntValue(); bytecode[2] = LITERAL.getIntValue();
bytecode[3] = 50; // health amount bytecode[3] = 50; // health amount
bytecode[4] = SET_HEALTH.getIntValue(); bytecode[4] = SET_HEALTH.getIntValue();
bytecode[5] = LITERAL.getIntValue();; bytecode[5] = LITERAL.getIntValue();
bytecode[6] = wizardNumber; bytecode[6] = wizardNumber;
bytecode[7] = GET_HEALTH.getIntValue(); bytecode[7] = GET_HEALTH.getIntValue();
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>caching</artifactId> <artifactId>caching</artifactId>
<dependencies> <dependencies>
......
...@@ -29,7 +29,7 @@ package com.iluwatar.caching; ...@@ -29,7 +29,7 @@ package com.iluwatar.caching;
public enum CachingPolicy { public enum CachingPolicy {
THROUGH("through"), AROUND("around"), BEHIND("behind"), ASIDE("aside"); THROUGH("through"), AROUND("around"), BEHIND("behind"), ASIDE("aside");
private String policy; private final String policy;
CachingPolicy(String policy) { CachingPolicy(String policy) {
this.policy = policy; this.policy = policy;
......
...@@ -27,12 +27,23 @@ import org.junit.jupiter.api.Test; ...@@ -27,12 +27,23 @@ import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Tests that Caching example runs without errors. * Tests that Caching example runs without errors.
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>callback</artifactId> <artifactId>callback</artifactId>
<dependencies> <dependencies>
......
/*
* 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.
*/
module com.iluwatar.callback {
requires org.slf4j;
}
\ No newline at end of file
...@@ -25,12 +25,23 @@ package com.iluwatar.callback; ...@@ -25,12 +25,23 @@ package com.iluwatar.callback;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Tests that Callback example runs without errors. * Tests that Callback example runs without errors.
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -65,7 +65,7 @@ Then the request handler hierarchy ...@@ -65,7 +65,7 @@ Then the request handler hierarchy
```java ```java
public abstract class RequestHandler { public abstract class RequestHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class); private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class);
private RequestHandler next; private final RequestHandler next;
public RequestHandler(RequestHandler next) { public RequestHandler(RequestHandler next) {
this.next = next; this.next = next;
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>chain</artifactId> <artifactId>chain</artifactId>
<dependencies> <dependencies>
......
...@@ -33,7 +33,7 @@ public abstract class RequestHandler { ...@@ -33,7 +33,7 @@ public abstract class RequestHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class); private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class);
private RequestHandler next; private final RequestHandler next;
public RequestHandler(RequestHandler next) { public RequestHandler(RequestHandler next) {
this.next = next; this.next = next;
......
/*
* 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.
*/
module com.iluwatar.chain {
requires org.slf4j;
}
\ No newline at end of file
...@@ -25,13 +25,23 @@ package com.iluwatar.chain; ...@@ -25,13 +25,23 @@ package com.iluwatar.chain;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** /**
* Application test * Application test
*/ */
public class AppTest { class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
public void test() { void shouldExecuteApplicationWithoutException() {
App.main(new String[]{});
assertDoesNotThrow(() -> App.main(new String[]{}));
} }
} }
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>circuit-breaker</artifactId> <artifactId>circuit-breaker</artifactId>
<dependencies> <dependencies>
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>collection-pipeline</artifactId> <artifactId>collection-pipeline</artifactId>
<dependencies> <dependencies>
......
...@@ -87,10 +87,7 @@ public class Car { ...@@ -87,10 +87,7 @@ public class Car {
} else if (!model.equals(other.model)) { } else if (!model.equals(other.model)) {
return false; return false;
} }
if (year != other.year) { return year == other.year;
return false;
}
return true;
} }
public String getMake() { public String getMake() {
......
...@@ -29,7 +29,7 @@ import java.util.List; ...@@ -29,7 +29,7 @@ import java.util.List;
* A Person class that has the list of cars that the person owns and use. * A Person class that has the list of cars that the person owns and use.
*/ */
public class Person { public class Person {
private List<Car> cars; private final List<Car> cars;
/** /**
* Constructor to create an instance of person. * Constructor to create an instance of person.
......
/*
* 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.
*/
module com.iluwatar.collectionpipeline {
requires org.slf4j;
}
\ No newline at end of file
...@@ -37,7 +37,7 @@ import org.slf4j.LoggerFactory; ...@@ -37,7 +37,7 @@ import org.slf4j.LoggerFactory;
public class AppTest { public class AppTest {
private static final Logger LOGGER = LoggerFactory.getLogger(AppTest.class); private static final Logger LOGGER = LoggerFactory.getLogger(AppTest.class);
private List<Car> cars = CarFactory.createCars(); private final List<Car> cars = CarFactory.createCars();
@Test @Test
public void testGetModelsAfter2000UsingFor() { public void testGetModelsAfter2000UsingFor() {
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>combinator</artifactId> <artifactId>combinator</artifactId>
...@@ -39,6 +39,12 @@ ...@@ -39,6 +39,12 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>
...@@ -25,12 +25,19 @@ package com.iluwatar.combinator; ...@@ -25,12 +25,19 @@ package com.iluwatar.combinator;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
public class CombinatorAppTest { public class CombinatorAppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link CombinatorApp#main(String[])}
* throws an exception.
*/
@Test @Test
public void main() { public void shouldExecuteApplicationWithoutException() {
CombinatorApp.main(new String[]{}); assertDoesNotThrow(() -> CombinatorApp.main(new String[]{}));
} }
} }
\ No newline at end of file
...@@ -36,8 +36,8 @@ public class Wizard { ...@@ -36,8 +36,8 @@ public class Wizard {
private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class); private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class);
private Deque<Command> undoStack = new LinkedList<>(); private final Deque<Command> undoStack = new LinkedList<>();
private Deque<Command> redoStack = new LinkedList<>(); private final Deque<Command> redoStack = new LinkedList<>();
public Wizard() {} public Wizard() {}
...@@ -75,19 +75,18 @@ public class Wizard { ...@@ -75,19 +75,18 @@ public class Wizard {
Next we present the spell hierarchy. Next we present the spell hierarchy.
```java ```java
public abstract class Command { public interface Command {
public abstract void execute(Target target); void execute(Target target);
public abstract void undo(); void undo();
public abstract void redo(); void redo();
@Override String toString();
public abstract String toString();
} }
public class InvisibilitySpell extends Command { public class InvisibilitySpell implements Command {
private Target target; private Target target;
...@@ -117,7 +116,7 @@ public class InvisibilitySpell extends Command { ...@@ -117,7 +116,7 @@ public class InvisibilitySpell extends Command {
} }
} }
public class ShrinkSpell extends Command { public class ShrinkSpell implements Command {
private Size oldSize; private Size oldSize;
private Target target; private Target target;
......
command/etc/command.png

27.0 KB | W: | H:

command/etc/command.png

75.3 KB | W: | H:

command/etc/command.png
command/etc/command.png
command/etc/command.png
command/etc/command.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -4,7 +4,7 @@ package com.iluwatar.command { ...@@ -4,7 +4,7 @@ package com.iluwatar.command {
+ App() + App()
+ main(args : String[]) {static} + main(args : String[]) {static}
} }
abstract class Command { interface Command {
+ Command() + Command()
+ execute(Target) {abstract} + execute(Target) {abstract}
+ redo() {abstract} + redo() {abstract}
...@@ -77,7 +77,7 @@ ShrinkSpell --> "-oldSize" Size ...@@ -77,7 +77,7 @@ ShrinkSpell --> "-oldSize" Size
InvisibilitySpell --> "-target" Target InvisibilitySpell --> "-target" Target
ShrinkSpell --> "-target" Target ShrinkSpell --> "-target" Target
Target --> "-visibility" Visibility Target --> "-visibility" Visibility
Goblin --|> Target Goblin --|> Target
InvisibilitySpell --|> Command InvisibilitySpell ..|> Command
ShrinkSpell --|> Command ShrinkSpell ..|> Command
@enduml @enduml
\ No newline at end of file
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version> <version>1.24.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>command</artifactId> <artifactId>command</artifactId>
<dependencies> <dependencies>
......
...@@ -26,15 +26,12 @@ package com.iluwatar.command; ...@@ -26,15 +26,12 @@ package com.iluwatar.command;
/** /**
* Interface for Commands. * Interface for Commands.
*/ */
public abstract class Command { public interface Command {
void execute(Target target);
public abstract void execute(Target target); void undo();
public abstract void undo(); void redo();
public abstract void redo();
@Override
public abstract String toString();
String toString();
} }
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册