Merge branch 'master' into all-contributors/add-ravening

This commit is contained in:
Ilkka Seppälä 2020-09-06 19:38:44 +03:00 committed by GitHub
commit 9d75592e8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
533 changed files with 7805 additions and 2802 deletions

View File

@ -1085,6 +1085,116 @@
"code",
"review"
]
},
{
"login": "vINCENT8888801",
"name": "Wei Seng",
"avatar_url": "https://avatars0.githubusercontent.com/u/8037883?v=4",
"profile": "https://github.com/vINCENT8888801",
"contributions": [
"code"
]
},
{
"login": "ashishtrivedi16",
"name": "Ashish Trivedi",
"avatar_url": "https://avatars3.githubusercontent.com/u/23194128?v=4",
"profile": "https://www.linkedin.com/in/ashish-trivedi-218379135/",
"contributions": [
"code"
]
},
{
"login": "RayYH",
"name": "洪月阳",
"avatar_url": "https://avatars1.githubusercontent.com/u/41055099?v=4",
"profile": "https://rayyounghong.com",
"contributions": [
"code"
]
},
{
"login": "xdvrx1",
"name": "xdvrx1",
"avatar_url": "https://avatars0.githubusercontent.com/u/47092464?v=4",
"profile": "https://xdvrx1.github.io/",
"contributions": [
"review",
"ideas"
]
},
{
"login": "ohbus",
"name": "Subhrodip Mohanta",
"avatar_url": "https://avatars0.githubusercontent.com/u/13291222?v=4",
"profile": "http://subho.xyz",
"contributions": [
"code",
"review"
]
},
{
"login": "nahteb",
"name": "Bethan Palmer",
"avatar_url": "https://avatars3.githubusercontent.com/u/13121570?v=4",
"profile": "https://github.com/nahteb",
"contributions": [
"code"
]
},
{
"login": "ToxicDreamz",
"name": "Toxic Dreamz",
"avatar_url": "https://avatars0.githubusercontent.com/u/45225562?v=4",
"profile": "https://github.com/ToxicDreamz",
"contributions": [
"code"
]
},
{
"login": "edycutjong",
"name": "Edy Cu Tjong",
"avatar_url": "https://avatars1.githubusercontent.com/u/1098102?v=4",
"profile": "http://www.edycutjong.com",
"contributions": [
"doc"
]
},
{
"login": "mkrzywanski",
"name": "Michał Krzywański",
"avatar_url": "https://avatars0.githubusercontent.com/u/15279585?v=4",
"profile": "https://github.com/mkrzywanski",
"contributions": [
"code"
]
},
{
"login": "stefanbirkner",
"name": "Stefan Birkner",
"avatar_url": "https://avatars1.githubusercontent.com/u/711349?v=4",
"profile": "https://www.stefan-birkner.de",
"contributions": [
"code"
]
},
{
"login": "fedorskvorcov",
"name": "Fedor Skvorcov",
"avatar_url": "https://avatars3.githubusercontent.com/u/43882212?v=4",
"profile": "https://github.com/fedorskvorcov",
"contributions": [
"code"
]
},
{
"login": "samilAyoub",
"name": "samilAyoub",
"avatar_url": "https://avatars0.githubusercontent.com/u/61546990?v=4",
"profile": "https://github.com/samilAyoub",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 4,

View File

@ -24,37 +24,41 @@
# 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 with Maven
name: Java CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
# Disabling shallow clone for improving relevancy of SonarQube reporting
fetch-depth: 0
- 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
run: sudo apt-get install -y xvfb
# The SonarQube analysis is only for the master branch of the main repository.
# SonarQube scan does not work for forked repositories try changing it to xvfb-run mvn clean verify
# 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.REPOSITORY_ACCESS_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

57
.github/workflows/maven-pr-builder.yml vendored Normal file
View File

@ -0,0 +1,57 @@
#
# 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 PR Builder
on:
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-20.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 -y xvfb
# This worflow is only for building Pull Requests, the master branch runs Sonar analysis on the main repository.
# SonarQube scan does not work for forked repositories.
# See https://jira.sonarsource.com/browse/MMF-1371
- name: Build with Maven
run: xvfb-run mvn clean verify

View File

@ -6,10 +6,11 @@
![Java CI with Maven](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI%20with%20Maven/badge.svg)
[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=alert_status)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-119-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-131-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
# Introduction
@ -245,7 +246,26 @@ This project is licensed under the terms of the MIT license.
<tr>
<td align="center"><a href="https://github.com/nishant"><img src="https://avatars2.githubusercontent.com/u/15331971?v=4" width="100px;" alt=""/><br /><sub><b>Nishant Arora</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=nishant" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/raja-peeyush-kumar-singh"><img src="https://avatars0.githubusercontent.com/u/5496024?v=4" width="100px;" alt=""/><br /><sub><b>Peeyush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=raja-peeyush-kumar-singh" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/ravening"><img src="https://avatars1.githubusercontent.com/u/10645273?v=4" width="100px;" alt=""/><br /><sub><b>Rakesh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ravening" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/vINCENT8888801"><img src="https://avatars0.githubusercontent.com/u/8037883?v=4" width="100px;" alt=""/><br /><sub><b>Wei Seng</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=vINCENT8888801" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://www.linkedin.com/in/ashish-trivedi-218379135/"><img src="https://avatars3.githubusercontent.com/u/23194128?v=4" width="100px;" alt=""/><br /><sub><b>Ashish Trivedi</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ashishtrivedi16" title="Code">💻</a></td>
<td align="center"><a href="https://rayyounghong.com"><img src="https://avatars1.githubusercontent.com/u/41055099?v=4" width="100px;" alt=""/><br /><sub><b>洪月阳</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=RayYH" title="Code">💻</a></td>
<td align="center"><a href="https://xdvrx1.github.io/"><img src="https://avatars0.githubusercontent.com/u/47092464?v=4" width="100px;" alt=""/><br /><sub><b>xdvrx1</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Axdvrx1" title="Reviewed Pull Requests">👀</a> <a href="#ideas-xdvrx1" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://subho.xyz"><img src="https://avatars0.githubusercontent.com/u/13291222?v=4" width="100px;" alt=""/><br /><sub><b>Subhrodip Mohanta</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ohbus" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Aohbus" title="Reviewed Pull Requests">👀</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/nahteb"><img src="https://avatars3.githubusercontent.com/u/13121570?v=4" width="100px;" alt=""/><br /><sub><b>Bethan Palmer</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=nahteb" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/ToxicDreamz"><img src="https://avatars0.githubusercontent.com/u/45225562?v=4" width="100px;" alt=""/><br /><sub><b>Toxic Dreamz</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ToxicDreamz" title="Code">💻</a></td>
<td align="center"><a href="http://www.edycutjong.com"><img src="https://avatars1.githubusercontent.com/u/1098102?v=4" width="100px;" alt=""/><br /><sub><b>Edy Cu Tjong</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=edycutjong" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/mkrzywanski"><img src="https://avatars0.githubusercontent.com/u/15279585?v=4" width="100px;" alt=""/><br /><sub><b>Michał Krzywański</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=mkrzywanski" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/ravening"><img src="https://avatars1.githubusercontent.com/u/10645273?v=4" width="100px;" alt=""/><br /><sub><b>Rakesh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ravening" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Aravening" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://www.stefan-birkner.de"><img src="https://avatars1.githubusercontent.com/u/711349?v=4" width="100px;" alt=""/><br /><sub><b>Stefan Birkner</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=stefanbirkner" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/fedorskvorcov"><img src="https://avatars3.githubusercontent.com/u/43882212?v=4" width="100px;" alt=""/><br /><sub><b>Fedor Skvorcov</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=fedorskvorcov" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/samilAyoub"><img src="https://avatars0.githubusercontent.com/u/61546990?v=4" width="100px;" alt=""/><br /><sub><b>samilAyoub</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=samilAyoub" title="Code">💻</a></td>
</tr>
</table>

View File

@ -21,7 +21,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>abstract-document</artifactId>
<dependencies>

View File

@ -25,14 +25,23 @@ package com.iluwatar.abstractdocument;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* 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
public void shouldExecuteAppWithoutException() {
App.main(null);
void shouldExecuteAppWithoutException() {
assertDoesNotThrow(() -> App.main(null));
}
}

View File

@ -22,7 +22,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>abstract-factory</artifactId>
<dependencies>

View File

@ -25,12 +25,23 @@ package com.iluwatar.abstractfactory;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* 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
public void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -101,7 +101,7 @@ public class ConfigureForUnixVisitor implements ZoomVisitor {
}
```
Finally here are the visitors in action.
Finally, here are the visitors in action.
```java
var conUnix = new ConfigureForUnixVisitor();

View File

@ -21,7 +21,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>acyclic-visitor</artifactId>

View File

@ -25,13 +25,23 @@ package com.iluwatar.acyclicvisitor;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* 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
public void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -12,9 +12,8 @@ tags:
Wrapper
## Intent
Convert the interface of a class into another interface the clients
expect. Adapter lets classes work together that couldn't otherwise because of
incompatible interfaces.
Convert the interface of a class into another interface the clients expect. Adapter lets classes work together that
couldn't otherwise because of incompatible interfaces.
## Explanation

View File

@ -22,7 +22,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>adapter</artifactId>
<dependencies>

View File

@ -25,12 +25,23 @@ package com.iluwatar.adapter;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* 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
public void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -6,6 +6,8 @@ permalink: /patterns/aggregator-microservices/
categories: Architectural
tags:
- Cloud distributed
- Decoupling
- Microservices
---
## Intent
@ -17,7 +19,7 @@ The user makes a single call to the aggregator service, and the aggregator then
Real world example
> Our web marketplace needs information about products and their current inventory. It makes a call to an aggregator
> service that in turn calls the product information microservice and product inventory microservice returning the
> service which in turn calls the product information microservice and product inventory microservice returning the
> combined information.
In plain words
@ -41,7 +43,7 @@ public class Product {
}
```
Next we can introduct our `Aggregator` microservice. It contains clients `ProductInformationClient` and
Next we can introduce our `Aggregator` microservice. It contains clients `ProductInformationClient` and
`ProductInventoryClient` for calling respective microservices.
```java

View File

@ -20,7 +20,7 @@
<parent>
<artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>aggregator-service</artifactId>

View File

@ -20,7 +20,7 @@
<parent>
<artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -20,7 +20,7 @@
<parent>
<artifactId>aggregator-microservices</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>inventory-microservice</artifactId>

View File

@ -29,7 +29,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>aggregator-microservices</artifactId>

View File

@ -10,28 +10,37 @@ tags:
---
## Intent
Provide a helper service instance on a client and offload common functionality away from a shared resource.
## Explanation
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
> 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
> 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**
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
interface RemoteServiceInterface {
long doRemoteFunction(int value) throws Exception;
}
```
@ -136,7 +145,7 @@ public class Client {
}
```
And here are two clients using the service.
Here are two clients using the service.
```java
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
![alt text](./etc/ambassador.urm.png "Ambassador class diagram")
## 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
be implemented on the client avoiding the need for changes on the remote service.
Ambassador is applicable when working with a legacy remote service which cannot be modified or would be extremely
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 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
* Offload remote service tasks
* Facilitate network connection
## Real world examples
## Known uses
* [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador)
## Related patterns
* [Proxy](https://java-design-patterns.com/patterns/proxy/)
## Credits
* [Ambassador pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador)

View File

@ -20,7 +20,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ambassador</artifactId>

View File

@ -62,7 +62,7 @@ public class RemoteService implements RemoteServiceInterface {
*
* @param value integer value to be multiplied.
* @return if waitTime is less than {@link RemoteService#THRESHOLD}, it returns value * 10,
* otherwise {@link RemoteServiceInterface#FAILURE}.
* otherwise {@link RemoteServiceStatus#FAILURE}.
*/
@Override
public long doRemoteFunction(int value) {
@ -74,6 +74,7 @@ public class RemoteService implements RemoteServiceInterface {
} catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e);
}
return waitTime <= THRESHOLD ? value * 10 : FAILURE;
return waitTime <= THRESHOLD ? value * 10
: RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue();
}
}

View File

@ -27,7 +27,6 @@ package com.iluwatar.ambassador;
* Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}).
*/
interface RemoteServiceInterface {
int FAILURE = -1;
long doRemoteFunction(int value) throws Exception;
long doRemoteFunction(int value);
}

View File

@ -0,0 +1,48 @@
/*
* 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;
}
}

View File

@ -23,6 +23,7 @@
package com.iluwatar.ambassador;
import static com.iluwatar.ambassador.RemoteServiceStatus.FAILURE;
import static java.lang.Thread.sleep;
import org.slf4j.Logger;
@ -58,14 +59,14 @@ public class ServiceAmbassador implements RemoteServiceInterface {
private long safeCall(int value) {
var retries = 0;
var result = (long) FAILURE;
var result = FAILURE.getRemoteServiceStatusValue();
for (int i = 0; i < RETRIES; i++) {
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) + ")");
retries++;
try {

View File

@ -25,13 +25,23 @@ package com.iluwatar.ambassador;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Application test
*/
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
void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -37,6 +37,6 @@ class ClientTest {
Client client = new Client();
var result = client.useService(10);
assertTrue(result == 100 || result == RemoteService.FAILURE);
assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
}
}

View File

@ -37,7 +37,7 @@ class RemoteServiceTest {
void testFailedCall() {
var remoteService = new RemoteService(new StaticRandomProvider(0.21));
var result = remoteService.doRemoteFunction(10);
assertEquals(RemoteServiceInterface.FAILURE, result);
assertEquals(RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue(), result);
}
@Test

View File

@ -35,6 +35,6 @@ class ServiceAmbassadorTest {
@Test
void test() {
long result = new ServiceAmbassador().doRemoteFunction(10);
assertTrue(result == 100 || result == RemoteServiceInterface.FAILURE);
assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
}
}

View File

@ -12,49 +12,53 @@ tags:
## Intent
Aggregate calls to microservices in a single location: the API Gateway. The user makes a single call to the API Gateway,
and the API Gateway then calls each relevant microservice.
Aggregate calls to microservices in a single location, the API Gateway. The user makes a single call
to the API Gateway, and the API Gateway then calls each relevant microservice.
## Explanation
With the Microservices pattern, a client may need data from multiple different microservices. If the client called each
microservice directly, that could contribute to longer load times, since the client would have to make a network request
for each microservice called. Moreover, having the client call each microservice directly ties the client to that
microservice - if the internal 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
With the Microservices pattern, a client may need data from multiple different microservices. If the
client called each microservice directly, that could contribute to longer load times, since the
client would have to make a network request for each microservice called. Moreover, having the
client call each microservice directly ties the client to that microservice - if the internal
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.
The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway pattern, an additional
entity (the API Gateway) is placed between the client and the microservices. The job of the API Gateway is to aggregate
the calls to the microservices. Rather than the client 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.
The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway
pattern, an additional entity (the API Gateway) is placed between the client and the microservices.
The job of the API Gateway is to aggregate the calls to the microservices. Rather than the client
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
> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system the API Gateway makes
calls to the Image and Price microservices.
> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system
> the API Gateway makes calls to the Image and Price microservices.
In plain words
> For a system implemented using microservices architecture, API Gateway is the single entry point that aggregates the
calls to the individual microservices.
> For a system implemented using microservices architecture, API Gateway is the single entry point
> that aggregates the calls to the individual microservices.
Wikipedia says
> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling and security
policies, passes requests to the back-end service and then passes the response back to the requester. A gateway often
includes a transformation engine to orchestrate and modify the requests and responses on the fly. A gateway can also
provide functionality such as collecting analytics data and providing caching. The gateway can provide functionality to
support authentication, authorization, security, audit and regulatory compliance.
> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling
> and security policies, passes requests to the back-end service and then passes the response back
> to the requester. A gateway often includes a transformation engine to orchestrate and modify the
> requests and responses on the fly. A gateway can also provide functionality such as collecting
> analytics data and providing caching. The gateway can provide functionality to support
> authentication, authorization, security, audit and regulatory compliance.
**Programmatic Example**
This implementation shows what the API Gateway pattern could look like for an e-commerce site. The `ApiGateway` makes
calls to the Image and Price microservices using the `ImageClientImpl` and `PriceClientImpl` respectively. Customers
viewing the site on a desktop device can see both price information and an image of a product, so the `ApiGateway` calls
both of the microservices and aggregates the data in the `DesktopProduct` model. However, mobile users only see price
information; they do not see a product image. For mobile users, the `ApiGateway` only retrieves price information, which
it uses to populate the `MobileProduct`.
This implementation shows what the API Gateway pattern could look like for an e-commerce site. The
`ApiGateway` makes calls to the Image and Price microservices using the `ImageClientImpl` and
`PriceClientImpl` respectively. Customers viewing the site on a desktop device can see both price
information and an image of a product, so the `ApiGateway` calls both of the microservices and
aggregates the data in the `DesktopProduct` model. However, mobile users only see price information;
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.
@ -64,7 +68,6 @@ public interface ImageClient {
}
public class ImageClientImpl implements ImageClient {
@Override
public String getImagePath() {
var httpClient = HttpClient.newHttpClient();
@ -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
public class ApiGateway {

View File

@ -29,7 +29,7 @@
<parent>
<artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway-service</artifactId>

View File

@ -23,11 +23,16 @@
package com.iluwatar.api.gateway;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
/**
@ -35,6 +40,8 @@ import org.springframework.stereotype.Component;
*/
@Component
public class ImageClientImpl implements ImageClient {
private static final Logger LOGGER = getLogger(ImageClientImpl.class);
/**
* Makes a simple HTTP Get request to the Image microservice.
*
@ -49,12 +56,26 @@ public class ImageClientImpl implements ImageClient {
.build();
try {
LOGGER.info("Sending request to fetch image path");
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
logResponse(httpResponse);
return httpResponse.body();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
LOGGER.error("Failure occurred while getting image path", e);
}
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;
}
}

View File

@ -23,18 +23,26 @@
package com.iluwatar.api.gateway;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
/**
* An adapter to communicate with the Price microservice.
*/
@Component
public class PriceClientImpl implements PriceClient {
private static final Logger LOGGER = getLogger(PriceClientImpl.class);
/**
* Makes a simple HTTP Get request to the Price microservice.
*
@ -49,12 +57,26 @@ public class PriceClientImpl implements PriceClient {
.build();
try {
LOGGER.info("Sending request to fetch price info");
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
logResponse(httpResponse);
return httpResponse.body();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
LOGGER.error("Failure occurred while getting price info", e);
}
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;
}
}

View File

@ -29,7 +29,7 @@
<parent>
<artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>image-microservice</artifactId>

View File

@ -23,15 +23,20 @@
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.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* Exposes the Image microservice's endpoints.
*/
@RestController
public class ImageController {
private static final Logger LOGGER = getLogger(ImageController.class);
/**
* An endpoint for a user to retrieve an image path.
@ -40,6 +45,7 @@ public class ImageController {
*/
@RequestMapping(value = "/image-path", method = RequestMethod.GET)
public String getImagePath() {
LOGGER.info("Successfully found image path");
return "/product-image.png";
}
}

View File

@ -29,7 +29,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway</artifactId>

View File

@ -29,7 +29,7 @@
<parent>
<artifactId>api-gateway</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -23,15 +23,20 @@
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.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* Exposes the Price microservice's endpoints.
*/
@RestController
public class PriceController {
private static final Logger LOGGER = getLogger(PriceController.class);
/**
* An endpoint for a user to retrieve a product's price.
@ -40,6 +45,7 @@ public class PriceController {
*/
@RequestMapping(value = "/price", method = RequestMethod.GET)
public String getPrice() {
LOGGER.info("Successfully found price info");
return "20";
}
}

View File

@ -9,22 +9,137 @@ tags:
---
## Also known as
Given/When/Then
## 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:
1. Arrange: Perform the setup and initialization required for the test.
2. Act: Take action(s) required for 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
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
* [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/)
* [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)

View File

@ -29,7 +29,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -22,7 +22,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>async-method-invocation</artifactId>
<dependencies>

View File

@ -25,12 +25,24 @@ package com.iluwatar.async.method.invocation;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Application test
*/
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
void test() throws Exception {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -20,7 +20,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -24,15 +24,26 @@
package com.iluwatar.balking;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Application test
*/
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
void main() {
App.main();
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow((Executable) App::main);
}
}

View File

@ -9,20 +9,26 @@ tags:
---
## Also known as
Handle/Body
## Intent
Decouple an abstraction from its implementation so that the two can vary independently.
## Explanation
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
> 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
@ -30,7 +36,7 @@ Wikipedia says
**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
public interface Weapon {
@ -105,7 +111,7 @@ public class Hammer implements Weapon {
}
```
And the separate enchantment hierarchy
Here's the separate enchantment hierarchy:
```java
public interface Enchantment {
@ -151,7 +157,7 @@ public class SoulEatingEnchantment implements Enchantment {
}
```
And both the hierarchies in action
Here are both hierarchies in action:
```java
var enchantedSword = new Sword(new SoulEatingEnchantment());
@ -178,18 +184,21 @@ hammer.unwield();
```
## Class diagram
![alt text](./etc/bridge.urm.png "Bridge class diagram")
## Applicability
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.
* 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.
* 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 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.
* 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 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
* [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java)
## Credits

View File

@ -22,7 +22,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>bridge</artifactId>
<dependencies>

View File

@ -25,12 +25,22 @@ package com.iluwatar.bridge;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Application test
*/
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
void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -9,36 +9,47 @@ tags:
---
## Intent
Separate the construction of a complex object from its
representation so that the same construction process can create different
representations.
Separate the construction of a complex object from its representation so that the same construction
process can create different representations.
## Explanation
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
> 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
> 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
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**
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
public final class Hero {
@ -60,7 +71,7 @@ public final class Hero {
}
```
And then we have the builder
Then we have the builder:
```java
public static class 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
var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build();
```
## Class diagram
![alt text](./etc/builder.urm.png "Builder class diagram")
## Applicability
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 construction process must allow different representations for the object that's constructed
* 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
## Real world examples

View File

@ -22,7 +22,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>builder</artifactId>
<dependencies>

View File

@ -25,12 +25,23 @@ package com.iluwatar.builder;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Application test
*/
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
void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -22,7 +22,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>business-delegate</artifactId>
<dependencies>

View File

@ -27,13 +27,23 @@ import org.junit.jupiter.api.Test;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* 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
public void test() throws IOException {
String[] args = {};
App.main(args);
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -20,7 +20,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -58,12 +58,14 @@ public class App {
var vm = new VirtualMachine();
vm.getWizards()[0] = wizard;
interpretInstruction("LITERAL 0", vm);
interpretInstruction("LITERAL 0", vm);
String literal = "LITERAL 0";
interpretInstruction(literal, vm);
interpretInstruction(literal, vm);
interpretInstruction("GET_HEALTH", vm);
interpretInstruction("LITERAL 0", vm);
interpretInstruction(literal, vm);
interpretInstruction("GET_AGILITY", vm);
interpretInstruction("LITERAL 0", vm);
interpretInstruction(literal, vm);
interpretInstruction("GET_WISDOM ", vm);
interpretInstruction("ADD", vm);
interpretInstruction("LITERAL 2", vm);

View File

@ -25,13 +25,22 @@ package com.iluwatar.bytecode;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* 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
public void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -29,7 +29,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>caching</artifactId>
<dependencies>

View File

@ -27,12 +27,23 @@ import org.junit.jupiter.api.Test;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* 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
public void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -9,14 +9,16 @@ tags:
---
## Intent
Callback is a piece of executable code that is passed as an argument to other code, which is expected to call back
(execute) the argument at some convenient time.
Callback is a piece of executable code that is passed as an argument to other code, which is
expected to call back (execute) the argument at some convenient time.
## Explanation
Real world example
> We need to be notified after executing task has finished. We pass a callback method for the executor and wait for it to call back on us.
> We need to be notified after executing task has finished. We pass a callback method for
> the executor and wait for it to call back on us.
In plain words
@ -24,7 +26,9 @@ In plain words
Wikipedia says
> In computer programming, a callback, also known as a "call-after" function, is any executable code that is passed as an argument to other code; that other code is expected to call back (execute) the argument at a given time.
> In computer programming, a callback, also known as a "call-after" function, is any executable
> code that is passed as an argument to other code; that other code is expected to call
> back (execute) the argument at a given time.
**Programmatic Example**
@ -61,7 +65,7 @@ public final class SimpleTask extends Task {
}
```
Finally here's how we execute a task and receive a callback when it's finished.
Finally, here's how we execute a task and receive a callback when it's finished.
```java
var task = new SimpleTask();
@ -69,13 +73,15 @@ Finally here's how we execute a task and receive a callback when it's finished.
```
## Class diagram
![alt text](./etc/callback.png "Callback")
## Applicability
Use the Callback pattern when
* when some arbitrary synchronous or asynchronous action must be performed after execution of some defined activity.
## Real world examples
* [CyclicBarrier](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html#CyclicBarrier%28int,%20java.lang.Runnable%29) constructor can accept callback that will be triggered every time when barrier is tripped.
* [CyclicBarrier](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html#CyclicBarrier%28int,%20java.lang.Runnable%29) constructor can accept a callback that will be triggered every time a barrier is tripped.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -8,11 +8,6 @@ package com.iluwatar.callback {
interface Callback {
+ call() {abstract}
}
class LambdasApp {
- LOGGER : Logger {static}
- LambdasApp()
+ main(args : String[]) {static}
}
class SimpleTask {
- LOGGER : Logger {static}
+ SimpleTask()

View File

@ -29,7 +29,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>callback</artifactId>
<dependencies>

View File

@ -25,12 +25,23 @@ package com.iluwatar.callback;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* 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
public void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -9,27 +9,32 @@ tags:
---
## Intent
Avoid coupling the sender of a request to its receiver by giving
more than one object a chance to handle the request. Chain the receiving
objects and pass the request along the chain until an object handles it.
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to
handle the request. Chain the receiving objects and pass the request along the chain until an object
handles it.
## Explanation
Real world example
> The Orc King gives loud orders to his army. The closest one to react is the commander, then officer and then soldier. The commander, officer and soldier here form a chain of responsibility.
> The Orc King gives loud orders to his army. The closest one to react is the commander, then
> officer and then soldier. The commander, officer and soldier here form a chain of responsibility.
In plain words
> It helps building a chain of objects. Request enters from one end and keeps going from object to object till it finds the suitable handler.
> It helps to build a chain of objects. A request enters from one end and keeps going from an object
> to another until it finds a suitable handler.
Wikipedia says
> In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain.
> In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of
> a source of command objects and a series of processing objects. Each processing object contains
> logic that defines the types of command objects that it can handle; the rest are passed to the
> next processing object in the chain.
**Programmatic Example**
Translating our example with orcs from above. First we have the request class
Translating our example with the orcs from above. First we have the `Request` class:
```java
public class Request {
@ -140,14 +145,16 @@ king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); // Orc so
```
## Class diagram
![alt text](./etc/chain.urm.png "Chain of Responsibility class diagram")
## Applicability
Use Chain of Responsibility when
* more than one object may handle a request, and the handler isn't known a priori. The handler should be ascertained automatically
* you want to issue a request to one of several objects without specifying the receiver explicitly
* the set of objects that can handle a request should be specified dynamically
* More than one object may handle a request, and the handler isn't known a priori. The handler should be ascertained automatically.
* You want to issue a request to one of several objects without specifying the receiver explicitly.
* The set of objects that can handle a request should be specified dynamically.
## Real world examples

View File

@ -29,7 +29,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>chain</artifactId>
<dependencies>

View File

@ -25,13 +25,23 @@ package com.iluwatar.chain;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* 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
public void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -12,32 +12,43 @@ tags:
## Intent
Handle costly remote *procedure/service* calls in such a way that the failure of a **single** service/component cannot bring the whole application down, and we can reconnect to the service as soon as possible.
Handle costly remote service calls in such a way that the failure of a single service/component
cannot bring the whole application down, and we can reconnect to the service as soon as possible.
## Explanation
Real world example
> Imagine a Web App that has both local (example: files and images) and remote (example: database entries) to serve. The database might not be responding due to a variety of reasons, so if the application keeps trying to read from the database using multiple threads/processes, soon all of them will hang and our entire web application will crash. We should be able to detect this situation and show the user an appropriate message so that he/she can explore other parts of the app unaffected by the database failure without any problem.
> Imagine a web application that has both local files/images and remote database entries to serve.
> The database might not be responding due to a variety of reasons, so if the application keeps
> trying to read from the database using multiple threads/processes, soon all of them will hang
> causing our entire web application will crash. We should be able to detect this situation and show
> the user an appropriate message so that he/she can explore other parts of the app unaffected by
> the database failure.
In plain words
> Allows us to save resources when we know a remote service failed. Useful when all parts of our application are highly decoupled from each other, and failure of one component doesn't mean the other parts will stop working.
> Circuit Breaker allows graceful handling of failed remote services. It's especially useful when
> all parts of our application are highly decoupled from each other, and failure of one component
> doesn't mean the other parts will stop working.
Wikipedia says
> **Circuit breaker** is a design pattern used in modern software development. It is used to detect failures and encapsulates the logic of preventing a failure from constantly recurring, during maintenance, temporary external system failure or unexpected system difficulties.
So, how does this all come together?
> Circuit breaker is a design pattern used in modern software development. It is used to detect
> failures and encapsulates the logic of preventing a failure from constantly recurring, during
> maintenance, temporary external system failure or unexpected system difficulties.
## Programmatic Example
With the above example in mind we will imitate the functionality in a simple manner. We have two services: A *monitoring service* which will mimic the web app and will make both **local** and **remote** calls.
So, how does this all come together? With the above example in mind we will imitate the
functionality in a simple example. A monitoring service mimics the web app and makes both local and
remote calls.
The service architecture is as follows:
![alt text](./etc/ServiceDiagram.PNG "Service Diagram")
In terms of code, the End user application is:
In terms of code, the end user application is:
```java
public class App {
@ -62,7 +73,7 @@ public class App {
}
```
The monitoring service is:
The monitoring service:
``` java
public class MonitoringService {
@ -80,7 +91,8 @@ public class MonitoringService {
}
}
```
As it can be seen, it does the call to get local resources directly, but it wraps the call to remote (costly) service in a circuit breaker object, which prevents faults as follows:
As it can be seen, it does the call to get local resources directly, but it wraps the call to
remote (costly) service in a circuit breaker object, which prevents faults as follows:
```java
public class CircuitBreaker {
@ -155,24 +167,27 @@ public class CircuitBreaker {
}
```
How does the above pattern prevent failures? Let's understand via this finite state machine implemented by it.
How does the above pattern prevent failures? Let's understand via this finite state machine
implemented by it.
![alt text](./etc/StateDiagram.PNG "State Diagram")
- We initialize the Circuit Breaker object with certain parameters: **timeout**, **failureThreshold** and **retryTimePeriod** which help determine how resilient the API is.
- Initially, we are in the **closed** state and the remote call to API happens.
- We initialize the Circuit Breaker object with certain parameters: `timeout`, `failureThreshold` and `retryTimePeriod` which help determine how resilient the API is.
- Initially, we are in the `closed` state and nos remote calls to the API have occurred.
- Every time the call succeeds, we reset the state to as it was in the beginning.
- If the number of failures cross a certain threshold, we move to the **open** state, which acts just like an open circuit and prevents remote service calls from being made, thus saving resources. (Here, we return the response called ```stale response from API```)
- Once we exceed the retry timeout period, we move to the **half-open** state and make another call to the remote service again to check if the service is working so that we can serve fresh content. A *failure* sets it back to **open** state and another attempt is made after retry timeout period, while a *success* sets it to **closed** state so that everything starts working normally again.
- If the number of failures cross a certain threshold, we move to the `open` state, which acts just like an open circuit and prevents remote service calls from being made, thus saving resources. (Here, we return the response called ```stale response from API```)
- Once we exceed the retry timeout period, we move to the `half-open` state and make another call to the remote service again to check if the service is working so that we can serve fresh content. A failure sets it back to `open` state and another attempt is made after retry timeout period, while a success sets it to `closed` state so that everything starts working normally again.
## Class diagram
![alt text](./etc/circuit-breaker.urm.png "Circuit Breaker class diagram")
## Applicability
Use the Circuit Breaker pattern when
- Building a fault-tolerant application where failure of some services shouldn't bring the entire application down.
- Building an continuously incremental/continuous delivery application, as some of it's components can be upgraded without shutting it down entirely.
- Building a continuously running (always-on) application, so that its components can be upgraded without shutting it down entirely.
## Related Patterns

View File

@ -27,7 +27,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>circuit-breaker</artifactId>
<dependencies>

View File

@ -27,7 +27,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>collection-pipeline</artifactId>
<dependencies>

View File

@ -29,7 +29,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>combinator</artifactId>
@ -39,6 +39,12 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -25,12 +25,19 @@ package com.iluwatar.combinator;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
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
public void main() {
CombinatorApp.main(new String[]{});
public void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> CombinatorApp.main(new String[]{}));
}
}

View File

@ -9,15 +9,20 @@ tags:
---
## Also known as
Action, Transaction
## Intent
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Encapsulate a request as an object, thereby letting you parameterize clients with different
requests, queue or log requests, and support undoable operations.
## Explanation
Real world example
> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one. The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses the spells one by one. Each spell here is a command object that can be undone.
> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one.
> The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses
> the spells one by one. Each spell here is a command object that can be undone.
In plain words
@ -25,11 +30,13 @@ In plain words
Wikipedia says
> In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time.
> In object-oriented programming, the command pattern is a behavioral design pattern in which an
> object is used to encapsulate all information needed to perform an action or trigger an event at
> a later time.
**Programmatic Example**
Here's the sample code with wizard and goblin. Let's start from the wizard class.
Here's the sample code with wizard and goblin. Let's start from the `Wizard` class.
```java
public class Wizard {
@ -149,7 +156,7 @@ public class ShrinkSpell implements Command {
}
```
And last we have the goblin who's the target of the spells.
Finally, we have the goblin who's the target of the spells.
```java
public abstract class Target {
@ -199,44 +206,67 @@ public class Goblin extends Target {
}
```
Finally here's the whole example in action.
Here's the whole example in action.
```java
var wizard = new Wizard();
var goblin = new Goblin();
goblin.printStatus();
// Goblin, [size=normal] [visibility=visible]
wizard.castSpell(new ShrinkSpell(), goblin);
// Wizard casts Shrink spell at Goblin
goblin.printStatus();
// Goblin, [size=small] [visibility=visible]
wizard.castSpell(new InvisibilitySpell(), goblin);
// Wizard casts Invisibility spell at Goblin
goblin.printStatus();
// Goblin, [size=small] [visibility=invisible]
wizard.undoLastSpell();
// Wizard undoes Invisibility spell
goblin.printStatus();
```
Here's the program output:
```java
// Goblin, [size=normal] [visibility=visible]
// Wizard casts Shrink spell at Goblin
// Goblin, [size=small] [visibility=visible]
// Wizard casts Invisibility spell at Goblin
// Goblin, [size=small] [visibility=invisible]
// Wizard undoes Invisibility spell
// Goblin, [size=small] [visibility=visible]
```
## Class diagram
![alt text](./etc/command.png "Command")
## Applicability
Use the Command pattern when you want to
* parameterize objects by an action to perform. You can express such parameterization in a procedural language with a callback function, that is, a function that's registered somewhere to be called at a later point. Commands are an object-oriented replacement for callbacks.
* specify, queue, and execute requests at different times. A Command object can have a lifetime independent of the original request. If the receiver of a request can be represented in an address space-independent way, then you can transfer a command object for the request to a different process and fulfill the request there
* support undo. The Command's execute operation can store state for reversing its effects in the command itself. The Command interface must have an added Unexecute operation that reverses the effects of a previous call to execute. Executed commands are stored in a history list. Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling unexecute and execute, respectively
* support logging changes so that they can be reapplied in case of a system crash. By augmenting the Command interface with load and store operations, you can keep a persistent log of changes. Recovering from a crash involves reloading logged commands from disk and re-executing them with the execute operation
* structure a system around high-level operations build on primitive operations. Such a structure is common in information systems that support transactions. A transaction encapsulates a set of changes to data. The Command pattern offers a way to model transactions. Commands have a common interface, letting you invoke all transactions the same way. The pattern also makes it easy to extend the system with new transactions
Use the Command pattern when you want to:
* Parameterize objects by an action to perform. You can express such parameterization in a
procedural language with a callback function, that is, a function that's registered somewhere to be
called at a later point. Commands are an object-oriented replacement for callbacks.
* Specify, queue, and execute requests at different times. A Command object can have a lifetime
independent of the original request. If the receiver of a request can be represented in an address
space-independent way, then you can transfer a command object for the request to a different process
and fulfill the request there.
* Support undo. The Command's execute operation can store state for reversing its effects in the
command itself. The Command interface must have an added un-execute operation that reverses the
effects of a previous call to execute. The executed commands are stored in a history list.
Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling
un-execute and execute, respectively.
* Support logging changes so that they can be reapplied in case of a system crash. By augmenting the
Command interface with load and store operations, you can keep a persistent log of changes.
Recovering from a crash involves reloading logged commands from disk and re-executing them with
the execute operation.
* Structure a system around high-level operations build on primitive operations. Such a structure is
common in information systems that support transactions. A transaction encapsulates a set of changes
to data. The Command pattern offers a way to model transactions. Commands have a common interface,
letting you invoke all transactions the same way. The pattern also makes it easy to extend the
system with new transactions.
## Typical Use Case
* to keep a history of requests
* implement callback functionality
* implement the undo functionality
* To keep a history of requests
* Implement callback functionality
* Implement the undo functionality
## Real world examples

View File

@ -29,7 +29,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>command</artifactId>
<dependencies>

View File

@ -25,12 +25,21 @@ package com.iluwatar.command;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Tests that Command 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#main(String[])}
* throws an exception.
*/
@Test
public void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -27,7 +27,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>commander</artifactId>
<dependencies>

View File

@ -36,9 +36,11 @@ import com.iluwatar.commander.queue.QueueDatabase;
import com.iluwatar.commander.queue.QueueTask;
import com.iluwatar.commander.queue.QueueTask.TaskType;
import com.iluwatar.commander.shippingservice.ShippingService;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>Commander pattern is used to handle all issues that can come up while making a
* distributed transaction. The idea is to have a commander, which coordinates the execution of all
@ -159,8 +161,8 @@ public class Commander {
private void sendPaymentRequest(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.paymentTime) {
if (order.paid.equals(PaymentStatus.Trying)) {
order.paid = PaymentStatus.NotDone;
if (order.paid.equals(PaymentStatus.TRYING)) {
order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(order);
LOG.error("Order " + order.id + ": Payment time for order over, failed and returning..");
} //if succeeded or failed, would have been dequeued, no attempt to make payment
@ -172,15 +174,15 @@ public class Commander {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to payment service,"
+ " trying again..");
+ " trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating payment request..");
}
throw l.remove(0);
}
if (order.paid.equals(PaymentStatus.Trying)) {
if (order.paid.equals(PaymentStatus.TRYING)) {
var transactionId = paymentService.receiveRequest(order.price);
order.paid = PaymentStatus.Done;
order.paid = PaymentStatus.DONE;
LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId);
if (!finalSiteMsgShown) {
LOG.info("Payment made successfully, thank you for shopping with us!!");
@ -193,26 +195,26 @@ public class Commander {
if (PaymentDetailsErrorException.class.isAssignableFrom(err.getClass())) {
if (!finalSiteMsgShown) {
LOG.info("There was an error in payment. Your account/card details "
+ "may have been incorrect. "
+ "Meanwhile, your order has been converted to COD and will be shipped.");
+ "may have been incorrect. "
+ "Meanwhile, your order has been converted to COD and will be shipped.");
finalSiteMsgShown = true;
}
LOG.error("Order " + order.id + ": Payment details incorrect, failed..");
o.paid = PaymentStatus.NotDone;
o.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(o);
} else {
if (o.messageSent.equals(MessageSent.NoneSent)) {
if (o.messageSent.equals(MessageSent.NONE_SENT)) {
if (!finalSiteMsgShown) {
LOG.info("There was an error in payment. We are on it, and will get back to you "
+ "asap. Don't worry, your order has been placed and will be shipped.");
+ "asap. Don't worry, your order has been placed and will be shipped.");
finalSiteMsgShown = true;
}
LOG.warn("Order " + order.id + ": Payment error, going to queue..");
sendPaymentPossibleErrorMsg(o);
}
if (o.paid.equals(PaymentStatus.Trying) && System
.currentTimeMillis() - o.createdTime < paymentTime) {
var qt = new QueueTask(o, TaskType.Payment, -1);
if (o.paid.equals(PaymentStatus.TRYING) && System
.currentTimeMillis() - o.createdTime < paymentTime) {
var qt = new QueueTask(o, TaskType.PAYMENT, -1);
updateQueue(qt);
}
}
@ -234,12 +236,12 @@ public class Commander {
// additional check not needed
LOG.trace("Order " + qt.order.id + ": Queue time for order over, failed..");
return;
} else if (qt.taskType.equals(TaskType.Payment) && !qt.order.paid.equals(PaymentStatus.Trying)
|| qt.taskType.equals(TaskType.Messaging) && (qt.messageType == 1
&& !qt.order.messageSent.equals(MessageSent.NoneSent)
|| qt.order.messageSent.equals(MessageSent.PaymentFail)
|| qt.order.messageSent.equals(MessageSent.PaymentSuccessful))
|| qt.taskType.equals(TaskType.EmployeeDb) && qt.order.addedToEmployeeHandle) {
} else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING)
|| qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1
&& !qt.order.messageSent.equals(MessageSent.NONE_SENT)
|| qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL))
|| qt.taskType.equals(TaskType.EMPLOYEE_DB) && qt.order.addedToEmployeeHandle) {
LOG.trace("Order " + qt.order.id + ": Not queueing task since task already done..");
return;
}
@ -256,8 +258,8 @@ public class Commander {
tryDoingTasksInQueue();
};
Retry.HandleErrorIssue<QueueTask> handleError = (qt1, err) -> {
if (qt1.taskType.equals(TaskType.Payment)) {
qt1.order.paid = PaymentStatus.NotDone;
if (qt1.taskType.equals(TaskType.PAYMENT)) {
qt1.order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(qt1.order);
LOG.error("Order " + qt1.order.id + ": Unable to enqueue payment task,"
+ " payment failed..");
@ -331,35 +333,9 @@ public class Commander {
}
var list = messagingService.exceptionsList;
Thread t = new Thread(() -> {
Retry.Operation op = (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Success msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Success"
+ " messaging request..");
}
throw l.remove(0);
}
if (!order.messageSent.equals(MessageSent.PaymentFail)
&& !order.messageSent.equals(MessageSent.PaymentSuccessful)) {
var requestId = messagingService.receiveRequest(2);
order.messageSent = MessageSent.PaymentSuccessful;
LOG.info("Order " + order.id + ": Payment Success message sent,"
+ " request Id: " + requestId);
}
};
Retry.Operation op = handleSuccessMessageRetryOperation(order);
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
if ((o.messageSent.equals(MessageSent.NoneSent) || o.messageSent
.equals(MessageSent.PaymentTrying))
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.Messaging, 2);
updateQueue(qt);
LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to"
+ " queue task and add to employee handle..");
employeeHandleIssue(order);
}
handleSuccessMessageErrorIssue(order, o);
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
@ -372,6 +348,40 @@ public class Commander {
t.start();
}
private void handleSuccessMessageErrorIssue(Order order, Order o) {
if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent
.equals(MessageSent.PAYMENT_TRYING))
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 2);
updateQueue(qt);
LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to"
+ " queue task and add to employee handle..");
employeeHandleIssue(order);
}
}
private Retry.Operation handleSuccessMessageRetryOperation(Order order) {
return (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Success msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Success"
+ " messaging request..");
}
throw l.remove(0);
}
if (!order.messageSent.equals(MessageSent.PAYMENT_FAIL)
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
var requestId = messagingService.receiveRequest(2);
order.messageSent = MessageSent.PAYMENT_SUCCESSFUL;
LOG.info("Order " + order.id + ": Payment Success message sent,"
+ " request Id: " + requestId);
}
};
}
private void sendPaymentFailureMessage(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
LOG.trace("Order " + order.id + ": Message time for order over, returning..");
@ -380,34 +390,10 @@ public class Commander {
var list = messagingService.exceptionsList;
var t = new Thread(() -> {
Retry.Operation op = (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Failure msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Failure"
+ " message request..");
}
throw l.remove(0);
}
if (!order.messageSent.equals(MessageSent.PaymentFail)
&& !order.messageSent.equals(MessageSent.PaymentSuccessful)) {
var requestId = messagingService.receiveRequest(0);
order.messageSent = MessageSent.PaymentFail;
LOG.info("Order " + order.id + ": Payment Failure message sent successfully,"
+ " request Id: " + requestId);
}
handlePaymentFailureRetryOperation(order, l);
};
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
if ((o.messageSent.equals(MessageSent.NoneSent) || o.messageSent
.equals(MessageSent.PaymentTrying))
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.Messaging, 0);
updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, "
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
handlePaymentErrorIssue(order, o);
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
@ -420,6 +406,38 @@ public class Commander {
t.start();
}
private void handlePaymentErrorIssue(Order order, Order o) {
if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent
.equals(MessageSent.PAYMENT_TRYING))
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 0);
updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, "
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
}
private void handlePaymentFailureRetryOperation(Order order, List<Exception> l) throws Exception {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Failure msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Failure"
+ " message request..");
}
throw l.remove(0);
}
if (!order.messageSent.equals(MessageSent.PAYMENT_FAIL)
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
var requestId = messagingService.receiveRequest(0);
order.messageSent = MessageSent.PAYMENT_FAIL;
LOG.info("Order " + order.id + ": Payment Failure message sent successfully,"
+ " request Id: " + requestId);
}
}
private void sendPaymentPossibleErrorMsg(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.messageTime) {
LOG.trace("Message time for order over, returning..");
@ -428,34 +446,10 @@ public class Commander {
var list = messagingService.exceptionsList;
var t = new Thread(() -> {
Retry.Operation op = (l) -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Error msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Error"
+ " messaging request..");
}
throw l.remove(0);
}
if (order.paid.equals(PaymentStatus.Trying) && order.messageSent
.equals(MessageSent.NoneSent)) {
var requestId = messagingService.receiveRequest(1);
order.messageSent = MessageSent.PaymentTrying;
LOG.info("Order " + order.id + ": Payment Error message sent successfully,"
+ " request Id: " + requestId);
}
handlePaymentPossibleErrorMsgRetryOperation(order, l);
};
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
if (o.messageSent.equals(MessageSent.NoneSent) && order.paid
.equals(PaymentStatus.Trying)
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.Messaging, 1);
updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in sending Payment Error message, "
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
handlePaymentPossibleErrorMsgErrorIssue(order, o);
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
@ -468,6 +462,39 @@ public class Commander {
t.start();
}
private void handlePaymentPossibleErrorMsgErrorIssue(Order order, Order o) {
if (o.messageSent.equals(MessageSent.NONE_SENT) && order.paid
.equals(PaymentStatus.TRYING)
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 1);
updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in sending Payment Error message, "
+ "trying to queue task and add to employee handle..");
employeeHandleIssue(o);
}
}
private void handlePaymentPossibleErrorMsgRetryOperation(Order order, List<Exception> l)
throws Exception {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug("Order " + order.id + ": Error in connecting to messaging service "
+ "(Payment Error msg), trying again..");
} else {
LOG.debug("Order " + order.id + ": Error in creating Payment Error"
+ " messaging request..");
}
throw l.remove(0);
}
if (order.paid.equals(PaymentStatus.TRYING) && order.messageSent
.equals(MessageSent.NONE_SENT)) {
var requestId = messagingService.receiveRequest(1);
order.messageSent = MessageSent.PAYMENT_TRYING;
LOG.info("Order " + order.id + ": Payment Error message sent successfully,"
+ " request Id: " + requestId);
}
}
private void employeeHandleIssue(Order order) {
if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) {
LOG.trace("Order " + order.id + ": Employee handle time for order over, returning..");
@ -490,7 +517,7 @@ public class Commander {
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
if (!o.addedToEmployeeHandle && System
.currentTimeMillis() - order.createdTime < employeeTime) {
var qt = new QueueTask(order, TaskType.EmployeeDb, -1);
var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1);
updateQueue(qt);
LOG.warn("Order " + order.id + ": Error in adding to employee db,"
+ " trying to queue task..");
@ -520,21 +547,21 @@ public class Commander {
LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType()
+ " does not need to be done anymore (timeout), dequeue..");
} else {
if (qt.taskType.equals(TaskType.Payment)) {
if (!qt.order.paid.equals(PaymentStatus.Trying)) {
if (qt.taskType.equals(TaskType.PAYMENT)) {
if (!qt.order.paid.equals(PaymentStatus.TRYING)) {
tryDequeue();
LOG.trace("Order " + qt.order.id + ": This payment task already done, dequeueing..");
} else {
sendPaymentRequest(qt.order);
LOG.debug("Order " + qt.order.id + ": Trying to connect to payment service..");
}
} else if (qt.taskType.equals(TaskType.Messaging)) {
if (qt.order.messageSent.equals(MessageSent.PaymentFail)
|| qt.order.messageSent.equals(MessageSent.PaymentSuccessful)) {
} else if (qt.taskType.equals(TaskType.MESSAGING)) {
if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
tryDequeue();
LOG.trace("Order " + qt.order.id + ": This messaging task already done, dequeue..");
} else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NoneSent)
|| !qt.order.paid.equals(PaymentStatus.Trying))) {
} else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT)
|| !qt.order.paid.equals(PaymentStatus.TRYING))) {
tryDequeue();
LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done,"
+ " dequeue..");
@ -548,7 +575,7 @@ public class Commander {
sendSuccessMessage(qt.order);
LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service..");
}
} else if (qt.taskType.equals(TaskType.EmployeeDb)) {
} else if (qt.taskType.equals(TaskType.EMPLOYEE_DB)) {
if (qt.order.addedToEmployeeHandle) {
tryDequeue();
LOG.trace("Order " + qt.order.id + ": This employee handle task already done,"

View File

@ -33,11 +33,11 @@ import java.util.Random;
public class Order { //can store all transactions ids also
enum PaymentStatus {
NotDone, Trying, Done
NOT_DONE, TRYING, DONE
}
enum MessageSent {
NoneSent, PaymentFail, PaymentTrying, PaymentSuccessful
NONE_SENT, PAYMENT_FAIL, PAYMENT_TRYING, PAYMENT_SUCCESSFUL
}
final User user;
@ -65,8 +65,8 @@ public class Order { //can store all transactions ids also
}
this.id = id;
USED_IDS.put(this.id, true);
this.paid = PaymentStatus.Trying;
this.messageSent = MessageSent.NoneSent;
this.paid = PaymentStatus.TRYING;
this.messageSent = MessageSent.NONE_SENT;
this.addedToEmployeeHandle = false;
}

View File

@ -38,7 +38,7 @@ public class MessagingService extends Service {
private static final Logger LOGGER = LoggerFactory.getLogger(MessagingService.class);
enum MessageToSend {
PaymentFail, PaymentTrying, PaymentSuccessful
PAYMENT_FAIL, PAYMENT_TRYING, PAYMENT_SUCCESSFUL
}
class MessageRequest {
@ -63,11 +63,11 @@ public class MessagingService extends Service {
var id = generateId();
MessageToSend msg;
if (messageToSend == 0) {
msg = MessageToSend.PaymentFail;
msg = MessageToSend.PAYMENT_FAIL;
} else if (messageToSend == 1) {
msg = MessageToSend.PaymentTrying;
msg = MessageToSend.PAYMENT_TRYING;
} else { //messageToSend == 2
msg = MessageToSend.PaymentSuccessful;
msg = MessageToSend.PAYMENT_SUCCESSFUL;
}
var req = new MessageRequest(id, msg);
return updateDb(req);
@ -84,10 +84,10 @@ public class MessagingService extends Service {
}
String sendMessage(MessageToSend m) {
if (m.equals(MessageToSend.PaymentSuccessful)) {
if (m.equals(MessageToSend.PAYMENT_SUCCESSFUL)) {
return "Msg: Your order has been placed and paid for successfully!"
+ " Thank you for shopping with us!";
} else if (m.equals(MessageToSend.PaymentTrying)) {
} else if (m.equals(MessageToSend.PAYMENT_TRYING)) {
return "Msg: There was an error in your payment process,"
+ " we are working on it and will return back to you shortly."
+ " Meanwhile, your order has been placed and will be shipped.";

View File

@ -36,7 +36,7 @@ public class QueueTask {
*/
public enum TaskType {
Messaging, Payment, EmployeeDb
MESSAGING, PAYMENT, EMPLOYEE_DB
}
public Order order;
@ -68,7 +68,7 @@ public class QueueTask {
* @return String representing type of task
*/
public String getType() {
if (!this.taskType.equals(TaskType.Messaging)) {
if (!this.taskType.equals(TaskType.MESSAGING)) {
return this.taskType.toString();
} else {
if (this.messageType == 0) {

View File

@ -9,15 +9,17 @@ tags:
---
## Intent
Compose objects into tree structures to represent part-whole
hierarchies. Composite lets clients treat individual objects and compositions
of objects uniformly.
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients
treat individual objects and compositions of objects uniformly.
## Explanation
Real world example
> Every sentence is composed of words which are in turn composed of characters. Each of these objects is printable and they can have something printed before or after them like sentence always ends with full stop and word always has space before it
> Every sentence is composed of words which are in turn composed of characters. Each of these
> objects is printable and they can have something printed before or after them like sentence always
> ends with full stop and word always has space before it.
In plain words
@ -25,11 +27,16 @@ In plain words
Wikipedia says
> In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly.
> In software engineering, the composite pattern is a partitioning design pattern. The composite
> pattern describes that a group of objects is to be treated in the same way as a single instance of
> an object. The intent of a composite is to "compose" objects into tree structures to represent
> part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects
> and compositions uniformly.
**Programmatic Example**
Taking our sentence example from above. Here we have the base class and different printable types
Taking our sentence example from above. Here we have the base class `LetterComposite` and the
different printable types `Letter`, `Word` and `Sentence`.
```java
public abstract class LetterComposite {
@ -102,7 +109,7 @@ public class Sentence extends LetterComposite {
}
```
Then we have a messenger to carry messages
Then we have a messenger to carry messages:
```java
public class Messenger {
@ -143,7 +150,7 @@ public class Messenger {
}
```
And then it can be used as
And then it can be used as:
```java
var orcMessage = new Messenger().messageFromOrcs();
@ -153,13 +160,16 @@ elfMessage.print(); // Much wind pours from your mouth.
```
## Class diagram
![alt text](./etc/composite.urm.png "Composite class diagram")
## Applicability
Use the Composite pattern when
* you want to represent part-whole hierarchies of objects
* you want clients to be able to ignore the difference between compositions of objects and individual objects. Clients will treat all objects in the composite structure uniformly
* You want to represent part-whole hierarchies of objects.
* You want clients to be able to ignore the difference between compositions of objects and
individual objects. Clients will treat all objects in the composite structure uniformly.
## Real world examples

View File

@ -29,7 +29,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>composite</artifactId>
<dependencies>

View File

@ -23,15 +23,23 @@
package com.iluwatar.composite;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.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#main(String[])}
* throws an exception.
*/
@Test
public void test() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
Assertions.assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -9,16 +9,19 @@ tags:
---
## Intent
The purpose of the Converter Pattern is to provide a generic, common way of bidirectional
The purpose of the Converter pattern is to provide a generic, common way of bidirectional
conversion between corresponding types, allowing a clean implementation in which the types do not
need to be aware of each other. Moreover, the Converter Pattern introduces bidirectional collection
need to be aware of each other. Moreover, the Converter pattern introduces bidirectional collection
mapping, reducing a boilerplate code to minimum.
## Explanation
Real world example
> In real world applications it is often the case that database layer consists of entities that need to be mapped into DTOs for use on the business logic layer. Similar mapping is done for potentially huge amount of classes and we need a generic way to achieve this.
> In real world applications it is often the case that database layer consists of entities that need
> to be mapped into DTOs for use on the business logic layer. Similar mapping is done for
> potentially huge amount of classes and we need a generic way to achieve this.
In plain words
@ -26,7 +29,8 @@ In plain words
**Programmatic Example**
We need a generic solution for the mapping problem. To achieve this, let's introduce a generic converter.
We need a generic solution for the mapping problem. To achieve this, let's introduce a generic
converter.
```java
public class Converter<T, U> {
@ -77,7 +81,7 @@ public class UserConverter extends Converter<UserDto, User> {
}
```
Now mapping between User and UserDto becomes trivial.
Now mapping between `User` and `UserDto` becomes trivial.
```java
var userConverter = new UserConverter();
@ -86,14 +90,18 @@ var user = userConverter.convertFromDto(dtoUser);
```
## Class diagram
![alt text](./etc/converter.png "Converter Pattern")
## Applicability
Use the Converter Pattern in the following situations:
* When you have types that logically correspond which other and you need to convert entities between them
* When you want to provide different ways of types conversions depending on a context
* Whenever you introduce a DTO (Data transfer object), you will probably need to convert it into the domain equivalence
* When you have types that logically correspond with each other and you need to convert entities
between them.
* When you want to provide different ways of types conversions depending on the context.
* Whenever you introduce a DTO (Data transfer object), you will probably need to convert it into the
domain equivalence.
## Credits

View File

@ -20,7 +20,7 @@
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>converter</artifactId>
<modelVersion>4.0.0</modelVersion>

View File

@ -25,14 +25,24 @@ package com.iluwatar.converter;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* App running 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#main(String[])}
* throws an exception.
*/
@Test
public void testMain() {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -30,7 +30,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>cqrs</artifactId>
<dependencies>

View File

@ -9,13 +9,15 @@ tags:
---
## Intent
Object provides an abstract interface to some type of database or other persistence mechanism.
## Explanation
Real world example
> There's a set of customers that need to be persisted to database. Additionally we need the whole set of CRUD (create/read/update/delete) operations so we can operate on customers easily.
> There's a set of customers that need to be persisted to database. Additionally we need the whole
> set of CRUD (create/read/update/delete) operations so we can operate on customers easily.
In plain words
@ -23,11 +25,12 @@ In plain words
Wikipedia says
> In computer software, a data access object (DAO) is a pattern that provides an abstract interface to some type of database or other persistence mechanism.
> In computer software, a data access object (DAO) is a pattern that provides an abstract interface
> to some type of database or other persistence mechanism.
**Programmatic Example**
Walking through our customers example, here's the basic Customer entity.
Walking through our customers example, here's the basic `Customer` entity.
```java
public class Customer {
@ -41,60 +44,13 @@ public class Customer {
this.firstName = firstName;
this.lastName = lastName;
}
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(final String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(final String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return "Customer{" + "id=" + getId() + ", firstName='" + getFirstName() + '\'' + ", lastName='"
+ getLastName() + '\'' + '}';
}
@Override
public boolean equals(final Object that) {
var isEqual = false;
if (this == that) {
isEqual = true;
} else if (that != null && getClass() == that.getClass()) {
final var customer = (Customer) that;
if (getId() == customer.getId()) {
isEqual = true;
}
}
return isEqual;
}
@Override
public int hashCode() {
return getId();
}
// getters and setters ->
...
}
```
Here's the DAO interface and two different implementations for it. InMemoryCustomerDao keeps a simple map of customers
in memory while DBCustomerDao is the real RDBMS implementation.
Here's the `CustomerDao` interface and two different implementations for it. `InMemoryCustomerDao`
keeps a simple map of customers in memory while `DBCustomerDao` is the real RDBMS implementation.
```java
public interface CustomerDao {
@ -114,35 +70,8 @@ public class InMemoryCustomerDao implements CustomerDao {
private final Map<Integer, Customer> idToCustomer = new HashMap<>();
@Override
public Stream<Customer> getAll() {
return idToCustomer.values().stream();
}
@Override
public Optional<Customer> getById(final int id) {
return Optional.ofNullable(idToCustomer.get(id));
}
@Override
public boolean add(final Customer customer) {
if (getById(customer.getId()).isPresent()) {
return false;
}
idToCustomer.put(customer.getId(), customer);
return true;
}
@Override
public boolean update(final Customer customer) {
return idToCustomer.replace(customer.getId(), customer) != null;
}
@Override
public boolean delete(final Customer customer) {
return idToCustomer.remove(customer.getId()) != null;
}
// implement the interface using the map
...
}
public class DbCustomerDao implements CustomerDao {
@ -155,121 +84,8 @@ public class DbCustomerDao implements CustomerDao {
this.dataSource = dataSource;
}
@Override
public Stream<Customer> getAll() throws Exception {
try {
var connection = getConnection();
var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS");
var resultSet = statement.executeQuery();
return StreamSupport.stream(new Spliterators.AbstractSpliterator<Customer>(Long.MAX_VALUE,
Spliterator.ORDERED) {
@Override
public boolean tryAdvance(Consumer<? super Customer> action) {
try {
if (!resultSet.next()) {
return false;
}
action.accept(createCustomer(resultSet));
return true;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}, false).onClose(() -> mutedClose(connection, statement, resultSet));
} catch (SQLException e) {
throw new CustomException(e.getMessage(), e);
}
}
private Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
private void mutedClose(Connection connection, PreparedStatement statement, ResultSet resultSet) {
try {
resultSet.close();
statement.close();
connection.close();
} catch (SQLException e) {
LOGGER.info("Exception thrown " + e.getMessage());
}
}
private Customer createCustomer(ResultSet resultSet) throws SQLException {
return new Customer(resultSet.getInt("ID"),
resultSet.getString("FNAME"),
resultSet.getString("LNAME"));
}
@Override
public Optional<Customer> getById(int id) throws Exception {
ResultSet resultSet = null;
try (var connection = getConnection();
var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) {
statement.setInt(1, id);
resultSet = statement.executeQuery();
if (resultSet.next()) {
return Optional.of(createCustomer(resultSet));
} else {
return Optional.empty();
}
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
} finally {
if (resultSet != null) {
resultSet.close();
}
}
}
@Override
public boolean add(Customer customer) throws Exception {
if (getById(customer.getId()).isPresent()) {
return false;
}
try (var connection = getConnection();
var statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) {
statement.setInt(1, customer.getId());
statement.setString(2, customer.getFirstName());
statement.setString(3, customer.getLastName());
statement.execute();
return true;
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
}
}
@Override
public boolean update(Customer customer) throws Exception {
try (var connection = getConnection();
var statement =
connection
.prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) {
statement.setString(1, customer.getFirstName());
statement.setString(2, customer.getLastName());
statement.setInt(3, customer.getId());
return statement.executeUpdate() > 0;
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
}
}
@Override
public boolean delete(Customer customer) throws Exception {
try (var connection = getConnection();
var statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) {
statement.setInt(1, customer.getId());
return statement.executeUpdate() > 0;
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
}
}
}
// implement the interface using the data source
...
```
Finally here's how we use our DAO to manage customers.
@ -301,15 +117,45 @@ Finally here's how we use our DAO to manage customers.
deleteSchema(dataSource);
```
The program output:
```java
customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'}
customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}]
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@7cef4e59
customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'}
Customer{id=4, firstName='Daniel', lastName='Danielson'}
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@2db0f6b2
customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'}
customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}]
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@12c8a2c0
customerDao.getAllCustomers():
Customer{id=1, firstName='Adam', lastName='Adamson'}
Customer{id=2, firstName='Bob', lastName='Bobson'}
Customer{id=3, firstName='Carl', lastName='Carlson'}
Customer{id=4, firstName='Daniel', lastName='Danielson'}
customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@6ec8211c
```
## Class diagram
![alt text](./etc/dao.png "Data Access Object")
## Applicability
Use the Data Access Object in any of the following situations
* when you want to consolidate how the data layer is accessed
* when you want to avoid writing multiple data retrieval/persistence layers
Use the Data Access Object in any of the following situations:
* When you want to consolidate how the data layer is accessed.
* When you want to avoid writing multiple data retrieval/persistence layers.
## Credits

View File

@ -30,7 +30,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>dao</artifactId>

View File

@ -25,12 +25,22 @@ package com.iluwatar.dao;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Tests that DAO 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#main(String[])}
* throws an exception.
*/
@Test
public void test() throws Exception {
App.main(new String[]{});
void shouldExecuteDaoWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -33,7 +33,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>data-bus</artifactId>
<dependencies>

View File

@ -30,7 +30,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>data-locality</artifactId>

View File

@ -43,6 +43,6 @@ public class AiComponent implements Component {
@Override
public void render() {
// Do Nothing.
}
}

View File

@ -40,7 +40,7 @@ public class AiComponentManager {
private final int numEntities;
private static final Component[] AI_COMPONENTS = new AiComponent[MAX_ENTITIES];
private final Component[] aiComponents = new AiComponent[MAX_ENTITIES];
public AiComponentManager(int numEntities) {
this.numEntities = numEntities;
@ -51,7 +51,7 @@ public class AiComponentManager {
*/
public void start() {
LOGGER.info("Start AI Game Component");
IntStream.range(0, numEntities).forEach(i -> AI_COMPONENTS[i] = new AiComponent());
IntStream.range(0, numEntities).forEach(i -> aiComponents[i] = new AiComponent());
}
/**
@ -60,7 +60,7 @@ public class AiComponentManager {
public void update() {
LOGGER.info("Update AI Game Component");
IntStream.range(0, numEntities)
.filter(i -> AI_COMPONENTS.length > i && AI_COMPONENTS[i] != null)
.forEach(i -> AI_COMPONENTS[i].update());
.filter(i -> aiComponents.length > i && aiComponents[i] != null)
.forEach(i -> aiComponents[i].update());
}
}

View File

@ -40,7 +40,7 @@ public class PhysicsComponentManager {
private final int numEntities;
private static final Component[] PHYSICS_COMPONENTS = new PhysicsComponent[MAX_ENTITIES];
private final Component[] physicsComponents = new PhysicsComponent[MAX_ENTITIES];
public PhysicsComponentManager(int numEntities) {
this.numEntities = numEntities;
@ -51,7 +51,7 @@ public class PhysicsComponentManager {
*/
public void start() {
LOGGER.info("Start Physics Game Component ");
IntStream.range(0, numEntities).forEach(i -> PHYSICS_COMPONENTS[i] = new PhysicsComponent());
IntStream.range(0, numEntities).forEach(i -> physicsComponents[i] = new PhysicsComponent());
}
@ -62,7 +62,7 @@ public class PhysicsComponentManager {
LOGGER.info("Update Physics Game Component ");
// Process physics.
IntStream.range(0, numEntities)
.filter(i -> PHYSICS_COMPONENTS.length > i && PHYSICS_COMPONENTS[i] != null)
.forEach(i -> PHYSICS_COMPONENTS[i].update());
.filter(i -> physicsComponents.length > i && physicsComponents[i] != null)
.forEach(i -> physicsComponents[i].update());
}
}

View File

@ -40,7 +40,7 @@ public class RenderComponentManager {
private final int numEntities;
private static final Component[] RENDER_COMPONENTS = new RenderComponent[MAX_ENTITIES];
private final Component[] renderComponents = new RenderComponent[MAX_ENTITIES];
public RenderComponentManager(int numEntities) {
this.numEntities = numEntities;
@ -51,7 +51,7 @@ public class RenderComponentManager {
*/
public void start() {
LOGGER.info("Start Render Game Component ");
IntStream.range(0, numEntities).forEach(i -> RENDER_COMPONENTS[i] = new RenderComponent());
IntStream.range(0, numEntities).forEach(i -> renderComponents[i] = new RenderComponent());
}
@ -62,7 +62,7 @@ public class RenderComponentManager {
LOGGER.info("Update Render Game Component ");
// Process Render.
IntStream.range(0, numEntities)
.filter(i -> RENDER_COMPONENTS.length > i && RENDER_COMPONENTS[i] != null)
.forEach(i -> RENDER_COMPONENTS[i].render());
.filter(i -> renderComponents.length > i && renderComponents[i] != null)
.forEach(i -> renderComponents[i].render());
}
}

View File

@ -26,16 +26,22 @@ package com.iluwatar.data.locality;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Test Game Application
*/
class ApplicationTest {
/**
* Test run
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link Application#main(String[])}
* throws an exception.
*/
@Test
void main() {
Application.main(new String[] {});
void shouldExecuteGameApplicationWithoutException() {
assertDoesNotThrow(() -> Application.main(new String[] {}));
}
}

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>data-mapper</artifactId>
<dependencies>

View File

@ -24,14 +24,25 @@
package com.iluwatar.datamapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Tests that Data-Mapper example runs without errors.
*/
public final class AppTest {
final 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#main(String[])}
* throws an exception.
*/
@Test
public void test() {
App.main();
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow((Executable) App::main);
}
}

View File

@ -9,13 +9,16 @@ tags:
---
## Intent
Pass data with multiple attributes in one shot from client to server, to avoid multiple calls to remote server.
Pass data with multiple attributes in one shot from client to server, to avoid multiple calls to
remote server.
## Explanation
Real world example
> We need to fetch information about customers from remote database. Instead of querying the attributes one at a time, we use DTOs to transfer all the relevant attributes in a single shot.
> We need to fetch information about customers from remote database. Instead of querying the
> attributes one at a time, we use DTOs to transfer all the relevant attributes in a single shot.
In plain words
@ -23,16 +26,17 @@ In plain words
Wikipedia says
> In the field of programming a data transfer object (DTO) is an object that carries data between processes. The
motivation for its use is that communication between processes is usually done resorting to remote interfaces
(e.g., web services), where each call is an expensive operation. Because the majority of the cost of each call is
related to the round-trip time between the client and the server, one way of reducing the number of calls is to use an
object (the DTO) that aggregates the data that would have been transferred by the several calls, but that is served by
one call only.
> In the field of programming a data transfer object (DTO) is an object that carries data between
> processes. The motivation for its use is that communication between processes is usually done
> resorting to remote interfaces (e.g. web services), where each call is an expensive operation.
> Because the majority of the cost of each call is related to the round-trip time between the client
> and the server, one way of reducing the number of calls is to use an object (the DTO) that
> aggregates the data that would have been transferred by the several calls, but that is served by
> one call only.
**Programmatic Example**
Let's first introduce our simple customer DTO class.
Let's first introduce our simple `CustomerDTO` class.
```java
public class CustomerDto {
@ -60,7 +64,7 @@ public class CustomerDto {
}
```
Customer resource class acts as the server for customer information.
`CustomerResource` class acts as the server for customer information.
```java
public class CustomerResource {
@ -94,10 +98,12 @@ Now fetching customer information is easy since we have the DTOs.
```
## Class diagram
![alt text](./etc/data-transfer-object.urm.png "data-transfer-object")
## Applicability
Use the Data Transfer Object pattern when
Use the Data Transfer Object pattern when:
* The client is asking for multiple information. And the information is related.
* When you want to boost the performance to get resources.

View File

@ -28,7 +28,7 @@
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.23.0-SNAPSHOT</version>
<version>1.24.0-SNAPSHOT</version>
</parent>
<artifactId>data-transfer-object</artifactId>
<dependencies>

View File

@ -25,9 +25,19 @@ package com.iluwatar.datatransfer;
import org.junit.jupiter.api.Test;
public class AppTest {
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
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#main(String[])}
* throws an exception.
*/
@Test
public void test() throws Exception {
App.main(new String[]{});
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

Some files were not shown because too many files have changed in this diff Show More