Compare commits
106 Commits
all-contri
...
all-contri
Author | SHA1 | Date | |
---|---|---|---|
|
3e77480f67 | ||
|
a0c75438eb | ||
|
2aa4c963eb | ||
|
8632962362 | ||
|
5bc61c8c28 | ||
|
a94615ac54 | ||
|
14c4710435 | ||
|
428cbc1027 | ||
|
a118a995ec | ||
|
759c99d078 | ||
|
e9f73bcf0b | ||
|
29ceac2fb0 | ||
|
7255c2c5e7 | ||
|
68308dc550 | ||
|
1e10951c23 | ||
|
f3c876ed2e | ||
|
ecd1a5d07f | ||
|
8b15c24753 | ||
|
f5a6161044 | ||
|
76eefa80b5 | ||
|
c282ab80fd | ||
|
1edfb44642 | ||
|
e185c497ac | ||
|
d5a054c1f8 | ||
|
ced7a9deb0 | ||
|
90c6cf94d5 | ||
|
996bd937fb | ||
|
7931471b99 | ||
|
b8ecbaa451 | ||
|
fb4df48cb3 | ||
|
96fadf3bd7 | ||
|
dd599595cc | ||
|
fcd7785f0d | ||
|
2432d120b4 | ||
|
4b18e223cd | ||
|
13c6de036f | ||
|
6f979d0cb2 | ||
|
f084f8bf41 | ||
|
6c95868b8d | ||
|
4c7f1b7822 | ||
|
0c44b53909 | ||
|
9ead3adf73 | ||
|
97e3a3debc | ||
|
43ed09015d | ||
|
7eee546208 | ||
|
dc31960710 | ||
|
7c0fdad5a2 | ||
|
5a8933ea17 | ||
|
d02233f0b7 | ||
|
d42bcab9fc | ||
|
77b2ff2150 | ||
|
cff072d1ca | ||
|
80ba0407db | ||
|
6a09b909f2 | ||
|
53ccc0e7e6 | ||
|
a7a8e23b01 | ||
|
2783251d00 | ||
|
ac432968ae | ||
|
26b5364cbd | ||
|
f006782805 | ||
|
38cc490e3f | ||
|
911cfd64af | ||
|
2332520d67 | ||
|
af1b611136 | ||
|
4ff196ce35 | ||
|
242ae6a412 | ||
|
b689fe0a26 | ||
|
7aea765dd1 | ||
|
ea49cbfe94 | ||
|
1f4a412e70 | ||
|
36d0a3718c | ||
|
195a735814 | ||
|
633d45aa67 | ||
|
adc267e48e | ||
|
cc8d209c22 | ||
|
578b3528d5 | ||
|
5674f3c52e | ||
|
4ad2bfefad | ||
|
e5d219609d | ||
|
21ca47a22e | ||
|
ebd38bcfaa | ||
|
a3753807ae | ||
|
64266d63fd | ||
|
4f62070eb2 | ||
|
b29bd66369 | ||
|
ad435dd2fd | ||
|
a125879d15 | ||
|
dac8e659ce | ||
|
a1515b3b52 | ||
|
d2e070d21d | ||
|
82eb41b641 | ||
|
24e8fa1bad | ||
|
9017975276 | ||
|
87093cf221 | ||
|
6cb5b4a683 | ||
|
e6ddff5f25 | ||
|
b5fddd469a | ||
|
9cbc509c3a | ||
|
9c648cbdb8 | ||
|
e6cca86e25 | ||
|
c204b242df | ||
|
2e98dcf217 | ||
|
9d44eea64f | ||
|
93aa1046aa | ||
|
46d4155328 | ||
|
a968dce586 |
@@ -315,7 +315,8 @@
|
|||||||
"avatar_url": "https://avatars1.githubusercontent.com/u/6295975?v=4",
|
"avatar_url": "https://avatars1.githubusercontent.com/u/6295975?v=4",
|
||||||
"profile": "https://github.com/Anurag870",
|
"profile": "https://github.com/Anurag870",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code",
|
||||||
|
"doc"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -946,7 +947,8 @@
|
|||||||
"avatar_url": "https://avatars3.githubusercontent.com/u/23739158?v=4",
|
"avatar_url": "https://avatars3.githubusercontent.com/u/23739158?v=4",
|
||||||
"profile": "https://github.com/grzesiekkedzior",
|
"profile": "https://github.com/grzesiekkedzior",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code",
|
||||||
|
"review"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1213,6 +1215,132 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ChFlick",
|
||||||
|
"name": "Christoph Flick",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/4465376?v=4",
|
||||||
|
"profile": "http://christophflick.de",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Ascenio",
|
||||||
|
"name": "Ascênio",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/7662016?v=4",
|
||||||
|
"profile": "https://github.com/Ascenio",
|
||||||
|
"contributions": [
|
||||||
|
"review"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "dsibilio",
|
||||||
|
"name": "Domenico Sibilio",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/24280982?v=4",
|
||||||
|
"profile": "https://www.linkedin.com/in/domenico-sibilio/",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "akashchandwani",
|
||||||
|
"name": "Akash Chandwani",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/3483277?v=4",
|
||||||
|
"profile": "https://github.com/akashchandwani",
|
||||||
|
"contributions": [
|
||||||
|
"review"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "manannikov",
|
||||||
|
"name": "Pavlo Manannikov",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/7019769?v=4",
|
||||||
|
"profile": "http://www.linkedin.com/in/manannikov",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "eimanip",
|
||||||
|
"name": "Eiman",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/20307301?v=4",
|
||||||
|
"profile": "https://github.com/eimanip",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "OrangePants-R",
|
||||||
|
"name": "Rocky",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/42976136?v=4",
|
||||||
|
"profile": "https://github.com/OrangePants-R",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ibrahimAlii",
|
||||||
|
"name": "Ibrahim ali abdelghany",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/21141301?v=4",
|
||||||
|
"profile": "https://ibrahimalii.github.io/",
|
||||||
|
"contributions": [
|
||||||
|
"review"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "gkulkarni2020",
|
||||||
|
"name": "Girish Kulkarni",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/5161548?v=4",
|
||||||
|
"profile": "https://github.com/gkulkarni2020",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "omk13",
|
||||||
|
"name": "Omar Karazoun",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/59054172?v=4",
|
||||||
|
"profile": "https://github.com/omk13",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "jeff303",
|
||||||
|
"name": "Jeff Evans",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/3521562?v=4",
|
||||||
|
"profile": "https://github.com/jeff303",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "viveksb007",
|
||||||
|
"name": "Vivek Singh",
|
||||||
|
"avatar_url": "https://avatars1.githubusercontent.com/u/12713808?v=4",
|
||||||
|
"profile": "https://viveksb007.github.io",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "siavashsoleymani",
|
||||||
|
"name": "siavash",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/18074419?v=4",
|
||||||
|
"profile": "https://github.com/siavashsoleymani",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "ruchpeanuts",
|
||||||
|
"name": "ruchpeanuts",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/29301900?v=4",
|
||||||
|
"profile": "https://github.com/ruchpeanuts",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 4,
|
"contributorsPerLine": 4,
|
||||||
|
20
.github/workflows/maven-ci.yml
vendored
@@ -30,29 +30,45 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
# Disabling shallow clone for improving relevancy of SonarQube reporting
|
# Disabling shallow clone for improving relevancy of SonarQube reporting
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 11
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 11
|
||||||
- uses: actions/cache@v2
|
|
||||||
|
- name: Cache SonarCloud packages
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.sonar/cache
|
||||||
|
key: ${{ runner.os }}-sonar
|
||||||
|
restore-keys: ${{ runner.os }}-sonar
|
||||||
|
|
||||||
|
- name: Cache Maven dependencies
|
||||||
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.m2/repository
|
path: ~/.m2/repository
|
||||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-maven-
|
${{ runner.os }}-maven-
|
||||||
|
|
||||||
# Some tests need screen access
|
# Some tests need screen access
|
||||||
- name: Install xvfb
|
- name: Install xvfb
|
||||||
run: sudo apt-get install -y xvfb
|
run: sudo apt-get install -y xvfb
|
||||||
|
|
||||||
# The SonarQube analysis is only for the master branch of the main repository.
|
# 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
|
# 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
|
# See https://jira.sonarsource.com/browse/MMF-1371
|
||||||
|
11
.github/workflows/maven-pr-builder.yml
vendored
@@ -29,6 +29,7 @@ name: Java PR Builder
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
types: [ opened, reopened, synchronize, labeled, unlabeled ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -36,20 +37,26 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 11
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 11
|
||||||
- uses: actions/cache@v2
|
|
||||||
|
- name: Cache Maven Dependecies
|
||||||
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.m2/repository
|
path: ~/.m2/repository
|
||||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-maven-
|
${{ runner.os }}-maven-
|
||||||
|
|
||||||
# Some tests need screen access
|
# Some tests need screen access
|
||||||
- name: Install xvfb
|
- name: Install xvfb
|
||||||
run: sudo apt-get install -y 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.
|
# 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.
|
# SonarQube scan does not work for forked repositories.
|
||||||
# See https://jira.sonarsource.com/browse/MMF-1371
|
# See https://jira.sonarsource.com/browse/MMF-1371
|
||||||
|
28
README.md
@@ -10,7 +10,7 @@
|
|||||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
[](#contributors-)
|
[](#contributors-)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
@@ -31,7 +31,7 @@ This site showcases Java Design Patterns. The solutions have been developed by
|
|||||||
experienced programmers and architects from the open source community. The
|
experienced programmers and architects from the open source community. The
|
||||||
patterns can be browsed by their high level descriptions or by looking at their
|
patterns can be browsed by their high level descriptions or by looking at their
|
||||||
source code. The source code examples are well commented and can be thought as
|
source code. The source code examples are well commented and can be thought as
|
||||||
programming tutorials how to implement a specific pattern. We use the most
|
programming tutorials on how to implement a specific pattern. We use the most
|
||||||
popular battle-proven open source Java technologies.
|
popular battle-proven open source Java technologies.
|
||||||
|
|
||||||
Before you dive into the material, you should be familiar with various
|
Before you dive into the material, you should be familiar with various
|
||||||
@@ -119,7 +119,7 @@ This project is licensed under the terms of the MIT license.
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://amarnath510.github.io/portfolio"><img src="https://avatars0.githubusercontent.com/u/4599623?v=4" width="100px;" alt=""/><br /><sub><b>Amarnath Chandana</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Amarnath510" title="Code">💻</a></td>
|
<td align="center"><a href="https://amarnath510.github.io/portfolio"><img src="https://avatars0.githubusercontent.com/u/4599623?v=4" width="100px;" alt=""/><br /><sub><b>Amarnath Chandana</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Amarnath510" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/Anurag870"><img src="https://avatars1.githubusercontent.com/u/6295975?v=4" width="100px;" alt=""/><br /><sub><b>Anurag870</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Anurag870" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/Anurag870"><img src="https://avatars1.githubusercontent.com/u/6295975?v=4" width="100px;" alt=""/><br /><sub><b>Anurag870</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Anurag870" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=Anurag870" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="http://theerroris.me"><img src="https://avatars0.githubusercontent.com/u/1685953?v=4" width="100px;" alt=""/><br /><sub><b>Wes Gilleland</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Deathnerd" title="Code">💻</a></td>
|
<td align="center"><a href="http://theerroris.me"><img src="https://avatars0.githubusercontent.com/u/1685953?v=4" width="100px;" alt=""/><br /><sub><b>Wes Gilleland</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Deathnerd" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/Harshrajsinh"><img src="https://avatars2.githubusercontent.com/u/22811531?v=4" width="100px;" alt=""/><br /><sub><b>Harshraj Thakor</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Harshrajsinh" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/Harshrajsinh"><img src="https://avatars2.githubusercontent.com/u/22811531?v=4" width="100px;" alt=""/><br /><sub><b>Harshraj Thakor</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Harshrajsinh" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -223,7 +223,7 @@ This project is licensed under the terms of the MIT license.
|
|||||||
<td align="center"><a href="http://vk.com/yuri.orlov"><img src="https://avatars0.githubusercontent.com/u/1595733?v=4" width="100px;" alt=""/><br /><sub><b>Yuri Orlov</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yorlov" title="Code">💻</a></td>
|
<td align="center"><a href="http://vk.com/yuri.orlov"><img src="https://avatars0.githubusercontent.com/u/1595733?v=4" width="100px;" alt=""/><br /><sub><b>Yuri Orlov</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yorlov" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://www.linkedin.com/in/varunu28/"><img src="https://avatars0.githubusercontent.com/u/7676016?v=4" width="100px;" alt=""/><br /><sub><b>Varun Upadhyay</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=varunu28" title="Code">💻</a></td>
|
<td align="center"><a href="https://www.linkedin.com/in/varunu28/"><img src="https://avatars0.githubusercontent.com/u/7676016?v=4" width="100px;" alt=""/><br /><sub><b>Varun Upadhyay</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=varunu28" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/PalAditya"><img src="https://avatars2.githubusercontent.com/u/25523604?v=4" width="100px;" alt=""/><br /><sub><b>Aditya Pal</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=PalAditya" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/PalAditya"><img src="https://avatars2.githubusercontent.com/u/25523604?v=4" width="100px;" alt=""/><br /><sub><b>Aditya Pal</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=PalAditya" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/grzesiekkedzior"><img src="https://avatars3.githubusercontent.com/u/23739158?v=4" width="100px;" alt=""/><br /><sub><b>grzesiekkedzior</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=grzesiekkedzior" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/grzesiekkedzior"><img src="https://avatars3.githubusercontent.com/u/23739158?v=4" width="100px;" alt=""/><br /><sub><b>grzesiekkedzior</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=grzesiekkedzior" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Agrzesiekkedzior" title="Reviewed Pull Requests">👀</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/sivasubramanim"><img src="https://avatars2.githubusercontent.com/u/51107434?v=4" width="100px;" alt=""/><br /><sub><b>Sivasubramani M</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=sivasubramanim" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/sivasubramanim"><img src="https://avatars2.githubusercontent.com/u/51107434?v=4" width="100px;" alt=""/><br /><sub><b>Sivasubramani M</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=sivasubramanim" title="Code">💻</a></td>
|
||||||
@@ -269,6 +269,26 @@ This project is licensed under the terms of the MIT license.
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/swarajsaaj"><img src="https://avatars2.githubusercontent.com/u/6285049?v=4" width="100px;" alt=""/><br /><sub><b>Swaraj</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=swarajsaaj" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/swarajsaaj"><img src="https://avatars2.githubusercontent.com/u/6285049?v=4" width="100px;" alt=""/><br /><sub><b>Swaraj</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=swarajsaaj" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="http://christophflick.de"><img src="https://avatars0.githubusercontent.com/u/4465376?v=4" width="100px;" alt=""/><br /><sub><b>Christoph Flick</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ChFlick" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/Ascenio"><img src="https://avatars1.githubusercontent.com/u/7662016?v=4" width="100px;" alt=""/><br /><sub><b>Ascênio</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AAscenio" title="Reviewed Pull Requests">👀</a></td>
|
||||||
|
<td align="center"><a href="https://www.linkedin.com/in/domenico-sibilio/"><img src="https://avatars2.githubusercontent.com/u/24280982?v=4" width="100px;" alt=""/><br /><sub><b>Domenico Sibilio</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=dsibilio" title="Documentation">📖</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/akashchandwani"><img src="https://avatars2.githubusercontent.com/u/3483277?v=4" width="100px;" alt=""/><br /><sub><b>Akash Chandwani</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Aakashchandwani" title="Reviewed Pull Requests">👀</a></td>
|
||||||
|
<td align="center"><a href="http://www.linkedin.com/in/manannikov"><img src="https://avatars2.githubusercontent.com/u/7019769?v=4" width="100px;" alt=""/><br /><sub><b>Pavlo Manannikov</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=manannikov" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/eimanip"><img src="https://avatars0.githubusercontent.com/u/20307301?v=4" width="100px;" alt=""/><br /><sub><b>Eiman</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=eimanip" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/OrangePants-R"><img src="https://avatars0.githubusercontent.com/u/42976136?v=4" width="100px;" alt=""/><br /><sub><b>Rocky</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=OrangePants-R" title="Documentation">📖</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://ibrahimalii.github.io/"><img src="https://avatars2.githubusercontent.com/u/21141301?v=4" width="100px;" alt=""/><br /><sub><b>Ibrahim ali abdelghany</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AibrahimAlii" title="Reviewed Pull Requests">👀</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/gkulkarni2020"><img src="https://avatars3.githubusercontent.com/u/5161548?v=4" width="100px;" alt=""/><br /><sub><b>Girish Kulkarni</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=gkulkarni2020" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/omk13"><img src="https://avatars0.githubusercontent.com/u/59054172?v=4" width="100px;" alt=""/><br /><sub><b>Omar Karazoun</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=omk13" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/jeff303"><img src="https://avatars0.githubusercontent.com/u/3521562?v=4" width="100px;" alt=""/><br /><sub><b>Jeff Evans</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=jeff303" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://viveksb007.github.io"><img src="https://avatars1.githubusercontent.com/u/12713808?v=4" width="100px;" alt=""/><br /><sub><b>Vivek Singh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=viveksb007" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/siavashsoleymani"><img src="https://avatars2.githubusercontent.com/u/18074419?v=4" width="100px;" alt=""/><br /><sub><b>siavash</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=siavashsoleymani" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/ruchpeanuts"><img src="https://avatars0.githubusercontent.com/u/29301900?v=4" width="100px;" alt=""/><br /><sub><b>ruchpeanuts</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ruchpeanuts" title="Documentation">📖</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@@ -1,3 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.abstractfactory;
|
package com.iluwatar.abstractfactory;
|
||||||
|
|
||||||
public class Kingdom {
|
public class Kingdom {
|
||||||
|
@@ -19,12 +19,15 @@ cannot bring the whole application down, and we can reconnect to the service as
|
|||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> Imagine a web application that has both local files/images and remote database entries to serve.
|
> Imagine a web application that has both local files/images and remote services that are used for
|
||||||
> The database might not be responding due to a variety of reasons, so if the application keeps
|
> fetching data. These remote services may be either healthy and responsive at times, or may become
|
||||||
> trying to read from the database using multiple threads/processes, soon all of them will hang
|
> slow and unresponsive at some point of time due to variety of reasons. So if one of the remote
|
||||||
> causing our entire web application will crash. We should be able to detect this situation and show
|
> services is slow or not responding successfully, our application will try to fetch response from
|
||||||
> the user an appropriate message so that he/she can explore other parts of the app unaffected by
|
> the remote service using multiple threads/processes, soon all of them will hang (also called
|
||||||
> the database failure.
|
> [thread starvation](https://en.wikipedia.org/wiki/Starvation_(computer_science))) causing our entire web application to 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 remote service failure. Meanwhile, the other services that are working
|
||||||
|
> normally, should keep functioning unaffected by this failure.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
@@ -52,40 +55,104 @@ In terms of code, the end user application is:
|
|||||||
|
|
||||||
```java
|
```java
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program entry point.
|
||||||
|
*
|
||||||
|
* @param args command line args
|
||||||
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
var obj = new MonitoringService();
|
|
||||||
var circuitBreaker = new CircuitBreaker(3000, 1, 2000 * 1000 * 1000);
|
|
||||||
var serverStartTime = System.nanoTime();
|
var serverStartTime = System.nanoTime();
|
||||||
while (true) {
|
|
||||||
LOGGER.info(obj.localResourceResponse());
|
var delayedService = new DelayedRemoteService(serverStartTime, 5);
|
||||||
LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime));
|
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
|
||||||
LOGGER.info(circuitBreaker.getState());
|
2000 * 1000 * 1000);
|
||||||
try {
|
|
||||||
Thread.sleep(5 * 1000);
|
var quickService = new QuickRemoteService();
|
||||||
} catch (InterruptedException e) {
|
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
|
||||||
LOGGER.error(e.getMessage());
|
2000 * 1000 * 1000);
|
||||||
}
|
|
||||||
|
//Create an object of monitoring service which makes both local and remote calls
|
||||||
|
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
|
||||||
|
quickServiceCircuitBreaker);
|
||||||
|
|
||||||
|
//Fetch response from local resource
|
||||||
|
LOGGER.info(monitoringService.localResourceResponse());
|
||||||
|
|
||||||
|
//Fetch response from delayed service 2 times, to meet the failure threshold
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
|
||||||
|
//Fetch current state of delayed service circuit breaker after crossing failure threshold limit
|
||||||
|
//which is OPEN now
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Meanwhile, the delayed service is down, fetch response from the healthy quick service
|
||||||
|
LOGGER.info(monitoringService.quickServiceResponse());
|
||||||
|
LOGGER.info(quickServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Wait for the delayed service to become responsive
|
||||||
|
try {
|
||||||
|
LOGGER.info("Waiting for delayed service to become responsive");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
//Check the state of delayed circuit breaker, should be HALF_OPEN
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Fetch response from delayed service, which should be healthy by now
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
//As successful response is fetched, it should be CLOSED again.
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The monitoring service:
|
The monitoring service:
|
||||||
|
|
||||||
``` java
|
```java
|
||||||
public class MonitoringService {
|
public class MonitoringService {
|
||||||
|
|
||||||
|
private final CircuitBreaker delayedService;
|
||||||
|
|
||||||
|
private final CircuitBreaker quickService;
|
||||||
|
|
||||||
|
public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) {
|
||||||
|
this.delayedService = delayedService;
|
||||||
|
this.quickService = quickService;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
|
||||||
public String localResourceResponse() {
|
public String localResourceResponse() {
|
||||||
return "Local Service is working";
|
return "Local Service is working";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String remoteResourceResponse(CircuitBreaker circuitBreaker, long serverStartTime) {
|
/**
|
||||||
|
* Fetch response from the delayed service (with some simulated startup time).
|
||||||
|
*
|
||||||
|
* @return response string
|
||||||
|
*/
|
||||||
|
public String delayedServiceResponse() {
|
||||||
try {
|
try {
|
||||||
return circuitBreaker.call("delayedService", serverStartTime);
|
return this.delayedService.attemptRequest();
|
||||||
} catch (Exception e) {
|
} catch (RemoteServiceException e) {
|
||||||
|
return e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches response from a healthy service without any failure.
|
||||||
|
*
|
||||||
|
* @return response string
|
||||||
|
*/
|
||||||
|
public String quickServiceResponse() {
|
||||||
|
try {
|
||||||
|
return this.quickService.attemptRequest();
|
||||||
|
} catch (RemoteServiceException e) {
|
||||||
return e.getMessage();
|
return e.getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,72 +162,127 @@ As it can be seen, it does the call to get local resources directly, but it wrap
|
|||||||
remote (costly) service in a circuit breaker object, which prevents faults as follows:
|
remote (costly) service in a circuit breaker object, which prevents faults as follows:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class CircuitBreaker {
|
public class DefaultCircuitBreaker implements CircuitBreaker {
|
||||||
|
|
||||||
private final long timeout;
|
private final long timeout;
|
||||||
private final long retryTimePeriod;
|
private final long retryTimePeriod;
|
||||||
|
private final RemoteService service;
|
||||||
long lastFailureTime;
|
long lastFailureTime;
|
||||||
|
private String lastFailureResponse;
|
||||||
int failureCount;
|
int failureCount;
|
||||||
private final int failureThreshold;
|
private final int failureThreshold;
|
||||||
private State state;
|
private State state;
|
||||||
private final long futureTime = 1000 * 1000 * 1000 * 1000;
|
private final long futureTime = 1000 * 1000 * 1000 * 1000;
|
||||||
|
|
||||||
CircuitBreaker(long timeout, int failureThreshold, long retryTimePeriod) {
|
/**
|
||||||
|
* Constructor to create an instance of Circuit Breaker.
|
||||||
|
*
|
||||||
|
* @param timeout Timeout for the API request. Not necessary for this simple example
|
||||||
|
* @param failureThreshold Number of failures we receive from the depended service before changing
|
||||||
|
* state to 'OPEN'
|
||||||
|
* @param retryTimePeriod Time period after which a new request is made to remote service for
|
||||||
|
* status check.
|
||||||
|
*/
|
||||||
|
DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold,
|
||||||
|
long retryTimePeriod) {
|
||||||
|
this.service = serviceToCall;
|
||||||
|
// We start in a closed state hoping that everything is fine
|
||||||
this.state = State.CLOSED;
|
this.state = State.CLOSED;
|
||||||
this.failureThreshold = failureThreshold;
|
this.failureThreshold = failureThreshold;
|
||||||
|
// Timeout for the API request.
|
||||||
|
// Used to break the calls made to remote resource if it exceeds the limit
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
this.retryTimePeriod = retryTimePeriod;
|
this.retryTimePeriod = retryTimePeriod;
|
||||||
|
//An absurd amount of time in future which basically indicates the last failure never happened
|
||||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||||
this.failureCount = 0;
|
this.failureCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
// Reset everything to defaults
|
||||||
|
@Override
|
||||||
|
public void recordSuccess() {
|
||||||
this.failureCount = 0;
|
this.failureCount = 0;
|
||||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||||
this.state = State.CLOSED;
|
this.state = State.CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recordFailure() {
|
@Override
|
||||||
|
public void recordFailure(String response) {
|
||||||
failureCount = failureCount + 1;
|
failureCount = failureCount + 1;
|
||||||
this.lastFailureTime = System.nanoTime();
|
this.lastFailureTime = System.nanoTime();
|
||||||
|
// Cache the failure response for returning on open state
|
||||||
|
this.lastFailureResponse = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setState() {
|
// Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
|
||||||
if (failureCount > failureThreshold) {
|
protected void evaluateState() {
|
||||||
|
if (failureCount >= failureThreshold) { //Then something is wrong with remote service
|
||||||
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
|
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
|
||||||
|
//We have waited long enough and should try checking if service is up
|
||||||
state = State.HALF_OPEN;
|
state = State.HALF_OPEN;
|
||||||
} else {
|
} else {
|
||||||
|
//Service would still probably be down
|
||||||
state = State.OPEN;
|
state = State.OPEN;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//Everything is working fine
|
||||||
state = State.CLOSED;
|
state = State.CLOSED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getState() {
|
public String getState() {
|
||||||
|
evaluateState();
|
||||||
return state.name();
|
return state.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStateForBypass(State state) {
|
/**
|
||||||
|
* Break the circuit beforehand if it is known service is down Or connect the circuit manually if
|
||||||
|
* service comes online before expected.
|
||||||
|
*
|
||||||
|
* @param state State at which circuit is in
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setState(State state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
switch (state) {
|
||||||
|
case OPEN:
|
||||||
|
this.failureCount = failureThreshold;
|
||||||
|
this.lastFailureTime = System.nanoTime();
|
||||||
|
break;
|
||||||
|
case HALF_OPEN:
|
||||||
|
this.failureCount = failureThreshold;
|
||||||
|
this.lastFailureTime = System.nanoTime() - retryTimePeriod;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.failureCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String call(String serviceToCall, long serverStartTime) throws Exception {
|
/**
|
||||||
setState();
|
* Executes service call.
|
||||||
|
*
|
||||||
|
* @return Value from the remote resource, stale response or a custom exception
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String attemptRequest() throws RemoteServiceException {
|
||||||
|
evaluateState();
|
||||||
if (state == State.OPEN) {
|
if (state == State.OPEN) {
|
||||||
return "This is stale response from API";
|
// return cached response if the circuit is in OPEN state
|
||||||
|
return this.lastFailureResponse;
|
||||||
} else {
|
} else {
|
||||||
if (serviceToCall.equals("delayedService")) {
|
// Make the API request if the circuit is not OPEN
|
||||||
var delayedService = new DelayedService(20);
|
try {
|
||||||
var response = delayedService.response(serverStartTime);
|
//In a real application, this would be run in a thread and the timeout
|
||||||
if (response.split(" ")[3].equals("working")) {
|
//parameter of the circuit breaker would be utilized to know if service
|
||||||
reset();
|
//is working. Here, we simulate that based on server response itself
|
||||||
return response;
|
var response = service.call();
|
||||||
} else {
|
// Yay!! the API responded fine. Let's reset everything.
|
||||||
recordFailure();
|
recordSuccess();
|
||||||
throw new Exception("Remote service not responding");
|
return response;
|
||||||
}
|
} catch (RemoteServiceException ex) {
|
||||||
} else {
|
recordFailure(ex.getMessage());
|
||||||
throw new Exception("Unknown Service Name");
|
throw ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 79 KiB |
@@ -5,32 +5,52 @@ package com.iluwatar.circuitbreaker {
|
|||||||
+ App()
|
+ App()
|
||||||
+ main(args : String[]) {static}
|
+ main(args : String[]) {static}
|
||||||
}
|
}
|
||||||
class CircuitBreaker {
|
interface CircuitBreaker {
|
||||||
|
+ attemptRequest() : String {abstract}
|
||||||
|
+ getState() : String {abstract}
|
||||||
|
+ recordFailure(String) {abstract}
|
||||||
|
+ recordSuccess() {abstract}
|
||||||
|
+ setState(State) {abstract}
|
||||||
|
}
|
||||||
|
class DefaultCircuitBreaker {
|
||||||
~ failureCount : int
|
~ failureCount : int
|
||||||
- failureThreshold : int
|
- failureThreshold : int
|
||||||
- futureTime : long
|
- futureTime : long
|
||||||
|
- lastFailureResponse : String
|
||||||
~ lastFailureTime : long
|
~ lastFailureTime : long
|
||||||
- retryTimePeriod : long
|
- retryTimePeriod : long
|
||||||
|
- service : RemoteService
|
||||||
- state : State
|
- state : State
|
||||||
- timeout : long
|
- timeout : long
|
||||||
~ CircuitBreaker(timeout : long, failureThreshold : int, retryTimePeriod : long)
|
~ DefaultCircuitBreaker(serviceToCall : RemoteService, timeout : long, failureThreshold : int, retryTimePeriod : long)
|
||||||
+ call(serviceToCall : String, serverStartTime : long) : String
|
+ attemptRequest() : String
|
||||||
|
# evaluateState()
|
||||||
+ getState() : String
|
+ getState() : String
|
||||||
- recordFailure()
|
+ recordFailure(response : String)
|
||||||
- reset()
|
+ recordSuccess()
|
||||||
# setState()
|
+ setState(state : State)
|
||||||
+ setStateForBypass(state : State)
|
|
||||||
}
|
}
|
||||||
class DelayedService {
|
class DelayedRemoteService {
|
||||||
- delay : int
|
- delay : int
|
||||||
+ DelayedService()
|
- serverStartTime : long
|
||||||
+ DelayedService(delay : int)
|
+ DelayedRemoteService()
|
||||||
+ response(serverStartTime : long) : String
|
+ DelayedRemoteService(serverStartTime : long, delay : int)
|
||||||
|
+ call() : String
|
||||||
}
|
}
|
||||||
class MonitoringService {
|
class MonitoringService {
|
||||||
+ MonitoringService()
|
- delayedService : CircuitBreaker
|
||||||
|
- quickService : CircuitBreaker
|
||||||
|
+ MonitoringService(delayedService : CircuitBreaker, quickService : CircuitBreaker)
|
||||||
|
+ delayedServiceResponse() : String
|
||||||
+ localResourceResponse() : String
|
+ localResourceResponse() : String
|
||||||
+ remoteResourceResponse(circuitBreaker : CircuitBreaker, serverStartTime : long) : String
|
+ quickServiceResponse() : String
|
||||||
|
}
|
||||||
|
class QuickRemoteService {
|
||||||
|
+ QuickRemoteService()
|
||||||
|
+ call() : String
|
||||||
|
}
|
||||||
|
interface RemoteService {
|
||||||
|
+ call() : String {abstract}
|
||||||
}
|
}
|
||||||
enum State {
|
enum State {
|
||||||
+ CLOSED {static}
|
+ CLOSED {static}
|
||||||
@@ -40,5 +60,10 @@ package com.iluwatar.circuitbreaker {
|
|||||||
+ values() : State[] {static}
|
+ values() : State[] {static}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CircuitBreaker --> "-state" State
|
DefaultCircuitBreaker --> "-state" State
|
||||||
|
MonitoringService --> "-delayedService" CircuitBreaker
|
||||||
|
DefaultCircuitBreaker --> "-service" RemoteService
|
||||||
|
DefaultCircuitBreaker ..|> CircuitBreaker
|
||||||
|
DelayedRemoteService ..|> RemoteService
|
||||||
|
QuickRemoteService ..|> RemoteService
|
||||||
@enduml
|
@enduml
|
@@ -36,17 +36,18 @@ import org.slf4j.LoggerFactory;
|
|||||||
* operational again, so that we can use it
|
* operational again, so that we can use it
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* In this example, the circuit breaker pattern is demonstrated by using two services: {@link
|
* In this example, the circuit breaker pattern is demonstrated by using three services: {@link
|
||||||
* MonitoringService} and {@link DelayedService}. The monitoring service is responsible for calling
|
* DelayedRemoteService}, {@link QuickRemoteService} and {@link MonitoringService}. The monitoring
|
||||||
* two services: a local service and a remote service {@link DelayedService} , and by using the
|
* service is responsible for calling three services: a local service, a quick remove service
|
||||||
* circuit breaker construction we ensure that if the call to remote service is going to fail, we
|
* {@link QuickRemoteService} and a delayed remote service {@link DelayedRemoteService} , and by
|
||||||
* are going to save our resources and not make the function call at all, by wrapping our call to
|
* using the circuit breaker construction we ensure that if the call to remote service is going to
|
||||||
* the remote service in the circuit breaker object.
|
* fail, we are going to save our resources and not make the function call at all, by wrapping our
|
||||||
|
* call to the remote services in the {@link DefaultCircuitBreaker} implementation object.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* This works as follows: The {@link CircuitBreaker} object can be in one of three states:
|
* This works as follows: The {@link DefaultCircuitBreaker} object can be in one of three states:
|
||||||
* <b>Open</b>, <b>Closed</b> and <b>Half-Open</b>, which represents the real world circuits. If the
|
* <b>Open</b>, <b>Closed</b> and <b>Half-Open</b>, which represents the real world circuits. If
|
||||||
* state is closed (initial), we assume everything is alright and perform the function call.
|
* the state is closed (initial), we assume everything is alright and perform the function call.
|
||||||
* However, every time the call fails, we note it and once it crosses a threshold, we set the state
|
* However, every time the call fails, we note it and once it crosses a threshold, we set the state
|
||||||
* to Open, preventing any further calls to the remote server. Then, after a certain retry period
|
* to Open, preventing any further calls to the remote server. Then, after a certain retry period
|
||||||
* (during which we expect thee service to recover), we make another call to the remote server and
|
* (during which we expect thee service to recover), we make another call to the remote server and
|
||||||
@@ -63,22 +64,50 @@ public class App {
|
|||||||
*
|
*
|
||||||
* @param args command line args
|
* @param args command line args
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("squid:S2189")
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
//Create an object of monitoring service which makes both local and remote calls
|
|
||||||
var obj = new MonitoringService();
|
|
||||||
//Set the circuit Breaker parameters
|
|
||||||
var circuitBreaker = new CircuitBreaker(3000, 1, 2000 * 1000 * 1000);
|
|
||||||
var serverStartTime = System.nanoTime();
|
var serverStartTime = System.nanoTime();
|
||||||
while (true) {
|
|
||||||
LOGGER.info(obj.localResourceResponse());
|
var delayedService = new DelayedRemoteService(serverStartTime, 5);
|
||||||
LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime));
|
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
|
||||||
LOGGER.info(circuitBreaker.getState());
|
2000 * 1000 * 1000);
|
||||||
try {
|
|
||||||
Thread.sleep(5 * 1000);
|
var quickService = new QuickRemoteService();
|
||||||
} catch (InterruptedException e) {
|
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
|
||||||
LOGGER.error(e.getMessage());
|
2000 * 1000 * 1000);
|
||||||
}
|
|
||||||
|
//Create an object of monitoring service which makes both local and remote calls
|
||||||
|
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
|
||||||
|
quickServiceCircuitBreaker);
|
||||||
|
|
||||||
|
//Fetch response from local resource
|
||||||
|
LOGGER.info(monitoringService.localResourceResponse());
|
||||||
|
|
||||||
|
//Fetch response from delayed service 2 times, to meet the failure threshold
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
|
||||||
|
//Fetch current state of delayed service circuit breaker after crossing failure threshold limit
|
||||||
|
//which is OPEN now
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Meanwhile, the delayed service is down, fetch response from the healthy quick service
|
||||||
|
LOGGER.info(monitoringService.quickServiceResponse());
|
||||||
|
LOGGER.info(quickServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Wait for the delayed service to become responsive
|
||||||
|
try {
|
||||||
|
LOGGER.info("Waiting for delayed service to become responsive");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
//Check the state of delayed circuit breaker, should be HALF_OPEN
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Fetch response from delayed service, which should be healthy by now
|
||||||
|
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||||
|
//As successful response is fetched, it should be CLOSED again.
|
||||||
|
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,114 +24,22 @@
|
|||||||
package com.iluwatar.circuitbreaker;
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The circuit breaker class with all configurations.
|
* The Circuit breaker interface.
|
||||||
*/
|
*/
|
||||||
public class CircuitBreaker {
|
public interface CircuitBreaker {
|
||||||
private final long timeout;
|
|
||||||
private final long retryTimePeriod;
|
|
||||||
long lastFailureTime;
|
|
||||||
int failureCount;
|
|
||||||
private final int failureThreshold;
|
|
||||||
private State state;
|
|
||||||
private final long futureTime = 1000 * 1000 * 1000 * 1000;
|
|
||||||
|
|
||||||
/**
|
// Success response. Reset everything to defaults
|
||||||
* Constructor to create an instance of Circuit Breaker.
|
void recordSuccess();
|
||||||
*
|
|
||||||
* @param timeout Timeout for the API request. Not necessary for this simple example
|
|
||||||
* @param failureThreshold Number of failures we receive from the depended service before changing
|
|
||||||
* state to 'OPEN'
|
|
||||||
* @param retryTimePeriod Time period after which a new request is made to remote service for
|
|
||||||
* status check.
|
|
||||||
*/
|
|
||||||
CircuitBreaker(long timeout, int failureThreshold, long retryTimePeriod) {
|
|
||||||
// We start in a closed state hoping that everything is fine
|
|
||||||
this.state = State.CLOSED;
|
|
||||||
this.failureThreshold = failureThreshold;
|
|
||||||
// Timeout for the API request.
|
|
||||||
// Used to break the calls made to remote resource if it exceeds the limit
|
|
||||||
this.timeout = timeout;
|
|
||||||
this.retryTimePeriod = retryTimePeriod;
|
|
||||||
//An absurd amount of time in future which basically indicates the last failure never happened
|
|
||||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
|
||||||
this.failureCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Reset everything to defaults
|
// Failure response. Handle accordingly with response and change state if required.
|
||||||
private void reset() {
|
void recordFailure(String response);
|
||||||
this.failureCount = 0;
|
|
||||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
|
||||||
this.state = State.CLOSED;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recordFailure() {
|
// Get the current state of circuit breaker
|
||||||
failureCount = failureCount + 1;
|
String getState();
|
||||||
this.lastFailureTime = System.nanoTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setState() {
|
// Set the specific state manually.
|
||||||
if (failureCount > failureThreshold) { //Then something is wrong with remote service
|
void setState(State state);
|
||||||
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
|
|
||||||
//We have waited long enough and should try checking if service is up
|
|
||||||
state = State.HALF_OPEN;
|
|
||||||
} else {
|
|
||||||
//Service would still probably be down
|
|
||||||
state = State.OPEN;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//Everything is working fine
|
|
||||||
state = State.CLOSED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getState() {
|
// Attempt to fetch response from the remote service.
|
||||||
return state.name();
|
String attemptRequest() throws RemoteServiceException;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Break the circuit beforehand if it is known service is down Or connect the circuit manually if
|
|
||||||
* service comes online before expected.
|
|
||||||
*
|
|
||||||
* @param state State at which circuit is in
|
|
||||||
*/
|
|
||||||
public void setStateForBypass(State state) {
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes service call.
|
|
||||||
*
|
|
||||||
* @param serviceToCall The name of the service in String. Can be changed to data URLs in case
|
|
||||||
* of web applications
|
|
||||||
* @param serverStartTime Time at which actual server was started which makes calls to this
|
|
||||||
* service
|
|
||||||
* @return Value from the remote resource, stale response or a custom exception
|
|
||||||
*/
|
|
||||||
public String call(String serviceToCall, long serverStartTime) throws Exception {
|
|
||||||
setState();
|
|
||||||
if (state == State.OPEN) {
|
|
||||||
// return cached response if no the circuit is in OPEN state
|
|
||||||
return "This is stale response from API";
|
|
||||||
} else {
|
|
||||||
// Make the API request if the circuit is not OPEN
|
|
||||||
if (serviceToCall.equals("delayedService")) {
|
|
||||||
var delayedService = new DelayedService(20);
|
|
||||||
var response = delayedService.response(serverStartTime);
|
|
||||||
//In a real application, this would be run in a thread and the timeout
|
|
||||||
//parameter of the circuit breaker would be utilized to know if service
|
|
||||||
//is working. Here, we simulate that based on server response itself
|
|
||||||
if (response.split(" ")[3].equals("working")) {
|
|
||||||
// Yay!! the API responded fine. Let's reset everything.
|
|
||||||
reset();
|
|
||||||
return response;
|
|
||||||
} else {
|
|
||||||
// Uh-oh!! the call still failed. Let's update that in our records.
|
|
||||||
recordFailure();
|
|
||||||
throw new Exception("Remote service not responding");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Exception("Unknown Service Name");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* 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.circuitbreaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The delay based Circuit breaker implementation that works in a
|
||||||
|
* CLOSED->OPEN-(retry_time_period)->HALF_OPEN->CLOSED flow with some retry time period for failed
|
||||||
|
* services and a failure threshold for service to open circuit.
|
||||||
|
*/
|
||||||
|
public class DefaultCircuitBreaker implements CircuitBreaker {
|
||||||
|
|
||||||
|
private final long timeout;
|
||||||
|
private final long retryTimePeriod;
|
||||||
|
private final RemoteService service;
|
||||||
|
long lastFailureTime;
|
||||||
|
private String lastFailureResponse;
|
||||||
|
int failureCount;
|
||||||
|
private final int failureThreshold;
|
||||||
|
private State state;
|
||||||
|
private final long futureTime = 1000 * 1000 * 1000 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor to create an instance of Circuit Breaker.
|
||||||
|
*
|
||||||
|
* @param timeout Timeout for the API request. Not necessary for this simple example
|
||||||
|
* @param failureThreshold Number of failures we receive from the depended service before changing
|
||||||
|
* state to 'OPEN'
|
||||||
|
* @param retryTimePeriod Time period after which a new request is made to remote service for
|
||||||
|
* status check.
|
||||||
|
*/
|
||||||
|
DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold,
|
||||||
|
long retryTimePeriod) {
|
||||||
|
this.service = serviceToCall;
|
||||||
|
// We start in a closed state hoping that everything is fine
|
||||||
|
this.state = State.CLOSED;
|
||||||
|
this.failureThreshold = failureThreshold;
|
||||||
|
// Timeout for the API request.
|
||||||
|
// Used to break the calls made to remote resource if it exceeds the limit
|
||||||
|
this.timeout = timeout;
|
||||||
|
this.retryTimePeriod = retryTimePeriod;
|
||||||
|
//An absurd amount of time in future which basically indicates the last failure never happened
|
||||||
|
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||||
|
this.failureCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset everything to defaults
|
||||||
|
@Override
|
||||||
|
public void recordSuccess() {
|
||||||
|
this.failureCount = 0;
|
||||||
|
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||||
|
this.state = State.CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recordFailure(String response) {
|
||||||
|
failureCount = failureCount + 1;
|
||||||
|
this.lastFailureTime = System.nanoTime();
|
||||||
|
// Cache the failure response for returning on open state
|
||||||
|
this.lastFailureResponse = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
|
||||||
|
protected void evaluateState() {
|
||||||
|
if (failureCount >= failureThreshold) { //Then something is wrong with remote service
|
||||||
|
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
|
||||||
|
//We have waited long enough and should try checking if service is up
|
||||||
|
state = State.HALF_OPEN;
|
||||||
|
} else {
|
||||||
|
//Service would still probably be down
|
||||||
|
state = State.OPEN;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Everything is working fine
|
||||||
|
state = State.CLOSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getState() {
|
||||||
|
evaluateState();
|
||||||
|
return state.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Break the circuit beforehand if it is known service is down Or connect the circuit manually if
|
||||||
|
* service comes online before expected.
|
||||||
|
*
|
||||||
|
* @param state State at which circuit is in
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setState(State state) {
|
||||||
|
this.state = state;
|
||||||
|
switch (state) {
|
||||||
|
case OPEN:
|
||||||
|
this.failureCount = failureThreshold;
|
||||||
|
this.lastFailureTime = System.nanoTime();
|
||||||
|
break;
|
||||||
|
case HALF_OPEN:
|
||||||
|
this.failureCount = failureThreshold;
|
||||||
|
this.lastFailureTime = System.nanoTime() - retryTimePeriod;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.failureCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes service call.
|
||||||
|
*
|
||||||
|
* @return Value from the remote resource, stale response or a custom exception
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String attemptRequest() throws RemoteServiceException {
|
||||||
|
evaluateState();
|
||||||
|
if (state == State.OPEN) {
|
||||||
|
// return cached response if the circuit is in OPEN state
|
||||||
|
return this.lastFailureResponse;
|
||||||
|
} else {
|
||||||
|
// Make the API request if the circuit is not OPEN
|
||||||
|
try {
|
||||||
|
//In a real application, this would be run in a thread and the timeout
|
||||||
|
//parameter of the circuit breaker would be utilized to know if service
|
||||||
|
//is working. Here, we simulate that based on server response itself
|
||||||
|
var response = service.call();
|
||||||
|
// Yay!! the API responded fine. Let's reset everything.
|
||||||
|
recordSuccess();
|
||||||
|
return response;
|
||||||
|
} catch (RemoteServiceException ex) {
|
||||||
|
recordFailure(ex.getMessage());
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -27,7 +27,9 @@ package com.iluwatar.circuitbreaker;
|
|||||||
* This simulates the remote service It responds only after a certain timeout period (default set to
|
* This simulates the remote service It responds only after a certain timeout period (default set to
|
||||||
* 20 seconds).
|
* 20 seconds).
|
||||||
*/
|
*/
|
||||||
public class DelayedService {
|
public class DelayedRemoteService implements RemoteService {
|
||||||
|
|
||||||
|
private final long serverStartTime;
|
||||||
private final int delay;
|
private final int delay;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,22 +37,23 @@ public class DelayedService {
|
|||||||
*
|
*
|
||||||
* @param delay the delay after which service would behave properly, in seconds
|
* @param delay the delay after which service would behave properly, in seconds
|
||||||
*/
|
*/
|
||||||
public DelayedService(int delay) {
|
public DelayedRemoteService(long serverStartTime, int delay) {
|
||||||
|
this.serverStartTime = serverStartTime;
|
||||||
this.delay = delay;
|
this.delay = delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DelayedService() {
|
public DelayedRemoteService() {
|
||||||
this.delay = 60;
|
this.serverStartTime = System.nanoTime();
|
||||||
|
this.delay = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responds based on delay, current time and server start time if the service is down / working.
|
* Responds based on delay, current time and server start time if the service is down / working.
|
||||||
*
|
*
|
||||||
* @param serverStartTime Time at which actual server was started which makes calls to this
|
|
||||||
* service
|
|
||||||
* @return The state of the service
|
* @return The state of the service
|
||||||
*/
|
*/
|
||||||
public String response(long serverStartTime) {
|
@Override
|
||||||
|
public String call() throws RemoteServiceException {
|
||||||
var currentTime = System.nanoTime();
|
var currentTime = System.nanoTime();
|
||||||
//Since currentTime and serverStartTime are both in nanoseconds, we convert it to
|
//Since currentTime and serverStartTime are both in nanoseconds, we convert it to
|
||||||
//seconds by diving by 10e9 and ensure floating point division by multiplying it
|
//seconds by diving by 10e9 and ensure floating point division by multiplying it
|
||||||
@@ -58,9 +61,8 @@ public class DelayedService {
|
|||||||
//send the reply
|
//send the reply
|
||||||
if ((currentTime - serverStartTime) * 1.0 / (1000 * 1000 * 1000) < delay) {
|
if ((currentTime - serverStartTime) * 1.0 / (1000 * 1000 * 1000) < delay) {
|
||||||
//Can use Thread.sleep() here to block and simulate a hung server
|
//Can use Thread.sleep() here to block and simulate a hung server
|
||||||
return "Delayed service is down";
|
throw new RemoteServiceException("Delayed service is down");
|
||||||
} else {
|
|
||||||
return "Delayed service is working";
|
|
||||||
}
|
}
|
||||||
|
return "Delayed service is working";
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -24,28 +24,47 @@
|
|||||||
package com.iluwatar.circuitbreaker;
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service class which makes local and remote calls Uses {@link CircuitBreaker} object to ensure
|
* The service class which makes local and remote calls Uses {@link DefaultCircuitBreaker} object to
|
||||||
* remote calls don't use up resources.
|
* ensure remote calls don't use up resources.
|
||||||
*/
|
*/
|
||||||
public class MonitoringService {
|
public class MonitoringService {
|
||||||
|
|
||||||
|
private final CircuitBreaker delayedService;
|
||||||
|
|
||||||
|
private final CircuitBreaker quickService;
|
||||||
|
|
||||||
|
public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) {
|
||||||
|
this.delayedService = delayedService;
|
||||||
|
this.quickService = quickService;
|
||||||
|
}
|
||||||
|
|
||||||
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
|
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
|
||||||
public String localResourceResponse() {
|
public String localResourceResponse() {
|
||||||
return "Local Service is working";
|
return "Local Service is working";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to get result from remote server.
|
* Fetch response from the delayed service (with some simulated startup time).
|
||||||
*
|
*
|
||||||
* @param circuitBreaker The circuitBreaker object with all parameters
|
* @return response string
|
||||||
* @param serverStartTime Time at which actual server was started which makes calls to this
|
|
||||||
* service
|
|
||||||
* @return result from the remote response or exception raised by it.
|
|
||||||
*/
|
*/
|
||||||
public String remoteResourceResponse(CircuitBreaker circuitBreaker, long serverStartTime) {
|
public String delayedServiceResponse() {
|
||||||
try {
|
try {
|
||||||
return circuitBreaker.call("delayedService", serverStartTime);
|
return this.delayedService.attemptRequest();
|
||||||
} catch (Exception e) {
|
} catch (RemoteServiceException e) {
|
||||||
|
return e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches response from a healthy service without any failure.
|
||||||
|
*
|
||||||
|
* @return response string
|
||||||
|
*/
|
||||||
|
public String quickServiceResponse() {
|
||||||
|
try {
|
||||||
|
return this.quickService.attemptRequest();
|
||||||
|
} catch (RemoteServiceException e) {
|
||||||
return e.getMessage();
|
return e.getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,19 +23,13 @@
|
|||||||
|
|
||||||
package com.iluwatar.circuitbreaker;
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitoring Service test
|
* A quick response remote service, that responds healthy without any delay or failure.
|
||||||
*/
|
*/
|
||||||
public class DelayedServiceTest {
|
public class QuickRemoteService implements RemoteService {
|
||||||
|
|
||||||
//Improves code coverage
|
@Override
|
||||||
@Test
|
public String call() throws RemoteServiceException {
|
||||||
public void testDefaultConstructor() {
|
return "Quick Service is working";
|
||||||
var obj = new DelayedService();
|
|
||||||
assertEquals(obj.response(System.nanoTime()), "Delayed service is down");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.circuitbreaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Remote service interface, used by {@link CircuitBreaker} for fetching response from remote
|
||||||
|
* services.
|
||||||
|
*/
|
||||||
|
public interface RemoteService {
|
||||||
|
|
||||||
|
//Fetch response from remote service.
|
||||||
|
String call() throws RemoteServiceException;
|
||||||
|
}
|
@@ -21,39 +21,14 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.iluwatar.command;
|
package com.iluwatar.circuitbreaker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ShrinkSpell is a concrete command.
|
* Exception thrown when {@link RemoteService} does not respond successfully.
|
||||||
*/
|
*/
|
||||||
public class ShrinkSpell implements Command {
|
public class RemoteServiceException extends Exception {
|
||||||
|
|
||||||
private Size oldSize;
|
public RemoteServiceException(String message) {
|
||||||
private Target target;
|
super(message);
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(Target target) {
|
|
||||||
oldSize = target.getSize();
|
|
||||||
target.setSize(Size.SMALL);
|
|
||||||
this.target = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void undo() {
|
|
||||||
if (oldSize != null && target != null) {
|
|
||||||
var temp = target.getSize();
|
|
||||||
target.setSize(oldSize);
|
|
||||||
oldSize = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void redo() {
|
|
||||||
undo();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Shrink spell";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* 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.circuitbreaker;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App Test showing usage of circuit breaker.
|
||||||
|
*/
|
||||||
|
public class AppTest {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(AppTest.class);
|
||||||
|
|
||||||
|
//Startup delay for delayed service (in seconds)
|
||||||
|
private static final int STARTUP_DELAY = 4;
|
||||||
|
|
||||||
|
//Number of failed requests for circuit breaker to open
|
||||||
|
private static final int FAILURE_THRESHOLD = 1;
|
||||||
|
|
||||||
|
//Time period in seconds for circuit breaker to retry service
|
||||||
|
private static final int RETRY_PERIOD = 2;
|
||||||
|
|
||||||
|
private MonitoringService monitoringService;
|
||||||
|
|
||||||
|
private CircuitBreaker delayedServiceCircuitBreaker;
|
||||||
|
|
||||||
|
private CircuitBreaker quickServiceCircuitBreaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup the circuit breakers and services, where {@link DelayedRemoteService} will be start with
|
||||||
|
* a delay of 4 seconds and a {@link QuickRemoteService} responding healthy. Both services are
|
||||||
|
* wrapped in a {@link DefaultCircuitBreaker} implementation with failure threshold of 1 failure
|
||||||
|
* and retry time period of 2 seconds.
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
public void setupCircuitBreakers() {
|
||||||
|
var delayedService = new DelayedRemoteService(System.nanoTime(), STARTUP_DELAY);
|
||||||
|
//Set the circuit Breaker parameters
|
||||||
|
delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
|
||||||
|
FAILURE_THRESHOLD,
|
||||||
|
RETRY_PERIOD * 1000 * 1000 * 1000);
|
||||||
|
|
||||||
|
var quickService = new QuickRemoteService();
|
||||||
|
//Set the circuit Breaker parameters
|
||||||
|
quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, FAILURE_THRESHOLD,
|
||||||
|
RETRY_PERIOD * 1000 * 1000 * 1000);
|
||||||
|
|
||||||
|
monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
|
||||||
|
quickServiceCircuitBreaker);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailure_OpenStateTransition() {
|
||||||
|
//Calling delayed service, which will be unhealthy till 4 seconds
|
||||||
|
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
|
||||||
|
//As failure threshold is "1", the circuit breaker is changed to OPEN
|
||||||
|
assertEquals("OPEN", delayedServiceCircuitBreaker.getState());
|
||||||
|
//As circuit state is OPEN, we expect a quick fallback response from circuit breaker.
|
||||||
|
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
|
||||||
|
|
||||||
|
//Meanwhile, the quick service is responding and the circuit state is CLOSED
|
||||||
|
assertEquals("Quick Service is working", monitoringService.quickServiceResponse());
|
||||||
|
assertEquals("CLOSED", quickServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailure_HalfOpenStateTransition() {
|
||||||
|
//Calling delayed service, which will be unhealthy till 4 seconds
|
||||||
|
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
|
||||||
|
//As failure threshold is "1", the circuit breaker is changed to OPEN
|
||||||
|
assertEquals("OPEN", delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Waiting for recovery period of 2 seconds for circuit breaker to retry service.
|
||||||
|
try {
|
||||||
|
LOGGER.info("Waiting 2s for delayed service to become responsive");
|
||||||
|
Thread.sleep(2000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
//After 2 seconds, the circuit breaker should move to "HALF_OPEN" state and retry fetching response from service again
|
||||||
|
assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecovery_ClosedStateTransition() {
|
||||||
|
//Calling delayed service, which will be unhealthy till 4 seconds
|
||||||
|
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
|
||||||
|
//As failure threshold is "1", the circuit breaker is changed to OPEN
|
||||||
|
assertEquals("OPEN", delayedServiceCircuitBreaker.getState());
|
||||||
|
|
||||||
|
//Waiting for 4 seconds, which is enough for DelayedService to become healthy and respond successfully.
|
||||||
|
try {
|
||||||
|
LOGGER.info("Waiting 4s for delayed service to become responsive");
|
||||||
|
Thread.sleep(4000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
//As retry period is 2 seconds (<4 seconds of wait), hence the circuit breaker should be back in HALF_OPEN state.
|
||||||
|
assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState());
|
||||||
|
//Check the success response from delayed service.
|
||||||
|
assertEquals("Delayed service is working", monitoringService.delayedServiceResponse());
|
||||||
|
//As the response is success, the state should be CLOSED
|
||||||
|
assertEquals("CLOSED", delayedServiceCircuitBreaker.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -25,56 +25,60 @@ package com.iluwatar.circuitbreaker;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.rmi.Remote;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Circuit Breaker test
|
* Circuit Breaker test
|
||||||
*/
|
*/
|
||||||
public class CircuitBreakerTest {
|
public class DefaultCircuitBreakerTest {
|
||||||
|
|
||||||
//long timeout, int failureThreshold, long retryTimePeriod
|
//long timeout, int failureThreshold, long retryTimePeriod
|
||||||
@Test
|
@Test
|
||||||
public void testSetState() {
|
public void testEvaluateState() {
|
||||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 100);
|
||||||
//Right now, failureCount<failureThreshold, so state should be closed
|
//Right now, failureCount<failureThreshold, so state should be closed
|
||||||
assertEquals(circuitBreaker.getState(), "CLOSED");
|
assertEquals(circuitBreaker.getState(), "CLOSED");
|
||||||
circuitBreaker.failureCount = 4;
|
circuitBreaker.failureCount = 4;
|
||||||
circuitBreaker.lastFailureTime = System.nanoTime();
|
circuitBreaker.lastFailureTime = System.nanoTime();
|
||||||
circuitBreaker.setState();
|
circuitBreaker.evaluateState();
|
||||||
//Since failureCount>failureThreshold, and lastFailureTime is nearly equal to current time,
|
//Since failureCount>failureThreshold, and lastFailureTime is nearly equal to current time,
|
||||||
//state should be half-open
|
//state should be half-open
|
||||||
assertEquals(circuitBreaker.getState(), "HALF_OPEN");
|
assertEquals(circuitBreaker.getState(), "HALF_OPEN");
|
||||||
//Since failureCount>failureThreshold, and lastFailureTime is much lesser current time,
|
//Since failureCount>failureThreshold, and lastFailureTime is much lesser current time,
|
||||||
//state should be open
|
//state should be open
|
||||||
circuitBreaker.lastFailureTime = System.nanoTime() - 1000 * 1000 * 1000 * 1000;
|
circuitBreaker.lastFailureTime = System.nanoTime() - 1000 * 1000 * 1000 * 1000;
|
||||||
circuitBreaker.setState();
|
circuitBreaker.evaluateState();
|
||||||
assertEquals(circuitBreaker.getState(), "OPEN");
|
assertEquals(circuitBreaker.getState(), "OPEN");
|
||||||
//Now set it back again to closed to test idempotency
|
//Now set it back again to closed to test idempotency
|
||||||
circuitBreaker.failureCount = 0;
|
circuitBreaker.failureCount = 0;
|
||||||
circuitBreaker.setState();
|
circuitBreaker.evaluateState();
|
||||||
assertEquals(circuitBreaker.getState(), "CLOSED");
|
assertEquals(circuitBreaker.getState(), "CLOSED");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetStateForBypass() {
|
public void testSetStateForBypass() {
|
||||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 2000 * 1000 * 1000);
|
||||||
//Right now, failureCount<failureThreshold, so state should be closed
|
//Right now, failureCount<failureThreshold, so state should be closed
|
||||||
//Bypass it and set it to open
|
//Bypass it and set it to open
|
||||||
circuitBreaker.setStateForBypass(State.OPEN);
|
circuitBreaker.setState(State.OPEN);
|
||||||
assertEquals(circuitBreaker.getState(), "OPEN");
|
assertEquals(circuitBreaker.getState(), "OPEN");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testApiResponses() {
|
public void testApiResponses() throws RemoteServiceException {
|
||||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
RemoteService mockService = new RemoteService() {
|
||||||
try {
|
@Override
|
||||||
//Call with the paramater start_time set to huge amount of time in past so that service
|
public String call() throws RemoteServiceException {
|
||||||
//replies with "Ok". Also, state is CLOSED in start
|
return "Remote Success";
|
||||||
var serviceStartTime = System.nanoTime() - 60 * 1000 * 1000 * 1000;
|
}
|
||||||
var response = circuitBreaker.call("delayedService", serviceStartTime);
|
};
|
||||||
assertEquals(response, "Delayed service is working");
|
var circuitBreaker = new DefaultCircuitBreaker(mockService, 1, 1, 100);
|
||||||
} catch (Exception e) {
|
//Call with the paramater start_time set to huge amount of time in past so that service
|
||||||
System.out.println(e.getMessage());
|
//replies with "Ok". Also, state is CLOSED in start
|
||||||
}
|
var serviceStartTime = System.nanoTime() - 60 * 1000 * 1000 * 1000;
|
||||||
|
var response = circuitBreaker.attemptRequest();
|
||||||
|
assertEquals(response, "Remote Success");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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.circuitbreaker;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitoring Service test
|
||||||
|
*/
|
||||||
|
public class DelayedRemoteServiceTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testing immediate response of the delayed service.
|
||||||
|
*
|
||||||
|
* @throws RemoteServiceException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDefaultConstructor() throws RemoteServiceException {
|
||||||
|
Assertions.assertThrows(RemoteServiceException.class, () -> {
|
||||||
|
var obj = new DelayedRemoteService();
|
||||||
|
obj.call();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testing server started in past (2 seconds ago) and with a simulated delay of 1 second.
|
||||||
|
*
|
||||||
|
* @throws RemoteServiceException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testParameterizedConstructor() throws RemoteServiceException {
|
||||||
|
var obj = new DelayedRemoteService(System.nanoTime()-2000*1000*1000,1);
|
||||||
|
assertEquals("Delayed service is working",obj.call());
|
||||||
|
}
|
||||||
|
}
|
@@ -35,28 +35,45 @@ public class MonitoringServiceTest {
|
|||||||
//long timeout, int failureThreshold, long retryTimePeriod
|
//long timeout, int failureThreshold, long retryTimePeriod
|
||||||
@Test
|
@Test
|
||||||
public void testLocalResponse() {
|
public void testLocalResponse() {
|
||||||
var monitoringService = new MonitoringService();
|
var monitoringService = new MonitoringService(null,null);
|
||||||
var response = monitoringService.localResourceResponse();
|
var response = monitoringService.localResourceResponse();
|
||||||
assertEquals(response, "Local Service is working");
|
assertEquals(response, "Local Service is working");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoteResponse() {
|
public void testDelayedRemoteResponseSuccess() {
|
||||||
var monitoringService = new MonitoringService();
|
var delayedService = new DelayedRemoteService(System.nanoTime()-2*1000*1000*1000, 2);
|
||||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
|
||||||
|
1,
|
||||||
|
2 * 1000 * 1000 * 1000);
|
||||||
|
|
||||||
|
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null);
|
||||||
//Set time in past to make the server work
|
//Set time in past to make the server work
|
||||||
var serverStartTime = System.nanoTime() / 10;
|
var response = monitoringService.delayedServiceResponse();
|
||||||
var response = monitoringService.remoteResourceResponse(circuitBreaker, serverStartTime);
|
|
||||||
assertEquals(response, "Delayed service is working");
|
assertEquals(response, "Delayed service is working");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoteResponse2() {
|
public void testDelayedRemoteResponseFailure() {
|
||||||
var monitoringService = new MonitoringService();
|
var delayedService = new DelayedRemoteService(System.nanoTime(), 2);
|
||||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
|
||||||
|
1,
|
||||||
|
2 * 1000 * 1000 * 1000);
|
||||||
|
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null);
|
||||||
//Set time as current time as initially server fails
|
//Set time as current time as initially server fails
|
||||||
var serverStartTime = System.nanoTime();
|
var response = monitoringService.delayedServiceResponse();
|
||||||
var response = monitoringService.remoteResourceResponse(circuitBreaker, serverStartTime);
|
assertEquals(response, "Delayed service is down");
|
||||||
assertEquals(response, "Remote service not responding");
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQuickRemoteServiceResponse() {
|
||||||
|
var delayedService = new QuickRemoteService();
|
||||||
|
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
|
||||||
|
1,
|
||||||
|
2 * 1000 * 1000 * 1000);
|
||||||
|
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null);
|
||||||
|
//Set time as current time as initially server fails
|
||||||
|
var response = monitoringService.delayedServiceResponse();
|
||||||
|
assertEquals(response, "Quick Service is working");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,14 +14,14 @@ Action, Transaction
|
|||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
Encapsulate a request as an object, thereby letting you parameterize clients with different
|
Encapsulate a request as an object, thereby letting you parameterize clients with different
|
||||||
requests, queue or log requests, and support undoable operations.
|
requests, queue or log requests, and support undoable operations.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one.
|
> 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 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.
|
> the spells one by one. Each spell here is a command object that can be undone.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
@@ -30,8 +30,8 @@ In plain words
|
|||||||
|
|
||||||
Wikipedia says
|
Wikipedia says
|
||||||
|
|
||||||
> In object-oriented programming, the command pattern is a behavioral design pattern in which an
|
> 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
|
> object is used to encapsulate all information needed to perform an action or trigger an event at
|
||||||
> a later time.
|
> a later time.
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
@@ -41,25 +41,21 @@ Here's the sample code with wizard and goblin. Let's start from the `Wizard` cla
|
|||||||
```java
|
```java
|
||||||
public class Wizard {
|
public class Wizard {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class);
|
|
||||||
|
|
||||||
private final Deque<Command> undoStack = new LinkedList<>();
|
private final Deque<Command> undoStack = new LinkedList<>();
|
||||||
private final Deque<Command> redoStack = new LinkedList<>();
|
private final Deque<Command> redoStack = new LinkedList<>();
|
||||||
|
|
||||||
public Wizard() {}
|
public Wizard() {}
|
||||||
|
|
||||||
public void castSpell(Command command, Target target) {
|
public void castSpell(Runnable runnable) {
|
||||||
LOGGER.info("{} casts {} at {}", this, command, target);
|
runnable.run();
|
||||||
command.execute(target);
|
undoStack.offerLast(runnable);
|
||||||
undoStack.offerLast(command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void undoLastSpell() {
|
public void undoLastSpell() {
|
||||||
if (!undoStack.isEmpty()) {
|
if (!undoStack.isEmpty()) {
|
||||||
var previousSpell = undoStack.pollLast();
|
var previousSpell = undoStack.pollLast();
|
||||||
redoStack.offerLast(previousSpell);
|
redoStack.offerLast(previousSpell);
|
||||||
LOGGER.info("{} undoes {}", this, previousSpell);
|
previousSpell.run();
|
||||||
previousSpell.undo();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +63,7 @@ public class Wizard {
|
|||||||
if (!redoStack.isEmpty()) {
|
if (!redoStack.isEmpty()) {
|
||||||
var previousSpell = redoStack.pollLast();
|
var previousSpell = redoStack.pollLast();
|
||||||
undoStack.offerLast(previousSpell);
|
undoStack.offerLast(previousSpell);
|
||||||
LOGGER.info("{} redoes {}", this, previousSpell);
|
previousSpell.run();
|
||||||
previousSpell.redo();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,84 +74,7 @@ public class Wizard {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next we present the spell hierarchy.
|
Next, we have the goblin who's the target of the spells.
|
||||||
|
|
||||||
```java
|
|
||||||
public interface Command {
|
|
||||||
|
|
||||||
void execute(Target target);
|
|
||||||
|
|
||||||
void undo();
|
|
||||||
|
|
||||||
void redo();
|
|
||||||
|
|
||||||
String toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InvisibilitySpell implements Command {
|
|
||||||
|
|
||||||
private Target target;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(Target target) {
|
|
||||||
target.setVisibility(Visibility.INVISIBLE);
|
|
||||||
this.target = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void undo() {
|
|
||||||
if (target != null) {
|
|
||||||
target.setVisibility(Visibility.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void redo() {
|
|
||||||
if (target != null) {
|
|
||||||
target.setVisibility(Visibility.INVISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Invisibility spell";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ShrinkSpell implements Command {
|
|
||||||
|
|
||||||
private Size oldSize;
|
|
||||||
private Target target;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(Target target) {
|
|
||||||
oldSize = target.getSize();
|
|
||||||
target.setSize(Size.SMALL);
|
|
||||||
this.target = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void undo() {
|
|
||||||
if (oldSize != null && target != null) {
|
|
||||||
var temp = target.getSize();
|
|
||||||
target.setSize(oldSize);
|
|
||||||
oldSize = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void redo() {
|
|
||||||
undo();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Shrink spell";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, we have the goblin who's the target of the spells.
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public abstract class Target {
|
public abstract class Target {
|
||||||
@@ -203,33 +121,73 @@ public class Goblin extends Target {
|
|||||||
return "Goblin";
|
return "Goblin";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void changeSize() {
|
||||||
|
var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL;
|
||||||
|
setSize(oldSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeVisibility() {
|
||||||
|
var visible = getVisibility() == Visibility.INVISIBLE
|
||||||
|
? Visibility.VISIBLE : Visibility.INVISIBLE;
|
||||||
|
setVisibility(visible);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Finally we have the wizard in main function who casts spell
|
||||||
|
|
||||||
|
```java
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var wizard = new Wizard();
|
||||||
|
var goblin = new Goblin();
|
||||||
|
|
||||||
|
// casts shrink/unshrink spell
|
||||||
|
wizard.castSpell(goblin::changeSize);
|
||||||
|
|
||||||
|
// casts visible/invisible spell
|
||||||
|
wizard.castSpell(goblin::changeVisibility);
|
||||||
|
|
||||||
|
// undo and redo casts
|
||||||
|
wizard.undoLastSpell();
|
||||||
|
wizard.redoLastSpell();
|
||||||
|
```
|
||||||
|
|
||||||
Here's the whole example in action.
|
Here's the whole example in action.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
var wizard = new Wizard();
|
var wizard = new Wizard();
|
||||||
var goblin = new Goblin();
|
var goblin = new Goblin();
|
||||||
|
|
||||||
goblin.printStatus();
|
goblin.printStatus();
|
||||||
wizard.castSpell(new ShrinkSpell(), goblin);
|
wizard.castSpell(goblin::changeSize);
|
||||||
goblin.printStatus();
|
goblin.printStatus();
|
||||||
wizard.castSpell(new InvisibilitySpell(), goblin);
|
|
||||||
|
wizard.castSpell(goblin::changeVisibility);
|
||||||
goblin.printStatus();
|
goblin.printStatus();
|
||||||
|
|
||||||
wizard.undoLastSpell();
|
wizard.undoLastSpell();
|
||||||
goblin.printStatus();
|
goblin.printStatus();
|
||||||
|
|
||||||
|
wizard.undoLastSpell();
|
||||||
|
goblin.printStatus();
|
||||||
|
|
||||||
|
wizard.redoLastSpell();
|
||||||
|
goblin.printStatus();
|
||||||
|
|
||||||
|
wizard.redoLastSpell();
|
||||||
|
goblin.printStatus();
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's the program output:
|
Here's the program output:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// Goblin, [size=normal] [visibility=visible]
|
Goblin, [size=normal] [visibility=visible]
|
||||||
// Wizard casts Shrink spell at Goblin
|
Goblin, [size=small] [visibility=visible]
|
||||||
// Goblin, [size=small] [visibility=visible]
|
Goblin, [size=small] [visibility=invisible]
|
||||||
// Wizard casts Invisibility spell at Goblin
|
Goblin, [size=small] [visibility=visible]
|
||||||
// Goblin, [size=small] [visibility=invisible]
|
Goblin, [size=normal] [visibility=visible]
|
||||||
// Wizard undoes Invisibility spell
|
Goblin, [size=small] [visibility=visible]
|
||||||
// Goblin, [size=small] [visibility=visible]
|
Goblin, [size=small] [visibility=invisible]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
@@ -240,26 +198,26 @@ Here's the program output:
|
|||||||
|
|
||||||
Use the Command pattern when you want to:
|
Use the Command pattern when you want to:
|
||||||
|
|
||||||
* Parameterize objects by an action to perform. You can express such parameterization in a
|
* 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
|
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.
|
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
|
* 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
|
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
|
space-independent way, then you can transfer a command object for the request to a different process
|
||||||
and fulfill the request there.
|
and fulfill the request there.
|
||||||
* Support undo. The Command's execute operation can store state for reversing its effects in the
|
* 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
|
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.
|
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
|
Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling
|
||||||
un-execute and execute, respectively.
|
un-execute and execute, respectively.
|
||||||
* Support logging changes so that they can be reapplied in case of a system crash. By augmenting the
|
* 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.
|
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
|
Recovering from a crash involves reloading logged commands from disk and re-executing them with
|
||||||
the execute operation.
|
the execute operation.
|
||||||
* Structure a system around high-level operations build on primitive operations. Such a structure is
|
* 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
|
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,
|
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
|
letting you invoke all transactions the same way. The pattern also makes it easy to extend the
|
||||||
system with new transactions.
|
system with new transactions.
|
||||||
|
|
||||||
## Typical Use Case
|
## Typical Use Case
|
||||||
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 50 KiB |
@@ -1,116 +1,89 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
||||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
||||||
<class id="1" language="java" name="com.iluwatar.command.ShrinkSpell" project="command"
|
<class id="2" language="java" name="com.iluwatar.command.Goblin" project="command"
|
||||||
file="/command/src/main/java/com/iluwatar/command/ShrinkSpell.java" binary="false" corner="BOTTOM_RIGHT">
|
file="/command/src/main/java/com/iluwatar/command/Goblin.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
<position height="178" width="141" x="-30" y="681"/>
|
<position height="-1" width="-1" x="129" y="1223"/>
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
sort-features="false" accessors="true" visibility="true">
|
sort-features="false" accessors="true" visibility="true">
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
</display>
|
</display>
|
||||||
</class>
|
</class>
|
||||||
<class id="2" language="java" name="com.iluwatar.command.Goblin" project="command"
|
<class id="3" language="java" name="com.iluwatar.command.Wizard" project="command"
|
||||||
file="/command/src/main/java/com/iluwatar/command/Goblin.java" binary="false" corner="BOTTOM_RIGHT">
|
file="/command/src/main/java/com/iluwatar/command/Wizard.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
<position height="-1" width="-1" x="129" y="1223"/>
|
<position height="-1" width="-1" x="129" y="362"/>
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
sort-features="false" accessors="true" visibility="true">
|
sort-features="false" accessors="true" visibility="true">
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
</display>
|
</display>
|
||||||
</class>
|
</class>
|
||||||
<class id="3" language="java" name="com.iluwatar.command.Wizard" project="command"
|
<class id="6" language="java" name="com.iluwatar.command.Target" project="command"
|
||||||
file="/command/src/main/java/com/iluwatar/command/Wizard.java" binary="false" corner="BOTTOM_RIGHT">
|
file="/command/src/main/java/com/iluwatar/command/Target.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
<position height="-1" width="-1" x="129" y="362"/>
|
<position height="-1" width="-1" x="129" y="1014"/>
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
sort-features="false" accessors="true" visibility="true">
|
sort-features="false" accessors="true" visibility="true">
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
</display>
|
</display>
|
||||||
</class>
|
</class>
|
||||||
<class id="4" language="java" name="com.iluwatar.command.Command" project="command"
|
<association id="7">
|
||||||
file="/command/src/main/java/com/iluwatar/command/Command.java" binary="false" corner="BOTTOM_RIGHT">
|
<end type="SOURCE" refId="3" navigable="false">
|
||||||
<position height="-1" width="-1" x="129" y="561"/>
|
<attribute id="8" name="redoStack">
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
<position height="20" width="67" x="140" y="451"/>
|
||||||
sort-features="false" accessors="true" visibility="true">
|
</attribute>
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
<multiplicity id="9" minimum="0" maximum="2147483647">
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
<position height="18" width="25" x="221" y="452"/>
|
||||||
</display>
|
</multiplicity>
|
||||||
</class>
|
</end>
|
||||||
<class id="5" language="java" name="com.iluwatar.command.InvisibilitySpell" project="command"
|
<end type="TARGET" refId="4" navigable="true"/>
|
||||||
file="/command/src/main/java/com/iluwatar/command/InvisibilitySpell.java" binary="false" corner="BOTTOM_RIGHT">
|
<display labels="true" multiplicity="true"/>
|
||||||
<position height="160" width="141" x="151" y="681"/>
|
</association>
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
<generalization id="10">
|
||||||
sort-features="false" accessors="true" visibility="true">
|
<end type="SOURCE" refId="2"/>
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
<end type="TARGET" refId="6"/>
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
</generalization>
|
||||||
</display>
|
<association id="11">
|
||||||
</class>
|
<end type="SOURCE" refId="1" navigable="false">
|
||||||
<class id="6" language="java" name="com.iluwatar.command.Target" project="command"
|
<attribute id="12" name="target"/>
|
||||||
file="/command/src/main/java/com/iluwatar/command/Target.java" binary="false" corner="BOTTOM_RIGHT">
|
<multiplicity id="13" minimum="0" maximum="1"/>
|
||||||
<position height="-1" width="-1" x="129" y="1014"/>
|
</end>
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
<end type="TARGET" refId="6" navigable="true"/>
|
||||||
sort-features="false" accessors="true" visibility="true">
|
<display labels="true" multiplicity="true"/>
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
</association>
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
<generalization id="14">
|
||||||
</display>
|
<end type="SOURCE" refId="1"/>
|
||||||
</class>
|
<end type="TARGET" refId="4"/>
|
||||||
<association id="7">
|
</generalization>
|
||||||
<end type="SOURCE" refId="3" navigable="false">
|
<association id="15">
|
||||||
<attribute id="8" name="redoStack">
|
<end type="SOURCE" refId="3" navigable="false">
|
||||||
<position height="20" width="67" x="140" y="451"/>
|
<attribute id="16" name="undoStack">
|
||||||
</attribute>
|
<position height="20" width="70" x="-17" y="451"/>
|
||||||
<multiplicity id="9" minimum="0" maximum="2147483647">
|
</attribute>
|
||||||
<position height="18" width="25" x="221" y="452"/>
|
<multiplicity id="17" minimum="0" maximum="2147483647">
|
||||||
</multiplicity>
|
<position height="18" width="25" x="60" y="452"/>
|
||||||
</end>
|
</multiplicity>
|
||||||
<end type="TARGET" refId="4" navigable="true"/>
|
</end>
|
||||||
<display labels="true" multiplicity="true"/>
|
<end type="TARGET" refId="4" navigable="true"/>
|
||||||
</association>
|
<display labels="true" multiplicity="true"/>
|
||||||
<generalization id="10">
|
</association>
|
||||||
<end type="SOURCE" refId="2"/>
|
<generalization id="18">
|
||||||
<end type="TARGET" refId="6"/>
|
<end type="SOURCE" refId="5"/>
|
||||||
</generalization>
|
<end type="TARGET" refId="4"/>
|
||||||
<association id="11">
|
</generalization>
|
||||||
<end type="SOURCE" refId="1" navigable="false">
|
<association id="19">
|
||||||
<attribute id="12" name="target"/>
|
<end type="SOURCE" refId="5" navigable="false">
|
||||||
<multiplicity id="13" minimum="0" maximum="1"/>
|
<attribute id="20" name="target"/>
|
||||||
</end>
|
<multiplicity id="21" minimum="0" maximum="1"/>
|
||||||
<end type="TARGET" refId="6" navigable="true"/>
|
</end>
|
||||||
<display labels="true" multiplicity="true"/>
|
<end type="TARGET" refId="6" navigable="true"/>
|
||||||
</association>
|
<display labels="true" multiplicity="true"/>
|
||||||
<generalization id="14">
|
</association>
|
||||||
<end type="SOURCE" refId="1"/>
|
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
<end type="TARGET" refId="4"/>
|
sort-features="false" accessors="true" visibility="true">
|
||||||
</generalization>
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
<association id="15">
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
<end type="SOURCE" refId="3" navigable="false">
|
</classifier-display>
|
||||||
<attribute id="16" name="undoStack">
|
|
||||||
<position height="20" width="70" x="-17" y="451"/>
|
|
||||||
</attribute>
|
|
||||||
<multiplicity id="17" minimum="0" maximum="2147483647">
|
|
||||||
<position height="18" width="25" x="60" y="452"/>
|
|
||||||
</multiplicity>
|
|
||||||
</end>
|
|
||||||
<end type="TARGET" refId="4" navigable="true"/>
|
|
||||||
<display labels="true" multiplicity="true"/>
|
|
||||||
</association>
|
|
||||||
<generalization id="18">
|
|
||||||
<end type="SOURCE" refId="5"/>
|
|
||||||
<end type="TARGET" refId="4"/>
|
|
||||||
</generalization>
|
|
||||||
<association id="19">
|
|
||||||
<end type="SOURCE" refId="5" navigable="false">
|
|
||||||
<attribute id="20" name="target"/>
|
|
||||||
<multiplicity id="21" minimum="0" maximum="1"/>
|
|
||||||
</end>
|
|
||||||
<end type="TARGET" refId="6" navigable="true"/>
|
|
||||||
<display labels="true" multiplicity="true"/>
|
|
||||||
</association>
|
|
||||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</classifier-display>
|
|
||||||
<association-display labels="true" multiplicity="true"/>
|
<association-display labels="true" multiplicity="true"/>
|
||||||
</class-diagram>
|
</class-diagram>
|
||||||
|
@@ -4,33 +4,11 @@ package com.iluwatar.command {
|
|||||||
+ App()
|
+ App()
|
||||||
+ main(args : String[]) {static}
|
+ main(args : String[]) {static}
|
||||||
}
|
}
|
||||||
interface Command {
|
|
||||||
+ Command()
|
|
||||||
+ execute(Target) {abstract}
|
|
||||||
+ redo() {abstract}
|
|
||||||
+ toString() : String {abstract}
|
|
||||||
+ undo() {abstract}
|
|
||||||
}
|
|
||||||
class Goblin {
|
class Goblin {
|
||||||
+ Goblin()
|
+ Goblin()
|
||||||
+ toString() : String
|
+ toString() : String
|
||||||
}
|
+ changeSize()
|
||||||
class InvisibilitySpell {
|
+ changeVisibility()
|
||||||
- target : Target
|
|
||||||
+ InvisibilitySpell()
|
|
||||||
+ execute(target : Target)
|
|
||||||
+ redo()
|
|
||||||
+ toString() : String
|
|
||||||
+ undo()
|
|
||||||
}
|
|
||||||
class ShrinkSpell {
|
|
||||||
- oldSize : Size
|
|
||||||
- target : Target
|
|
||||||
+ ShrinkSpell()
|
|
||||||
+ execute(target : Target)
|
|
||||||
+ redo()
|
|
||||||
+ toString() : String
|
|
||||||
+ undo()
|
|
||||||
}
|
}
|
||||||
enum Size {
|
enum Size {
|
||||||
+ NORMAL {static}
|
+ NORMAL {static}
|
||||||
@@ -62,22 +40,19 @@ package com.iluwatar.command {
|
|||||||
}
|
}
|
||||||
class Wizard {
|
class Wizard {
|
||||||
- LOGGER : Logger {static}
|
- LOGGER : Logger {static}
|
||||||
- redoStack : Deque<Command>
|
- redoStack : Deque<Runnable>
|
||||||
- undoStack : Deque<Command>
|
- undoStack : Deque<Runnable>
|
||||||
+ Wizard()
|
+ Wizard()
|
||||||
+ castSpell(command : Command, target : Target)
|
+ castSpell(Runnable : runnable)
|
||||||
+ redoLastSpell()
|
+ redoLastSpell()
|
||||||
+ toString() : String
|
+ toString() : String
|
||||||
+ undoLastSpell()
|
+ undoLastSpell()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Target --> "-size" Size
|
Target --> "-size" Size
|
||||||
Wizard --> "-undoStack" Command
|
Wizard --> "-changeSize" Goblin
|
||||||
ShrinkSpell --> "-oldSize" Size
|
Wizard --> "-changeVisibility" Goblin
|
||||||
InvisibilitySpell --> "-target" Target
|
|
||||||
ShrinkSpell --> "-target" Target
|
|
||||||
Target --> "-visibility" Visibility
|
Target --> "-visibility" Visibility
|
||||||
Goblin --|> Target
|
Goblin --|> Target
|
||||||
InvisibilitySpell ..|> Command
|
App --> "castSpell" Wizard
|
||||||
ShrinkSpell ..|> Command
|
|
||||||
@enduml
|
@enduml
|
||||||
|
@@ -30,12 +30,10 @@ package com.iluwatar.command;
|
|||||||
*
|
*
|
||||||
* <p>Four terms always associated with the command pattern are command, receiver, invoker and
|
* <p>Four terms always associated with the command pattern are command, receiver, invoker and
|
||||||
* client. A command object (spell) knows about the receiver (target) and invokes a method of the
|
* client. A command object (spell) knows about the receiver (target) and invokes a method of the
|
||||||
* receiver. Values for parameters of the receiver method are stored in the command. The receiver
|
* receiver. An invoker object (wizard) receives a reference to the command to be executed and
|
||||||
* then does the work. An invoker object (wizard) knows how to execute a command, and optionally
|
* optionally does bookkeeping about the command execution. The invoker does not know anything
|
||||||
* does bookkeeping about the command execution. The invoker does not know anything about a concrete
|
* about how the command is executed. The client decides which commands to execute at which
|
||||||
* command, it knows only about command interface. Both an invoker object and several command
|
* points. To execute a command, it passes a reference of the function to the invoker object.
|
||||||
* objects are held by a client object (app). The client decides which commands to execute at which
|
|
||||||
* points. To execute a command, it passes the command object to the invoker object.
|
|
||||||
*
|
*
|
||||||
* <p>In other words, in this example the wizard casts spells on the goblin. The wizard keeps track
|
* <p>In other words, in this example the wizard casts spells on the goblin. The wizard keeps track
|
||||||
* of the previous spells cast, so it is easy to undo them. In addition, the wizard keeps track of
|
* of the previous spells cast, so it is easy to undo them. In addition, the wizard keeps track of
|
||||||
@@ -54,10 +52,10 @@ public class App {
|
|||||||
|
|
||||||
goblin.printStatus();
|
goblin.printStatus();
|
||||||
|
|
||||||
wizard.castSpell(new ShrinkSpell(), goblin);
|
wizard.castSpell(goblin::changeSize);
|
||||||
goblin.printStatus();
|
goblin.printStatus();
|
||||||
|
|
||||||
wizard.castSpell(new InvisibilitySpell(), goblin);
|
wizard.castSpell(goblin::changeVisibility);
|
||||||
goblin.printStatus();
|
goblin.printStatus();
|
||||||
|
|
||||||
wizard.undoLastSpell();
|
wizard.undoLastSpell();
|
||||||
|
@@ -37,5 +37,4 @@ public class Goblin extends Target {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "Goblin";
|
return "Goblin";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -62,4 +62,21 @@ public abstract class Target {
|
|||||||
public void printStatus() {
|
public void printStatus() {
|
||||||
LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility());
|
LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the size of the target.
|
||||||
|
*/
|
||||||
|
public void changeSize() {
|
||||||
|
var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL;
|
||||||
|
setSize(oldSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the visibility of the target.
|
||||||
|
*/
|
||||||
|
public void changeVisibility() {
|
||||||
|
var visible = getVisibility() == Visibility.INVISIBLE
|
||||||
|
? Visibility.VISIBLE : Visibility.INVISIBLE;
|
||||||
|
setVisibility(visible);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,30 +25,24 @@ package com.iluwatar.command;
|
|||||||
|
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wizard is the invoker of the commands.
|
* Wizard is the invoker of the commands.
|
||||||
*/
|
*/
|
||||||
public class Wizard {
|
public class Wizard {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class);
|
private final Deque<Runnable> undoStack = new LinkedList<>();
|
||||||
|
private final Deque<Runnable> redoStack = new LinkedList<>();
|
||||||
private final Deque<Command> undoStack = new LinkedList<>();
|
|
||||||
private final Deque<Command> redoStack = new LinkedList<>();
|
|
||||||
|
|
||||||
public Wizard() {
|
public Wizard() {
|
||||||
// comment to ignore sonar issue: LEVEL critical
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cast spell.
|
* Cast spell.
|
||||||
*/
|
*/
|
||||||
public void castSpell(Command command, Target target) {
|
public void castSpell(Runnable runnable) {
|
||||||
LOGGER.info("{} casts {} at {}", this, command, target);
|
runnable.run();
|
||||||
command.execute(target);
|
undoStack.offerLast(runnable);
|
||||||
undoStack.offerLast(command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,8 +52,7 @@ public class Wizard {
|
|||||||
if (!undoStack.isEmpty()) {
|
if (!undoStack.isEmpty()) {
|
||||||
var previousSpell = undoStack.pollLast();
|
var previousSpell = undoStack.pollLast();
|
||||||
redoStack.offerLast(previousSpell);
|
redoStack.offerLast(previousSpell);
|
||||||
LOGGER.info("{} undoes {}", this, previousSpell);
|
previousSpell.run();
|
||||||
previousSpell.undo();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,8 +63,7 @@ public class Wizard {
|
|||||||
if (!redoStack.isEmpty()) {
|
if (!redoStack.isEmpty()) {
|
||||||
var previousSpell = redoStack.pollLast();
|
var previousSpell = redoStack.pollLast();
|
||||||
undoStack.offerLast(previousSpell);
|
undoStack.offerLast(previousSpell);
|
||||||
LOGGER.info("{} redoes {}", this, previousSpell);
|
previousSpell.run();
|
||||||
previousSpell.redo();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -56,10 +56,10 @@ public class CommandTest {
|
|||||||
var wizard = new Wizard();
|
var wizard = new Wizard();
|
||||||
var goblin = new Goblin();
|
var goblin = new Goblin();
|
||||||
|
|
||||||
wizard.castSpell(new ShrinkSpell(), goblin);
|
wizard.castSpell(goblin::changeSize);
|
||||||
verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.VISIBLE);
|
verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.VISIBLE);
|
||||||
|
|
||||||
wizard.castSpell(new InvisibilitySpell(), goblin);
|
wizard.castSpell(goblin::changeVisibility);
|
||||||
verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.INVISIBLE);
|
verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.INVISIBLE);
|
||||||
|
|
||||||
wizard.undoLastSpell();
|
wizard.undoLastSpell();
|
||||||
|
54
data-transfer-object-enum-impl/README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
layout: pattern
|
||||||
|
title: Data Transfer Object
|
||||||
|
folder: data-transfer-object
|
||||||
|
permalink: /patterns/data-transfer-object/
|
||||||
|
categories: Architectural
|
||||||
|
tags:
|
||||||
|
- Performance
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
In plain words
|
||||||
|
|
||||||
|
> Using DTO relevant information can be fetched with a single backend query.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Class diagram
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Applicability
|
||||||
|
|
||||||
|
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.
|
||||||
|
* You want reduced number of remote calls.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
* [Design Pattern - Transfer Object Pattern](https://www.tutorialspoint.com/design_pattern/transfer_object_pattern.htm)
|
||||||
|
* [Data Transfer Object](https://msdn.microsoft.com/en-us/library/ff649585.aspx)
|
||||||
|
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94)
|
||||||
|
* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=014237a67c9d46f384b35e10151956bd)
|
@@ -0,0 +1,129 @@
|
|||||||
|
@startuml
|
||||||
|
package com.iluwatar.datatransferenum {
|
||||||
|
class App {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
+ App()
|
||||||
|
+ main(args : String[]) {static}
|
||||||
|
}
|
||||||
|
class Product {
|
||||||
|
- cost : Double
|
||||||
|
- id : Long
|
||||||
|
- name : String
|
||||||
|
- price : Double
|
||||||
|
- supplier : String
|
||||||
|
+ Product()
|
||||||
|
+ getCost() : Double
|
||||||
|
+ getId() : Long
|
||||||
|
+ getName() : String
|
||||||
|
+ getPrice() : Double
|
||||||
|
+ getSupplier() : String
|
||||||
|
+ setCost(cost : Double) : Product
|
||||||
|
+ setId(id : Long) : Product
|
||||||
|
+ setName(name : String) : Product
|
||||||
|
+ setPrice(price : Double) : Product
|
||||||
|
+ setSupplier(supplier : String) : Product
|
||||||
|
+ toString() : String
|
||||||
|
}
|
||||||
|
enum ProductDTO {
|
||||||
|
+ valueOf(name : String) : ProductDTO {static}
|
||||||
|
+ values() : ProductDTO[] {static}
|
||||||
|
}
|
||||||
|
-interface Cost {
|
||||||
|
+ getCost() : Double {abstract}
|
||||||
|
}
|
||||||
|
-interface Id {
|
||||||
|
+ getId() : Long {abstract}
|
||||||
|
}
|
||||||
|
-interface Name {
|
||||||
|
+ getName() : String {abstract}
|
||||||
|
}
|
||||||
|
-interface Price {
|
||||||
|
+ getPrice() : Double {abstract}
|
||||||
|
}
|
||||||
|
enum Request {
|
||||||
|
+ valueOf(name : String) : Request {static}
|
||||||
|
+ values() : Request[] {static}
|
||||||
|
}
|
||||||
|
class Create {
|
||||||
|
- cost : Double
|
||||||
|
- name : String
|
||||||
|
- price : Double
|
||||||
|
- supplier : String
|
||||||
|
+ Create()
|
||||||
|
+ getCost() : Double
|
||||||
|
+ getName() : String
|
||||||
|
+ getPrice() : Double
|
||||||
|
+ getSupplier() : String
|
||||||
|
+ setCost(cost : Double) : Create
|
||||||
|
+ setName(name : String) : Create
|
||||||
|
+ setPrice(price : Double) : Create
|
||||||
|
+ setSupplier(supplier : String) : Create
|
||||||
|
}
|
||||||
|
enum Response {
|
||||||
|
+ valueOf(name : String) : Response {static}
|
||||||
|
+ values() : Response[] {static}
|
||||||
|
}
|
||||||
|
class Private {
|
||||||
|
- cost : Double
|
||||||
|
- id : Long
|
||||||
|
- name : String
|
||||||
|
- price : Double
|
||||||
|
+ Private()
|
||||||
|
+ getCost() : Double
|
||||||
|
+ getId() : Long
|
||||||
|
+ getName() : String
|
||||||
|
+ getPrice() : Double
|
||||||
|
+ setCost(cost : Double) : Private
|
||||||
|
+ setId(id : Long) : Private
|
||||||
|
+ setName(name : String) : Private
|
||||||
|
+ setPrice(price : Double) : Private
|
||||||
|
+ toString() : String
|
||||||
|
}
|
||||||
|
class Public {
|
||||||
|
- id : Long
|
||||||
|
- name : String
|
||||||
|
- price : Double
|
||||||
|
+ Public()
|
||||||
|
+ getId() : Long
|
||||||
|
+ getName() : String
|
||||||
|
+ getPrice() : Double
|
||||||
|
+ setId(id : Long) : Public
|
||||||
|
+ setName(name : String) : Public
|
||||||
|
+ setPrice(price : Double) : Public
|
||||||
|
+ toString() : String
|
||||||
|
}
|
||||||
|
-interface Supplier {
|
||||||
|
+ getSupplier() : String {abstract}
|
||||||
|
}
|
||||||
|
class ProductResource {
|
||||||
|
- products : List<Product>
|
||||||
|
+ ProductResource(products : List<Product>)
|
||||||
|
+ getAllProductsForAdmin() : List<Private>
|
||||||
|
+ getAllProductsForCustomer() : List<Public>
|
||||||
|
+ getProducts() : List<Product>
|
||||||
|
+ save(createProductDTO : Create)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Create ..+ Request
|
||||||
|
Request ..+ ProductDTO
|
||||||
|
Private ..+ Response
|
||||||
|
Supplier ..+ ProductDTO
|
||||||
|
Name ..+ ProductDTO
|
||||||
|
ProductResource --> "-products" Product
|
||||||
|
Public ..+ Response
|
||||||
|
Id ..+ ProductDTO
|
||||||
|
Price ..+ ProductDTO
|
||||||
|
Response ..+ ProductDTO
|
||||||
|
Cost ..+ ProductDTO
|
||||||
|
Create ..|> Name
|
||||||
|
Create ..|> Price
|
||||||
|
Create ..|> Cost
|
||||||
|
Create ..|> Supplier
|
||||||
|
Private ..|> Id
|
||||||
|
Private ..|> Name
|
||||||
|
Private ..|> Price
|
||||||
|
Private ..|> Cost
|
||||||
|
Public ..|> Id
|
||||||
|
Public ..|> Name
|
||||||
|
Public ..|> Price
|
||||||
|
@enduml
|
BIN
data-transfer-object-enum-impl/etc/dto-enum-uml.png
Normal file
After Width: | Height: | Size: 120 KiB |
61
data-transfer-object-enum-impl/pom.xml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<version>1.24.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>data-transfer-object-enum-impl</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>com.iluwatar.datatransferenum.App</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
@@ -0,0 +1,59 @@
|
|||||||
|
package com.iluwatar.datatransferenum;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Data Transfer Object pattern is a design pattern in which an data transfer object is used to
|
||||||
|
* serve related information together to avoid multiple call for each piece of information.
|
||||||
|
*
|
||||||
|
* <p>In this example, ({@link App}) as as product details consumer i.e. client to
|
||||||
|
* request for product details to server.
|
||||||
|
*
|
||||||
|
* <p>productResource ({@link ProductResource}) act as server to serve product information. And
|
||||||
|
* The productDto ({@link ProductDto} is data transfer object to share product information.
|
||||||
|
*/
|
||||||
|
public class App {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method as act client and request to server for details.
|
||||||
|
*
|
||||||
|
* @param args program argument.
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Product tv =
|
||||||
|
new Product().setId(1L).setName("TV").setSupplier("Sony").setPrice(1000D).setCost(1090D);
|
||||||
|
Product microwave =
|
||||||
|
new Product().setId(2L).setName("microwave").setSupplier("Delonghi").setPrice(1000D)
|
||||||
|
.setCost(1090D);
|
||||||
|
Product refrigerator =
|
||||||
|
new Product().setId(3L).setName("refrigerator").setSupplier("Botsch").setPrice(1000D)
|
||||||
|
.setCost(1090D);
|
||||||
|
Product airConditioner =
|
||||||
|
new Product().setId(4L).setName("airConditioner").setSupplier("LG").setPrice(1000D)
|
||||||
|
.setCost(1090D);
|
||||||
|
List<Product> products =
|
||||||
|
new ArrayList<>(Arrays.asList(tv, microwave, refrigerator, airConditioner));
|
||||||
|
ProductResource productResource = new ProductResource(products);
|
||||||
|
|
||||||
|
LOGGER.info("####### List of products including sensitive data just for admins: \n {}",
|
||||||
|
Arrays.toString(productResource.getAllProductsForAdmin().toArray()));
|
||||||
|
LOGGER.info("####### List of products for customers: \n {}",
|
||||||
|
Arrays.toString(productResource.getAllProductsForCustomer().toArray()));
|
||||||
|
|
||||||
|
LOGGER.info("####### Going to save Sony PS5 ...");
|
||||||
|
ProductDto.Request.Create createProductRequestDto = new ProductDto.Request.Create()
|
||||||
|
.setName("PS5")
|
||||||
|
.setCost(1000D)
|
||||||
|
.setPrice(1220D)
|
||||||
|
.setSupplier("Sony");
|
||||||
|
productResource.save(createProductRequestDto);
|
||||||
|
LOGGER.info("####### List of products after adding PS5: {}",
|
||||||
|
Arrays.toString(productResource.getProducts().toArray()));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,91 @@
|
|||||||
|
package com.iluwatar.datatransferenum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Product} is a entity class for product entity. This class act as entity in the demo.
|
||||||
|
*/
|
||||||
|
public final class Product {
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private Double price;
|
||||||
|
private Double cost;
|
||||||
|
private String supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param id product id
|
||||||
|
* @param name product name
|
||||||
|
* @param price product price
|
||||||
|
* @param cost product cost
|
||||||
|
* @param supplier product supplier
|
||||||
|
*/
|
||||||
|
public Product(Long id, String name, Double price, Double cost, String supplier) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.price = price;
|
||||||
|
this.cost = cost;
|
||||||
|
this.supplier = supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public Product() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Product setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Product setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Product setPrice(Double price) {
|
||||||
|
this.price = price;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getCost() {
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Product setCost(Double cost) {
|
||||||
|
this.cost = cost;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSupplier() {
|
||||||
|
return supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Product setSupplier(String supplier) {
|
||||||
|
this.supplier = supplier;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Product{"
|
||||||
|
+ "id=" + id
|
||||||
|
+ ", name='" + name + '\''
|
||||||
|
+ ", price=" + price
|
||||||
|
+ ", cost=" + cost
|
||||||
|
+ ", supplier='" + supplier + '\''
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,264 @@
|
|||||||
|
package com.iluwatar.datatransferenum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ProductDto} is a data transfer object POJO.
|
||||||
|
* Instead of sending individual information to
|
||||||
|
* client We can send related information together in POJO.
|
||||||
|
*
|
||||||
|
* <p>Dto will not have any business logic in it.
|
||||||
|
*/
|
||||||
|
public enum ProductDto {
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is Request class which consist of Create or any other request DTO's
|
||||||
|
* you might want to use in your API.
|
||||||
|
*/
|
||||||
|
public enum Request {
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is Create dto class for requesting create new product.
|
||||||
|
*/
|
||||||
|
public static final class Create implements Name, Price, Cost, Supplier {
|
||||||
|
private String name;
|
||||||
|
private Double price;
|
||||||
|
private Double cost;
|
||||||
|
private String supplier;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Create setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Create setPrice(Double price) {
|
||||||
|
this.price = price;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getCost() {
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Create setCost(Double cost) {
|
||||||
|
this.cost = cost;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSupplier() {
|
||||||
|
return supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Create setSupplier(String supplier) {
|
||||||
|
this.supplier = supplier;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is Response class which consist of any response DTO's
|
||||||
|
* you might want to provide to your clients.
|
||||||
|
*/
|
||||||
|
public enum Response {
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is Public dto class for API response with the lowest data security.
|
||||||
|
*/
|
||||||
|
public static final class Public implements Id, Name, Price {
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private Double price;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Public setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Public setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Public setPrice(Double price) {
|
||||||
|
this.price = price;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Public{"
|
||||||
|
+ "id="
|
||||||
|
+ id
|
||||||
|
+ ", name='"
|
||||||
|
+ name
|
||||||
|
+ '\''
|
||||||
|
+ ", price="
|
||||||
|
+ price
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is Private dto class for API response with the highest data security.
|
||||||
|
*/
|
||||||
|
public static final class Private implements Id, Name, Price, Cost {
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private Double price;
|
||||||
|
private Double cost;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Private setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Private setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Private setPrice(Double price) {
|
||||||
|
this.price = price;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getCost() {
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Private setCost(Double cost) {
|
||||||
|
this.cost = cost;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Private{"
|
||||||
|
+
|
||||||
|
"id="
|
||||||
|
+ id
|
||||||
|
+
|
||||||
|
", name='"
|
||||||
|
+ name
|
||||||
|
+ '\''
|
||||||
|
+
|
||||||
|
", price="
|
||||||
|
+ price
|
||||||
|
+
|
||||||
|
", cost="
|
||||||
|
+ cost
|
||||||
|
+
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this interface whenever you want to provide the product Id in your DTO.
|
||||||
|
*/
|
||||||
|
private interface Id {
|
||||||
|
/**
|
||||||
|
* Unique identifier of the product.
|
||||||
|
*
|
||||||
|
* @return : id of the product.
|
||||||
|
*/
|
||||||
|
Long getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this interface whenever you want to provide the product Name in your DTO.
|
||||||
|
*/
|
||||||
|
private interface Name {
|
||||||
|
/**
|
||||||
|
* The name of the product.
|
||||||
|
*
|
||||||
|
* @return : name of the product.
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this interface whenever you want to provide the product Price in your DTO.
|
||||||
|
*/
|
||||||
|
private interface Price {
|
||||||
|
/**
|
||||||
|
* The amount we sell a product for.
|
||||||
|
* <b>This data is not confidential</b>
|
||||||
|
*
|
||||||
|
* @return : price of the product.
|
||||||
|
*/
|
||||||
|
Double getPrice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this interface whenever you want to provide the product Cost in your DTO.
|
||||||
|
*/
|
||||||
|
private interface Cost {
|
||||||
|
/**
|
||||||
|
* The amount that it costs us to purchase this product
|
||||||
|
* For the amount we sell a product for, see the {@link Price Price} parameter.
|
||||||
|
* <b>This data is confidential</b>
|
||||||
|
*
|
||||||
|
* @return : cost of the product.
|
||||||
|
*/
|
||||||
|
Double getCost();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this interface whenever you want to provide the product Supplier in your DTO.
|
||||||
|
*/
|
||||||
|
private interface Supplier {
|
||||||
|
/**
|
||||||
|
* The name of supplier of the product or its manufacturer.
|
||||||
|
* <b>This data is highly confidential</b>
|
||||||
|
*
|
||||||
|
* @return : supplier of the product.
|
||||||
|
*/
|
||||||
|
String getSupplier();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,71 @@
|
|||||||
|
package com.iluwatar.datatransferenum;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource class which serves product information. This class act as server in the demo. Which
|
||||||
|
* has all product details.
|
||||||
|
*/
|
||||||
|
public class ProductResource {
|
||||||
|
private final List<Product> products;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise resource with existing products.
|
||||||
|
*
|
||||||
|
* @param products initialize resource with existing products. Act as database.
|
||||||
|
*/
|
||||||
|
public ProductResource(final List<Product> products) {
|
||||||
|
this.products = products;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all products.
|
||||||
|
*
|
||||||
|
* @return : all products in list but in the scheme of private dto.
|
||||||
|
*/
|
||||||
|
public List<ProductDto.Response.Private> getAllProductsForAdmin() {
|
||||||
|
return products
|
||||||
|
.stream()
|
||||||
|
.map(p -> new ProductDto.Response.Private().setId(p.getId()).setName(p.getName())
|
||||||
|
.setCost(p.getCost())
|
||||||
|
.setPrice(p.getPrice()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all products.
|
||||||
|
*
|
||||||
|
* @return : all products in list but in the scheme of public dto.
|
||||||
|
*/
|
||||||
|
public List<ProductDto.Response.Public> getAllProductsForCustomer() {
|
||||||
|
return products
|
||||||
|
.stream()
|
||||||
|
.map(p -> new ProductDto.Response.Public().setId(p.getId()).setName(p.getName())
|
||||||
|
.setPrice(p.getPrice()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save new product.
|
||||||
|
*
|
||||||
|
* @param createProductDto save new product to list.
|
||||||
|
*/
|
||||||
|
public void save(ProductDto.Request.Create createProductDto) {
|
||||||
|
products.add(new Product()
|
||||||
|
.setId((long) (products.size() + 1))
|
||||||
|
.setName(createProductDto.getName())
|
||||||
|
.setSupplier(createProductDto.getSupplier())
|
||||||
|
.setPrice(createProductDto.getPrice())
|
||||||
|
.setCost(createProductDto.getCost()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all products in an entity representation.
|
||||||
|
*
|
||||||
|
* @return : all the products entity that stored in the products list
|
||||||
|
*/
|
||||||
|
public List<Product> getProducts() {
|
||||||
|
return products;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.datatransferenum;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class AppTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue: Add at least one assertion to this test case.
|
||||||
|
* <p>
|
||||||
|
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
|
||||||
|
* throws an exception.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExecuteApplicationWithoutException() {
|
||||||
|
assertDoesNotThrow(() -> App.main(new String[] {}));
|
||||||
|
}
|
||||||
|
}
|
@@ -45,7 +45,7 @@ public class SpaceStationMir extends GameObject {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void collisionResolve(FlamingAsteroid asteroid) {
|
public void collisionResolve(FlamingAsteroid asteroid) {
|
||||||
LOGGER.info(AppConstants.HITS, " {} is damaged! {} is set on fire!", asteroid.getClass()
|
LOGGER.info(AppConstants.HITS + " {} is damaged! {} is set on fire!", asteroid.getClass()
|
||||||
.getSimpleName(),
|
.getSimpleName(),
|
||||||
this.getClass().getSimpleName(), this.getClass().getSimpleName(), this.getClass()
|
this.getClass().getSimpleName(), this.getClass().getSimpleName(), this.getClass()
|
||||||
.getSimpleName());
|
.getSimpleName());
|
||||||
@@ -55,14 +55,14 @@ public class SpaceStationMir extends GameObject {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void collisionResolve(Meteoroid meteoroid) {
|
public void collisionResolve(Meteoroid meteoroid) {
|
||||||
LOGGER.info(AppConstants.HITS, " {} is damaged!", meteoroid.getClass().getSimpleName(),
|
LOGGER.info(AppConstants.HITS + " {} is damaged!", meteoroid.getClass().getSimpleName(),
|
||||||
this.getClass().getSimpleName(), this.getClass().getSimpleName());
|
this.getClass().getSimpleName(), this.getClass().getSimpleName());
|
||||||
setDamaged(true);
|
setDamaged(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void collisionResolve(SpaceStationMir mir) {
|
public void collisionResolve(SpaceStationMir mir) {
|
||||||
LOGGER.info(AppConstants.HITS, " {} is damaged!", mir.getClass().getSimpleName(),
|
LOGGER.info(AppConstants.HITS + " {} is damaged!", mir.getClass().getSimpleName(),
|
||||||
this.getClass().getSimpleName(), this.getClass().getSimpleName());
|
this.getClass().getSimpleName(), this.getClass().getSimpleName());
|
||||||
setDamaged(true);
|
setDamaged(true);
|
||||||
}
|
}
|
||||||
|
@@ -24,11 +24,11 @@ Use the Event Sourcing pattern when
|
|||||||
|
|
||||||
## Real world examples
|
## Real world examples
|
||||||
|
|
||||||
* [The Lmax Architecture] (https://martinfowler.com/articles/lmax.html)
|
* [The Lmax Architecture](https://martinfowler.com/articles/lmax.html)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* [Martin Fowler - Event Sourcing] (https://martinfowler.com/eaaDev/EventSourcing.html)
|
* [Martin Fowler - Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html)
|
||||||
* [Event Sourcing | Microsoft Docs] (https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
|
* [Event Sourcing in Microsoft's documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
|
||||||
* [Reference 3: Introducing Event Sourcing] (https://msdn.microsoft.com/en-us/library/jj591559.aspx)
|
* [Reference 3: Introducing Event Sourcing](https://msdn.microsoft.com/en-us/library/jj591559.aspx)
|
||||||
* [Event Sourcing pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
|
* [Event Sourcing pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
|
||||||
|
@@ -81,7 +81,7 @@ public enum CarType {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
Then we have the static method `getCar` to create car objects encapsulated in the factory class
|
Then we have the static method `getCar` to create car objects encapsulated in the factory class
|
||||||
`CarSimpleFactory`.
|
`CarsFactory`.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class CarsFactory {
|
public class CarsFactory {
|
||||||
@@ -98,7 +98,7 @@ Now on the client code we can create different types of cars using the factory c
|
|||||||
var car1 = CarsFactory.getCar(CarType.FORD);
|
var car1 = CarsFactory.getCar(CarType.FORD);
|
||||||
var car2 = CarsFactory.getCar(CarType.FERRARI);
|
var car2 = CarsFactory.getCar(CarType.FERRARI);
|
||||||
LOGGER.info(car1.getDescription());
|
LOGGER.info(car1.getDescription());
|
||||||
LOGGER.info(car2.getDescription());;
|
LOGGER.info(car2.getDescription());
|
||||||
```
|
```
|
||||||
|
|
||||||
Program output:
|
Program output:
|
||||||
@@ -119,13 +119,23 @@ and manage it.
|
|||||||
|
|
||||||
Pros
|
Pros
|
||||||
|
|
||||||
* Allows keeping all objects creation in one place and avoid of spreading 'new' key value across codebase.
|
* Allows keeping all objects creation in one place and avoid of spreading 'new' keyword across codebase.
|
||||||
* Allows to writs loosely coupled code. Some of its main advantages include better testability, easy-to-understand code, swappable components, scalability and isolated features.
|
* Allows to write loosely coupled code. Some of its main advantages include better testability, easy-to-understand code, swappable components, scalability and isolated features.
|
||||||
|
|
||||||
Cons
|
Cons
|
||||||
|
|
||||||
* The code becomes more complicated than it should be.
|
* The code becomes more complicated than it should be.
|
||||||
|
|
||||||
|
## Real world examples
|
||||||
|
|
||||||
|
* [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--)
|
||||||
|
* [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-)
|
||||||
|
* [java.text.NumberFormat#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--)
|
||||||
|
* [java.nio.charset.Charset#forName()](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-)
|
||||||
|
* [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) (Returns different singleton objects, depending on a protocol)
|
||||||
|
* [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E))
|
||||||
|
* [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) and other similar methods.
|
||||||
|
|
||||||
## Related patterns
|
## Related patterns
|
||||||
|
|
||||||
* [Factory Method](https://java-design-patterns.com/patterns/factory-method/)
|
* [Factory Method](https://java-design-patterns.com/patterns/factory-method/)
|
||||||
|
@@ -1,3 +1,27 @@
|
|||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
@@ -1,3 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.factory;
|
package com.iluwatar.factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,3 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.factory;
|
package com.iluwatar.factory;
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@@ -1,3 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.factory;
|
package com.iluwatar.factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,3 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.factory;
|
package com.iluwatar.factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,3 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.factory;
|
package com.iluwatar.factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,3 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.factory;
|
package com.iluwatar.factory;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
@@ -1,3 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.factory;
|
package com.iluwatar.factory;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
package com.iluwatar.gameloop;
|
package com.iluwatar.gameloop;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
|
@@ -23,10 +23,11 @@
|
|||||||
|
|
||||||
package com.iluwatar.gameloop;
|
package com.iluwatar.gameloop;
|
||||||
|
|
||||||
import org.junit.After;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.Test;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FixedStepGameLoop unit test class.
|
* FixedStepGameLoop unit test class.
|
||||||
@@ -35,12 +36,12 @@ public class FixedStepGameLoopTest {
|
|||||||
|
|
||||||
private FixedStepGameLoop gameLoop;
|
private FixedStepGameLoop gameLoop;
|
||||||
|
|
||||||
@Before
|
@BeforeEach
|
||||||
public void setup() {
|
public void setup() {
|
||||||
gameLoop = new FixedStepGameLoop();
|
gameLoop = new FixedStepGameLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@AfterEach
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
gameLoop = null;
|
gameLoop = null;
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,7 @@ public class FixedStepGameLoopTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testUpdate() {
|
public void testUpdate() {
|
||||||
gameLoop.update();
|
gameLoop.update();
|
||||||
Assert.assertEquals(0.01f, gameLoop.controller.getBulletPosition(), 0);
|
assertEquals(0.01f, gameLoop.controller.getBulletPosition(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,10 +23,10 @@
|
|||||||
|
|
||||||
package com.iluwatar.gameloop;
|
package com.iluwatar.gameloop;
|
||||||
|
|
||||||
import org.junit.After;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.Test;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FrameBasedGameLoop unit test class.
|
* FrameBasedGameLoop unit test class.
|
||||||
@@ -35,19 +35,19 @@ public class FrameBasedGameLoopTest {
|
|||||||
|
|
||||||
private FrameBasedGameLoop gameLoop;
|
private FrameBasedGameLoop gameLoop;
|
||||||
|
|
||||||
@Before
|
@BeforeEach
|
||||||
public void setup() {
|
public void setup() {
|
||||||
gameLoop = new FrameBasedGameLoop();
|
gameLoop = new FrameBasedGameLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@AfterEach
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
gameLoop = null;
|
gameLoop = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@org.junit.jupiter.api.Test
|
||||||
public void testUpdate() {
|
public void testUpdate() {
|
||||||
gameLoop.update();
|
gameLoop.update();
|
||||||
Assert.assertEquals(0.5f, gameLoop.controller.getBulletPosition(), 0);
|
assertEquals(0.5f, gameLoop.controller.getBulletPosition(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,34 +23,33 @@
|
|||||||
|
|
||||||
package com.iluwatar.gameloop;
|
package com.iluwatar.gameloop;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.Assert;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.Before;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class GameControllerTest {
|
public class GameControllerTest {
|
||||||
|
|
||||||
private GameController controller;
|
private GameController controller;
|
||||||
|
|
||||||
@Before
|
@BeforeEach
|
||||||
public void setup() {
|
public void setup() {
|
||||||
controller = new GameController();
|
controller = new GameController();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@AfterEach
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
controller = null;
|
controller = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@org.junit.jupiter.api.Test
|
||||||
public void testMoveBullet() {
|
public void testMoveBullet() {
|
||||||
controller.moveBullet(1.5f);
|
controller.moveBullet(1.5f);
|
||||||
Assert.assertEquals(1.5f, controller.bullet.getPosition(), 0);
|
Assertions.assertEquals(1.5f, controller.bullet.getPosition(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@org.junit.jupiter.api.Test
|
||||||
public void testGetBulletPosition() {
|
public void testGetBulletPosition() {
|
||||||
Assert.assertEquals(controller.bullet.getPosition(), controller.getBulletPosition(), 0);
|
Assertions.assertEquals(controller.bullet.getPosition(), controller.getBulletPosition(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,10 +23,9 @@
|
|||||||
|
|
||||||
package com.iluwatar.gameloop;
|
package com.iluwatar.gameloop;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.Assert;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.Before;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GameLoop unit test class.
|
* GameLoop unit test class.
|
||||||
@@ -38,7 +37,7 @@ public class GameLoopTest {
|
|||||||
/**
|
/**
|
||||||
* Create mock implementation of GameLoop.
|
* Create mock implementation of GameLoop.
|
||||||
*/
|
*/
|
||||||
@Before
|
@BeforeEach
|
||||||
public void setup() {
|
public void setup() {
|
||||||
gameLoop = new GameLoop() {
|
gameLoop = new GameLoop() {
|
||||||
@Override
|
@Override
|
||||||
@@ -46,26 +45,26 @@ public class GameLoopTest {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@AfterEach
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
gameLoop = null;
|
gameLoop = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@org.junit.jupiter.api.Test
|
||||||
public void testRun() {
|
public void testRun() {
|
||||||
gameLoop.run();
|
gameLoop.run();
|
||||||
Assert.assertEquals(GameStatus.RUNNING, gameLoop.status);
|
Assertions.assertEquals(GameStatus.RUNNING, gameLoop.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@org.junit.jupiter.api.Test
|
||||||
public void testStop() {
|
public void testStop() {
|
||||||
gameLoop.stop();
|
gameLoop.stop();
|
||||||
Assert.assertEquals(GameStatus.STOPPED, gameLoop.status);
|
Assertions.assertEquals(GameStatus.STOPPED, gameLoop.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@org.junit.jupiter.api.Test
|
||||||
public void testIsGameRunning() {
|
public void testIsGameRunning() {
|
||||||
Assert.assertFalse(gameLoop.isGameRunning());
|
Assertions.assertFalse(gameLoop.isGameRunning());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,11 +23,9 @@
|
|||||||
|
|
||||||
package com.iluwatar.gameloop;
|
package com.iluwatar.gameloop;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.After;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.Assert;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VariableStepGameLoop unit test class.
|
* VariableStepGameLoop unit test class.
|
||||||
@@ -36,19 +34,19 @@ public class VariableStepGameLoopTest {
|
|||||||
|
|
||||||
private VariableStepGameLoop gameLoop;
|
private VariableStepGameLoop gameLoop;
|
||||||
|
|
||||||
@Before
|
@BeforeEach
|
||||||
public void setup() {
|
public void setup() {
|
||||||
gameLoop = new VariableStepGameLoop();
|
gameLoop = new VariableStepGameLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@AfterEach
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
gameLoop = null;
|
gameLoop = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@org.junit.jupiter.api.Test
|
||||||
public void testUpdate() {
|
public void testUpdate() {
|
||||||
gameLoop.update(20L);
|
gameLoop.update(20L);
|
||||||
Assert.assertEquals(0.01f, gameLoop.controller.getBulletPosition(), 0);
|
Assertions.assertEquals(0.01f, gameLoop.controller.getBulletPosition(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -140,6 +140,10 @@ Use the Iterator pattern
|
|||||||
* To support multiple traversals of aggregate objects.
|
* To support multiple traversals of aggregate objects.
|
||||||
* To provide a uniform interface for traversing different aggregate structures.
|
* To provide a uniform interface for traversing different aggregate structures.
|
||||||
|
|
||||||
|
## Tutorials
|
||||||
|
|
||||||
|
* [How to Use Iterator?](http://www.tutorialspoint.com/java/java_using_iterator.htm)
|
||||||
|
|
||||||
## Real world examples
|
## Real world examples
|
||||||
|
|
||||||
* [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html)
|
* [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html)
|
||||||
|
@@ -9,6 +9,10 @@ tags:
|
|||||||
- Performance
|
- Performance
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Also known as
|
||||||
|
|
||||||
|
Resource Pool
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
When objects are expensive to create and they are needed only for short periods of time it is
|
When objects are expensive to create and they are needed only for short periods of time it is
|
||||||
|
3
pom.xml
@@ -89,6 +89,7 @@
|
|||||||
<module>state</module>
|
<module>state</module>
|
||||||
<module>strategy</module>
|
<module>strategy</module>
|
||||||
<module>template-method</module>
|
<module>template-method</module>
|
||||||
|
<module>version-number</module>
|
||||||
<module>visitor</module>
|
<module>visitor</module>
|
||||||
<module>double-checked-locking</module>
|
<module>double-checked-locking</module>
|
||||||
<module>servant</module>
|
<module>servant</module>
|
||||||
@@ -194,9 +195,11 @@
|
|||||||
<module>strangler</module>
|
<module>strangler</module>
|
||||||
<module>arrange-act-assert</module>
|
<module>arrange-act-assert</module>
|
||||||
<module>transaction-script</module>
|
<module>transaction-script</module>
|
||||||
|
<module>registry</module>
|
||||||
<module>filterer</module>
|
<module>filterer</module>
|
||||||
<module>factory</module>
|
<module>factory</module>
|
||||||
<module>separated-interface</module>
|
<module>separated-interface</module>
|
||||||
|
<module>data-transfer-object-enum-impl</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
|
86
registry/README.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
layout: pattern
|
||||||
|
title: Registry
|
||||||
|
folder: registry
|
||||||
|
permalink: /patterns/registry/
|
||||||
|
categories: Creational
|
||||||
|
tags:
|
||||||
|
- Instantiation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
Stores the objects of a single class and provide a global point of access to them.
|
||||||
|
Similar to Multiton pattern, only difference is that in a registry there is no restriction on the number of objects.
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
In Plain Words
|
||||||
|
|
||||||
|
> Registry is a well-known object that other objects can use to find common objects and services.
|
||||||
|
|
||||||
|
**Programmatic Example**
|
||||||
|
Below is a `Customer` Class
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Customer {
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public Customer(String id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This registry of the `Customer` objects is `CustomerRegistry`
|
||||||
|
```java
|
||||||
|
public final class CustomerRegistry {
|
||||||
|
|
||||||
|
private static final CustomerRegistry instance = new CustomerRegistry();
|
||||||
|
|
||||||
|
public static CustomerRegistry getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, Customer> customerMap;
|
||||||
|
|
||||||
|
private CustomerRegistry() {
|
||||||
|
customerMap = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Customer addCustomer(Customer customer) {
|
||||||
|
return customerMap.put(customer.getId(), customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Customer getCustomer(String id) {
|
||||||
|
return customerMap.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Class diagram
|
||||||
|

|
||||||
|
|
||||||
|
## Applicability
|
||||||
|
Use Registry pattern when
|
||||||
|
|
||||||
|
* client wants reference of some object, so client can lookup for that object in the object's registry.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
Large number of bulky objects added to registry would result in a lot of memory consumption as objects in the registry are not garbage collected.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
* https://www.martinfowler.com/eaaCatalog/registry.html
|
||||||
|
* https://wiki.c2.com/?RegistryPattern
|
BIN
registry/etc/registry.png
Normal file
After Width: | Height: | Size: 16 KiB |
21
registry/etc/registry.urm.puml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
@startuml
|
||||||
|
package com.iluwatar.registry {
|
||||||
|
class App {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
+ App()
|
||||||
|
+ main(args : String[]) {static}
|
||||||
|
}
|
||||||
|
class Customer {
|
||||||
|
- id : String
|
||||||
|
- name : String
|
||||||
|
+ getId() : String
|
||||||
|
+ getName() : String
|
||||||
|
+ toString() : String
|
||||||
|
}
|
||||||
|
class CustomerRegistry {
|
||||||
|
+ addCustomer(customer : Customer)
|
||||||
|
+ getCustomer(id : String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Customer --> "-addCustomer" CustomerRegistry
|
||||||
|
@enduml
|
46
registry/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<version>1.24.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>registry</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>com.iluwatar.registry.App</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
27
registry/src/main/java/com/iluwatar/registry/App.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package com.iluwatar.registry;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class App {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program entry point.
|
||||||
|
*
|
||||||
|
* @param args command line args
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
CustomerRegistry customerRegistry = CustomerRegistry.getInstance();
|
||||||
|
var john = new Customer("1", "John");
|
||||||
|
customerRegistry.addCustomer(john);
|
||||||
|
|
||||||
|
var julia = new Customer("2", "Julia");
|
||||||
|
customerRegistry.addCustomer(julia);
|
||||||
|
|
||||||
|
LOGGER.info("John {}", customerRegistry.getCustomer("1"));
|
||||||
|
LOGGER.info("Julia {}", customerRegistry.getCustomer("2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
registry/src/main/java/com/iluwatar/registry/Customer.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package com.iluwatar.registry;
|
||||||
|
|
||||||
|
public class Customer {
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public Customer(String id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Customer{"
|
||||||
|
+ "id='" + id + '\''
|
||||||
|
+ ", name='" + name + '\''
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,28 @@
|
|||||||
|
package com.iluwatar.registry;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class CustomerRegistry {
|
||||||
|
|
||||||
|
private static final CustomerRegistry instance = new CustomerRegistry();
|
||||||
|
|
||||||
|
public static CustomerRegistry getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, Customer> customerMap;
|
||||||
|
|
||||||
|
private CustomerRegistry() {
|
||||||
|
customerMap = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Customer addCustomer(Customer customer) {
|
||||||
|
return customerMap.put(customer.getId(), customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Customer getCustomer(String id) {
|
||||||
|
return customerMap.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
package com.iluwatar.registry;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
public class CustomerRegistryTest {
|
||||||
|
|
||||||
|
private static CustomerRegistry customerRegistry;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUp() {
|
||||||
|
customerRegistry = CustomerRegistry.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldBeAbleToAddAndQueryCustomerObjectFromRegistry() {
|
||||||
|
Customer john = new Customer("1", "john");
|
||||||
|
Customer julia = new Customer("2", "julia");
|
||||||
|
|
||||||
|
customerRegistry.addCustomer(john);
|
||||||
|
customerRegistry.addCustomer(julia);
|
||||||
|
|
||||||
|
Customer customerWithId1 = customerRegistry.getCustomer("1");
|
||||||
|
assertNotNull(customerWithId1);
|
||||||
|
assertEquals("1", customerWithId1.getId());
|
||||||
|
assertEquals("john", customerWithId1.getName());
|
||||||
|
|
||||||
|
Customer customerWithId2 = customerRegistry.getCustomer("2");
|
||||||
|
assertNotNull(customerWithId2);
|
||||||
|
assertEquals("2", customerWithId2.getId());
|
||||||
|
assertEquals("julia", customerWithId2.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnNullWhenQueriedCustomerIsNotInRegistry() {
|
||||||
|
Customer customerWithId5 = customerRegistry.getCustomer("5");
|
||||||
|
assertNull(customerWithId5);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -10,23 +10,35 @@ tags:
|
|||||||
|
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Separate the interface definition and implementation in different packages. This allows the client to be completely unaware of the implementation.
|
|
||||||
|
Separate the interface definition and implementation in different packages. This allows the client
|
||||||
|
to be completely unaware of the implementation.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> An Invoice generator may be created with ability to use different Tax calculators that may be added in the invoice depending upon type of purchase, region etc.
|
> An Invoice generator may be created with ability to use different Tax calculators that may be
|
||||||
|
> added in the invoice depending upon type of purchase, region etc.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
> Separated interface pattern encourages to keep the implementations of an interface decoupled from the client and its definition, so the client is not dependent on the implementation.
|
> Separated interface pattern encourages to keep the implementations of an interface decoupled from
|
||||||
|
> the client and its definition, so the client is not dependent on the implementation.
|
||||||
|
|
||||||
A client code may abstract some specific functionality to an interface, and define the definition of the interface as an SPI ([Service Programming Interface](https://en.wikipedia.org/wiki/Service_provider_interface) is an API intended and open to be implemented or extended by a third party). Another package may implement this interface definition with a concrete logic, which will be injected into the client code at runtime (with a third class, injecting the implementation in the client) or at compile time (using Plugin pattern with some configurable file).
|
A client code may abstract some specific functionality to an interface, and define the definition of
|
||||||
|
the interface as an SPI ([Service Programming Interface](https://en.wikipedia.org/wiki/Service_provider_interface)
|
||||||
|
is an API intended and open to be implemented or extended by a third party). Another package may
|
||||||
|
implement this interface definition with a concrete logic, which will be injected into the client
|
||||||
|
code at runtime (with a third class, injecting the implementation in the client) or at compile time
|
||||||
|
(using Plugin pattern with some configurable file).
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
**Client** An Invoice generator class accepts the cost of the product and calculates the total amount payable inclusive of tax
|
**Client**
|
||||||
|
|
||||||
|
`InvoiceGenerator` class accepts the cost of the product and calculates the total
|
||||||
|
amount payable inclusive of tax.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class InvoiceGenerator {
|
public class InvoiceGenerator {
|
||||||
@@ -46,21 +58,23 @@ public class InvoiceGenerator {
|
|||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The tax calculation logic is delegated to the ```TaxCalculator``` interface
|
|
||||||
|
The tax calculation logic is delegated to the `TaxCalculator` interface.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
||||||
public interface TaxCalculator {
|
public interface TaxCalculator {
|
||||||
|
|
||||||
double calculate(double amount);
|
double calculate(double amount);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Implementation package**
|
**Implementation package**
|
||||||
In another package (which the client is completely unaware of) there exist multiple implementations of the ```TaxCalculator``` interface
|
|
||||||
```ForeignTaxCalculator``` which levies 60% tax for international products.
|
In another package (which the client is completely unaware of) there exist multiple implementations
|
||||||
|
of the `TaxCalculator` interface. `ForeignTaxCalculator` is one of them which levies 60% tax
|
||||||
|
for international products.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class ForeignTaxCalculator implements TaxCalculator {
|
public class ForeignTaxCalculator implements TaxCalculator {
|
||||||
|
|
||||||
@@ -74,7 +88,8 @@ public class ForeignTaxCalculator implements TaxCalculator {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```DomesticTaxCalculator``` which levies 20% tax for international products.
|
Another is `DomesticTaxCalculator` which levies 20% tax for international products.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class DomesticTaxCalculator implements TaxCalculator {
|
public class DomesticTaxCalculator implements TaxCalculator {
|
||||||
|
|
||||||
@@ -88,7 +103,8 @@ public class DomesticTaxCalculator implements TaxCalculator {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
These both implementations are instantiated and injected in the client class by the ```App.java``` class
|
These both implementations are instantiated and injected in the client class by the ```App.java```
|
||||||
|
class.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, new ForeignTaxCalculator());
|
var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, new ForeignTaxCalculator());
|
||||||
@@ -101,9 +117,11 @@ These both implementations are instantiated and injected in the client class by
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Separated interface pattern when
|
Use the Separated interface pattern when
|
||||||
|
|
||||||
* You are developing a framework package, and your framework needs to call some application code through interfaces.
|
* You are developing a framework package, and your framework needs to call some application code through interfaces.
|
||||||
@@ -117,3 +135,4 @@ Use the Separated interface pattern when
|
|||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* [Martin Fowler](https://www.martinfowler.com/eaaCatalog/separatedInterface.html)
|
* [Martin Fowler](https://www.martinfowler.com/eaaCatalog/separatedInterface.html)
|
||||||
|
* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=e08dfb7f2cf6153542ef1b5a00b10abc)
|
||||||
|
@@ -9,14 +9,16 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Ensure a class only has one instance, and provide a global point of
|
|
||||||
access to it.
|
Ensure a class only has one instance, and provide a global point of access to it.
|
||||||
|
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> There can only be one ivory tower where the wizards study their magic. The same enchanted ivory tower is always used by the wizards. Ivory tower here is singleton.
|
> There can only be one ivory tower where the wizards study their magic. The same enchanted ivory
|
||||||
|
> tower is always used by the wizards. Ivory tower here is singleton.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
@@ -24,7 +26,9 @@ In plain words
|
|||||||
|
|
||||||
Wikipedia says
|
Wikipedia says
|
||||||
|
|
||||||
> In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
|
> In software engineering, the singleton pattern is a software design pattern that restricts the
|
||||||
|
> instantiation of a class to one object. This is useful when exactly one object is needed to
|
||||||
|
> coordinate actions across the system.
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
@@ -38,7 +42,7 @@ public enum EnumIvoryTower {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Then in order to use
|
Then in order to use:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
var enumIvoryTower1 = EnumIvoryTower.INSTANCE;
|
var enumIvoryTower1 = EnumIvoryTower.INSTANCE;
|
||||||
@@ -47,9 +51,11 @@ assertEquals(enumIvoryTower1, enumIvoryTower2); // true
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Singleton pattern when
|
Use the Singleton pattern when
|
||||||
|
|
||||||
* There must be exactly one instance of a class, and it must be accessible to clients from a well-known access point
|
* There must be exactly one instance of a class, and it must be accessible to clients from a well-known access point
|
||||||
|
@@ -10,12 +10,17 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
As explained in the book [Game Programming Patterns](http://gameprogrammingpatterns.com/spatial-partition.html) by Bob Nystrom, spatial partition pattern helps to
|
|
||||||
|
|
||||||
> efficiently locate objects by storing them in a data structure organized by their positions.
|
As explained in the book [Game Programming Patterns](http://gameprogrammingpatterns.com/spatial-partition.html)
|
||||||
|
by Bob Nystrom, spatial partition pattern helps to efficiently locate objects by storing them in a
|
||||||
|
data structure organized by their positions.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
Say, you are building a war game with hundreds, or maybe even thousands of players, who are clashing on the battle field. Each player's position is getting updated every frame. The simple way to handle all interactions taking place on the field is to check each player's position against every other player's position:
|
|
||||||
|
Say, you are building a war game with hundreds, or maybe even thousands of players, who are clashing
|
||||||
|
on the battle field. Each player's position is getting updated every frame. The simple way to handle
|
||||||
|
all interactions taking place on the field is to check each player's position against every other
|
||||||
|
player's position:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public void handleMeLee(Unit units[], int numUnits) {
|
public void handleMeLee(Unit units[], int numUnits) {
|
||||||
@@ -32,21 +37,33 @@ public void handleMeLee(Unit units[], int numUnits) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This will include a lot of unnecessary checks between players which are too far apart to have any influence on each other. The nested loops gives this operation an O(n^2) complexity, which has to be performed every frame since many of the objects on the field may be moving each frame.
|
This will include a lot of unnecessary checks between players which are too far apart to have any
|
||||||
The idea behind the Spatial Partition design pattern is to enable quick location of objects using a data structure that is organised by their positions, so when performing an operation like the one above, every object's position need not be checked against all other objects' positions. The data structure can be used to store moving and static objects, though in order to keep track of the moving objects, their positions will have to be reset each time they move. This would mean having to create a new instance of the data structure each time an object moves, which would use up additional memory. The common data structures used for this design pattern are:
|
influence on each other. The nested loops gives this operation an O(n^2) complexity, which has to be
|
||||||
|
performed every frame since many of the objects on the field may be moving each frame. The idea
|
||||||
|
behind the Spatial Partition design pattern is to enable quick location of objects using a data
|
||||||
|
structure that is organised by their positions, so when performing an operation like the one above,
|
||||||
|
every object's position need not be checked against all other objects' positions. The data structure
|
||||||
|
can be used to store moving and static objects, though in order to keep track of the moving objects,
|
||||||
|
their positions will have to be reset each time they move. This would mean having to create a new
|
||||||
|
instance of the data structure each time an object moves, which would use up additional memory. The
|
||||||
|
common data structures used for this design pattern are:
|
||||||
|
|
||||||
* Grid
|
* Grid
|
||||||
* Quad tree
|
* Quad tree
|
||||||
* k-d tree
|
* K-d tree
|
||||||
* BSP
|
* BSP
|
||||||
* Boundary volume hierarchy
|
* Boundary volume hierarchy
|
||||||
|
|
||||||
In our implementation, we use the Quadtree data structure which will reduce the time complexity of finding the objects within a certain range from O(n^2) to O(nlogn), decreasing the computations required significantly in case of large number of objects.
|
In our implementation, we use the Quadtree data structure which will reduce the time complexity of
|
||||||
|
finding the objects within a certain range from O(n^2) to O(nlogn), decreasing the computations
|
||||||
|
required significantly in case of large number of objects.
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
This pattern can be used:
|
This pattern can be used:
|
||||||
|
|
||||||
* When you need to keep track of a large number of objects' positions, which are getting updated every frame.
|
* When you need to keep track of a large number of objects' positions, which are getting updated every frame.
|
||||||
|
@@ -24,7 +24,6 @@
|
|||||||
package com.iluwatar.spatialpartition;
|
package com.iluwatar.spatialpartition;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -9,36 +9,46 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Also known as
|
## Also known as
|
||||||
|
|
||||||
Filter, Criteria
|
Filter, Criteria
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Specification pattern separates the statement of how to match a
|
|
||||||
candidate, from the candidate object that it is matched against. As well as its
|
Specification pattern separates the statement of how to match a candidate, from the candidate object
|
||||||
usefulness in selection, it is also valuable for validation and for building to
|
that it is matched against. As well as its usefulness in selection, it is also valuable for
|
||||||
order.
|
validation and for building to order.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> There is a pool of different creatures and we often need to select some subset of them.
|
> There is a pool of different creatures and we often need to select some subset of them. We can
|
||||||
> We can write our search specification such as "creatures that can fly", "creatures heavier than 500 kilograms", or as a combination of other search specifications, and then give it to the party that will perform the filtering.
|
> write our search specification such as "creatures that can fly", "creatures heavier than 500
|
||||||
|
> kilograms", or as a combination of other search specifications, and then give it to the party that
|
||||||
|
> will perform the filtering.
|
||||||
|
|
||||||
In Plain Words
|
In Plain Words
|
||||||
|
|
||||||
> Specification pattern allows us to separate the search criteria from the object that performs the search.
|
> Specification pattern allows us to separate the search criteria from the object that performs the
|
||||||
|
> search.
|
||||||
|
|
||||||
Wikipedia says
|
Wikipedia says
|
||||||
|
|
||||||
> In computer programming, the specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic.
|
> In computer programming, the specification pattern is a particular software design pattern,
|
||||||
|
> whereby business rules can be recombined by chaining the business rules together using boolean
|
||||||
|
> logic.
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
If we look at our creature pool example from above, we have a set of creatures with certain properties.
|
If we look at our creature pool example from above, we have a set of creatures with certain
|
||||||
Those properties can be part of a pre-defined, limited set (represented here by the enums Size, Movement and Color); but they can also be continuous values (e.g. the mass of a Creature).
|
properties. Those properties can be part of a pre-defined, limited set (represented here by the
|
||||||
In this case, it is more appropriate to use what we call "parameterized specification", where the property value can be given as an argument when the Creature is instantiated, allowing for more flexibility.
|
enums Size, Movement and Color); but they can also be continuous values (e.g. the mass of a
|
||||||
A third option is to combine pre-defined and/or parameterized properties using boolean logic, allowing for near-endless selection possibilities (this is called "composite specification", see below).
|
Creature). In this case, it is more appropriate to use what we call "parameterized specification",
|
||||||
The pros and cons of each approach are detailed in the table at the end of this document.
|
where the property value can be given as an argument when the Creature is instantiated, allowing for
|
||||||
|
more flexibility. A third option is to combine pre-defined and/or parameterized properties using
|
||||||
|
boolean logic, allowing for near-endless selection possibilities (this is called "composite
|
||||||
|
specification", see below). The pros and cons of each approach are detailed in the table at the end
|
||||||
|
of this document.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public interface Creature {
|
public interface Creature {
|
||||||
@@ -50,7 +60,7 @@ public interface Creature {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
And ``Dragon`` implementation looks like this.
|
And `Dragon` implementation looks like this.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class Dragon extends AbstractCreature {
|
public class Dragon extends AbstractCreature {
|
||||||
@@ -61,7 +71,8 @@ public class Dragon extends AbstractCreature {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now that we want to select some subset of them, we use selectors. To select creatures that fly, we should use ``MovementSelector``.
|
Now that we want to select some subset of them, we use selectors. To select creatures that fly, we
|
||||||
|
should use `MovementSelector`.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class MovementSelector extends AbstractSelector<Creature> {
|
public class MovementSelector extends AbstractSelector<Creature> {
|
||||||
@@ -79,7 +90,8 @@ public class MovementSelector extends AbstractSelector<Creature> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
On the other hand, when selecting creatures heavier than a chosen amount, we use ``MassGreaterThanSelector``.
|
On the other hand, when selecting creatures heavier than a chosen amount, we use
|
||||||
|
`MassGreaterThanSelector`.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class MassGreaterThanSelector extends AbstractSelector<Creature> {
|
public class MassGreaterThanSelector extends AbstractSelector<Creature> {
|
||||||
@@ -111,7 +123,8 @@ But we could also use our parameterized selector like this:
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
```
|
```
|
||||||
|
|
||||||
Our third option is to combine multiple selectors together. Performing a search for special creatures (defined as red, flying, and not small) could be done as follows:
|
Our third option is to combine multiple selectors together. Performing a search for special
|
||||||
|
creatures (defined as red, flying, and not small) could be done as follows:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
var specialCreaturesSelector =
|
var specialCreaturesSelector =
|
||||||
@@ -123,8 +136,9 @@ Our third option is to combine multiple selectors together. Performing a search
|
|||||||
|
|
||||||
**More on Composite Specification**
|
**More on Composite Specification**
|
||||||
|
|
||||||
In Composite Specification, we will create custom instances of ``AbstractSelector`` by combining other selectors (called "leaves") using the three basic logical operators.
|
In Composite Specification, we will create custom instances of `AbstractSelector` by combining
|
||||||
These are implemented in ``ConjunctionSelector``, ``DisjunctionSelector`` and ``NegationSelector``.
|
other selectors (called "leaves") using the three basic logical operators. These are implemented in
|
||||||
|
`ConjunctionSelector`, `DisjunctionSelector` and `NegationSelector`.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public abstract class AbstractSelector<T> implements Predicate<T> {
|
public abstract class AbstractSelector<T> implements Predicate<T> {
|
||||||
@@ -163,12 +177,14 @@ public class ConjunctionSelector<T> extends AbstractSelector<T> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
All that is left to do is now to create leaf selectors (be it hard-coded or parameterized ones) that are as generic as possible,
|
All that is left to do is now to create leaf selectors (be it hard-coded or parameterized ones) that
|
||||||
and we will be able to instantiate the ``AbstractSelector`` class by combining any amount of selectors, as exemplified above.
|
are as generic as possible, and we will be able to instantiate the ``AbstractSelector`` class by
|
||||||
We should be careful though, as it is easy to make a mistake when combining many logical operators; in particular, we should pay attention to the priority of the operations.\
|
combining any amount of selectors, as exemplified above. We should be careful though, as it is easy
|
||||||
In general, Composite Specification is a great way to write more reusable code, as there is no need to create a Selector class for each filtering operation.
|
to make a mistake when combining many logical operators; in particular, we should pay attention to
|
||||||
Instead, we just create an instance of ``AbstractSelector`` "on the spot", using tour generic "leaf" selectors and some basic boolean logic.
|
the priority of the operations. In general, Composite Specification is a great way to write more
|
||||||
|
reusable code, as there is no need to create a Selector class for each filtering operation. Instead,
|
||||||
|
we just create an instance of ``AbstractSelector`` "on the spot", using tour generic "leaf"
|
||||||
|
selectors and some basic boolean logic.
|
||||||
|
|
||||||
**Comparison of the different approaches**
|
**Comparison of the different approaches**
|
||||||
|
|
||||||
@@ -181,9 +197,11 @@ Instead, we just create an instance of ``AbstractSelector`` "on the spot", using
|
|||||||
| | | + Supports logical operations | - You still need to create the base classes used as leaves |
|
| | | + Supports logical operations | - You still need to create the base classes used as leaves |
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Specification pattern when
|
Use the Specification pattern when
|
||||||
|
|
||||||
* You need to select a subset of objects based on some criteria, and to refresh the selection at various times.
|
* You need to select a subset of objects based on some criteria, and to refresh the selection at various times.
|
||||||
|
@@ -9,15 +9,21 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Also known as
|
## Also known as
|
||||||
|
|
||||||
Objects for States
|
Objects for States
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
|
|
||||||
|
Allow an object to alter its behavior when its internal state changes. The object will appear to
|
||||||
|
change its class.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> When observing a mammoth in its natural habitat it seems to change its behavior based on the situation. It may first appear calm but over time when it detects a threat it gets angry and dangerous to its surroundings.
|
> When observing a mammoth in its natural habitat it seems to change its behavior based on the
|
||||||
|
> situation. It may first appear calm but over time when it detects a threat it gets angry and
|
||||||
|
> dangerous to its surroundings.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
@@ -25,7 +31,10 @@ In plain words
|
|||||||
|
|
||||||
Wikipedia says
|
Wikipedia says
|
||||||
|
|
||||||
> The state pattern is a behavioral software design pattern that allows an object to alter its behavior when its internal state changes. This pattern is close to the concept of finite-state machines. The state pattern can be interpreted as a strategy pattern, which is able to switch a strategy through invocations of methods defined in the pattern's interface.
|
> The state pattern is a behavioral software design pattern that allows an object to alter its
|
||||||
|
> behavior when its internal state changes. This pattern is close to the concept of finite-state
|
||||||
|
> machines. The state pattern can be interpreted as a strategy pattern, which is able to switch a
|
||||||
|
> strategy through invocations of methods defined in the pattern's interface.
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
@@ -126,17 +135,23 @@ And here is the full example how the mammoth behaves over time.
|
|||||||
mammoth.observe();
|
mammoth.observe();
|
||||||
mammoth.timePasses();
|
mammoth.timePasses();
|
||||||
mammoth.observe();
|
mammoth.observe();
|
||||||
|
```
|
||||||
// The mammoth gets angry!
|
|
||||||
// The mammoth is furious!
|
Program output:
|
||||||
// The mammoth calms down.
|
|
||||||
// The mammoth is calm and peaceful.
|
```java
|
||||||
|
The mammoth gets angry!
|
||||||
|
The mammoth is furious!
|
||||||
|
The mammoth calms down.
|
||||||
|
The mammoth is calm and peaceful.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the State pattern in either of the following cases
|
Use the State pattern in either of the following cases
|
||||||
|
|
||||||
* An object's behavior depends on its state, and it must change its behavior at run-time depending on that state
|
* An object's behavior depends on its state, and it must change its behavior at run-time depending on that state
|
||||||
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,80 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
|
||||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
|
||||||
<class id="1" language="java" name="com.iluwatar.state.AngryState" project="state"
|
|
||||||
file="/state/src/main/java/com/iluwatar/state/AngryState.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="124" width="157" x="98" y="310"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="2" language="java" name="com.iluwatar.state.PeacefulState" project="state"
|
|
||||||
file="/state/src/main/java/com/iluwatar/state/PeacefulState.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="124" width="173" x="295" y="310"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<interface id="3" language="java" name="com.iluwatar.state.State" project="state"
|
|
||||||
file="/state/src/main/java/com/iluwatar/state/State.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="106" width="136" x="138" y="674"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</interface>
|
|
||||||
<class id="4" language="java" name="com.iluwatar.state.Mammoth" project="state"
|
|
||||||
file="/state/src/main/java/com/iluwatar/state/Mammoth.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="160" width="177" x="138" y="474"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<realization id="5">
|
|
||||||
<bendpoint x="107" y="474"/>
|
|
||||||
<end type="SOURCE" refId="1"/>
|
|
||||||
<end type="TARGET" refId="3"/>
|
|
||||||
</realization>
|
|
||||||
<association id="6">
|
|
||||||
<end type="SOURCE" refId="4" navigable="false">
|
|
||||||
<attribute id="7" name="state"/>
|
|
||||||
<multiplicity id="8" minimum="0" maximum="1"/>
|
|
||||||
</end>
|
|
||||||
<end type="TARGET" refId="3" navigable="true"/>
|
|
||||||
<display labels="true" multiplicity="true"/>
|
|
||||||
</association>
|
|
||||||
<realization id="9">
|
|
||||||
<bendpoint x="345" y="474"/>
|
|
||||||
<end type="SOURCE" refId="2"/>
|
|
||||||
<end type="TARGET" refId="3"/>
|
|
||||||
</realization>
|
|
||||||
<association id="10">
|
|
||||||
<end type="SOURCE" refId="1" navigable="false">
|
|
||||||
<attribute id="11" name="mammoth"/>
|
|
||||||
<multiplicity id="12" minimum="0" maximum="1"/>
|
|
||||||
</end>
|
|
||||||
<end type="TARGET" refId="4" navigable="true"/>
|
|
||||||
<display labels="true" multiplicity="true"/>
|
|
||||||
</association>
|
|
||||||
<association id="13">
|
|
||||||
<end type="SOURCE" refId="2" navigable="false">
|
|
||||||
<attribute id="14" name="mammoth"/>
|
|
||||||
<multiplicity id="15" minimum="0" maximum="1"/>
|
|
||||||
</end>
|
|
||||||
<end type="TARGET" refId="4" navigable="true"/>
|
|
||||||
<display labels="true" multiplicity="true"/>
|
|
||||||
</association>
|
|
||||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</classifier-display>
|
|
||||||
<association-display labels="true" multiplicity="true"/>
|
|
||||||
</class-diagram>
|
|
Before Width: | Height: | Size: 33 KiB |
BIN
state/etc/state_urm.png
Normal file
After Width: | Height: | Size: 34 KiB |
@@ -9,17 +9,20 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Also known as
|
## Also known as
|
||||||
|
|
||||||
Policy
|
Policy
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary
|
|
||||||
independently from clients that use it.
|
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets
|
||||||
|
the algorithm vary independently from clients that use it.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> Slaying dragons is a dangerous profession. With experience it becomes easier. Veteran dragonslayers have developed different fighting strategies against different types of dragons.
|
> Slaying dragons is a dangerous job. With experience it becomes easier. Veteran
|
||||||
|
> dragonslayers have developed different fighting strategies against different types of dragons.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
@@ -27,7 +30,8 @@ In plain words
|
|||||||
|
|
||||||
Wikipedia says
|
Wikipedia says
|
||||||
|
|
||||||
> In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime.
|
> In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral
|
||||||
|
> software design pattern that enables selecting an algorithm at runtime.
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
@@ -71,7 +75,8 @@ public class SpellStrategy implements DragonSlayingStrategy {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
And here is the mighty dragonslayer who is able to pick his fighting strategy based on the opponent.
|
And here is the mighty dragonslayer, who is able to pick his fighting strategy based on the
|
||||||
|
opponent.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class DragonSlayer {
|
public class DragonSlayer {
|
||||||
@@ -92,7 +97,7 @@ public class DragonSlayer {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally here's dragonslayer in action.
|
Finally here's the dragonslayer in action.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
LOGGER.info("Green dragon spotted ahead!");
|
LOGGER.info("Green dragon spotted ahead!");
|
||||||
@@ -104,19 +109,25 @@ Finally here's dragonslayer in action.
|
|||||||
LOGGER.info("Black dragon lands before you.");
|
LOGGER.info("Black dragon lands before you.");
|
||||||
dragonSlayer.changeStrategy(new SpellStrategy());
|
dragonSlayer.changeStrategy(new SpellStrategy());
|
||||||
dragonSlayer.goToBattle();
|
dragonSlayer.goToBattle();
|
||||||
|
```
|
||||||
// Green dragon spotted ahead!
|
|
||||||
// With your Excalibur you sever the dragon's head!
|
Program output:
|
||||||
// Red dragon emerges.
|
|
||||||
// You shoot the dragon with the magical crossbow and it falls dead on the ground!
|
```
|
||||||
// Black dragon lands before you.
|
Green dragon spotted ahead!
|
||||||
// You cast the spell of disintegration and the dragon vaporizes in a pile of dust!
|
With your Excalibur you sever the dragon's head!
|
||||||
|
Red dragon emerges.
|
||||||
|
You shoot the dragon with the magical crossbow and it falls dead on the ground!
|
||||||
|
Black dragon lands before you.
|
||||||
|
You cast the spell of disintegration and the dragon vaporizes in a pile of dust!
|
||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Strategy pattern when
|
Use the Strategy pattern when
|
||||||
|
|
||||||
* Many related classes differ only in their behavior. Strategies provide a way to configure a class either one of many behaviors
|
* Many related classes differ only in their behavior. Strategies provide a way to configure a class either one of many behaviors
|
||||||
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,75 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
|
||||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
|
||||||
<interface id="1" language="java" name="com.iluwatar.strategy.DragonSlayingStrategy" project="strategy"
|
|
||||||
file="/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="88" width="186" x="525" y="428"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</interface>
|
|
||||||
<class id="2" language="java" name="com.iluwatar.strategy.ProjectileStrategy" project="strategy"
|
|
||||||
file="/strategy/src/main/java/com/iluwatar/strategy/ProjectileStrategy.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="106" width="151" x="218" y="264"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="3" language="java" name="com.iluwatar.strategy.SpellStrategy" project="strategy"
|
|
||||||
file="/strategy/src/main/java/com/iluwatar/strategy/SpellStrategy.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="106" width="125" x="409" y="264"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="4" language="java" name="com.iluwatar.strategy.DragonSlayer" project="strategy"
|
|
||||||
file="/strategy/src/main/java/com/iluwatar/strategy/DragonSlayer.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="124" width="284" x="574" y="264"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="5" language="java" name="com.iluwatar.strategy.MeleeStrategy" project="strategy"
|
|
||||||
file="/strategy/src/main/java/com/iluwatar/strategy/MeleeStrategy.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="106" width="127" x="898" y="264"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<realization id="6">
|
|
||||||
<end type="SOURCE" refId="5"/>
|
|
||||||
<end type="TARGET" refId="1"/>
|
|
||||||
</realization>
|
|
||||||
<realization id="7">
|
|
||||||
<end type="SOURCE" refId="2"/>
|
|
||||||
<end type="TARGET" refId="1"/>
|
|
||||||
</realization>
|
|
||||||
<realization id="8">
|
|
||||||
<end type="SOURCE" refId="3"/>
|
|
||||||
<end type="TARGET" refId="1"/>
|
|
||||||
</realization>
|
|
||||||
<association id="9">
|
|
||||||
<end type="SOURCE" refId="4" navigable="false">
|
|
||||||
<attribute id="10" name="strategy"/>
|
|
||||||
<multiplicity id="11" minimum="0" maximum="1"/>
|
|
||||||
</end>
|
|
||||||
<end type="TARGET" refId="1" navigable="true"/>
|
|
||||||
<display labels="true" multiplicity="true"/>
|
|
||||||
</association>
|
|
||||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</classifier-display>
|
|
||||||
<association-display labels="true" multiplicity="true"/>
|
|
||||||
</class-diagram>
|
|
Before Width: | Height: | Size: 28 KiB |
BIN
strategy/etc/strategy_urm.png
Normal file
After Width: | Height: | Size: 29 KiB |
@@ -9,21 +9,30 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template method lets
|
|
||||||
subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
|
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template
|
||||||
|
Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's
|
||||||
|
structure.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> The general steps in stealing an item are the same. First you pick the target, next you confuse him somehow and finally you steal the item. However there are many ways to implement these steps.
|
> The general steps in stealing an item are the same. First you pick the target, next you confuse
|
||||||
|
> him somehow and finally you steal the item. However there are many ways to implement these steps.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
> Template Method pattern outlines the general steps in the parent class and lets the concrete child implementations define the details.
|
> Template Method pattern outlines the general steps in the parent class and lets the concrete child
|
||||||
|
> implementations define the details.
|
||||||
|
|
||||||
Wikipedia says
|
Wikipedia says
|
||||||
|
|
||||||
> In object-oriented programming, the template method is one of the behavioral design patterns identified by Gamma et al. in the book Design Patterns. The template method is a method in a superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of a number of high-level steps. These steps are themselves implemented by additional helper methods in the same class as the template method.
|
> In object-oriented programming, the template method is one of the behavioral design patterns
|
||||||
|
> identified by Gamma et al. in the book Design Patterns. The template method is a method in a
|
||||||
|
> superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of
|
||||||
|
> a number of high-level steps. These steps are themselves implemented by additional helper methods
|
||||||
|
> in the same class as the template method.
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
@@ -120,9 +129,11 @@ And finally we show how the halfling thief utilizes the different stealing metho
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
The Template Method pattern should be used
|
The Template Method pattern should be used
|
||||||
|
|
||||||
* To implement the invariant parts of an algorithm once and leave it up to subclasses to implement the behavior that can vary
|
* To implement the invariant parts of an algorithm once and leave it up to subclasses to implement the behavior that can vary
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,66 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
|
||||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
|
||||||
<class id="1" language="java" name="com.iluwatar.templatemethod.SubtleMethod" project="template-method"
|
|
||||||
file="/template-method/src/main/java/com/iluwatar/templatemethod/SubtleMethod.java" binary="false"
|
|
||||||
corner="BOTTOM_RIGHT">
|
|
||||||
<position height="142" width="174" x="-90" y="683"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="2" language="java" name="com.iluwatar.templatemethod.StealingMethod" project="template-method"
|
|
||||||
file="/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java" binary="false"
|
|
||||||
corner="BOTTOM_RIGHT">
|
|
||||||
<position height="159" width="177" x="17" y="484"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="3" language="java" name="com.iluwatar.templatemethod.HalflingThief" project="template-method"
|
|
||||||
file="/template-method/src/main/java/com/iluwatar/templatemethod/HalflingThief.java" binary="false"
|
|
||||||
corner="BOTTOM_RIGHT">
|
|
||||||
<position height="124" width="235" x="17" y="320"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="4" language="java" name="com.iluwatar.templatemethod.HitAndRunMethod" project="template-method"
|
|
||||||
file="/template-method/src/main/java/com/iluwatar/templatemethod/HitAndRunMethod.java" binary="false"
|
|
||||||
corner="BOTTOM_RIGHT">
|
|
||||||
<position height="142" width="174" x="124" y="683"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<association id="5">
|
|
||||||
<end type="SOURCE" refId="3" navigable="false">
|
|
||||||
<attribute id="6" name="method"/>
|
|
||||||
<multiplicity id="7" minimum="0" maximum="1"/>
|
|
||||||
</end>
|
|
||||||
<end type="TARGET" refId="2" navigable="true"/>
|
|
||||||
<display labels="true" multiplicity="true"/>
|
|
||||||
</association>
|
|
||||||
<generalization id="8">
|
|
||||||
<end type="SOURCE" refId="4"/>
|
|
||||||
<end type="TARGET" refId="2"/>
|
|
||||||
</generalization>
|
|
||||||
<generalization id="9">
|
|
||||||
<end type="SOURCE" refId="1"/>
|
|
||||||
<end type="TARGET" refId="2"/>
|
|
||||||
</generalization>
|
|
||||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</classifier-display>
|
|
||||||
<association-display labels="true" multiplicity="true"/>
|
|
||||||
</class-diagram>
|
|
Before Width: | Height: | Size: 34 KiB |
BIN
template-method/etc/template_method_urm.png
Normal file
After Width: | Height: | Size: 36 KiB |
@@ -9,16 +9,19 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
It is often the case that tasks to be executed are short-lived and
|
|
||||||
the number of tasks is large. Creating a new thread for each task would make
|
It is often the case that tasks to be executed are short-lived and the number of tasks is large.
|
||||||
the system spend more time creating and destroying the threads than executing
|
Creating a new thread for each task would make the system spend more time creating and destroying
|
||||||
the actual tasks. Thread Pool solves this problem by reusing existing threads
|
the threads than executing the actual tasks. Thread Pool solves this problem by reusing existing
|
||||||
and eliminating the latency of creating new threads.
|
threads and eliminating the latency of creating new threads.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we establish a thread pool.
|
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes
|
||||||
|
> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
|
||||||
|
> establish a thread pool.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
@@ -26,11 +29,18 @@ In plain words
|
|||||||
|
|
||||||
Wikipedia says
|
Wikipedia says
|
||||||
|
|
||||||
> In computer programming, a thread pool is a software design pattern for achieving concurrency of execution in a computer program. Often also called a replicated workers or worker-crew model, a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program. By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks. The number of available threads is tuned to the computing resources available to the program, such as a parallel task queue after completion of execution.
|
> In computer programming, a thread pool is a software design pattern for achieving concurrency of
|
||||||
|
> execution in a computer program. Often also called a replicated workers or worker-crew model,
|
||||||
|
> a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent
|
||||||
|
> execution by the supervising program. By maintaining a pool of threads, the model increases
|
||||||
|
> performance and avoids latency in execution due to frequent creation and destruction of threads
|
||||||
|
> for short-lived tasks. The number of available threads is tuned to the computing resources
|
||||||
|
> available to the program, such as a parallel task queue after completion of execution.
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
Let's first look at our task hierarchy. We have a base class and then concrete CoffeeMakingTask and PotatoPeelingTask.
|
Let's first look at our task hierarchy. We have a base class and then concrete `CoffeeMakingTask`
|
||||||
|
and `PotatoPeelingTask`.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public abstract class Task {
|
public abstract class Task {
|
||||||
@@ -88,8 +98,8 @@ public class PotatoPeelingTask extends Task {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next we present a runnable Worker class that the thread pool will utilize to handle all the potato peeling and coffee
|
Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
|
||||||
making.
|
peeling and coffee making.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class Worker implements Runnable {
|
public class Worker implements Runnable {
|
||||||
@@ -156,9 +166,11 @@ Now we are ready to show the full example in action.
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Thread Pool pattern when
|
Use the Thread Pool pattern when
|
||||||
|
|
||||||
* You have a large number of short-lived tasks to be executed in parallel
|
* You have a large number of short-lived tasks to be executed in parallel
|
||||||
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,62 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
|
||||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
|
||||||
<class id="1" language="java" name="com.iluwatar.Task" project="thread-pool"
|
|
||||||
file="/thread-pool/src/main/java/com/iluwatar/Task.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="-1" width="-1" x="343" y="579"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="2" language="java" name="com.iluwatar.Worker" project="thread-pool"
|
|
||||||
file="/thread-pool/src/main/java/com/iluwatar/Worker.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="-1" width="-1" x="344" y="389"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="3" language="java" name="com.iluwatar.CoffeeMakingTask" project="thread-pool"
|
|
||||||
file="/thread-pool/src/main/java/com/iluwatar/CoffeeMakingTask.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="124" width="156" x="194" y="717"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="4" language="java" name="com.iluwatar.PotatoPeelingTask" project="thread-pool"
|
|
||||||
file="/thread-pool/src/main/java/com/iluwatar/PotatoPeelingTask.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="124" width="169" x="390" y="717"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<association id="5">
|
|
||||||
<end type="SOURCE" refId="2" navigable="false">
|
|
||||||
<attribute id="6" name="task"/>
|
|
||||||
<multiplicity id="7" minimum="0" maximum="1"/>
|
|
||||||
</end>
|
|
||||||
<end type="TARGET" refId="1" navigable="true"/>
|
|
||||||
<display labels="true" multiplicity="true"/>
|
|
||||||
</association>
|
|
||||||
<generalization id="8">
|
|
||||||
<end type="SOURCE" refId="4"/>
|
|
||||||
<end type="TARGET" refId="1"/>
|
|
||||||
</generalization>
|
|
||||||
<generalization id="9">
|
|
||||||
<end type="SOURCE" refId="3"/>
|
|
||||||
<end type="TARGET" refId="1"/>
|
|
||||||
</generalization>
|
|
||||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</classifier-display>
|
|
||||||
<association-display labels="true" multiplicity="true"/>
|
|
||||||
</class-diagram>
|
|
BIN
thread-pool/etc/thread_pool_urm.png
Normal file
After Width: | Height: | Size: 29 KiB |
@@ -10,12 +10,15 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
Ensure that a given client is not able to access service resources more than the assigned limit.
|
Ensure that a given client is not able to access service resources more than the assigned limit.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> A large multinational corporation offers API to its customers. The API is rate-limited and each customer can only make certain amount of calls per second.
|
> A large multinational corporation offers API to its customers. The API is rate-limited and each
|
||||||
|
> customer can only make certain amount of calls per second.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
@@ -23,7 +26,9 @@ In plain words
|
|||||||
|
|
||||||
[Microsoft documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling) says
|
[Microsoft documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling) says
|
||||||
|
|
||||||
> Control the consumption of resources used by an instance of an application, an individual tenant, or an entire service. This can allow the system to continue to function and meet service level agreements, even when an increase in demand places an extreme load on resources.
|
> Control the consumption of resources used by an instance of an application, an individual tenant,
|
||||||
|
> or an entire service. This can allow the system to continue to function and meet service level
|
||||||
|
> agreements, even when an increase in demand places an extreme load on resources.
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
@@ -77,7 +82,8 @@ public final class CallsCount {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next we introduce the service that the tenants are calling. To track the call count we use the throttler timer.
|
Next we introduce the service that the tenants are calling. To track the call count we use the
|
||||||
|
throttler timer.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public interface Throttler {
|
public interface Throttler {
|
||||||
@@ -134,7 +140,8 @@ class B2BService {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per second and Nike to 6.
|
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
|
||||||
|
second and Nike to 6.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
@@ -171,9 +178,11 @@ Now we are ready to see the full example in action. Tenant Adidas is rate-limite
|
|||||||
|
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
The Throttling pattern should be used:
|
The Throttling pattern should be used:
|
||||||
|
|
||||||
* When a service access needs to be restricted to not have high impacts on the performance of the service.
|
* When a service access needs to be restricted to not have high impacts on the performance of the service.
|
||||||
|
Before Width: | Height: | Size: 49 KiB |
@@ -1,88 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<class-diagram version="1.2.2" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
|
|
||||||
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
|
|
||||||
<class id="1" language="java" name="com.iluwatar.throttling.CallsCount" project="throttling"
|
|
||||||
file="/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="211" width="256" x="656" y="228"/>
|
|
||||||
<display autosize="false" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="2" language="java" name="com.iluwatar.throttling.Tenant" project="throttling"
|
|
||||||
file="/throttling/src/main/java/com/iluwatar/throttling/Tenant.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="-1" width="-1" x="465" y="524"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="3" language="java" name="com.iluwatar.throttling.B2BService" project="throttling"
|
|
||||||
file="/throttling/src/main/java/com/iluwatar/throttling/B2BService.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="-1" width="-1" x="464" y="192"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<interface id="4" language="java" name="com.iluwatar.throttling.timer.Throttler" project="throttling"
|
|
||||||
file="/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="-1" width="-1" x="167" y="174"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</interface>
|
|
||||||
<class id="5" language="java" name="com.iluwatar.throttling.timer.ThrottleTimerImpl" project="throttling"
|
|
||||||
file="/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java" binary="false"
|
|
||||||
corner="BOTTOM_RIGHT">
|
|
||||||
<position height="-1" width="-1" x="166" y="396"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<association id="6">
|
|
||||||
<end type="SOURCE" refId="3" navigable="false">
|
|
||||||
<attribute id="7" name="callsCount"/>
|
|
||||||
<multiplicity id="8" minimum="0" maximum="1"/>
|
|
||||||
</end>
|
|
||||||
<end type="TARGET" refId="1" navigable="true"/>
|
|
||||||
<display labels="true" multiplicity="true"/>
|
|
||||||
</association>
|
|
||||||
<dependency id="9">
|
|
||||||
<end type="SOURCE" refId="3"/>
|
|
||||||
<end type="TARGET" refId="4"/>
|
|
||||||
</dependency>
|
|
||||||
<dependency id="10">
|
|
||||||
<end type="SOURCE" refId="3"/>
|
|
||||||
<end type="TARGET" refId="2"/>
|
|
||||||
</dependency>
|
|
||||||
<association id="11">
|
|
||||||
<end type="SOURCE" refId="5" navigable="false">
|
|
||||||
<attribute id="12" name="callsCount"/>
|
|
||||||
<multiplicity id="13" minimum="0" maximum="1"/>
|
|
||||||
</end>
|
|
||||||
<end type="TARGET" refId="1" navigable="true"/>
|
|
||||||
<display labels="true" multiplicity="true"/>
|
|
||||||
</association>
|
|
||||||
<dependency id="14">
|
|
||||||
<end type="SOURCE" refId="2"/>
|
|
||||||
<end type="TARGET" refId="1"/>
|
|
||||||
</dependency>
|
|
||||||
<realization id="15">
|
|
||||||
<end type="SOURCE" refId="5"/>
|
|
||||||
<end type="TARGET" refId="4"/>
|
|
||||||
</realization>
|
|
||||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</classifier-display>
|
|
||||||
<association-display labels="true" multiplicity="true"/>
|
|
||||||
</class-diagram>
|
|
BIN
throttling/etc/throttling_urm.png
Normal file
After Width: | Height: | Size: 52 KiB |
@@ -24,7 +24,6 @@
|
|||||||
package com.iluwatar.throttling;
|
package com.iluwatar.throttling;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@@ -9,14 +9,18 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Tolerant Reader is an integration pattern that helps creating robust communication systems. The idea is to be as
|
|
||||||
tolerant as possible when reading data from another service. This way, when the communication schema changes, the
|
Tolerant Reader is an integration pattern that helps creating robust communication systems. The idea
|
||||||
readers must not break.
|
is to be as tolerant as possible when reading data from another service. This way, when the
|
||||||
|
communication schema changes, the readers must not break.
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
|
|
||||||
> We are persisting rainbowfish objects to file and later on they need to be restored. What makes it problematic is that rainbowfish data structure is versioned and evolves over time. New version of rainbowfish needs to be able to restore old versions as well.
|
> We are persisting rainbowfish objects to file and later on they need to be restored. What makes it
|
||||||
|
> problematic is that rainbowfish data structure is versioned and evolves over time. New version of
|
||||||
|
> rainbowfish needs to be able to restore old versions as well.
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
|
|
||||||
@@ -24,11 +28,11 @@ In plain words
|
|||||||
|
|
||||||
[Robustness Principle](https://java-design-patterns.com/principles/#robustness-principle) says
|
[Robustness Principle](https://java-design-patterns.com/principles/#robustness-principle) says
|
||||||
|
|
||||||
> Be conservative in what you do, be liberal in what you accept from others
|
> Be conservative in what you do, be liberal in what you accept from others.
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
Here's the versioned rainbowfish. Notice how the second version introduces additional properties.
|
Here's the versioned `RainbowFish`. Notice how the second version introduces additional properties.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class RainbowFish implements Serializable {
|
public class RainbowFish implements Serializable {
|
||||||
@@ -104,7 +108,8 @@ public class RainbowFishV2 extends RainbowFish {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next we introduce the rainbowfish serializer. This is the class that implements the Tolerant Reader pattern.
|
Next we introduce the `RainbowFishSerializer`. This is the class that implements the Tolerant Reader
|
||||||
|
pattern.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public final class RainbowFishSerializer {
|
public final class RainbowFishSerializer {
|
||||||
@@ -185,18 +190,23 @@ And finally here's the full example in action.
|
|||||||
LOGGER.info("deserializedFishV2 name={} age={} length={} weight={}",
|
LOGGER.info("deserializedFishV2 name={} age={} length={} weight={}",
|
||||||
deserializedFishV2.getName(), deserializedFishV2.getAge(),
|
deserializedFishV2.getName(), deserializedFishV2.getAge(),
|
||||||
deserializedFishV2.getLengthMeters(), deserializedFishV2.getWeightTons());
|
deserializedFishV2.getLengthMeters(), deserializedFishV2.getWeightTons());
|
||||||
|
|
||||||
// fishV1 name=Zed age=10 length=11 weight=12
|
|
||||||
// deserializedFishV1 name=Zed age=10 length=11 weight=12
|
|
||||||
// fishV2 name=Scar age=5 length=12 weight=15 sleeping=true hungry=true angry=true
|
|
||||||
// deserializedFishV2 name=Scar age=5 length=12 weight=15
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Program output:
|
||||||
|
|
||||||
|
```
|
||||||
|
fishV1 name=Zed age=10 length=11 weight=12
|
||||||
|
deserializedFishV1 name=Zed age=10 length=11 weight=12
|
||||||
|
fishV2 name=Scar age=5 length=12 weight=15 sleeping=true hungry=true angry=true
|
||||||
|
deserializedFishV2 name=Scar age=5 length=12 weight=15
|
||||||
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Tolerant Reader pattern when
|
Use the Tolerant Reader pattern when
|
||||||
|
|
||||||
* The communication schema can evolve and change and yet the receiving side should not break
|
* The communication schema can evolve and change and yet the receiving side should not break
|
||||||
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,52 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
|
||||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
|
||||||
<class id="1" language="java" name="com.iluwatar.tolerantreader.RainbowFishSerializer" project="tolerant-reader"
|
|
||||||
file="/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java" binary="false"
|
|
||||||
corner="BOTTOM_RIGHT">
|
|
||||||
<position height="-1" width="-1" x="123" y="415"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="2" language="java" name="com.iluwatar.tolerantreader.RainbowFishV2" project="tolerant-reader"
|
|
||||||
file="/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java" binary="false"
|
|
||||||
corner="BOTTOM_RIGHT">
|
|
||||||
<position height="-1" width="-1" x="389" y="685"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<class id="3" language="java" name="com.iluwatar.tolerantreader.RainbowFish" project="tolerant-reader"
|
|
||||||
file="/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java" binary="false"
|
|
||||||
corner="BOTTOM_RIGHT">
|
|
||||||
<position height="-1" width="-1" x="389" y="404"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<dependency id="4">
|
|
||||||
<end type="SOURCE" refId="1"/>
|
|
||||||
<end type="TARGET" refId="3"/>
|
|
||||||
</dependency>
|
|
||||||
<dependency id="5">
|
|
||||||
<end type="SOURCE" refId="1"/>
|
|
||||||
<end type="TARGET" refId="2"/>
|
|
||||||
</dependency>
|
|
||||||
<generalization id="6">
|
|
||||||
<end type="SOURCE" refId="2"/>
|
|
||||||
<end type="TARGET" refId="3"/>
|
|
||||||
</generalization>
|
|
||||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</classifier-display>
|
|
||||||
<association-display labels="true" multiplicity="true"/>
|
|
||||||
</class-diagram>
|
|
BIN
tolerant-reader/etc/tolerant_reader_urm.png
Normal file
After Width: | Height: | Size: 45 KiB |