Compare commits
122 Commits
all-contri
...
all-contri
Author | SHA1 | Date | |
---|---|---|---|
be52efc49b | |||
e47a24643a | |||
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 | |||
6f592f5e8a | |||
74b968942f | |||
ebc0e8b3cd | |||
9088ac51f6 | |||
c8f7a8f0e6 | |||
c63af2ccbf | |||
9f3f5322d2 | |||
5607a4974c | |||
93aa1046aa | |||
a2967c5a40 | |||
7fd7735527 | |||
f6942cf18d | |||
ef326ee77e | |||
8b5f532a50 | |||
a1da1e4973 | |||
9d21dff855 | |||
bab48efd7c | |||
29eecfd048 | |||
87cf6b791c | |||
2e36a11e24 | |||
bd48d6ce10 | |||
e89042a782 | |||
fb890e80dd | |||
46d4155328 | |||
a968dce586 |
@ -315,7 +315,8 @@
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/6295975?v=4",
|
||||
"profile": "https://github.com/Anurag870",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -946,7 +947,8 @@
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/23739158?v=4",
|
||||
"profile": "https://github.com/grzesiekkedzior",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"review"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -1195,6 +1197,123 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "vdlald",
|
||||
"name": "Vladislav Golubinov",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/29997701?v=4",
|
||||
"profile": "https://github.com/vdlald",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "swarajsaaj",
|
||||
"name": "Swaraj",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/6285049?v=4",
|
||||
"profile": "https://github.com/swarajsaaj",
|
||||
"contributions": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 4,
|
||||
|
20
.github/workflows/maven-ci.yml
vendored
@ -30,29 +30,45 @@ on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Disabling shallow clone for improving relevancy of SonarQube reporting
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- uses: actions/cache@v2
|
||||
|
||||
- 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:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
# Some tests need screen access
|
||||
- name: Install xvfb
|
||||
run: sudo apt-get install -y xvfb
|
||||
|
||||
# The SonarQube analysis is only for the master branch of the main repository.
|
||||
# SonarQube scan does not work for forked repositories try changing it to xvfb-run mvn clean verify
|
||||
# See https://jira.sonarsource.com/browse/MMF-1371
|
||||
|
11
.github/workflows/maven-pr-builder.yml
vendored
@ -29,6 +29,7 @@ name: Java PR Builder
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
types: [ opened, reopened, synchronize, labeled, unlabeled ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -36,20 +37,26 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- uses: actions/cache@v2
|
||||
|
||||
- name: Cache Maven Dependecies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
# Some tests need screen access
|
||||
- name: Install xvfb
|
||||
run: sudo apt-get install -y xvfb
|
||||
|
||||
# This worflow is only for building Pull Requests, the master branch runs Sonar analysis on the main repository.
|
||||
# SonarQube scan does not work for forked repositories.
|
||||
# See https://jira.sonarsource.com/browse/MMF-1371
|
||||
|
30
README.md
@ -10,7 +10,7 @@
|
||||
[](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)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
# 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
|
||||
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
|
||||
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.
|
||||
|
||||
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>
|
||||
<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="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>
|
||||
@ -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="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/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>
|
||||
<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>
|
||||
@ -246,7 +246,7 @@ This project is licensed under the terms of the MIT license.
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/nishant"><img src="https://avatars2.githubusercontent.com/u/15331971?v=4" width="100px;" alt=""/><br /><sub><b>Nishant Arora</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=nishant" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/raja-peeyush-kumar-singh"><img src="https://avatars0.githubusercontent.com/u/5496024?v=4" width="100px;" alt=""/><br /><sub><b>Peeyush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=raja-peeyush-kumar-singh" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ravening"><img src="https://avatars1.githubusercontent.com/u/10645273?v=4" width="100px;" alt=""/><br /><sub><b>Rakesh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ravening" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ravening"><img src="https://avatars1.githubusercontent.com/u/10645273?v=4" width="100px;" alt=""/><br /><sub><b>Rakesh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ravening" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Aravening" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://github.com/vINCENT8888801"><img src="https://avatars0.githubusercontent.com/u/8037883?v=4" width="100px;" alt=""/><br /><sub><b>Wei Seng</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=vINCENT8888801" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -262,10 +262,28 @@ This project is licensed under the terms of the MIT license.
|
||||
<td align="center"><a href="https://github.com/mkrzywanski"><img src="https://avatars0.githubusercontent.com/u/15279585?v=4" width="100px;" alt=""/><br /><sub><b>Michał Krzywański</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=mkrzywanski" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ravening"><img src="https://avatars1.githubusercontent.com/u/10645273?v=4" width="100px;" alt=""/><br /><sub><b>Rakesh</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ravening" title="Code">💻</a> <a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Aravening" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://www.stefan-birkner.de"><img src="https://avatars1.githubusercontent.com/u/711349?v=4" width="100px;" alt=""/><br /><sub><b>Stefan Birkner</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=stefanbirkner" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/fedorskvorcov"><img src="https://avatars3.githubusercontent.com/u/43882212?v=4" width="100px;" alt=""/><br /><sub><b>Fedor Skvorcov</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=fedorskvorcov" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/samilAyoub"><img src="https://avatars0.githubusercontent.com/u/61546990?v=4" width="100px;" alt=""/><br /><sub><b>samilAyoub</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=samilAyoub" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/vdlald"><img src="https://avatars0.githubusercontent.com/u/29997701?v=4" width="100px;" alt=""/><br /><sub><b>Vladislav Golubinov</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=vdlald" title="Code">💻</a></td>
|
||||
</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="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>
|
||||
</table>
|
||||
|
||||
|
@ -23,7 +23,6 @@
|
||||
|
||||
package com.iluwatar.abstractfactory;
|
||||
|
||||
import com.iluwatar.abstractfactory.App.FactoryMaker.KingdomType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -41,84 +40,14 @@ import org.slf4j.LoggerFactory;
|
||||
* and its implementations ( {@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses
|
||||
* both concrete implementations to create a king, a castle and an army.
|
||||
*/
|
||||
public class App {
|
||||
public class App implements Runnable {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
private static Logger log = LoggerFactory.getLogger(App.class);
|
||||
|
||||
private King king;
|
||||
private Castle castle;
|
||||
private Army army;
|
||||
private final Kingdom kingdom = new Kingdom();
|
||||
|
||||
/**
|
||||
* Creates kingdom.
|
||||
*/
|
||||
public void createKingdom(final KingdomFactory factory) {
|
||||
setKing(factory.createKing());
|
||||
setCastle(factory.createCastle());
|
||||
setArmy(factory.createArmy());
|
||||
}
|
||||
|
||||
King getKing(final KingdomFactory factory) {
|
||||
return factory.createKing();
|
||||
}
|
||||
|
||||
public King getKing() {
|
||||
return king;
|
||||
}
|
||||
|
||||
private void setKing(final King king) {
|
||||
this.king = king;
|
||||
}
|
||||
|
||||
Castle getCastle(final KingdomFactory factory) {
|
||||
return factory.createCastle();
|
||||
}
|
||||
|
||||
public Castle getCastle() {
|
||||
return castle;
|
||||
}
|
||||
|
||||
private void setCastle(final Castle castle) {
|
||||
this.castle = castle;
|
||||
}
|
||||
|
||||
Army getArmy(final KingdomFactory factory) {
|
||||
return factory.createArmy();
|
||||
}
|
||||
|
||||
public Army getArmy() {
|
||||
return army;
|
||||
}
|
||||
|
||||
private void setArmy(final Army army) {
|
||||
this.army = army;
|
||||
}
|
||||
|
||||
/**
|
||||
* The factory of kingdom factories.
|
||||
*/
|
||||
public static class FactoryMaker {
|
||||
|
||||
/**
|
||||
* Enumeration for the different types of Kingdoms.
|
||||
*/
|
||||
public enum KingdomType {
|
||||
ELF, ORC
|
||||
}
|
||||
|
||||
/**
|
||||
* The factory method to create KingdomFactory concrete objects.
|
||||
*/
|
||||
public static KingdomFactory makeFactory(KingdomType type) {
|
||||
switch (type) {
|
||||
case ELF:
|
||||
return new ElfKingdomFactory();
|
||||
case ORC:
|
||||
return new OrcKingdomFactory();
|
||||
default:
|
||||
throw new IllegalArgumentException("KingdomType not supported.");
|
||||
}
|
||||
}
|
||||
public Kingdom getKingdom() {
|
||||
return kingdom;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -127,19 +56,33 @@ public class App {
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
var app = new App();
|
||||
app.run();
|
||||
}
|
||||
|
||||
LOGGER.info("Elf Kingdom");
|
||||
app.createKingdom(FactoryMaker.makeFactory(KingdomType.ELF));
|
||||
LOGGER.info(app.getArmy().getDescription());
|
||||
LOGGER.info(app.getCastle().getDescription());
|
||||
LOGGER.info(app.getKing().getDescription());
|
||||
@Override
|
||||
public void run() {
|
||||
log.info("Elf Kingdom");
|
||||
createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
||||
log.info(kingdom.getArmy().getDescription());
|
||||
log.info(kingdom.getCastle().getDescription());
|
||||
log.info(kingdom.getKing().getDescription());
|
||||
|
||||
LOGGER.info("Orc Kingdom");
|
||||
app.createKingdom(FactoryMaker.makeFactory(KingdomType.ORC));
|
||||
LOGGER.info(app.getArmy().getDescription());
|
||||
LOGGER.info(app.getCastle().getDescription());
|
||||
LOGGER.info(app.getKing().getDescription());
|
||||
log.info("Orc Kingdom");
|
||||
createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
|
||||
log.info(kingdom.getArmy().getDescription());
|
||||
log.info(kingdom.getCastle().getDescription());
|
||||
log.info(kingdom.getKing().getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates kingdom.
|
||||
* @param kingdomType type of Kingdom
|
||||
*/
|
||||
public void createKingdom(final Kingdom.FactoryMaker.KingdomType kingdomType) {
|
||||
final KingdomFactory kingdomFactory = Kingdom.FactoryMaker.makeFactory(kingdomType);
|
||||
kingdom.setKing(kingdomFactory.createKing());
|
||||
kingdom.setCastle(kingdomFactory.createCastle());
|
||||
kingdom.setArmy(kingdomFactory.createArmy());
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class Kingdom {
|
||||
|
||||
private King king;
|
||||
private Castle castle;
|
||||
private Army army;
|
||||
|
||||
public King getKing() {
|
||||
return king;
|
||||
}
|
||||
|
||||
public Castle getCastle() {
|
||||
return castle;
|
||||
}
|
||||
|
||||
public Army getArmy() {
|
||||
return army;
|
||||
}
|
||||
|
||||
public void setKing(King king) {
|
||||
this.king = king;
|
||||
}
|
||||
|
||||
public void setCastle(Castle castle) {
|
||||
this.castle = castle;
|
||||
}
|
||||
|
||||
public void setArmy(Army army) {
|
||||
this.army = army;
|
||||
}
|
||||
|
||||
/**
|
||||
* The factory of kingdom factories.
|
||||
*/
|
||||
public static class FactoryMaker {
|
||||
|
||||
/**
|
||||
* Enumeration for the different types of Kingdoms.
|
||||
*/
|
||||
public enum KingdomType {
|
||||
ELF, ORC
|
||||
}
|
||||
|
||||
/**
|
||||
* The factory method to create KingdomFactory concrete objects.
|
||||
*/
|
||||
public static KingdomFactory makeFactory(KingdomType type) {
|
||||
switch (type) {
|
||||
case ELF:
|
||||
return new ElfKingdomFactory();
|
||||
case ORC:
|
||||
return new OrcKingdomFactory();
|
||||
default:
|
||||
throw new IllegalArgumentException("KingdomType not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,65 +23,71 @@
|
||||
|
||||
package com.iluwatar.abstractfactory;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.iluwatar.abstractfactory.App.FactoryMaker;
|
||||
import com.iluwatar.abstractfactory.App.FactoryMaker.KingdomType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test for abstract factory.
|
||||
*/
|
||||
public class AbstractFactoryTest {
|
||||
|
||||
private final App app = new App();
|
||||
private KingdomFactory elfFactory;
|
||||
private KingdomFactory orcFactory;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
elfFactory = FactoryMaker.makeFactory(KingdomType.ELF);
|
||||
orcFactory = FactoryMaker.makeFactory(KingdomType.ORC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void king() {
|
||||
final var elfKing = app.getKing(elfFactory);
|
||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
||||
final var kingdom = app.getKingdom();
|
||||
|
||||
final var elfKing = kingdom.getKing();
|
||||
assertTrue(elfKing instanceof ElfKing);
|
||||
assertEquals(ElfKing.DESCRIPTION, elfKing.getDescription());
|
||||
final var orcKing = app.getKing(orcFactory);
|
||||
|
||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
|
||||
final var orcKing = kingdom.getKing();
|
||||
assertTrue(orcKing instanceof OrcKing);
|
||||
assertEquals(OrcKing.DESCRIPTION, orcKing.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castle() {
|
||||
final var elfCastle = app.getCastle(elfFactory);
|
||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
||||
final var kingdom = app.getKingdom();
|
||||
|
||||
final var elfCastle = kingdom.getCastle();
|
||||
assertTrue(elfCastle instanceof ElfCastle);
|
||||
assertEquals(ElfCastle.DESCRIPTION, elfCastle.getDescription());
|
||||
final var orcCastle = app.getCastle(orcFactory);
|
||||
|
||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
|
||||
final var orcCastle = kingdom.getCastle();
|
||||
assertTrue(orcCastle instanceof OrcCastle);
|
||||
assertEquals(OrcCastle.DESCRIPTION, orcCastle.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void army() {
|
||||
final var elfArmy = app.getArmy(elfFactory);
|
||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
||||
final var kingdom = app.getKingdom();
|
||||
|
||||
final var elfArmy = kingdom.getArmy();
|
||||
assertTrue(elfArmy instanceof ElfArmy);
|
||||
assertEquals(ElfArmy.DESCRIPTION, elfArmy.getDescription());
|
||||
final var orcArmy = app.getArmy(orcFactory);
|
||||
|
||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
|
||||
final var orcArmy = kingdom.getArmy();
|
||||
assertTrue(orcArmy instanceof OrcArmy);
|
||||
assertEquals(OrcArmy.DESCRIPTION, orcArmy.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createElfKingdom() {
|
||||
app.createKingdom(elfFactory);
|
||||
final var king = app.getKing();
|
||||
final var castle = app.getCastle();
|
||||
final var army = app.getArmy();
|
||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
||||
final var kingdom = app.getKingdom();
|
||||
|
||||
final var king = kingdom.getKing();
|
||||
final var castle = kingdom.getCastle();
|
||||
final var army = kingdom.getArmy();
|
||||
assertTrue(king instanceof ElfKing);
|
||||
assertEquals(ElfKing.DESCRIPTION, king.getDescription());
|
||||
assertTrue(castle instanceof ElfCastle);
|
||||
@ -92,10 +98,12 @@ public class AbstractFactoryTest {
|
||||
|
||||
@Test
|
||||
public void createOrcKingdom() {
|
||||
app.createKingdom(orcFactory);
|
||||
final var king = app.getKing();
|
||||
final var castle = app.getCastle();
|
||||
final var army = app.getArmy();
|
||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
|
||||
final var kingdom = app.getKingdom();
|
||||
|
||||
final var king = kingdom.getKing();
|
||||
final var castle = kingdom.getCastle();
|
||||
final var army = kingdom.getArmy();
|
||||
assertTrue(king instanceof OrcKing);
|
||||
assertEquals(OrcKing.DESCRIPTION, king.getDescription());
|
||||
assertTrue(castle instanceof OrcCastle);
|
||||
|
@ -19,12 +19,15 @@ cannot bring the whole application down, and we can reconnect to the service as
|
||||
|
||||
Real world example
|
||||
|
||||
> Imagine a web application that has both local files/images and remote database entries to serve.
|
||||
> The database might not be responding due to a variety of reasons, so if the application keeps
|
||||
> trying to read from the database using multiple threads/processes, soon all of them will hang
|
||||
> causing our entire web application will crash. We should be able to detect this situation and show
|
||||
> the user an appropriate message so that he/she can explore other parts of the app unaffected by
|
||||
> the database failure.
|
||||
> Imagine a web application that has both local files/images and remote services that are used for
|
||||
> fetching data. These remote services may be either healthy and responsive at times, or may become
|
||||
> slow and unresponsive at some point of time due to variety of reasons. So if one of the remote
|
||||
> services is slow or not responding successfully, our application will try to fetch response from
|
||||
> the remote service using multiple threads/processes, soon all of them will hang (also called
|
||||
> [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
|
||||
|
||||
@ -52,40 +55,104 @@ In terms of code, the end user application is:
|
||||
|
||||
```java
|
||||
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) {
|
||||
var obj = new MonitoringService();
|
||||
var circuitBreaker = new CircuitBreaker(3000, 1, 2000 * 1000 * 1000);
|
||||
|
||||
var serverStartTime = System.nanoTime();
|
||||
while (true) {
|
||||
LOGGER.info(obj.localResourceResponse());
|
||||
LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime));
|
||||
LOGGER.info(circuitBreaker.getState());
|
||||
try {
|
||||
Thread.sleep(5 * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
|
||||
var delayedService = new DelayedRemoteService(serverStartTime, 5);
|
||||
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
|
||||
2000 * 1000 * 1000);
|
||||
|
||||
var quickService = new QuickRemoteService();
|
||||
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
|
||||
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:
|
||||
|
||||
``` java
|
||||
```java
|
||||
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() {
|
||||
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 {
|
||||
return circuitBreaker.call("delayedService", serverStartTime);
|
||||
} catch (Exception e) {
|
||||
return this.delayedService.attemptRequest();
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
@ -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:
|
||||
|
||||
```java
|
||||
public class CircuitBreaker {
|
||||
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;
|
||||
|
||||
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.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;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
|
||||
// Reset everything to defaults
|
||||
@Override
|
||||
public void recordSuccess() {
|
||||
this.failureCount = 0;
|
||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||
this.state = State.CLOSED;
|
||||
}
|
||||
|
||||
private void recordFailure() {
|
||||
@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;
|
||||
}
|
||||
|
||||
protected void setState() {
|
||||
if (failureCount > failureThreshold) {
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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;
|
||||
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) {
|
||||
return "This is stale response from API";
|
||||
// return cached response if the circuit is in OPEN state
|
||||
return this.lastFailureResponse;
|
||||
} else {
|
||||
if (serviceToCall.equals("delayedService")) {
|
||||
var delayedService = new DelayedService(20);
|
||||
var response = delayedService.response(serverStartTime);
|
||||
if (response.split(" ")[3].equals("working")) {
|
||||
reset();
|
||||
return response;
|
||||
} else {
|
||||
recordFailure();
|
||||
throw new Exception("Remote service not responding");
|
||||
}
|
||||
} else {
|
||||
throw new Exception("Unknown Service Name");
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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()
|
||||
+ 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
|
||||
- failureThreshold : int
|
||||
- futureTime : long
|
||||
- lastFailureResponse : String
|
||||
~ lastFailureTime : long
|
||||
- retryTimePeriod : long
|
||||
- service : RemoteService
|
||||
- state : State
|
||||
- timeout : long
|
||||
~ CircuitBreaker(timeout : long, failureThreshold : int, retryTimePeriod : long)
|
||||
+ call(serviceToCall : String, serverStartTime : long) : String
|
||||
~ DefaultCircuitBreaker(serviceToCall : RemoteService, timeout : long, failureThreshold : int, retryTimePeriod : long)
|
||||
+ attemptRequest() : String
|
||||
# evaluateState()
|
||||
+ getState() : String
|
||||
- recordFailure()
|
||||
- reset()
|
||||
# setState()
|
||||
+ setStateForBypass(state : State)
|
||||
+ recordFailure(response : String)
|
||||
+ recordSuccess()
|
||||
+ setState(state : State)
|
||||
}
|
||||
class DelayedService {
|
||||
class DelayedRemoteService {
|
||||
- delay : int
|
||||
+ DelayedService()
|
||||
+ DelayedService(delay : int)
|
||||
+ response(serverStartTime : long) : String
|
||||
- serverStartTime : long
|
||||
+ DelayedRemoteService()
|
||||
+ DelayedRemoteService(serverStartTime : long, delay : int)
|
||||
+ call() : String
|
||||
}
|
||||
class MonitoringService {
|
||||
+ MonitoringService()
|
||||
- delayedService : CircuitBreaker
|
||||
- quickService : CircuitBreaker
|
||||
+ MonitoringService(delayedService : CircuitBreaker, quickService : CircuitBreaker)
|
||||
+ delayedServiceResponse() : String
|
||||
+ localResourceResponse() : String
|
||||
+ remoteResourceResponse(circuitBreaker : CircuitBreaker, serverStartTime : long) : String
|
||||
+ quickServiceResponse() : String
|
||||
}
|
||||
class QuickRemoteService {
|
||||
+ QuickRemoteService()
|
||||
+ call() : String
|
||||
}
|
||||
interface RemoteService {
|
||||
+ call() : String {abstract}
|
||||
}
|
||||
enum State {
|
||||
+ CLOSED {static}
|
||||
@ -40,5 +60,10 @@ package com.iluwatar.circuitbreaker {
|
||||
+ values() : State[] {static}
|
||||
}
|
||||
}
|
||||
CircuitBreaker --> "-state" State
|
||||
DefaultCircuitBreaker --> "-state" State
|
||||
MonitoringService --> "-delayedService" CircuitBreaker
|
||||
DefaultCircuitBreaker --> "-service" RemoteService
|
||||
DefaultCircuitBreaker ..|> CircuitBreaker
|
||||
DelayedRemoteService ..|> RemoteService
|
||||
QuickRemoteService ..|> RemoteService
|
||||
@enduml
|
@ -36,17 +36,18 @@ import org.slf4j.LoggerFactory;
|
||||
* operational again, so that we can use it
|
||||
* </p>
|
||||
* <p>
|
||||
* In this example, the circuit breaker pattern is demonstrated by using two services: {@link
|
||||
* MonitoringService} and {@link DelayedService}. The monitoring service is responsible for calling
|
||||
* two services: a local service and a remote service {@link DelayedService} , and by using the
|
||||
* circuit breaker construction we ensure that if the call to remote service is going to fail, we
|
||||
* are going to save our resources and not make the function call at all, by wrapping our call to
|
||||
* the remote service in the circuit breaker object.
|
||||
* In this example, the circuit breaker pattern is demonstrated by using three services: {@link
|
||||
* DelayedRemoteService}, {@link QuickRemoteService} and {@link MonitoringService}. The monitoring
|
||||
* service is responsible for calling three services: a local service, a quick remove service
|
||||
* {@link QuickRemoteService} and a delayed remote service {@link DelayedRemoteService} , and by
|
||||
* using the circuit breaker construction we ensure that if the call to remote service is going to
|
||||
* 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>
|
||||
* This works as follows: The {@link CircuitBreaker} 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
|
||||
* state is closed (initial), we assume everything is alright and perform the function call.
|
||||
* 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 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
|
||||
* 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
|
||||
@ -63,22 +64,50 @@ public class App {
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
@SuppressWarnings("squid:S2189")
|
||||
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();
|
||||
while (true) {
|
||||
LOGGER.info(obj.localResourceResponse());
|
||||
LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime));
|
||||
LOGGER.info(circuitBreaker.getState());
|
||||
try {
|
||||
Thread.sleep(5 * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
|
||||
var delayedService = new DelayedRemoteService(serverStartTime, 5);
|
||||
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
|
||||
2000 * 1000 * 1000);
|
||||
|
||||
var quickService = new QuickRemoteService();
|
||||
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
|
||||
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;
|
||||
|
||||
/**
|
||||
* The circuit breaker class with all configurations.
|
||||
* The Circuit breaker interface.
|
||||
*/
|
||||
public class 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;
|
||||
public interface CircuitBreaker {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
// Success response. Reset everything to defaults
|
||||
void recordSuccess();
|
||||
|
||||
//Reset everything to defaults
|
||||
private void reset() {
|
||||
this.failureCount = 0;
|
||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||
this.state = State.CLOSED;
|
||||
}
|
||||
// Failure response. Handle accordingly with response and change state if required.
|
||||
void recordFailure(String response);
|
||||
|
||||
private void recordFailure() {
|
||||
failureCount = failureCount + 1;
|
||||
this.lastFailureTime = System.nanoTime();
|
||||
}
|
||||
// Get the current state of circuit breaker
|
||||
String getState();
|
||||
|
||||
protected void setState() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
// Set the specific state manually.
|
||||
void setState(State state);
|
||||
|
||||
public String getState() {
|
||||
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
|
||||
*/
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Attempt to fetch response from the remote service.
|
||||
String attemptRequest() throws RemoteServiceException;
|
||||
}
|
||||
|
@ -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
|
||||
* 20 seconds).
|
||||
*/
|
||||
public class DelayedService {
|
||||
public class DelayedRemoteService implements RemoteService {
|
||||
|
||||
private final long serverStartTime;
|
||||
private final int delay;
|
||||
|
||||
/**
|
||||
@ -35,22 +37,23 @@ public class DelayedService {
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
public DelayedService() {
|
||||
this.delay = 60;
|
||||
public DelayedRemoteService() {
|
||||
this.serverStartTime = System.nanoTime();
|
||||
this.delay = 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public String response(long serverStartTime) {
|
||||
@Override
|
||||
public String call() throws RemoteServiceException {
|
||||
var currentTime = System.nanoTime();
|
||||
//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
|
||||
@ -58,9 +61,8 @@ public class DelayedService {
|
||||
//send the reply
|
||||
if ((currentTime - serverStartTime) * 1.0 / (1000 * 1000 * 1000) < delay) {
|
||||
//Can use Thread.sleep() here to block and simulate a hung server
|
||||
return "Delayed service is down";
|
||||
} else {
|
||||
return "Delayed service is working";
|
||||
throw new RemoteServiceException("Delayed service is down");
|
||||
}
|
||||
return "Delayed service is working";
|
||||
}
|
||||
}
|
@ -24,28 +24,47 @@
|
||||
package com.iluwatar.circuitbreaker;
|
||||
|
||||
/**
|
||||
* The service class which makes local and remote calls Uses {@link CircuitBreaker} object to ensure
|
||||
* remote calls don't use up resources.
|
||||
* The service class which makes local and remote calls Uses {@link DefaultCircuitBreaker} object to
|
||||
* ensure remote calls don't use up resources.
|
||||
*/
|
||||
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() {
|
||||
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
|
||||
* @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.
|
||||
* @return response string
|
||||
*/
|
||||
public String remoteResourceResponse(CircuitBreaker circuitBreaker, long serverStartTime) {
|
||||
public String delayedServiceResponse() {
|
||||
try {
|
||||
return circuitBreaker.call("delayedService", serverStartTime);
|
||||
} catch (Exception e) {
|
||||
return this.delayedService.attemptRequest();
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A quick response remote service, that responds healthy without any delay or failure.
|
||||
*/
|
||||
public class QuickRemoteService implements RemoteService {
|
||||
|
||||
@Override
|
||||
public String call() throws RemoteServiceException {
|
||||
return "Quick Service is working";
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* Exception thrown when {@link RemoteService} does not respond successfully.
|
||||
*/
|
||||
public class RemoteServiceException extends Exception {
|
||||
|
||||
public RemoteServiceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -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 java.rmi.Remote;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Circuit Breaker test
|
||||
*/
|
||||
public class CircuitBreakerTest {
|
||||
public class DefaultCircuitBreakerTest {
|
||||
|
||||
//long timeout, int failureThreshold, long retryTimePeriod
|
||||
@Test
|
||||
public void testSetState() {
|
||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
||||
public void testEvaluateState() {
|
||||
var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 100);
|
||||
//Right now, failureCount<failureThreshold, so state should be closed
|
||||
assertEquals(circuitBreaker.getState(), "CLOSED");
|
||||
circuitBreaker.failureCount = 4;
|
||||
circuitBreaker.lastFailureTime = System.nanoTime();
|
||||
circuitBreaker.setState();
|
||||
circuitBreaker.evaluateState();
|
||||
//Since failureCount>failureThreshold, and lastFailureTime is nearly equal to current time,
|
||||
//state should be half-open
|
||||
assertEquals(circuitBreaker.getState(), "HALF_OPEN");
|
||||
//Since failureCount>failureThreshold, and lastFailureTime is much lesser current time,
|
||||
//state should be open
|
||||
circuitBreaker.lastFailureTime = System.nanoTime() - 1000 * 1000 * 1000 * 1000;
|
||||
circuitBreaker.setState();
|
||||
circuitBreaker.evaluateState();
|
||||
assertEquals(circuitBreaker.getState(), "OPEN");
|
||||
//Now set it back again to closed to test idempotency
|
||||
circuitBreaker.failureCount = 0;
|
||||
circuitBreaker.setState();
|
||||
circuitBreaker.evaluateState();
|
||||
assertEquals(circuitBreaker.getState(), "CLOSED");
|
||||
}
|
||||
|
||||
@Test
|
||||
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
|
||||
//Bypass it and set it to open
|
||||
circuitBreaker.setStateForBypass(State.OPEN);
|
||||
circuitBreaker.setState(State.OPEN);
|
||||
assertEquals(circuitBreaker.getState(), "OPEN");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApiResponses() {
|
||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
||||
try {
|
||||
//Call with the paramater start_time set to huge amount of time in past so that service
|
||||
//replies with "Ok". Also, state is CLOSED in start
|
||||
var serviceStartTime = System.nanoTime() - 60 * 1000 * 1000 * 1000;
|
||||
var response = circuitBreaker.call("delayedService", serviceStartTime);
|
||||
assertEquals(response, "Delayed service is working");
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
public void testApiResponses() throws RemoteServiceException {
|
||||
RemoteService mockService = new RemoteService() {
|
||||
@Override
|
||||
public String call() throws RemoteServiceException {
|
||||
return "Remote Success";
|
||||
}
|
||||
};
|
||||
var circuitBreaker = new DefaultCircuitBreaker(mockService, 1, 1, 100);
|
||||
//Call with the paramater start_time set to huge amount of time in past so that service
|
||||
//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
|
||||
@Test
|
||||
public void testLocalResponse() {
|
||||
var monitoringService = new MonitoringService();
|
||||
var monitoringService = new MonitoringService(null,null);
|
||||
var response = monitoringService.localResourceResponse();
|
||||
assertEquals(response, "Local Service is working");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoteResponse() {
|
||||
var monitoringService = new MonitoringService();
|
||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
||||
public void testDelayedRemoteResponseSuccess() {
|
||||
var delayedService = new DelayedRemoteService(System.nanoTime()-2*1000*1000*1000, 2);
|
||||
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
|
||||
var serverStartTime = System.nanoTime() / 10;
|
||||
var response = monitoringService.remoteResourceResponse(circuitBreaker, serverStartTime);
|
||||
var response = monitoringService.delayedServiceResponse();
|
||||
assertEquals(response, "Delayed service is working");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoteResponse2() {
|
||||
var monitoringService = new MonitoringService();
|
||||
var circuitBreaker = new CircuitBreaker(1, 1, 100);
|
||||
public void testDelayedRemoteResponseFailure() {
|
||||
var delayedService = new DelayedRemoteService(System.nanoTime(), 2);
|
||||
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 serverStartTime = System.nanoTime();
|
||||
var response = monitoringService.remoteResourceResponse(circuitBreaker, serverStartTime);
|
||||
assertEquals(response, "Remote service not responding");
|
||||
var response = monitoringService.delayedServiceResponse();
|
||||
assertEquals(response, "Delayed service is down");
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
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.
|
||||
|
||||
## Explanation
|
||||
Real world example
|
||||
|
||||
> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one.
|
||||
> The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses
|
||||
> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one.
|
||||
> The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses
|
||||
> the spells one by one. Each spell here is a command object that can be undone.
|
||||
|
||||
In plain words
|
||||
@ -30,8 +30,8 @@ In plain words
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In object-oriented programming, the command pattern is a behavioral design pattern in which an
|
||||
> object is used to encapsulate all information needed to perform an action or trigger an event at
|
||||
> In object-oriented programming, the command pattern is a behavioral design pattern in which an
|
||||
> object is used to encapsulate all information needed to perform an action or trigger an event at
|
||||
> a later time.
|
||||
|
||||
**Programmatic Example**
|
||||
@ -41,25 +41,21 @@ Here's the sample code with wizard and goblin. Let's start from the `Wizard` cla
|
||||
```java
|
||||
public class Wizard {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class);
|
||||
|
||||
private final Deque<Command> undoStack = new LinkedList<>();
|
||||
private final Deque<Command> redoStack = new LinkedList<>();
|
||||
|
||||
public Wizard() {}
|
||||
|
||||
public void castSpell(Command command, Target target) {
|
||||
LOGGER.info("{} casts {} at {}", this, command, target);
|
||||
command.execute(target);
|
||||
undoStack.offerLast(command);
|
||||
public void castSpell(Runnable runnable) {
|
||||
runnable.run();
|
||||
undoStack.offerLast(runnable);
|
||||
}
|
||||
|
||||
public void undoLastSpell() {
|
||||
if (!undoStack.isEmpty()) {
|
||||
var previousSpell = undoStack.pollLast();
|
||||
redoStack.offerLast(previousSpell);
|
||||
LOGGER.info("{} undoes {}", this, previousSpell);
|
||||
previousSpell.undo();
|
||||
previousSpell.run();
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,8 +63,7 @@ public class Wizard {
|
||||
if (!redoStack.isEmpty()) {
|
||||
var previousSpell = redoStack.pollLast();
|
||||
undoStack.offerLast(previousSpell);
|
||||
LOGGER.info("{} redoes {}", this, previousSpell);
|
||||
previousSpell.redo();
|
||||
previousSpell.run();
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,84 +74,7 @@ public class Wizard {
|
||||
}
|
||||
```
|
||||
|
||||
Next we present the spell hierarchy.
|
||||
|
||||
```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.
|
||||
Next, we have the goblin who's the target of the spells.
|
||||
|
||||
```java
|
||||
public abstract class Target {
|
||||
@ -203,33 +121,73 @@ public class Goblin extends Target {
|
||||
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.
|
||||
|
||||
```java
|
||||
var wizard = new Wizard();
|
||||
var goblin = new Goblin();
|
||||
|
||||
goblin.printStatus();
|
||||
wizard.castSpell(new ShrinkSpell(), goblin);
|
||||
wizard.castSpell(goblin::changeSize);
|
||||
goblin.printStatus();
|
||||
wizard.castSpell(new InvisibilitySpell(), goblin);
|
||||
|
||||
wizard.castSpell(goblin::changeVisibility);
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.undoLastSpell();
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.undoLastSpell();
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.redoLastSpell();
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.redoLastSpell();
|
||||
goblin.printStatus();
|
||||
```
|
||||
|
||||
Here's the program output:
|
||||
|
||||
```java
|
||||
// Goblin, [size=normal] [visibility=visible]
|
||||
// Wizard casts Shrink spell at Goblin
|
||||
// Goblin, [size=small] [visibility=visible]
|
||||
// Wizard casts Invisibility spell at Goblin
|
||||
// Goblin, [size=small] [visibility=invisible]
|
||||
// Wizard undoes Invisibility spell
|
||||
// Goblin, [size=small] [visibility=visible]
|
||||
Goblin, [size=normal] [visibility=visible]
|
||||
Goblin, [size=small] [visibility=visible]
|
||||
Goblin, [size=small] [visibility=invisible]
|
||||
Goblin, [size=small] [visibility=visible]
|
||||
Goblin, [size=normal] [visibility=visible]
|
||||
Goblin, [size=small] [visibility=visible]
|
||||
Goblin, [size=small] [visibility=invisible]
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@ -240,26 +198,26 @@ Here's the program output:
|
||||
|
||||
Use the Command pattern when you want to:
|
||||
|
||||
* Parameterize objects by an action to perform. You can express such parameterization in a
|
||||
procedural language with a callback function, that is, a function that's registered somewhere to be
|
||||
* Parameterize objects by an action to perform. You can express such parameterization in a
|
||||
procedural language with a callback function, that is, a function that's registered somewhere to be
|
||||
called at a later point. Commands are an object-oriented replacement for callbacks.
|
||||
* Specify, queue, and execute requests at different times. A Command object can have a lifetime
|
||||
independent of the original request. If the receiver of a request can be represented in an address
|
||||
space-independent way, then you can transfer a command object for the request to a different process
|
||||
* Specify, queue, and execute requests at different times. A Command object can have a lifetime
|
||||
independent of the original request. If the receiver of a request can be represented in an address
|
||||
space-independent way, then you can transfer a command object for the request to a different process
|
||||
and fulfill the request there.
|
||||
* Support undo. The Command's execute operation can store state for reversing its effects in the
|
||||
command itself. The Command interface must have an added un-execute operation that reverses the
|
||||
effects of a previous call to execute. The executed commands are stored in a history list.
|
||||
Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling
|
||||
* Support undo. The Command's execute operation can store state for reversing its effects in the
|
||||
command itself. The Command interface must have an added un-execute operation that reverses the
|
||||
effects of a previous call to execute. The executed commands are stored in a history list.
|
||||
Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling
|
||||
un-execute and execute, respectively.
|
||||
* Support logging changes so that they can be reapplied in case of a system crash. By augmenting the
|
||||
Command interface with load and store operations, you can keep a persistent log of changes.
|
||||
Recovering from a crash involves reloading logged commands from disk and re-executing them with
|
||||
* Support logging changes so that they can be reapplied in case of a system crash. By augmenting the
|
||||
Command interface with load and store operations, you can keep a persistent log of changes.
|
||||
Recovering from a crash involves reloading logged commands from disk and re-executing them with
|
||||
the execute operation.
|
||||
* Structure a system around high-level operations build on primitive operations. Such a structure is
|
||||
common in information systems that support transactions. A transaction encapsulates a set of changes
|
||||
to data. The Command pattern offers a way to model transactions. Commands have a common interface,
|
||||
letting you invoke all transactions the same way. The pattern also makes it easy to extend the
|
||||
* Structure a system around high-level operations build on primitive operations. Such a structure is
|
||||
common in information systems that support transactions. A transaction encapsulates a set of changes
|
||||
to data. The Command pattern offers a way to model transactions. Commands have a common interface,
|
||||
letting you invoke all transactions the same way. The pattern also makes it easy to extend the
|
||||
system with new transactions.
|
||||
|
||||
## Typical Use Case
|
||||
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 50 KiB |
@ -1,116 +1,89 @@
|
||||
<?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.command.ShrinkSpell" project="command"
|
||||
file="/command/src/main/java/com/iluwatar/command/ShrinkSpell.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="178" width="141" x="-30" y="681"/>
|
||||
<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.command.Goblin" project="command"
|
||||
file="/command/src/main/java/com/iluwatar/command/Goblin.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="129" y="1223"/>
|
||||
<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.command.Wizard" project="command"
|
||||
file="/command/src/main/java/com/iluwatar/command/Wizard.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="129" y="362"/>
|
||||
<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.command.Command" project="command"
|
||||
file="/command/src/main/java/com/iluwatar/command/Command.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="129" y="561"/>
|
||||
<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.command.InvisibilitySpell" project="command"
|
||||
file="/command/src/main/java/com/iluwatar/command/InvisibilitySpell.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="160" width="141" x="151" y="681"/>
|
||||
<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="6" language="java" name="com.iluwatar.command.Target" project="command"
|
||||
file="/command/src/main/java/com/iluwatar/command/Target.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="129" y="1014"/>
|
||||
<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="7">
|
||||
<end type="SOURCE" refId="3" navigable="false">
|
||||
<attribute id="8" name="redoStack">
|
||||
<position height="20" width="67" x="140" y="451"/>
|
||||
</attribute>
|
||||
<multiplicity id="9" minimum="0" maximum="2147483647">
|
||||
<position height="18" width="25" x="221" y="452"/>
|
||||
</multiplicity>
|
||||
</end>
|
||||
<end type="TARGET" refId="4" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<generalization id="10">
|
||||
<end type="SOURCE" refId="2"/>
|
||||
<end type="TARGET" refId="6"/>
|
||||
</generalization>
|
||||
<association id="11">
|
||||
<end type="SOURCE" refId="1" navigable="false">
|
||||
<attribute id="12" name="target"/>
|
||||
<multiplicity id="13" minimum="0" maximum="1"/>
|
||||
</end>
|
||||
<end type="TARGET" refId="6" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<generalization id="14">
|
||||
<end type="SOURCE" refId="1"/>
|
||||
<end type="TARGET" refId="4"/>
|
||||
</generalization>
|
||||
<association id="15">
|
||||
<end type="SOURCE" refId="3" navigable="false">
|
||||
<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>
|
||||
<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="2" language="java" name="com.iluwatar.command.Goblin" project="command"
|
||||
file="/command/src/main/java/com/iluwatar/command/Goblin.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="129" y="1223"/>
|
||||
<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.command.Wizard" project="command"
|
||||
file="/command/src/main/java/com/iluwatar/command/Wizard.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="129" y="362"/>
|
||||
<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="6" language="java" name="com.iluwatar.command.Target" project="command"
|
||||
file="/command/src/main/java/com/iluwatar/command/Target.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="129" y="1014"/>
|
||||
<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="7">
|
||||
<end type="SOURCE" refId="3" navigable="false">
|
||||
<attribute id="8" name="redoStack">
|
||||
<position height="20" width="67" x="140" y="451"/>
|
||||
</attribute>
|
||||
<multiplicity id="9" minimum="0" maximum="2147483647">
|
||||
<position height="18" width="25" x="221" y="452"/>
|
||||
</multiplicity>
|
||||
</end>
|
||||
<end type="TARGET" refId="4" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<generalization id="10">
|
||||
<end type="SOURCE" refId="2"/>
|
||||
<end type="TARGET" refId="6"/>
|
||||
</generalization>
|
||||
<association id="11">
|
||||
<end type="SOURCE" refId="1" navigable="false">
|
||||
<attribute id="12" name="target"/>
|
||||
<multiplicity id="13" minimum="0" maximum="1"/>
|
||||
</end>
|
||||
<end type="TARGET" refId="6" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<generalization id="14">
|
||||
<end type="SOURCE" refId="1"/>
|
||||
<end type="TARGET" refId="4"/>
|
||||
</generalization>
|
||||
<association id="15">
|
||||
<end type="SOURCE" refId="3" navigable="false">
|
||||
<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"/>
|
||||
</class-diagram>
|
||||
</class-diagram>
|
||||
|
@ -4,33 +4,11 @@ package com.iluwatar.command {
|
||||
+ App()
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
interface Command {
|
||||
+ Command()
|
||||
+ execute(Target) {abstract}
|
||||
+ redo() {abstract}
|
||||
+ toString() : String {abstract}
|
||||
+ undo() {abstract}
|
||||
}
|
||||
class Goblin {
|
||||
+ Goblin()
|
||||
+ toString() : String
|
||||
}
|
||||
class InvisibilitySpell {
|
||||
- target : Target
|
||||
+ InvisibilitySpell()
|
||||
+ execute(target : Target)
|
||||
+ redo()
|
||||
+ toString() : String
|
||||
+ undo()
|
||||
}
|
||||
class ShrinkSpell {
|
||||
- oldSize : Size
|
||||
- target : Target
|
||||
+ ShrinkSpell()
|
||||
+ execute(target : Target)
|
||||
+ redo()
|
||||
+ toString() : String
|
||||
+ undo()
|
||||
+ changeSize()
|
||||
+ changeVisibility()
|
||||
}
|
||||
enum Size {
|
||||
+ NORMAL {static}
|
||||
@ -62,22 +40,19 @@ package com.iluwatar.command {
|
||||
}
|
||||
class Wizard {
|
||||
- LOGGER : Logger {static}
|
||||
- redoStack : Deque<Command>
|
||||
- undoStack : Deque<Command>
|
||||
- redoStack : Deque<Runnable>
|
||||
- undoStack : Deque<Runnable>
|
||||
+ Wizard()
|
||||
+ castSpell(command : Command, target : Target)
|
||||
+ castSpell(Runnable : runnable)
|
||||
+ redoLastSpell()
|
||||
+ toString() : String
|
||||
+ undoLastSpell()
|
||||
}
|
||||
}
|
||||
Target --> "-size" Size
|
||||
Wizard --> "-undoStack" Command
|
||||
ShrinkSpell --> "-oldSize" Size
|
||||
InvisibilitySpell --> "-target" Target
|
||||
ShrinkSpell --> "-target" Target
|
||||
Wizard --> "-changeSize" Goblin
|
||||
Wizard --> "-changeVisibility" Goblin
|
||||
Target --> "-visibility" Visibility
|
||||
Goblin --|> Target
|
||||
InvisibilitySpell ..|> Command
|
||||
ShrinkSpell ..|> Command
|
||||
App --> "castSpell" Wizard
|
||||
@enduml
|
||||
|
@ -30,12 +30,10 @@ package com.iluwatar.command;
|
||||
*
|
||||
* <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
|
||||
* receiver. Values for parameters of the receiver method are stored in the command. The receiver
|
||||
* then does the work. An invoker object (wizard) knows how to execute a command, and optionally
|
||||
* does bookkeeping about the command execution. The invoker does not know anything about a concrete
|
||||
* command, it knows only about command interface. Both an invoker object and several command
|
||||
* 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.
|
||||
* receiver. An invoker object (wizard) receives a reference to the command to be executed and
|
||||
* optionally does bookkeeping about the command execution. The invoker does not know anything
|
||||
* about how the command is executed. The client decides which commands to execute at which
|
||||
* points. To execute a command, it passes a reference of the function to the invoker object.
|
||||
*
|
||||
* <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
|
||||
@ -54,10 +52,10 @@ public class App {
|
||||
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.castSpell(new ShrinkSpell(), goblin);
|
||||
wizard.castSpell(goblin::changeSize);
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.castSpell(new InvisibilitySpell(), goblin);
|
||||
wizard.castSpell(goblin::changeVisibility);
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.undoLastSpell();
|
||||
|
@ -37,5 +37,4 @@ public class Goblin extends Target {
|
||||
public String toString() {
|
||||
return "Goblin";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -62,4 +62,21 @@ public abstract class Target {
|
||||
public void printStatus() {
|
||||
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.LinkedList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Wizard is the invoker of the commands.
|
||||
*/
|
||||
public class Wizard {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class);
|
||||
|
||||
private final Deque<Command> undoStack = new LinkedList<>();
|
||||
private final Deque<Command> redoStack = new LinkedList<>();
|
||||
private final Deque<Runnable> undoStack = new LinkedList<>();
|
||||
private final Deque<Runnable> redoStack = new LinkedList<>();
|
||||
|
||||
public Wizard() {
|
||||
// comment to ignore sonar issue: LEVEL critical
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast spell.
|
||||
*/
|
||||
public void castSpell(Command command, Target target) {
|
||||
LOGGER.info("{} casts {} at {}", this, command, target);
|
||||
command.execute(target);
|
||||
undoStack.offerLast(command);
|
||||
public void castSpell(Runnable runnable) {
|
||||
runnable.run();
|
||||
undoStack.offerLast(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,8 +52,7 @@ public class Wizard {
|
||||
if (!undoStack.isEmpty()) {
|
||||
var previousSpell = undoStack.pollLast();
|
||||
redoStack.offerLast(previousSpell);
|
||||
LOGGER.info("{} undoes {}", this, previousSpell);
|
||||
previousSpell.undo();
|
||||
previousSpell.run();
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,8 +63,7 @@ public class Wizard {
|
||||
if (!redoStack.isEmpty()) {
|
||||
var previousSpell = redoStack.pollLast();
|
||||
undoStack.offerLast(previousSpell);
|
||||
LOGGER.info("{} redoes {}", this, previousSpell);
|
||||
previousSpell.redo();
|
||||
previousSpell.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,10 +56,10 @@ public class CommandTest {
|
||||
var wizard = new Wizard();
|
||||
var goblin = new Goblin();
|
||||
|
||||
wizard.castSpell(new ShrinkSpell(), goblin);
|
||||
wizard.castSpell(goblin::changeSize);
|
||||
verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.VISIBLE);
|
||||
|
||||
wizard.castSpell(new InvisibilitySpell(), goblin);
|
||||
wizard.castSpell(goblin::changeVisibility);
|
||||
verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.INVISIBLE);
|
||||
|
||||
wizard.undoLastSpell();
|
||||
|
@ -45,7 +45,7 @@ public class SpaceStationMir extends GameObject {
|
||||
|
||||
@Override
|
||||
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(),
|
||||
this.getClass().getSimpleName(), this.getClass().getSimpleName(), this.getClass()
|
||||
.getSimpleName());
|
||||
@ -55,14 +55,14 @@ public class SpaceStationMir extends GameObject {
|
||||
|
||||
@Override
|
||||
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());
|
||||
setDamaged(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
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());
|
||||
setDamaged(true);
|
||||
}
|
||||
|
@ -24,11 +24,11 @@ Use the Event Sourcing pattern when
|
||||
|
||||
## Real world examples
|
||||
|
||||
* [The Lmax Architecture] (https://martinfowler.com/articles/lmax.html)
|
||||
* [The Lmax Architecture](https://martinfowler.com/articles/lmax.html)
|
||||
|
||||
## Credits
|
||||
|
||||
* [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)
|
||||
* [Reference 3: Introducing Event Sourcing] (https://msdn.microsoft.com/en-us/library/jj591559.aspx)
|
||||
* [Martin Fowler - Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html)
|
||||
* [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)
|
||||
* [Event Sourcing pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
|
||||
|
@ -15,35 +15,31 @@ tags:
|
||||
|
||||
## Intent
|
||||
|
||||
Providing a static method encapsulated in a class called factory, in order to hide the implementation logic and makes client code focus on usage rather then initialization new objects.
|
||||
Providing a static method encapsulated in a class called factory, in order to hide the
|
||||
implementation logic and makes client code focus on usage rather then initialization new objects.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> Lets say we have a web application connected to SQLServer, but now we want to switch to Oracle. To do so without modifying existing source code, we need to implements Simple Factory pattern, in which a static method can be invoked to create connection to a given database.
|
||||
> Lets say we have a web application connected to SQLServer, but now we want to switch to Oracle. To
|
||||
> do so without modifying existing source code, we need to implements Simple Factory pattern, in
|
||||
> which a static method can be invoked to create connection to a given database.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> Factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class.
|
||||
> Factory is an object for creating other objects – formally a factory is a function or method that
|
||||
> returns objects of a varying prototype or class.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
We have an interface 'Car' and tow implementations 'Ford' and 'Ferrari'.
|
||||
We have an interface `Car` and two implementations `Ford` and `Ferrari`.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Car interface.
|
||||
*/
|
||||
public interface Car {
|
||||
|
||||
public String getDescription();
|
||||
|
||||
String getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ford implementation.
|
||||
*/
|
||||
public class Ford implements Car {
|
||||
|
||||
static final String DESCRIPTION = "This is Ford.";
|
||||
@ -54,9 +50,6 @@ public class Ford implements Car {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ferrari implementation.
|
||||
*/
|
||||
public class Ferrari implements Car {
|
||||
|
||||
static final String DESCRIPTION = "This is Ferrari.";
|
||||
@ -68,14 +61,11 @@ public class Ferrari implements Car {
|
||||
}
|
||||
```
|
||||
|
||||
Enumeration above represents types of cars that we support (Ford and Ferrari).
|
||||
Enumeration above represents types of cars that we support (`Ford` and `Ferrari`).
|
||||
|
||||
```java
|
||||
public enum CarType {
|
||||
|
||||
/**
|
||||
* Enumeration for different types of cars.
|
||||
*/
|
||||
FORD(Ford::new),
|
||||
FERRARI(Ferrari::new);
|
||||
|
||||
@ -90,17 +80,12 @@ public enum CarType {
|
||||
}
|
||||
}
|
||||
```
|
||||
Then we have the static method 'getCar' to create car objects encapsulated in the factory class 'CarSimpleFactory'.
|
||||
Then we have the static method `getCar` to create car objects encapsulated in the factory class
|
||||
`CarsFactory`.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Factory of cars.
|
||||
*/
|
||||
public class CarsFactory {
|
||||
|
||||
/**
|
||||
* Factory method takes as parameter a car type and initiate the appropriate class.
|
||||
*/
|
||||
public static Car getCar(CarType type) {
|
||||
return type.getConstructor().get();
|
||||
}
|
||||
@ -113,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 car2 = CarsFactory.getCar(CarType.FERRARI);
|
||||
LOGGER.info(car1.getDescription());
|
||||
LOGGER.info(car2.getDescription());;
|
||||
LOGGER.info(car2.getDescription());
|
||||
```
|
||||
|
||||
Program output:
|
||||
@ -122,23 +107,27 @@ Program output:
|
||||
This is Ford.
|
||||
This Ferrari.
|
||||
```
|
||||
|
||||
## Class Diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
Use the Simple Factory pattern when you only care about the creation of a object, not how to create and manage it.
|
||||
|
||||
## Pros
|
||||
* Allows keeping all objects creation in one place and avoid of spreading 'new' key value 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.
|
||||
Use the Simple Factory pattern when you only care about the creation of a object, not how to create
|
||||
and manage it.
|
||||
|
||||
Pros
|
||||
|
||||
* Allows keeping all objects creation in one place and avoid of spreading 'new' keyword across codebase.
|
||||
* 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.
|
||||
|
||||
## Related patterns
|
||||
|
||||
[Factory Method](https://java-design-patterns.com/patterns/factory-method/)
|
||||
[Factory Kit](https://java-design-patterns.com/patterns/factory-kit/)
|
||||
[Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/)
|
||||
|
||||
|
||||
* [Factory Method](https://java-design-patterns.com/patterns/factory-method/)
|
||||
* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/)
|
||||
* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/)
|
||||
|
@ -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"
|
||||
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">
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
@ -5,6 +28,6 @@ package com.iluwatar.factory;
|
||||
*/
|
||||
public interface Car {
|
||||
|
||||
public String getDescription();
|
||||
String getDescription();
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
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;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
package com.iluwatar.gameloop;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
|
@ -23,10 +23,11 @@
|
||||
|
||||
package com.iluwatar.gameloop;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
/**
|
||||
* FixedStepGameLoop unit test class.
|
||||
@ -35,12 +36,12 @@ public class FixedStepGameLoopTest {
|
||||
|
||||
private FixedStepGameLoop gameLoop;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
gameLoop = new FixedStepGameLoop();
|
||||
}
|
||||
|
||||
@After
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
gameLoop = null;
|
||||
}
|
||||
@ -48,7 +49,7 @@ public class FixedStepGameLoopTest {
|
||||
@Test
|
||||
public void testUpdate() {
|
||||
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;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
/**
|
||||
* FrameBasedGameLoop unit test class.
|
||||
@ -35,19 +35,19 @@ public class FrameBasedGameLoopTest {
|
||||
|
||||
private FrameBasedGameLoop gameLoop;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
gameLoop = new FrameBasedGameLoop();
|
||||
}
|
||||
|
||||
@After
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
gameLoop = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Test
|
||||
public void testUpdate() {
|
||||
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;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
public class GameControllerTest {
|
||||
|
||||
private GameController controller;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
controller = new GameController();
|
||||
}
|
||||
|
||||
@After
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
controller = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Test
|
||||
public void testMoveBullet() {
|
||||
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() {
|
||||
Assert.assertEquals(controller.bullet.getPosition(), controller.getBulletPosition(), 0);
|
||||
Assertions.assertEquals(controller.bullet.getPosition(), controller.getBulletPosition(), 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,10 +23,9 @@
|
||||
|
||||
package com.iluwatar.gameloop;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
/**
|
||||
* GameLoop unit test class.
|
||||
@ -38,7 +37,7 @@ public class GameLoopTest {
|
||||
/**
|
||||
* Create mock implementation of GameLoop.
|
||||
*/
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
gameLoop = new GameLoop() {
|
||||
@Override
|
||||
@ -46,26 +45,26 @@ public class GameLoopTest {
|
||||
};
|
||||
}
|
||||
|
||||
@After
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
gameLoop = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Test
|
||||
public void testRun() {
|
||||
gameLoop.run();
|
||||
Assert.assertEquals(GameStatus.RUNNING, gameLoop.status);
|
||||
Assertions.assertEquals(GameStatus.RUNNING, gameLoop.status);
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Test
|
||||
public void testStop() {
|
||||
gameLoop.stop();
|
||||
Assert.assertEquals(GameStatus.STOPPED, gameLoop.status);
|
||||
Assertions.assertEquals(GameStatus.STOPPED, gameLoop.status);
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Test
|
||||
public void testIsGameRunning() {
|
||||
Assert.assertFalse(gameLoop.isGameRunning());
|
||||
Assertions.assertFalse(gameLoop.isGameRunning());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,11 +23,9 @@
|
||||
|
||||
package com.iluwatar.gameloop;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
/**
|
||||
* VariableStepGameLoop unit test class.
|
||||
@ -36,19 +34,19 @@ public class VariableStepGameLoopTest {
|
||||
|
||||
private VariableStepGameLoop gameLoop;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
gameLoop = new VariableStepGameLoop();
|
||||
}
|
||||
|
||||
@After
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
gameLoop = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Test
|
||||
public void testUpdate() {
|
||||
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 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
|
||||
|
||||
* [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html)
|
||||
|
@ -9,6 +9,10 @@ tags:
|
||||
- Performance
|
||||
---
|
||||
|
||||
## Also known as
|
||||
|
||||
Resource Pool
|
||||
|
||||
## Intent
|
||||
|
||||
When objects are expensive to create and they are needed only for short periods of time it is
|
||||
|
2
pom.xml
@ -89,6 +89,7 @@
|
||||
<module>state</module>
|
||||
<module>strategy</module>
|
||||
<module>template-method</module>
|
||||
<module>version-number</module>
|
||||
<module>visitor</module>
|
||||
<module>double-checked-locking</module>
|
||||
<module>servant</module>
|
||||
@ -196,6 +197,7 @@
|
||||
<module>transaction-script</module>
|
||||
<module>filterer</module>
|
||||
<module>factory</module>
|
||||
<module>separated-interface</module>
|
||||
</modules>
|
||||
|
||||
<repositories>
|
||||
|
138
separated-interface/README.md
Normal file
@ -0,0 +1,138 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Separated Interface
|
||||
folder: separated-interface
|
||||
permalink: /patterns/separated-interface/
|
||||
categories: Structural
|
||||
tags:
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
|
||||
## Intent
|
||||
|
||||
Separate the interface definition and implementation in different packages. This allows the client
|
||||
to be completely unaware of the implementation.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> An Invoice generator may be created with ability to use different Tax calculators that may be
|
||||
> added in the invoice depending upon type of purchase, region etc.
|
||||
|
||||
In plain words
|
||||
|
||||
> Separated interface pattern encourages to keep the implementations of an interface decoupled from
|
||||
> the client and its definition, so the client is not dependent on the implementation.
|
||||
|
||||
A client code may abstract some specific functionality to an interface, and define the definition of
|
||||
the interface as an SPI ([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**
|
||||
|
||||
**Client**
|
||||
|
||||
`InvoiceGenerator` class accepts the cost of the product and calculates the total
|
||||
amount payable inclusive of tax.
|
||||
|
||||
```java
|
||||
public class InvoiceGenerator {
|
||||
|
||||
private final TaxCalculator taxCalculator;
|
||||
|
||||
private final double amount;
|
||||
|
||||
public InvoiceGenerator(double amount, TaxCalculator taxCalculator) {
|
||||
this.amount = amount;
|
||||
this.taxCalculator = taxCalculator;
|
||||
}
|
||||
|
||||
public double getAmountWithTax() {
|
||||
return amount + taxCalculator.calculate(amount);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The tax calculation logic is delegated to the `TaxCalculator` interface.
|
||||
|
||||
```java
|
||||
public interface TaxCalculator {
|
||||
|
||||
double calculate(double amount);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation package**
|
||||
|
||||
In another package (which the client is completely unaware of) there exist multiple implementations
|
||||
of the `TaxCalculator` interface. `ForeignTaxCalculator` is one of them which levies 60% tax
|
||||
for international products.
|
||||
|
||||
```java
|
||||
public class ForeignTaxCalculator implements TaxCalculator {
|
||||
|
||||
public static final double TAX_PERCENTAGE = 60;
|
||||
|
||||
@Override
|
||||
public double calculate(double amount) {
|
||||
return amount * TAX_PERCENTAGE / 100.0;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Another is `DomesticTaxCalculator` which levies 20% tax for international products.
|
||||
|
||||
```java
|
||||
public class DomesticTaxCalculator implements TaxCalculator {
|
||||
|
||||
public static final double TAX_PERCENTAGE = 20;
|
||||
|
||||
@Override
|
||||
public double calculate(double amount) {
|
||||
return amount * TAX_PERCENTAGE / 100.0;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
These both implementations are instantiated and injected in the client class by the ```App.java```
|
||||
class.
|
||||
|
||||
```java
|
||||
var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, new ForeignTaxCalculator());
|
||||
|
||||
LOGGER.info("Foreign Tax applied: {}", "" + internationalProductInvoice.getAmountWithTax());
|
||||
|
||||
var domesticProductInvoice = new InvoiceGenerator(PRODUCT_COST, new DomesticTaxCalculator());
|
||||
|
||||
LOGGER.info("Domestic Tax applied: {}", "" + domesticProductInvoice.getAmountWithTax());
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Separated interface pattern when
|
||||
|
||||
* You are developing a framework package, and your framework needs to call some application code through interfaces.
|
||||
* You have separate packages implementing the functionalities which may be plugged in your client code at runtime or compile-time.
|
||||
* Your code resides in a layer that is not allowed to call the interface implementation layer by rule. For example, a domain layer needs to call a data mapper.
|
||||
|
||||
## Tutorial
|
||||
|
||||
* [Separated Interface Tutorial](https://www.youtube.com/watch?v=d3k-hOA7k2Y)
|
||||
|
||||
## Credits
|
||||
|
||||
* [Martin Fowler](https://www.martinfowler.com/eaaCatalog/separatedInterface.html)
|
||||
* [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)
|
BIN
separated-interface/etc/class_diagram.png
Normal file
After Width: | Height: | Size: 32 KiB |
36
separated-interface/etc/separated-interface.urm.puml
Normal file
@ -0,0 +1,36 @@
|
||||
@startuml
|
||||
package com.iluwatar.separatedinterface {
|
||||
class App {
|
||||
- LOGGER : Logger {static}
|
||||
+ PRODUCT_COST : double {static}
|
||||
+ App()
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
}
|
||||
package com.iluwatar.separatedinterface.taxes {
|
||||
class DomesticTaxCalculator {
|
||||
+ TAX_PERCENTAGE : double {static}
|
||||
+ DomesticTaxCalculator()
|
||||
+ calculate(amount : double) : double
|
||||
}
|
||||
class ForeignTaxCalculator {
|
||||
+ TAX_PERCENTAGE : double {static}
|
||||
+ ForeignTaxCalculator()
|
||||
+ calculate(amount : double) : double
|
||||
}
|
||||
}
|
||||
package com.iluwatar.separatedinterface.invoice {
|
||||
class InvoiceGenerator {
|
||||
- amount : double
|
||||
- taxCalculator : TaxCalculator
|
||||
+ InvoiceGenerator(amount : double, taxCalculator : TaxCalculator)
|
||||
+ getAmountWithTax() : double
|
||||
}
|
||||
interface TaxCalculator {
|
||||
+ calculate(double) : double {abstract}
|
||||
}
|
||||
}
|
||||
InvoiceGenerator --> "-taxCalculator" TaxCalculator
|
||||
DomesticTaxCalculator ..|> TaxCalculator
|
||||
ForeignTaxCalculator ..|> TaxCalculator
|
||||
@enduml
|
71
separated-interface/pom.xml
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
|
||||
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 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"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<version>1.24.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>separated-interface</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</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.separatedinterface.App</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.separatedinterface;
|
||||
|
||||
import com.iluwatar.separatedinterface.invoice.InvoiceGenerator;
|
||||
import com.iluwatar.separatedinterface.taxes.DomesticTaxCalculator;
|
||||
import com.iluwatar.separatedinterface.taxes.ForeignTaxCalculator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* <p>The Separated Interface pattern encourages to separate the interface definition and
|
||||
* implementation in different packages. This allows the client to be completely unaware of the
|
||||
* implementation.</p>
|
||||
*
|
||||
* <p>In this class the {@link InvoiceGenerator} class is injected with different instances of
|
||||
* {@link com.iluwatar.separatedinterface.invoice.TaxCalculator} implementations located in separate
|
||||
* packages, to receive different responses for both of the implementations.</p>
|
||||
*/
|
||||
public class App {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
public static final double PRODUCT_COST = 50.0;
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
//Create the invoice generator with product cost as 50 and foreign product tax
|
||||
var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST,
|
||||
new ForeignTaxCalculator());
|
||||
LOGGER.info("Foreign Tax applied: {}", "" + internationalProductInvoice.getAmountWithTax());
|
||||
|
||||
//Create the invoice generator with product cost as 50 and domestic product tax
|
||||
var domesticProductInvoice = new InvoiceGenerator(PRODUCT_COST, new DomesticTaxCalculator());
|
||||
LOGGER.info("Domestic Tax applied: {}", "" + domesticProductInvoice.getAmountWithTax());
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.separatedinterface.invoice;
|
||||
|
||||
/**
|
||||
* InvoiceGenerator class generates an invoice, accepting the product cost and calculating the total
|
||||
* price payable inclusive tax (calculated by {@link TaxCalculator}).
|
||||
*/
|
||||
public class InvoiceGenerator {
|
||||
|
||||
/**
|
||||
* The TaxCalculator interface to calculate the payable tax.
|
||||
*/
|
||||
private final TaxCalculator taxCalculator;
|
||||
|
||||
/**
|
||||
* The base product amount without tax.
|
||||
*/
|
||||
private final double amount;
|
||||
|
||||
public InvoiceGenerator(double amount, TaxCalculator taxCalculator) {
|
||||
this.amount = amount;
|
||||
this.taxCalculator = taxCalculator;
|
||||
}
|
||||
|
||||
public double getAmountWithTax() {
|
||||
return amount + taxCalculator.calculate(amount);
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +1,30 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.command;
|
||||
|
||||
/**
|
||||
* Interface for Commands.
|
||||
*/
|
||||
public interface Command {
|
||||
void execute(Target target);
|
||||
|
||||
void undo();
|
||||
|
||||
void redo();
|
||||
|
||||
String toString();
|
||||
}
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.separatedinterface.invoice;
|
||||
|
||||
public interface TaxCalculator {
|
||||
|
||||
double calculate(double amount);
|
||||
|
||||
}
|
@ -21,37 +21,20 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.command;
|
||||
package com.iluwatar.separatedinterface.taxes;
|
||||
|
||||
import com.iluwatar.separatedinterface.invoice.TaxCalculator;
|
||||
|
||||
/**
|
||||
* InvisibilitySpell is a concrete command.
|
||||
* TaxCalculator for Domestic goods with 20% tax.
|
||||
*/
|
||||
public class InvisibilitySpell implements Command {
|
||||
public class DomesticTaxCalculator implements TaxCalculator {
|
||||
|
||||
private Target target;
|
||||
public static final double TAX_PERCENTAGE = 20;
|
||||
|
||||
@Override
|
||||
public void execute(Target target) {
|
||||
target.setVisibility(Visibility.INVISIBLE);
|
||||
this.target = target;
|
||||
public double calculate(double amount) {
|
||||
return amount * TAX_PERCENTAGE / 100.0;
|
||||
}
|
||||
|
||||
@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";
|
||||
}
|
||||
}
|
@ -21,39 +21,20 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.command;
|
||||
package com.iluwatar.separatedinterface.taxes;
|
||||
|
||||
import com.iluwatar.separatedinterface.invoice.TaxCalculator;
|
||||
|
||||
/**
|
||||
* ShrinkSpell is a concrete command.
|
||||
* TaxCalculator for foreign goods with 60% tax.
|
||||
*/
|
||||
public class ShrinkSpell implements Command {
|
||||
public class ForeignTaxCalculator implements TaxCalculator {
|
||||
|
||||
private Size oldSize;
|
||||
private Target target;
|
||||
public static final double TAX_PERCENTAGE = 60;
|
||||
|
||||
@Override
|
||||
public void execute(Target target) {
|
||||
oldSize = target.getSize();
|
||||
target.setSize(Size.SMALL);
|
||||
this.target = target;
|
||||
public double calculate(double amount) {
|
||||
return amount * TAX_PERCENTAGE / 100.0;
|
||||
}
|
||||
|
||||
@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";
|
||||
}
|
||||
}
|
@ -21,21 +21,21 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.circuitbreaker;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
package com.iluwatar.separatedinterface;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Monitoring Service test
|
||||
*/
|
||||
public class DelayedServiceTest {
|
||||
import com.iluwatar.separatedinterface.App;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Application test.
|
||||
*/
|
||||
class AppTest {
|
||||
|
||||
//Improves code coverage
|
||||
@Test
|
||||
public void testDefaultConstructor() {
|
||||
var obj = new DelayedService();
|
||||
assertEquals(obj.response(System.nanoTime()), "Delayed service is down");
|
||||
void shouldExecuteWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.separatedinterface.invoice;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class InvoiceGeneratorTest {
|
||||
|
||||
private InvoiceGenerator target;
|
||||
|
||||
@Test
|
||||
public void testGenerateTax() {
|
||||
var productCost = 50.0;
|
||||
var tax = 10.0;
|
||||
TaxCalculator taxCalculatorMock = mock(TaxCalculator.class);
|
||||
doReturn(tax).when(taxCalculatorMock).calculate(productCost);
|
||||
|
||||
target = new InvoiceGenerator(productCost, taxCalculatorMock);
|
||||
|
||||
Assertions.assertEquals(target.getAmountWithTax(), productCost + tax);
|
||||
verify(taxCalculatorMock, times(1)).calculate(productCost);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.separatedinterface.taxes;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class DomesticTaxCalculatorTest {
|
||||
|
||||
private DomesticTaxCalculator target;
|
||||
|
||||
@Test
|
||||
public void testTaxCalculation(){
|
||||
target = new DomesticTaxCalculator();
|
||||
|
||||
var tax=target.calculate(100.0);
|
||||
Assertions.assertEquals(tax,20.0);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2019 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.separatedinterface.taxes;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ForeignTaxCalculatorTest {
|
||||
|
||||
private ForeignTaxCalculator target;
|
||||
|
||||
@Test
|
||||
public void testTaxCalculation(){
|
||||
target = new ForeignTaxCalculator();
|
||||
|
||||
var tax=target.calculate(100.0);
|
||||
Assertions.assertEquals(tax,60.0);
|
||||
}
|
||||
|
||||
}
|
@ -9,14 +9,16 @@ tags:
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
@ -24,7 +26,9 @@ In plain words
|
||||
|
||||
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**
|
||||
|
||||
@ -38,7 +42,7 @@ public enum EnumIvoryTower {
|
||||
}
|
||||
```
|
||||
|
||||
Then in order to use
|
||||
Then in order to use:
|
||||
|
||||
```java
|
||||
var enumIvoryTower1 = EnumIvoryTower.INSTANCE;
|
||||
@ -47,9 +51,11 @@ assertEquals(enumIvoryTower1, enumIvoryTower2); // true
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
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
|
||||
|
@ -10,12 +10,17 @@ tags:
|
||||
---
|
||||
|
||||
## 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
|
||||
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
|
||||
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.
|
||||
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:
|
||||
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. 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
|
||||
* Quad tree
|
||||
* k-d tree
|
||||
* K-d tree
|
||||
* BSP
|
||||
* 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
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
This pattern can be used:
|
||||
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
|
@ -9,36 +9,46 @@ tags:
|
||||
---
|
||||
|
||||
## Also known as
|
||||
|
||||
Filter, Criteria
|
||||
|
||||
## 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
|
||||
usefulness in selection, it is also valuable for validation and for building to
|
||||
order.
|
||||
|
||||
Specification pattern separates the statement of how to match a candidate, from the candidate object
|
||||
that it is matched against. As well as its usefulness in selection, it is also valuable for
|
||||
validation and for building to order.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> There is a pool of different creatures and we often need to select some subset of them.
|
||||
> 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.
|
||||
> There is a pool of different creatures and we often need to select some subset of them. 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.
|
||||
|
||||
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
|
||||
|
||||
> 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**
|
||||
|
||||
If we look at our creature pool example from above, we have a set of creatures with certain properties.
|
||||
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).
|
||||
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.
|
||||
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.
|
||||
If we look at our creature pool example from above, we have a set of creatures with certain
|
||||
properties. 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). 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. 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
|
||||
public interface Creature {
|
||||
@ -50,7 +60,7 @@ public interface Creature {
|
||||
}
|
||||
```
|
||||
|
||||
And ``Dragon`` implementation looks like this.
|
||||
And `Dragon` implementation looks like this.
|
||||
|
||||
```java
|
||||
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
|
||||
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
|
||||
public class MassGreaterThanSelector extends AbstractSelector<Creature> {
|
||||
@ -111,7 +123,8 @@ But we could also use our parameterized selector like this:
|
||||
.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
|
||||
var specialCreaturesSelector =
|
||||
@ -123,8 +136,9 @@ Our third option is to combine multiple selectors together. Performing a search
|
||||
|
||||
**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.
|
||||
These are implemented in ``ConjunctionSelector``, ``DisjunctionSelector`` and ``NegationSelector``.
|
||||
In Composite Specification, we will create custom instances of `AbstractSelector` by combining
|
||||
other selectors (called "leaves") using the three basic logical operators. These are implemented in
|
||||
`ConjunctionSelector`, `DisjunctionSelector` and `NegationSelector`.
|
||||
|
||||
```java
|
||||
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,
|
||||
and we will be able to instantiate the ``AbstractSelector`` class by combining any amount of selectors, as exemplified above.
|
||||
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.\
|
||||
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.
|
||||
|
||||
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, and we will be able to instantiate the ``AbstractSelector`` class by
|
||||
combining any amount of selectors, as exemplified above. 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. 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**
|
||||
|
||||
@ -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 |
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
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.
|
||||
|
@ -9,15 +9,21 @@ tags:
|
||||
---
|
||||
|
||||
## Also known as
|
||||
|
||||
Objects for States
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
@ -25,7 +31,10 @@ In plain words
|
||||
|
||||
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**
|
||||
|
||||
@ -126,17 +135,23 @@ And here is the full example how the mammoth behaves over time.
|
||||
mammoth.observe();
|
||||
mammoth.timePasses();
|
||||
mammoth.observe();
|
||||
|
||||
// The mammoth gets angry!
|
||||
// The mammoth is furious!
|
||||
// The mammoth calms down.
|
||||
// The mammoth is calm and peaceful.
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```java
|
||||
The mammoth gets angry!
|
||||
The mammoth is furious!
|
||||
The mammoth calms down.
|
||||
The mammoth is calm and peaceful.
|
||||
```
|
||||
|
||||
## Class diagram
|
||||

|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
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
|
||||
|
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
|
||||
|
||||
Policy
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
@ -27,7 +30,8 @@ In plain words
|
||||
|
||||
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**
|
||||
|
||||
@ -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
|
||||
public class DragonSlayer {
|
||||
@ -92,7 +97,7 @@ public class DragonSlayer {
|
||||
}
|
||||
```
|
||||
|
||||
Finally here's dragonslayer in action.
|
||||
Finally here's the dragonslayer in action.
|
||||
|
||||
```java
|
||||
LOGGER.info("Green dragon spotted ahead!");
|
||||
@ -104,19 +109,25 @@ Finally here's dragonslayer in action.
|
||||
LOGGER.info("Black dragon lands before you.");
|
||||
dragonSlayer.changeStrategy(new SpellStrategy());
|
||||
dragonSlayer.goToBattle();
|
||||
|
||||
// Green dragon spotted ahead!
|
||||
// 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!
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```
|
||||
Green dragon spotted ahead!
|
||||
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
|
||||

|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
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
|
||||
|
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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
> 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
|
||||
|
||||
> 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**
|
||||
|
||||
@ -120,9 +129,11 @@ And finally we show how the halfling thief utilizes the different stealing metho
|
||||
```
|
||||
|
||||
## Class diagram
|
||||

|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
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
|
||||
|
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
|
||||
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
|
||||
the system spend more time creating and destroying the threads than executing
|
||||
the actual tasks. Thread Pool solves this problem by reusing existing threads
|
||||
and eliminating the latency of creating new threads.
|
||||
|
||||
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 the system spend more time creating and destroying
|
||||
the threads than executing the actual tasks. Thread Pool solves this problem by reusing existing
|
||||
threads and eliminating the latency of creating new threads.
|
||||
|
||||
## Explanation
|
||||
|
||||
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
|
||||
|
||||
@ -26,11 +29,18 @@ In plain words
|
||||
|
||||
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**
|
||||
|
||||
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
|
||||
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
|
||||
making.
|
||||
Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
|
||||
peeling and coffee making.
|
||||
|
||||
```java
|
||||
public class Worker implements Runnable {
|
||||
@ -156,9 +166,11 @@ Now we are ready to show the full example in action.
|
||||
```
|
||||
|
||||
## Class diagram
|
||||

|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Thread Pool pattern when
|
||||
|
||||
* 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
|
||||
|
||||
Ensure that a given client is not able to access service resources more than the assigned limit.
|
||||
|
||||
## Explanation
|
||||
|
||||
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
|
||||
|
||||
@ -23,7 +26,9 @@ In plain words
|
||||
|
||||
[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**
|
||||
|
||||
@ -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
|
||||
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
|
||||
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
|
||||

|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
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.
|
||||
|
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;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -9,14 +9,18 @@ tags:
|
||||
---
|
||||
|
||||
## 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
|
||||
readers must not break.
|
||||
|
||||
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 readers must not break.
|
||||
|
||||
## Explanation
|
||||
|
||||
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
|
||||
|
||||
@ -24,11 +28,11 @@ In plain words
|
||||
|
||||
[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**
|
||||
|
||||
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
|
||||
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
|
||||
public final class RainbowFishSerializer {
|
||||
@ -185,18 +190,23 @@ And finally here's the full example in action.
|
||||
LOGGER.info("deserializedFishV2 name={} age={} length={} weight={}",
|
||||
deserializedFishV2.getName(), deserializedFishV2.getAge(),
|
||||
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
|
||||

|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Tolerant Reader pattern when
|
||||
|
||||
* 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 |
@ -10,22 +10,23 @@ tags:
|
||||
|
||||
## Intent
|
||||
|
||||
Trampoline pattern is used for implementing algorithms recursively in Java without blowing the stack and to interleave
|
||||
the execution of functions without hard coding them together.
|
||||
Trampoline pattern is used for implementing algorithms recursively in Java without blowing the stack
|
||||
and to interleave the execution of functions without hard coding them together.
|
||||
|
||||
## Explanation
|
||||
|
||||
Recursion is a frequently adopted technique for solving algorithmic problems in a divide and conquer
|
||||
style. For example calculating fibonacci accumulating sum and factorials. In these kinds of problems recursion is
|
||||
more straightforward than their loop counterpart. Furthermore recursion may need less code and looks more concise.
|
||||
There is a saying that every recursion problem can be solved using a loop with the cost of writing code that is more
|
||||
difficult to understand.
|
||||
style. For example calculating fibonacci accumulating sum and factorials. In these kinds of problems
|
||||
recursion is more straightforward than their loop counterpart. Furthermore recursion may need less
|
||||
code and looks more concise. There is a saying that every recursion problem can be solved using
|
||||
a loop with the cost of writing code that is more difficult to understand.
|
||||
|
||||
However recursion type solutions have one big caveat. For each recursive call it typically needs an intermediate value
|
||||
stored and there is a limited amount of stack memory available. Running out of stack memory creates a stack overflow
|
||||
error and halts the program execution.
|
||||
However recursion type solutions have one big caveat. For each recursive call it typically needs
|
||||
an intermediate value stored and there is a limited amount of stack memory available. Running out of
|
||||
stack memory creates a stack overflow error and halts the program execution.
|
||||
|
||||
Trampoline pattern is a trick that allows us define recursive algorithms in Java without blowing the stack.
|
||||
Trampoline pattern is a trick that allows us define recursive algorithms in Java without blowing the
|
||||
stack.
|
||||
|
||||
Real world example
|
||||
|
||||
@ -37,14 +38,18 @@ In plain words
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In Java, trampoline refers to using reflection to avoid using inner classes, for example in event listeners. The time overhead of a reflection call is traded for the space overhead of an inner class. Trampolines in Java usually involve the creation of a GenericListener to pass events to an outer class.
|
||||
> In Java, trampoline refers to using reflection to avoid using inner classes, for example in event
|
||||
> listeners. The time overhead of a reflection call is traded for the space overhead of an inner
|
||||
> class. Trampolines in Java usually involve the creation of a GenericListener to pass events to
|
||||
> an outer class.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Here's the `Trampoline` implementation in Java.
|
||||
|
||||
When `get` is called on the returned Trampoline, internally it will iterate calling `jump` on the returned `Trampoline`
|
||||
as long as the concrete instance returned is `Trampoline`, stopping once the returned instance is `done`.
|
||||
When `get` is called on the returned Trampoline, internally it will iterate calling `jump` on the
|
||||
returned `Trampoline` as long as the concrete instance returned is `Trampoline`, stopping once the
|
||||
returned instance is `done`.
|
||||
|
||||
```java
|
||||
public interface Trampoline<T> {
|
||||
@ -110,15 +115,21 @@ Using the `Trampoline` to get Fibonacci values.
|
||||
log.info("start pattern");
|
||||
var result = loop(10, 1).result();
|
||||
log.info("result {}", result);
|
||||
|
||||
// start pattern
|
||||
// result 3628800
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```
|
||||
start pattern
|
||||
result 3628800
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Trampoline pattern when
|
||||
|
||||
* For implementing tail recursive function. This pattern allows to switch on a stackless operation.
|
||||
|
@ -35,11 +35,6 @@
|
||||
</parent>
|
||||
<artifactId>trampoline</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
|
@ -98,12 +98,12 @@ public interface Trampoline<T> {
|
||||
return trampoline(this);
|
||||
}
|
||||
|
||||
T trampoline(final Trampoline<T> trampoline) {
|
||||
private T trampoline(final Trampoline<T> trampoline) {
|
||||
return Stream.iterate(trampoline, Trampoline::jump)
|
||||
.filter(Trampoline::complete)
|
||||
.findFirst()
|
||||
.map(Trampoline::result)
|
||||
.orElseThrow();
|
||||
.get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|