Compare commits
78 Commits
all-contri
...
all-contri
Author | SHA1 | Date | |
---|---|---|---|
b6eb26ee99 | |||
6e9c6d11c8 | |||
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 | |||
a3753807ae | |||
64266d63fd | |||
4f62070eb2 | |||
b29bd66369 | |||
ad435dd2fd | |||
dac8e659ce | |||
a1515b3b52 | |||
93aa1046aa | |||
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"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -1222,6 +1224,87 @@
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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
|
||||
|
21
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>
|
||||
@ -270,6 +270,19 @@ This project is licensed under the terms of the MIT license.
|
||||
<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>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -23,19 +23,13 @@
|
||||
|
||||
package com.iluwatar.circuitbreaker;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Monitoring Service test
|
||||
* A quick response remote service, that responds healthy without any delay or failure.
|
||||
*/
|
||||
public class DelayedServiceTest {
|
||||
public class QuickRemoteService implements RemoteService {
|
||||
|
||||
//Improves code coverage
|
||||
@Test
|
||||
public void testDefaultConstructor() {
|
||||
var obj = new DelayedService();
|
||||
assertEquals(obj.response(System.nanoTime()), "Delayed service is down");
|
||||
@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;
|
||||
}
|
@ -21,39 +21,14 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.command;
|
||||
package com.iluwatar.circuitbreaker;
|
||||
|
||||
/**
|
||||
* ShrinkSpell is a concrete command.
|
||||
* Exception thrown when {@link RemoteService} does not respond successfully.
|
||||
*/
|
||||
public class ShrinkSpell implements Command {
|
||||
public class RemoteServiceException extends Exception {
|
||||
|
||||
private Size oldSize;
|
||||
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";
|
||||
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);
|
||||
}
|
||||
|
@ -29,6 +29,6 @@ Use the Event Sourcing pattern when
|
||||
## 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)
|
||||
* [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)
|
||||
|
@ -81,7 +81,7 @@ public enum CarType {
|
||||
}
|
||||
```
|
||||
Then we have the static method `getCar` to create car objects encapsulated in the factory class
|
||||
`CarSimpleFactory`.
|
||||
`CarsFactory`.
|
||||
|
||||
```java
|
||||
public class CarsFactory {
|
||||
@ -98,7 +98,7 @@ Now on the client code we can create different types of cars using the factory c
|
||||
var car1 = CarsFactory.getCar(CarType.FORD);
|
||||
var car2 = CarsFactory.getCar(CarType.FERRARI);
|
||||
LOGGER.info(car1.getDescription());
|
||||
LOGGER.info(car2.getDescription());;
|
||||
LOGGER.info(car2.getDescription());
|
||||
```
|
||||
|
||||
Program output:
|
||||
@ -119,8 +119,8 @@ 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.
|
||||
* 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
|
||||
|
||||
|
@ -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)
|
||||
|
1
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>
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -23,10 +23,9 @@
|
||||
|
||||
package com.iluwatar.trampoline;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test for trampoline pattern.
|
||||
@ -37,7 +36,7 @@ public class TrampolineAppTest {
|
||||
@Test
|
||||
public void testTrampolineWithFactorialFunction() {
|
||||
long result = TrampolineApp.loop(10, 1).result();
|
||||
assertEquals("Be equal", 3628800, result);
|
||||
assertEquals(3_628_800, result);
|
||||
}
|
||||
|
||||
}
|
169
version-number/README.md
Normal file
@ -0,0 +1,169 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Version Number
|
||||
folder: versionnumber
|
||||
permalink: /patterns/versionnumber/
|
||||
description: Entity versioning with version number
|
||||
|
||||
categories:
|
||||
- Concurrency
|
||||
|
||||
tags:
|
||||
- Data access
|
||||
- Microservices
|
||||
---
|
||||
|
||||
## Name / classification
|
||||
|
||||
Version Number.
|
||||
|
||||
## Also known as
|
||||
|
||||
Entity Versioning, Optimistic Locking.
|
||||
|
||||
## Intent
|
||||
|
||||
Resolve concurrency conflicts when multiple clients are trying to update same entity simultaneously.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> Alice and Bob are working on the book, which stored in the database. Our heroes are making
|
||||
> changes simultaneously, and we need some mechanism to prevent them from overwriting each other.
|
||||
|
||||
In plain words
|
||||
|
||||
> Version Number pattern grants protection against concurrent updates to same entity.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> Optimistic concurrency control assumes that multiple transactions can frequently complete
|
||||
> without interfering with each other. While running, transactions use data resources without
|
||||
> acquiring locks on those resources. Before committing, each transaction verifies that no other
|
||||
> transaction has modified the data it has read. If the check reveals conflicting modifications,
|
||||
> the committing transaction rolls back and can be restarted.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
We have a `Book` entity, which is versioned, and has a copy-constructor:
|
||||
|
||||
```java
|
||||
public class Book {
|
||||
private long id;
|
||||
private String title = "";
|
||||
private String author = "";
|
||||
|
||||
private long version = 0; // version number
|
||||
|
||||
public Book(Book book) {
|
||||
this.id = book.id;
|
||||
this.title = book.title;
|
||||
this.author = book.author;
|
||||
this.version = book.version;
|
||||
}
|
||||
|
||||
// getters and setters are omitted here
|
||||
}
|
||||
```
|
||||
|
||||
We also have `BookRepository`, which implements concurrency control:
|
||||
|
||||
```java
|
||||
public class BookRepository {
|
||||
private final Map<Long, Book> collection = new HashMap<>();
|
||||
|
||||
public void update(Book book) throws BookNotFoundException, VersionMismatchException {
|
||||
if (!collection.containsKey(book.getId())) {
|
||||
throw new BookNotFoundException("Not found book with id: " + book.getId());
|
||||
}
|
||||
|
||||
var latestBook = collection.get(book.getId());
|
||||
if (book.getVersion() != latestBook.getVersion()) {
|
||||
throw new VersionMismatchException(
|
||||
"Tried to update stale version " + book.getVersion()
|
||||
+ " while actual version is " + latestBook.getVersion()
|
||||
);
|
||||
}
|
||||
|
||||
// update version, including client representation - modify by reference here
|
||||
book.setVersion(book.getVersion() + 1);
|
||||
|
||||
// save book copy to repository
|
||||
collection.put(book.getId(), new Book(book));
|
||||
}
|
||||
|
||||
public Book get(long bookId) throws BookNotFoundException {
|
||||
if (!collection.containsKey(bookId)) {
|
||||
throw new BookNotFoundException("Not found book with id: " + bookId);
|
||||
}
|
||||
|
||||
// return copy of the book
|
||||
return new Book(collection.get(bookId));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here's the concurrency control in action:
|
||||
|
||||
```java
|
||||
var bookId = 1;
|
||||
// Alice and Bob took the book concurrently
|
||||
final var aliceBook = bookRepository.get(bookId);
|
||||
final var bobBook = bookRepository.get(bookId);
|
||||
|
||||
aliceBook.setTitle("Kama Sutra"); // Alice has updated book title
|
||||
bookRepository.update(aliceBook); // and successfully saved book in database
|
||||
LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion());
|
||||
|
||||
// now Bob has the stale version of the book with empty title and version = 0
|
||||
// while actual book in database has filled title and version = 1
|
||||
bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author
|
||||
try {
|
||||
LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion());
|
||||
bookRepository.update(bobBook); // Bob tries to save his book to database
|
||||
} catch (VersionMismatchException e) {
|
||||
// Bob update fails, and book in repository remained untouchable
|
||||
LOGGER.info("Exception: {}", e.getMessage());
|
||||
// Now Bob should reread actual book from repository, do his changes again and save again
|
||||
}
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```java
|
||||
Alice updates the book with new version 1
|
||||
Bob tries to update the book with his version 0
|
||||
Exception: Tried to update stale version 0 while actual version is 1
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use Version Number for:
|
||||
|
||||
* resolving concurrent write-access to the data
|
||||
* strong data consistency
|
||||
|
||||
## Tutorials
|
||||
* [Version Number Pattern Tutorial](http://www.java2s.com/Tutorial/Java/0355__JPA/VersioningEntity.htm)
|
||||
|
||||
## Known uses
|
||||
* [Hibernate](https://vladmihalcea.com/jpa-entity-version-property-hibernate/)
|
||||
* [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning)
|
||||
* [Apache Solr](https://lucene.apache.org/solr/guide/6_6/updating-parts-of-documents.html)
|
||||
|
||||
## Consequences
|
||||
Version Number pattern allows to implement a concurrency control, which is usually done
|
||||
via Optimistic Offline Lock pattern.
|
||||
|
||||
## Related patterns
|
||||
* [Optimistic Offline Lock](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html)
|
||||
|
||||
## Credits
|
||||
* [Optimistic Locking in JPA](https://www.baeldung.com/jpa-optimistic-locking)
|
||||
* [JPA entity versioning](https://www.byteslounge.com/tutorials/jpa-entity-versioning-version-and-optimistic-locking)
|
||||
* [J2EE Design Patterns](http://ommolketab.ir/aaf-lib/axkwht7wxrhvgs2aqkxse8hihyu9zv.pdf)
|
BIN
version-number/etc/version-number.urm.png
Normal file
After Width: | Height: | Size: 22 KiB |
32
version-number/etc/version-number.urm.puml
Normal file
@ -0,0 +1,32 @@
|
||||
@startuml
|
||||
package com.iluwatar.versionnumber {
|
||||
class App {
|
||||
- LOGGER : Logger {static}
|
||||
+ App()
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
class Book {
|
||||
- author : String
|
||||
- id : long
|
||||
- title : String
|
||||
- version : long
|
||||
+ Book()
|
||||
+ Book(book : Book)
|
||||
+ getAuthor() : String
|
||||
+ getId() : long
|
||||
+ getTitle() : String
|
||||
+ getVersion() : long
|
||||
+ setAuthor(author : String)
|
||||
+ setId(id : long)
|
||||
+ setTitle(title : String)
|
||||
+ setVersion(version : long)
|
||||
}
|
||||
class BookRepository {
|
||||
- collection : Map<Long, Book>
|
||||
+ BookRepository()
|
||||
+ add(book : Book)
|
||||
+ get(bookId : long) : Book
|
||||
+ update(book : Book)
|
||||
}
|
||||
}
|
||||
@enduml
|
66
version-number/pom.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<?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>version-number</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</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.versionnumber.App</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.versionnumber;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The Version Number pattern helps to resolve concurrency conflicts in applications.
|
||||
* Usually these conflicts arise in database operations, when multiple clients are trying
|
||||
* to update the same record simultaneously.
|
||||
* Resolving such conflicts requires determining whether an object has changed.
|
||||
* For this reason we need a version number that is incremented with each change
|
||||
* to the underlying data, e.g. database. The version number can be used by repositories
|
||||
* to check for external changes and to report concurrency issues to the users.
|
||||
*
|
||||
* <p>In this example we show how Alice and Bob will try to update the {@link Book}
|
||||
* and save it simultaneously to {@link BookRepository}, which represents a typical database.
|
||||
*
|
||||
* <p>As in real databases, each client operates with copy of the data instead of original data
|
||||
* passed by reference, that's why we are using {@link Book} copy-constructor here.
|
||||
*/
|
||||
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) throws
|
||||
BookDuplicateException,
|
||||
BookNotFoundException,
|
||||
VersionMismatchException {
|
||||
var bookId = 1;
|
||||
|
||||
var bookRepository = new BookRepository();
|
||||
var book = new Book();
|
||||
book.setId(bookId);
|
||||
bookRepository.add(book); // adding a book with empty title and author
|
||||
LOGGER.info("An empty book with version {} was added to repository", book.getVersion());
|
||||
|
||||
// Alice and Bob took the book concurrently
|
||||
final var aliceBook = bookRepository.get(bookId);
|
||||
final var bobBook = bookRepository.get(bookId);
|
||||
|
||||
aliceBook.setTitle("Kama Sutra"); // Alice has updated book title
|
||||
bookRepository.update(aliceBook); // and successfully saved book in database
|
||||
LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion());
|
||||
|
||||
// now Bob has the stale version of the book with empty title and version = 0
|
||||
// while actual book in database has filled title and version = 1
|
||||
bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author
|
||||
try {
|
||||
LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion());
|
||||
bookRepository.update(bobBook); // Bob tries to save his book to database
|
||||
} catch (VersionMismatchException e) {
|
||||
// Bob update fails, and book in repository remained untouchable
|
||||
LOGGER.info("Exception: {}", e.getMessage());
|
||||
// Now Bob should reread actual book from repository, do his changes again and save again
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.versionnumber;
|
||||
|
||||
public class Book {
|
||||
private long id;
|
||||
private String title = "";
|
||||
private String author = "";
|
||||
|
||||
private long version = 0; // version number
|
||||
|
||||
public Book() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* We need this copy constructor to copy book representation in {@link BookRepository}.
|
||||
*/
|
||||
public Book(Book book) {
|
||||
this.id = book.id;
|
||||
this.title = book.title;
|
||||
this.author = book.author;
|
||||
this.version = book.version;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(long version) {
|
||||
this.version = version;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.versionnumber;
|
||||
|
||||
/**
|
||||
* When someone has tried to add a book which repository already have.
|
||||
*/
|
||||
public class BookDuplicateException extends Exception {
|
||||
public BookDuplicateException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.versionnumber;
|
||||
|
||||
/**
|
||||
* Client has tried to make an operation with book which repository does not have.
|
||||
*/
|
||||
public class BookNotFoundException extends Exception {
|
||||
public BookNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.versionnumber;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This repository represents simplified database.
|
||||
* As a typical database do, repository operates with copies of object.
|
||||
* So client and repo has different copies of book, which can lead to concurrency conflicts
|
||||
* as much as in real databases.
|
||||
*/
|
||||
public class BookRepository {
|
||||
private final Map<Long, Book> collection = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Adds book to collection.
|
||||
* Actually we are putting copy of book (saving a book by value, not by reference);
|
||||
*/
|
||||
public void add(Book book) throws BookDuplicateException {
|
||||
if (collection.containsKey(book.getId())) {
|
||||
throw new BookDuplicateException("Duplicated book with id: " + book.getId());
|
||||
}
|
||||
|
||||
// add copy of the book
|
||||
collection.put(book.getId(), new Book(book));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates book in collection only if client has modified the latest version of the book.
|
||||
*/
|
||||
public void update(Book book) throws BookNotFoundException, VersionMismatchException {
|
||||
if (!collection.containsKey(book.getId())) {
|
||||
throw new BookNotFoundException("Not found book with id: " + book.getId());
|
||||
}
|
||||
|
||||
var latestBook = collection.get(book.getId());
|
||||
if (book.getVersion() != latestBook.getVersion()) {
|
||||
throw new VersionMismatchException(
|
||||
"Tried to update stale version " + book.getVersion()
|
||||
+ " while actual version is " + latestBook.getVersion()
|
||||
);
|
||||
}
|
||||
|
||||
// update version, including client representation - modify by reference here
|
||||
book.setVersion(book.getVersion() + 1);
|
||||
|
||||
// save book copy to repository
|
||||
collection.put(book.getId(), new Book(book));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns book representation to the client.
|
||||
* Representation means we are returning copy of the book.
|
||||
*/
|
||||
public Book get(long bookId) throws BookNotFoundException {
|
||||
if (!collection.containsKey(bookId)) {
|
||||
throw new BookNotFoundException("Not found book with id: " + bookId);
|
||||
}
|
||||
|
||||
// return copy of the book
|
||||
return new Book(collection.get(bookId));
|
||||
}
|
||||
}
|
@ -1,37 +1,33 @@
|
||||
/*
|
||||
* 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.versionnumber;
|
||||
|
||||
/**
|
||||
* Client has tried to update a stale version of the book.
|
||||
*/
|
||||
public class VersionMismatchException extends Exception {
|
||||
public VersionMismatchException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -21,37 +21,26 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.command;
|
||||
package com.iluwatar.versionnumber;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* InvisibilitySpell is a concrete command.
|
||||
* Application test
|
||||
*/
|
||||
public class InvisibilitySpell implements Command {
|
||||
class AppTest {
|
||||
|
||||
private Target target;
|
||||
/**
|
||||
* Issue: Add at least one assertion to this test case.
|
||||
*
|
||||
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
@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";
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.versionnumber;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link BookRepository}
|
||||
*/
|
||||
class BookRepositoryTest {
|
||||
private final long bookId = 1;
|
||||
private final BookRepository bookRepository = new BookRepository();
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws BookDuplicateException {
|
||||
var book = new Book();
|
||||
book.setId(bookId);
|
||||
bookRepository.add(book);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultVersionRemainsZeroAfterAdd() throws BookNotFoundException {
|
||||
var book = bookRepository.get(bookId);
|
||||
assertEquals(0, book.getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAliceAndBobHaveDifferentVersionsAfterAliceUpdate() throws BookNotFoundException, VersionMismatchException {
|
||||
final var aliceBook = bookRepository.get(bookId);
|
||||
final var bobBook = bookRepository.get(bookId);
|
||||
|
||||
aliceBook.setTitle("Kama Sutra");
|
||||
bookRepository.update(aliceBook);
|
||||
|
||||
assertEquals(1, aliceBook.getVersion());
|
||||
assertEquals(0, bobBook.getVersion());
|
||||
var actualBook = bookRepository.get(bookId);
|
||||
assertEquals(aliceBook.getVersion(), actualBook.getVersion());
|
||||
assertEquals(aliceBook.getTitle(), actualBook.getTitle());
|
||||
assertNotEquals(aliceBook.getTitle(), bobBook.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldThrowVersionMismatchExceptionOnStaleUpdate() throws BookNotFoundException, VersionMismatchException {
|
||||
final var aliceBook = bookRepository.get(bookId);
|
||||
final var bobBook = bookRepository.get(bookId);
|
||||
|
||||
aliceBook.setTitle("Kama Sutra");
|
||||
bookRepository.update(aliceBook);
|
||||
|
||||
bobBook.setAuthor("Vatsyayana Mallanaga");
|
||||
try {
|
||||
bookRepository.update(bobBook);
|
||||
} catch (VersionMismatchException e) {
|
||||
assertEquals(0, bobBook.getVersion());
|
||||
var actualBook = bookRepository.get(bookId);
|
||||
assertEquals(1, actualBook.getVersion());
|
||||
assertEquals(aliceBook.getVersion(), actualBook.getVersion());
|
||||
assertEquals("", bobBook.getTitle());
|
||||
assertNotEquals(aliceBook.getAuthor(), bobBook.getAuthor());
|
||||
}
|
||||
}
|
||||
}
|
205
zh/bridge/README.md
Normal file
@ -0,0 +1,205 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Bridge
|
||||
folder: bridge
|
||||
permalink: /patterns/bridge/
|
||||
categories: Structural
|
||||
tags:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## 又被称为
|
||||
|
||||
手柄/身体模式
|
||||
|
||||
## 目的
|
||||
|
||||
将抽象与其实现分离,以便二者可以独立变化。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 考虑一下你拥有一种具有不同附魔的武器,并且应该允许将具有不同附魔的不同武器混合使用。 你会怎么做? 为每个附魔创建每种武器的多个副本,还是只是创建单独的附魔并根据需要为武器设置它? 桥接模式使您可以进行第二次操作。
|
||||
|
||||
通俗的说
|
||||
|
||||
> 桥接模式是一个更推荐组合而不是继承的模式。将实现细节从一个层次结构推送到具有单独层次结构的另一个对象。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 桥接模式是软件工程中使用的一种设计模式,旨在“将抽象与其实现分离,从而使两者可以独立变化”
|
||||
|
||||
**程序示例**
|
||||
|
||||
翻译一下上面的武器示例。下面我们有武器的类层级:
|
||||
|
||||
```java
|
||||
public interface Weapon {
|
||||
void wield();
|
||||
void swing();
|
||||
void unwield();
|
||||
Enchantment getEnchantment();
|
||||
}
|
||||
|
||||
public class Sword implements Weapon {
|
||||
|
||||
private final Enchantment enchantment;
|
||||
|
||||
public Sword(Enchantment enchantment) {
|
||||
this.enchantment = enchantment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wield() {
|
||||
LOGGER.info("The sword is wielded.");
|
||||
enchantment.onActivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swing() {
|
||||
LOGGER.info("The sword is swinged.");
|
||||
enchantment.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unwield() {
|
||||
LOGGER.info("The sword is unwielded.");
|
||||
enchantment.onDeactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enchantment getEnchantment() {
|
||||
return enchantment;
|
||||
}
|
||||
}
|
||||
|
||||
public class Hammer implements Weapon {
|
||||
|
||||
private final Enchantment enchantment;
|
||||
|
||||
public Hammer(Enchantment enchantment) {
|
||||
this.enchantment = enchantment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wield() {
|
||||
LOGGER.info("The hammer is wielded.");
|
||||
enchantment.onActivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swing() {
|
||||
LOGGER.info("The hammer is swinged.");
|
||||
enchantment.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unwield() {
|
||||
LOGGER.info("The hammer is unwielded.");
|
||||
enchantment.onDeactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enchantment getEnchantment() {
|
||||
return enchantment;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里是单独的附魔类结构:
|
||||
|
||||
```java
|
||||
public interface Enchantment {
|
||||
void onActivate();
|
||||
void apply();
|
||||
void onDeactivate();
|
||||
}
|
||||
|
||||
public class FlyingEnchantment implements Enchantment {
|
||||
|
||||
@Override
|
||||
public void onActivate() {
|
||||
LOGGER.info("The item begins to glow faintly.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
LOGGER.info("The item flies and strikes the enemies finally returning to owner's hand.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeactivate() {
|
||||
LOGGER.info("The item's glow fades.");
|
||||
}
|
||||
}
|
||||
|
||||
public class SoulEatingEnchantment implements Enchantment {
|
||||
|
||||
@Override
|
||||
public void onActivate() {
|
||||
LOGGER.info("The item spreads bloodlust.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
LOGGER.info("The item eats the soul of enemies.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeactivate() {
|
||||
LOGGER.info("Bloodlust slowly disappears.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里是两种层次结构的实践:
|
||||
|
||||
```java
|
||||
var enchantedSword = new Sword(new SoulEatingEnchantment());
|
||||
enchantedSword.wield();
|
||||
enchantedSword.swing();
|
||||
enchantedSword.unwield();
|
||||
// The sword is wielded.
|
||||
// The item spreads bloodlust.
|
||||
// The sword is swinged.
|
||||
// The item eats the soul of enemies.
|
||||
// The sword is unwielded.
|
||||
// Bloodlust slowly disappears.
|
||||
|
||||
var hammer = new Hammer(new FlyingEnchantment());
|
||||
hammer.wield();
|
||||
hammer.swing();
|
||||
hammer.unwield();
|
||||
// The hammer is wielded.
|
||||
// The item begins to glow faintly.
|
||||
// The hammer is swinged.
|
||||
// The item flies and strikes the enemies finally returning to owner's hand.
|
||||
// The hammer is unwielded.
|
||||
// The item's glow fades.
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
使用桥接模式当
|
||||
|
||||
* 你想永久性的避免抽象和他的实现之间的绑定。有可能是这种情况,当实现需要被选择或者在运行时切换。
|
||||
* 抽象和他们的实现应该能通过写子类来扩展。这种情况下,桥接模式让你可以组合不同的抽象和实现并独立的扩展他们。
|
||||
* 对抽象的实现的改动应当不会对客户产生影响;也就是说,他们的代码不必重新编译。
|
||||
* 你有种类繁多的类。这样的类层次结构表明需要将一个对象分为两部分。Rumbaugh 使用术语“嵌套归纳”来指代这种类层次结构。
|
||||
* 你想在多个对象间分享一种实现(可能使用引用计数),这个事实应该对客户隐藏。一个简单的示例是Coplien的String类,其中多个对象可以共享同一字符串表示形式
|
||||
|
||||
## 教程
|
||||
|
||||
* [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
139
zh/builder/README.md
Normal file
@ -0,0 +1,139 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Builder
|
||||
folder: builder
|
||||
permalink: /patterns/builder/
|
||||
categories: Creational
|
||||
tags:
|
||||
- Gang of Four
|
||||
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
将复杂对象的构造与其表示分开,以便同一构造过程可以创建不同的表示。
|
||||
|
||||
## 解释
|
||||
|
||||
现实世界例子
|
||||
|
||||
> 想象一个角色扮演游戏的角色生成器。最简单的选择是让计算机为你创建角色。但是如果你想选择一些像专业,性别,发色等角色细节时,这个角色生成就变成了一个渐进的过程。当所有选择完成时,该过程也将完成。
|
||||
|
||||
用通俗的话说
|
||||
|
||||
> 允许你创建不同口味的对象同时避免构造器污染。当一个对象可能有几种口味,或者一个对象的创建涉及到很多步骤时会很有用。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 建造者模式是一种对象创建的软件设计模式,旨在为伸缩构造器反模式寻找一个解决方案。
|
||||
|
||||
说了这么多,让我补充一下什么是伸缩构造函数反模式。我们肯定都见过像下面这样的构造器:
|
||||
|
||||
```java
|
||||
public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) {
|
||||
}
|
||||
```
|
||||
|
||||
就像你看到的构造器参数的数量很快就会失控同时参数的排列方式可能变得难以理解。另外,如果您将来希望添加更多选项,则此参数列表可能会继续增长。这就被称伸缩构造器反模式。
|
||||
|
||||
**编程示例**
|
||||
|
||||
明智的选择是使用建造者模式。首先我们有一个英雄要创建。
|
||||
|
||||
```java
|
||||
public final class Hero {
|
||||
private final Profession profession;
|
||||
private final String name;
|
||||
private final HairType hairType;
|
||||
private final HairColor hairColor;
|
||||
private final Armor armor;
|
||||
private final Weapon weapon;
|
||||
|
||||
private Hero(Builder builder) {
|
||||
this.profession = builder.profession;
|
||||
this.name = builder.name;
|
||||
this.hairColor = builder.hairColor;
|
||||
this.hairType = builder.hairType;
|
||||
this.weapon = builder.weapon;
|
||||
this.armor = builder.armor;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后我们有创建者
|
||||
|
||||
```java
|
||||
public static class Builder {
|
||||
private final Profession profession;
|
||||
private final String name;
|
||||
private HairType hairType;
|
||||
private HairColor hairColor;
|
||||
private Armor armor;
|
||||
private Weapon weapon;
|
||||
|
||||
public Builder(Profession profession, String name) {
|
||||
if (profession == null || name == null) {
|
||||
throw new IllegalArgumentException("profession and name can not be null");
|
||||
}
|
||||
this.profession = profession;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Builder withHairType(HairType hairType) {
|
||||
this.hairType = hairType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withHairColor(HairColor hairColor) {
|
||||
this.hairColor = hairColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withArmor(Armor armor) {
|
||||
this.armor = armor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withWeapon(Weapon weapon) {
|
||||
this.weapon = weapon;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Hero build() {
|
||||
return new Hero(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后可以这样使用
|
||||
|
||||
```java
|
||||
var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build();
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
使用建造者模式当
|
||||
|
||||
* 创建复杂对象的算法应独立于组成对象的零件及其组装方式
|
||||
* 构造过程必须允许所构造的对象具有不同的表示形式
|
||||
|
||||
## Java世界例子
|
||||
|
||||
* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html)
|
||||
* [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) as well as similar buffers such as FloatBuffer, IntBuffer and so on.
|
||||
* [java.lang.StringBuffer](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html#append-boolean-)
|
||||
* All implementations of [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html)
|
||||
* [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder)
|
||||
* [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
79
zh/callback/README.md
Normal file
@ -0,0 +1,79 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Callback
|
||||
folder: callback
|
||||
permalink: /patterns/callback/
|
||||
categories: Idiom
|
||||
tags:
|
||||
- Reactive
|
||||
---
|
||||
|
||||
## 目的
|
||||
回调是一部分被当为参数来传递给其他代码的可执行代码,接收方的代码可以在一些方便的时候来调用它。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 我们需要被通知当执行的任务结束时。我们为调用者传递一个回调方法然后等它调用通知我们。
|
||||
|
||||
通俗的讲
|
||||
|
||||
|
||||
> 回调是一个用来传递给调用者的方法,它将在定义的时刻被调用。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 在计算机编程中,回调又被称为“稍后调用”函数,可以是任何可执行的代码用来作为参数传递给其他代码;其它代码被期望在给定时间内调用回调方法。
|
||||
|
||||
**编程示例**
|
||||
|
||||
回调是一个只有一个方法的简单接口。
|
||||
|
||||
```java
|
||||
public interface Callback {
|
||||
|
||||
void call();
|
||||
}
|
||||
```
|
||||
|
||||
下面我们定义一个任务它将在任务执行完成后执行回调。
|
||||
|
||||
```java
|
||||
public abstract class Task {
|
||||
|
||||
final void executeWith(Callback callback) {
|
||||
execute();
|
||||
Optional.ofNullable(callback).ifPresent(Callback::call);
|
||||
}
|
||||
|
||||
public abstract void execute();
|
||||
}
|
||||
|
||||
public final class SimpleTask extends Task {
|
||||
|
||||
private static final Logger LOGGER = getLogger(SimpleTask.class);
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
LOGGER.info("Perform some important activity and after call the callback method.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后这里是我们如何执行一个任务然后接收一个回调当它完成时。
|
||||
|
||||
```java
|
||||
var task = new SimpleTask();
|
||||
task.executeWith(() -> LOGGER.info("I'm done now."));
|
||||
```
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用性
|
||||
使用回调模式当
|
||||
* 当一些同步或异步架构动作必须在一些定义好的活动执行后执行时。
|
||||
|
||||
## Java例子
|
||||
|
||||
* [CyclicBarrier](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html#CyclicBarrier%28int,%20java.lang.Runnable%29) 构造函数可以接受回调,该回调将在每次障碍被触发时触发。
|
159
zh/chain/README.md
Normal file
@ -0,0 +1,159 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Chain of responsibility
|
||||
folder: chain
|
||||
permalink: /patterns/chain/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## 目的
|
||||
通过给多个对象一个处理请求的机会,避免请求的发送者和它的接收者耦合。串联接收对象并在链条中传递请求直到一个对象处理它。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 兽王大声命令他的军队。最近响应的是指挥官,然后是军官,然后是士兵。指挥官,军官,士兵这里就形成了一个责任链。
|
||||
|
||||
通俗的说
|
||||
|
||||
> 它帮助构建一串对象。请求从一个对象中进入并结束然后进入到一个个对象中直到找到合适的处理器。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 在面向对象设计中,责任链模式是一种由源命令对象和一系列处理对象组成的设计模式。每个处理对象包含了其定义的可处理的命令对象类型的逻辑。剩下的会传递给链条中的下一个处理对象。
|
||||
|
||||
**程序示例**
|
||||
|
||||
用上面的兽人来翻译我们的示例。首先我们有请求类
|
||||
|
||||
```java
|
||||
public class Request {
|
||||
|
||||
private final RequestType requestType;
|
||||
private final String requestDescription;
|
||||
private boolean handled;
|
||||
|
||||
public Request(final RequestType requestType, final String requestDescription) {
|
||||
this.requestType = Objects.requireNonNull(requestType);
|
||||
this.requestDescription = Objects.requireNonNull(requestDescription);
|
||||
}
|
||||
|
||||
public String getRequestDescription() { return requestDescription; }
|
||||
|
||||
public RequestType getRequestType() { return requestType; }
|
||||
|
||||
public void markHandled() { this.handled = true; }
|
||||
|
||||
public boolean isHandled() { return this.handled; }
|
||||
|
||||
@Override
|
||||
public String toString() { return getRequestDescription(); }
|
||||
}
|
||||
|
||||
public enum RequestType {
|
||||
DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX
|
||||
}
|
||||
```
|
||||
|
||||
然后是请求处理器的层次结构
|
||||
|
||||
```java
|
||||
public abstract class RequestHandler {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class);
|
||||
private final RequestHandler next;
|
||||
|
||||
public RequestHandler(RequestHandler next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
public void handleRequest(Request req) {
|
||||
if (next != null) {
|
||||
next.handleRequest(req);
|
||||
}
|
||||
}
|
||||
|
||||
protected void printHandling(Request req) {
|
||||
LOGGER.info("{} handling request \"{}\"", this, req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
}
|
||||
|
||||
public class OrcCommander extends RequestHandler {
|
||||
public OrcCommander(RequestHandler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(Request req) {
|
||||
if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) {
|
||||
printHandling(req);
|
||||
req.markHandled();
|
||||
} else {
|
||||
super.handleRequest(req);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Orc commander";
|
||||
}
|
||||
}
|
||||
|
||||
// OrcOfficer和OrcSoldier的定义与OrcCommander类似
|
||||
|
||||
```
|
||||
|
||||
然后我们有兽王下达命令并形成链条
|
||||
|
||||
```java
|
||||
public class OrcKing {
|
||||
RequestHandler chain;
|
||||
|
||||
public OrcKing() {
|
||||
buildChain();
|
||||
}
|
||||
|
||||
private void buildChain() {
|
||||
chain = new OrcCommander(new OrcOfficer(new OrcSoldier(null)));
|
||||
}
|
||||
|
||||
public void makeRequest(Request req) {
|
||||
chain.handleRequest(req);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后这样使用它
|
||||
|
||||
```java
|
||||
var king = new OrcKing();
|
||||
king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle")); // Orc commander handling request "defend castle"
|
||||
king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner")); // Orc officer handling request "torture prisoner"
|
||||
king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); // Orc soldier handling request "collect tax"
|
||||
```
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用性
|
||||
使用责任链模式当
|
||||
|
||||
* 多于一个对象可能要处理请求,并且处理器并不知道一个优先级。处理器应自动确定。
|
||||
* 你想对多个对象之一发出请求而无需明确指定接收者
|
||||
* 处理请求的对象集合应该被动态指定时
|
||||
|
||||
## Java世界例子
|
||||
|
||||
* [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29)
|
||||
* [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html)
|
||||
* [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
253
zh/command/README.md
Normal file
@ -0,0 +1,253 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Command
|
||||
folder: command
|
||||
permalink: /patterns/command/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## 或称
|
||||
行动, 事务模式
|
||||
|
||||
## 目的
|
||||
将请求封装为对象,从而使你可以将具有不同请求的客户端参数化,队列或记录请求,并且支持可撤销操作。
|
||||
|
||||
## 解释
|
||||
真实世界例子
|
||||
|
||||
> 有一个巫师在地精上施放咒语。咒语在地精上一一执行。第一个咒语使地精缩小,第二个使他不可见。然后巫师将咒语一个个的反转。这里的每一个咒语都是一个可撤销的命令对象。
|
||||
|
||||
用通俗的话说
|
||||
|
||||
> 用命令对象的方式存储请求以在将来时可以执行它或撤销它。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 在面向对象编程中,命令模式是一种行为型设计模式,它把在稍后执行的一个动作或触发的一个事件所需要的所有信息封装到一个对象中。
|
||||
|
||||
**编程示例**
|
||||
|
||||
这是巫师和地精的示例代码。让我们从巫师类开始。
|
||||
|
||||
```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 undoLastSpell() {
|
||||
if (!undoStack.isEmpty()) {
|
||||
var previousSpell = undoStack.pollLast();
|
||||
redoStack.offerLast(previousSpell);
|
||||
LOGGER.info("{} undoes {}", this, previousSpell);
|
||||
previousSpell.undo();
|
||||
}
|
||||
}
|
||||
|
||||
public void redoLastSpell() {
|
||||
if (!redoStack.isEmpty()) {
|
||||
var previousSpell = redoStack.pollLast();
|
||||
undoStack.offerLast(previousSpell);
|
||||
LOGGER.info("{} redoes {}", this, previousSpell);
|
||||
previousSpell.redo();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Wizard";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
接下来我们介绍咒语层级
|
||||
|
||||
```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";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后我们有咒语的目标地精。
|
||||
|
||||
```java
|
||||
public abstract class Target {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Target.class);
|
||||
|
||||
private Size size;
|
||||
|
||||
private Visibility visibility;
|
||||
|
||||
public Size getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Size size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public void setVisibility(Visibility visibility) {
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
|
||||
public void printStatus() {
|
||||
LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility());
|
||||
}
|
||||
}
|
||||
|
||||
public class Goblin extends Target {
|
||||
|
||||
public Goblin() {
|
||||
setSize(Size.NORMAL);
|
||||
setVisibility(Visibility.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Goblin";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
最后是整个示例的实践。
|
||||
|
||||
```java
|
||||
var wizard = new Wizard();
|
||||
var goblin = new Goblin();
|
||||
goblin.printStatus();
|
||||
// Goblin, [size=normal] [visibility=visible]
|
||||
wizard.castSpell(new ShrinkSpell(), goblin);
|
||||
// Wizard casts Shrink spell at Goblin
|
||||
goblin.printStatus();
|
||||
// Goblin, [size=small] [visibility=visible]
|
||||
wizard.castSpell(new InvisibilitySpell(), goblin);
|
||||
// Wizard casts Invisibility spell at Goblin
|
||||
goblin.printStatus();
|
||||
// Goblin, [size=small] [visibility=invisible]
|
||||
wizard.undoLastSpell();
|
||||
// Wizard undoes Invisibility spell
|
||||
goblin.printStatus();
|
||||
// Goblin, [size=small] [visibility=visible]
|
||||
```
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用性
|
||||
使用命令模式当你想
|
||||
|
||||
* 通过操作将对象参数化。您可以使用回调函数(即,已在某处注册以便稍后调用的函数)以过程语言表示这种参数化。命令是回调的一种面向对象替代方案。
|
||||
* 在不同的时间指定,排队和执行请求。一个命令对象的生存期可以独立于原始请求。如果请求的接收方可以以地址空间无关的方式来表示,那么你可以将请求的命令对象传输到其他进程并在那里执行请求。
|
||||
* 支持撤销。命令的执行操作可以在命令本身中存储状态以反转其效果。命令接口必须有添加的反执行操作,该操作可以逆转上一次执行调用的效果。执行的命令存储在历史列表中。无限撤消和重做通过分别向后和向前遍历此列表来实现,分别调用unexecute和execute。
|
||||
* 支持日志记录更改,以便在系统崩溃时可以重新应用它们。通过使用加载和存储操作扩展命令接口,你可以保留更改的永久日志。从崩溃中恢复涉及从磁盘重新加载记录的命令,并通过执行操作重新执行它们。
|
||||
* 通过原始的操作来构建一个以高级操作围绕的系统。这种结构在支持事务的信息系统中很常见。事务封装了一组数据更改。命令模式提供了一种对事务进行建模的方法。命令具有公共接口,让你以相同的方式调用所有事务。该模式还可以通过新的事务来轻松扩展系统。
|
||||
|
||||
## 典型用例
|
||||
|
||||
* 保留请求历史
|
||||
* 实现回调功能
|
||||
* 实现撤销功能
|
||||
|
||||
## Java世界例子
|
||||
|
||||
* [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html)
|
||||
* [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java)
|
||||
* [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki)
|
||||
* [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
||||
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94)
|
137
zh/decorator/README.md
Normal file
@ -0,0 +1,137 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Decorator
|
||||
folder: decorator
|
||||
permalink: /patterns/decorator/
|
||||
categories: Structural
|
||||
tags:
|
||||
- Gang Of Four
|
||||
- Extensibility
|
||||
---
|
||||
|
||||
## 或称
|
||||
包装器
|
||||
|
||||
## 目的
|
||||
动态的为对象附加额外的职责。装饰器为子类提供了灵活的替代方案,以扩展功能。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 附近的山丘上住着一个愤怒的巨魔。通常它是徒手的,但有时它有武器。为了武装巨魔不必创建新的巨魔,而是用合适的武器动态的装饰它。
|
||||
|
||||
通俗的说
|
||||
|
||||
> 装饰者模式让你可以在运行时通过把对象包装进一个装饰类对象中来动态的改变一个对象的行为。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 在面向对象的编程中,装饰器模式是一种设计模式,它允许将行为静态或动态地添加到单个对象中,而不会影响同一类中其他对象的行为。装饰器模式通常对于遵守单一责任原则很有用,因为它允许将功能划分到具有唯一关注领域的类之间。
|
||||
|
||||
**程序示例**
|
||||
|
||||
以巨魔的为例。首先我有有一个简单的巨魔,实现了巨魔接口。
|
||||
|
||||
```java
|
||||
public interface Troll {
|
||||
void attack();
|
||||
int getAttackPower();
|
||||
void fleeBattle();
|
||||
}
|
||||
|
||||
public class SimpleTroll implements Troll {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleTroll.class);
|
||||
|
||||
@Override
|
||||
public void attack() {
|
||||
LOGGER.info("The troll tries to grab you!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttackPower() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fleeBattle() {
|
||||
LOGGER.info("The troll shrieks in horror and runs away!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下面我们想为巨魔添加球棒。我们可以用装饰者来动态的实现。
|
||||
|
||||
```java
|
||||
public class ClubbedTroll implements Troll {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ClubbedTroll.class);
|
||||
|
||||
private final Troll decorated;
|
||||
|
||||
public ClubbedTroll(Troll decorated) {
|
||||
this.decorated = decorated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attack() {
|
||||
decorated.attack();
|
||||
LOGGER.info("The troll swings at you with a club!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttackPower() {
|
||||
return decorated.getAttackPower() + 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fleeBattle() {
|
||||
decorated.fleeBattle();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里是巨魔的实战
|
||||
|
||||
```java
|
||||
// simple troll
|
||||
var troll = new SimpleTroll();
|
||||
troll.attack(); // The troll tries to grab you!
|
||||
troll.fleeBattle(); // The troll shrieks in horror and runs away!
|
||||
|
||||
// change the behavior of the simple troll by adding a decorator
|
||||
var clubbedTroll = new ClubbedTroll(troll);
|
||||
clubbedTroll.attack(); // The troll tries to grab you! The troll swings at you with a club!
|
||||
clubbedTroll.fleeBattle(); // The troll shrieks in horror and runs away!
|
||||
```
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用性
|
||||
使用装饰者
|
||||
|
||||
* 动态透明地向单个对象添加职责,即不影响其他对象
|
||||
* 对于可以撤销的责任
|
||||
* 当通过子类化进行扩展是不切实际的。有时可能会有大量的独立扩展,并且会产生大量的子类来支持每种组合。 否则类定义可能被隐藏或无法用于子类化。
|
||||
|
||||
## 教程
|
||||
* [Decorator Pattern Tutorial](https://www.journaldev.com/1540/decorator-design-pattern-in-java-example)
|
||||
|
||||
## Java世界的例子
|
||||
* [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html),
|
||||
[java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html)
|
||||
* [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-)
|
||||
* [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-)
|
||||
* [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-)
|
||||
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b)
|
||||
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
||||
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94)
|
101
zh/dependency-injection/README.md
Normal file
@ -0,0 +1,101 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Dependency Injection
|
||||
folder: dependency-injection
|
||||
permalink: /patterns/dependency-injection/
|
||||
categories: Creational
|
||||
tags:
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
依赖注入是一种软件设计模式,其中一个或多个依赖项(或服务)被注入或通过引用传递到一个依赖对象(或客户端)中,并成为客户端状态的一部分。该模式将客户的依赖关系的创建与其自身的行为分开,这使程序设计可以松散耦合,并遵循控制反转和单一职责原则。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 老巫师喜欢不时地装满烟斗抽烟。 但是,他不想只依赖一个烟草品牌,而是希望能够互换使用它们。
|
||||
|
||||
通俗的说
|
||||
|
||||
> 依赖注入将客户端依赖的创建与其自身行为分开。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 在软件工程中,依赖注入是一种对象接收其依赖的其他对象的技术。 这些其他对象称为依赖项。
|
||||
|
||||
**程序示例**
|
||||
|
||||
先介绍一下烟草接口和具体的品牌。
|
||||
|
||||
```java
|
||||
public abstract class Tobacco {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Tobacco.class);
|
||||
|
||||
public void smoke(Wizard wizard) {
|
||||
LOGGER.info("{} smoking {}", wizard.getClass().getSimpleName(),
|
||||
this.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
public class SecondBreakfastTobacco extends Tobacco {
|
||||
}
|
||||
|
||||
public class RivendellTobacco extends Tobacco {
|
||||
}
|
||||
|
||||
public class OldTobyTobacco extends Tobacco {
|
||||
}
|
||||
```
|
||||
|
||||
下面是老巫师的类的层次结构。
|
||||
|
||||
```java
|
||||
public interface Wizard {
|
||||
|
||||
void smoke();
|
||||
}
|
||||
|
||||
public class AdvancedWizard implements Wizard {
|
||||
|
||||
private final Tobacco tobacco;
|
||||
|
||||
public AdvancedWizard(Tobacco tobacco) {
|
||||
this.tobacco = tobacco;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smoke() {
|
||||
tobacco.smoke(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后我们可以看到给老巫师任意品牌的烟草是多么的简单。
|
||||
|
||||
```java
|
||||
var advancedWizard = new AdvancedWizard(new SecondBreakfastTobacco());
|
||||
advancedWizard.smoke();
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
使用依赖注入当:
|
||||
|
||||
- 当你需要从对象中移除掉具体的实现内容时
|
||||
|
||||
* 使用模拟对象或存根隔离地启用类的单元测试
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Dependency Injection Principles, Practices, and Patterns](https://www.amazon.com/gp/product/161729473X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=161729473X&linkId=57079257a5c7d33755493802f3b884bd)
|
||||
* [Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/gp/product/0132350882/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0132350882&linkCode=as2&tag=javadesignpat-20&linkId=2c390d89cc9e61c01b9e7005c7842871)
|
||||
* [Java 9 Dependency Injection: Write loosely coupled code with Spring 5 and Guice](https://www.amazon.com/gp/product/1788296257/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1788296257&linkId=4e9137a3bf722a8b5b156cce1eec0fc1)
|
||||
* [Google Guice Tutorial: Open source Java based dependency injection framework](https://www.amazon.com/gp/product/B083P7DZ8M/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B083P7DZ8M&linkId=04f0f902c877921e45215b624a124bfe)
|
207
zh/facade/README.md
Normal file
@ -0,0 +1,207 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Facade
|
||||
folder: facade
|
||||
permalink: /patterns/facade/
|
||||
categories: Structural
|
||||
tags:
|
||||
- Gang Of Four
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## 目的
|
||||
为一个子系统中的一系列接口提供一个统一的接口。外观定义了一个更高级别的接口以便子系统更容易使用。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界的例子
|
||||
|
||||
> 一个金矿是怎么工作的?“嗯,矿工下去然后挖金子!”你说。这是你所相信的因为你在使用一个金矿对外提供的一个简单接口,在内部它要却要做很多事情。这个简单的接口对复杂的子系统来说就是一个外观。
|
||||
|
||||
用通俗的话说
|
||||
|
||||
> 外观模式为一个复杂的子系统提供一个简单的接口。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 外观是为很大体量的代码(比如类库)提供简单接口的一种对象。
|
||||
|
||||
**程序示例**
|
||||
|
||||
使用上面金矿的例子。这里我们有矮人的矿工等级制度。
|
||||
|
||||
```java
|
||||
public abstract class DwarvenMineWorker {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenMineWorker.class);
|
||||
|
||||
public void goToSleep() {
|
||||
LOGGER.info("{} goes to sleep.", name());
|
||||
}
|
||||
|
||||
public void wakeUp() {
|
||||
LOGGER.info("{} wakes up.", name());
|
||||
}
|
||||
|
||||
public void goHome() {
|
||||
LOGGER.info("{} goes home.", name());
|
||||
}
|
||||
|
||||
public void goToMine() {
|
||||
LOGGER.info("{} goes to the mine.", name());
|
||||
}
|
||||
|
||||
private void action(Action action) {
|
||||
switch (action) {
|
||||
case GO_TO_SLEEP:
|
||||
goToSleep();
|
||||
break;
|
||||
case WAKE_UP:
|
||||
wakeUp();
|
||||
break;
|
||||
case GO_HOME:
|
||||
goHome();
|
||||
break;
|
||||
case GO_TO_MINE:
|
||||
goToMine();
|
||||
break;
|
||||
case WORK:
|
||||
work();
|
||||
break;
|
||||
default:
|
||||
LOGGER.info("Undefined action");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void action(Action... actions) {
|
||||
Arrays.stream(actions).forEach(this::action);
|
||||
}
|
||||
|
||||
public abstract void work();
|
||||
|
||||
public abstract String name();
|
||||
|
||||
enum Action {
|
||||
GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK
|
||||
}
|
||||
}
|
||||
|
||||
public class DwarvenTunnelDigger extends DwarvenMineWorker {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenTunnelDigger.class);
|
||||
|
||||
@Override
|
||||
public void work() {
|
||||
LOGGER.info("{} creates another promising tunnel.", name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "Dwarven tunnel digger";
|
||||
}
|
||||
}
|
||||
|
||||
public class DwarvenGoldDigger extends DwarvenMineWorker {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenGoldDigger.class);
|
||||
|
||||
@Override
|
||||
public void work() {
|
||||
LOGGER.info("{} digs for gold.", name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "Dwarf gold digger";
|
||||
}
|
||||
}
|
||||
|
||||
public class DwarvenCartOperator extends DwarvenMineWorker {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenCartOperator.class);
|
||||
|
||||
@Override
|
||||
public void work() {
|
||||
LOGGER.info("{} moves gold chunks out of the mine.", name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "Dwarf cart operator";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
为了操纵所有这些矿工我们有了这个外观
|
||||
|
||||
```java
|
||||
public class DwarvenGoldmineFacade {
|
||||
|
||||
private final List<DwarvenMineWorker> workers;
|
||||
|
||||
public DwarvenGoldmineFacade() {
|
||||
workers = List.of(
|
||||
new DwarvenGoldDigger(),
|
||||
new DwarvenCartOperator(),
|
||||
new DwarvenTunnelDigger());
|
||||
}
|
||||
|
||||
public void startNewDay() {
|
||||
makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE);
|
||||
}
|
||||
|
||||
public void digOutGold() {
|
||||
makeActions(workers, DwarvenMineWorker.Action.WORK);
|
||||
}
|
||||
|
||||
public void endDay() {
|
||||
makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP);
|
||||
}
|
||||
|
||||
private static void makeActions(Collection<DwarvenMineWorker> workers,
|
||||
DwarvenMineWorker.Action... actions) {
|
||||
workers.forEach(worker -> worker.action(actions));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在来使用外观
|
||||
|
||||
```java
|
||||
DwarvenGoldmineFacade facade = new DwarvenGoldmineFacade();
|
||||
facade.startNewDay();
|
||||
// Dwarf gold digger wakes up.
|
||||
// Dwarf gold digger goes to the mine.
|
||||
// Dwarf cart operator wakes up.
|
||||
// Dwarf cart operator goes to the mine.
|
||||
// Dwarven tunnel digger wakes up.
|
||||
// Dwarven tunnel digger goes to the mine.
|
||||
facade.digOutGold();
|
||||
// Dwarf gold digger digs for gold.
|
||||
// Dwarf cart operator moves gold chunks out of the mine.
|
||||
// Dwarven tunnel digger creates another promising tunnel.
|
||||
facade.endDay();
|
||||
// Dwarf gold digger goes home.
|
||||
// Dwarf gold digger goes to sleep.
|
||||
// Dwarf cart operator goes home.
|
||||
// Dwarf cart operator goes to sleep.
|
||||
// Dwarven tunnel digger goes home.
|
||||
// Dwarven tunnel digger goes to sleep.
|
||||
```
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用性
|
||||
使用外观模式当
|
||||
|
||||
* 你想为一个复杂的子系统提供一个简单的接口。随着子系统的发展,它们通常会变得更加复杂。多数模式在应用时会导致更多和更少的类。这使子系统更可重用,更易于自定义,但是对于不需要自定义它的客户来说,使用它也变得更加困难。 外观可以提供子系统的简单默认视图,足以满足大多数客户端的需求。只有需要更多可定制性的客户才需要查看外观外的东西(原子系统提供的接口)。
|
||||
* 客户端与抽象的实现类之间存在许多依赖关系。 引入外观以使子系统与客户端和其他子系统分离,从而提高子系统的独立性和可移植性。
|
||||
* 您想对子系统进行分层。 使用外观来定义每个子系统级别的入口点。 如果子系统是相关的,则可以通过使子系统仅通过其外观相互通信来简化它们之间的依赖性。
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
88
zh/factory-method/README.md
Normal file
@ -0,0 +1,88 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Factory Method
|
||||
folder: factory-method
|
||||
permalink: /patterns/factory-method/
|
||||
categories: Creational
|
||||
tags:
|
||||
- Extensibility
|
||||
- Gang Of Four
|
||||
---
|
||||
|
||||
## Also known as
|
||||
# 或称
|
||||
|
||||
虚拟构造器
|
||||
|
||||
## 目的
|
||||
为创建一个对象定义一个接口,但是让子类决定实例化哪个类。工厂方法允许类将实例化延迟到子类。
|
||||
|
||||
## 解释
|
||||
真实世界例子
|
||||
|
||||
> 铁匠生产武器。精灵需要精灵武器,而兽人需要兽人武器。根据客户来召唤正确类型的铁匠。
|
||||
|
||||
通俗的说
|
||||
|
||||
> 它为类提供了一种把实例化的逻辑委托给子类的方式。
|
||||
|
||||
维基百科上说
|
||||
|
||||
> 在基于类的编程中,工厂方法模式是一种创建型设计模式用来解决创建对象的问题,而不需要指定将要创建对象的确切类。这是通过调用工厂方法创建对象来完成的,而不是通过调用构造器。该工厂方法在接口中指定并由子类实现,或者在基类实现并可以选择由子类重写。
|
||||
|
||||
**程序示例**
|
||||
|
||||
以上面的铁匠为例,首先我们有铁匠的接口和一些它的实现。
|
||||
|
||||
```java
|
||||
public interface Blacksmith {
|
||||
Weapon manufactureWeapon(WeaponType weaponType);
|
||||
}
|
||||
|
||||
public class ElfBlacksmith implements Blacksmith {
|
||||
public Weapon manufactureWeapon(WeaponType weaponType) {
|
||||
return ELFARSENAL.get(weaponType);
|
||||
}
|
||||
}
|
||||
|
||||
public class OrcBlacksmith implements Blacksmith {
|
||||
public Weapon manufactureWeapon(WeaponType weaponType) {
|
||||
return ORCARSENAL.get(weaponType);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在随着客户的到来,会召唤出正确类型的铁匠并制造出要求的武器。
|
||||
|
||||
```java
|
||||
var blacksmith = new ElfBlacksmith();
|
||||
blacksmith.manufactureWeapon(WeaponType.SPEAR);
|
||||
blacksmith.manufactureWeapon(WeaponType.AXE);
|
||||
// Elvish weapons are created
|
||||
```
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用性
|
||||
使用工厂方法模式当
|
||||
|
||||
* 一个类无法预料它所要必须创建的对象的类
|
||||
* 一个类想要它的子类来指定它要创建的对象
|
||||
* 类将责任委派给几个帮助子类中的一个,而你想定位了解是具体之中的哪一个
|
||||
|
||||
## Java中的例子
|
||||
|
||||
* [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--)
|
||||
* [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-)
|
||||
* [java.text.NumberFormat](http://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--)
|
||||
* [java.nio.charset.Charset](http://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-)
|
||||
* [java.net.URLStreamHandlerFactory](http://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html#createURLStreamHandler-java.lang.String-)
|
||||
* [java.util.EnumSet](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of-E-)
|
||||
* [javax.xml.bind.JAXBContext](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
134
zh/iterator/README.md
Normal file
@ -0,0 +1,134 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Iterator
|
||||
folder: iterator
|
||||
permalink: /patterns/iterator/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## 又被称为
|
||||
游标
|
||||
|
||||
## 目的
|
||||
提供一种在不暴露其基础表示的情况下顺序访问聚合对象的元素的方法。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 百宝箱包含一组魔法物品。有多种物品,例如戒指,药水和武器。可以使用藏宝箱提供的迭代器按类型浏览商品。
|
||||
|
||||
通俗地说
|
||||
|
||||
> 容器可以提供与表示形式无关的迭代器接口,以提供对元素的访问。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 在面向对象的编程中,迭代器模式是一种设计模式,其中迭代器用于遍历容器并访问容器的元素。
|
||||
|
||||
**程序示例**
|
||||
|
||||
在我们的示例中包含物品的藏宝箱是主要类。
|
||||
|
||||
```java
|
||||
public class TreasureChest {
|
||||
|
||||
private final List<Item> items;
|
||||
|
||||
public TreasureChest() {
|
||||
items = List.of(
|
||||
new Item(ItemType.POTION, "Potion of courage"),
|
||||
new Item(ItemType.RING, "Ring of shadows"),
|
||||
new Item(ItemType.POTION, "Potion of wisdom"),
|
||||
new Item(ItemType.POTION, "Potion of blood"),
|
||||
new Item(ItemType.WEAPON, "Sword of silver +1"),
|
||||
new Item(ItemType.POTION, "Potion of rust"),
|
||||
new Item(ItemType.POTION, "Potion of healing"),
|
||||
new Item(ItemType.RING, "Ring of armor"),
|
||||
new Item(ItemType.WEAPON, "Steel halberd"),
|
||||
new Item(ItemType.WEAPON, "Dagger of poison"));
|
||||
}
|
||||
|
||||
public Iterator<Item> iterator(ItemType itemType) {
|
||||
return new TreasureChestItemIterator(this, itemType);
|
||||
}
|
||||
|
||||
public List<Item> getItems() {
|
||||
return new ArrayList<>(items);
|
||||
}
|
||||
}
|
||||
|
||||
public class Item {
|
||||
|
||||
private ItemType type;
|
||||
private final String name;
|
||||
|
||||
public Item(ItemType type, String name) {
|
||||
this.setType(type);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ItemType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public final void setType(ItemType type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ItemType {
|
||||
|
||||
ANY, WEAPON, RING, POTION
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
迭代器接口极度简单。
|
||||
|
||||
```java
|
||||
public interface Iterator<T> {
|
||||
|
||||
boolean hasNext();
|
||||
|
||||
T next();
|
||||
}
|
||||
```
|
||||
|
||||
在以下示例中,我们遍历在宝箱中找到的戒指类型物品。
|
||||
|
||||
```java
|
||||
var itemIterator = TREASURE_CHEST.iterator(ItemType.RING);
|
||||
while (itemIterator.hasNext()) {
|
||||
LOGGER.info(itemIterator.next().toString());
|
||||
}
|
||||
// Ring of shadows
|
||||
// Ring of armor
|
||||
```
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用性
|
||||
以下情况使用迭代器模式
|
||||
|
||||
* 在不暴露其内部表示的情况下访问聚合对象的内容
|
||||
* 为了支持聚合对象的多种遍历方式
|
||||
* 提供一个遍历不同聚合结构的统一接口
|
||||
|
||||
## Java世界例子
|
||||
|
||||
* [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html)
|
||||
* [java.util.Enumeration](http://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
155
zh/state/README.md
Normal file
@ -0,0 +1,155 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: State
|
||||
folder: state
|
||||
permalink: /patterns/state/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## 又被称为
|
||||
对象状态
|
||||
|
||||
## 目的
|
||||
允许对象在内部状态改变时改变它的行为。对象看起来好像修改了它的类。
|
||||
|
||||
## 解释
|
||||
真实世界例子
|
||||
|
||||
> 当在长毛象的自然栖息地观察长毛象时,似乎它会根据情况来改变自己的行为。它开始可能很平静但是随着时间推移当它检测到威胁时它会对周围的环境感到愤怒和危险。
|
||||
|
||||
通俗的说
|
||||
|
||||
> 状态模式允许对象改变它的行为。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 状态模式是一种允许对象在内部状态改变时改变它的行为的行为型设计模式。这种模式接近于有限状态机的概念。状态模式可以被理解为策略模式,它能够通过调用在模式接口中定义的方法来切换策略。
|
||||
|
||||
**编程示例**
|
||||
|
||||
这里是模式接口和它具体的实现。
|
||||
|
||||
```java
|
||||
public interface State {
|
||||
|
||||
void onEnterState();
|
||||
|
||||
void observe();
|
||||
}
|
||||
|
||||
public class PeacefulState implements State {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PeacefulState.class);
|
||||
|
||||
private final Mammoth mammoth;
|
||||
|
||||
public PeacefulState(Mammoth mammoth) {
|
||||
this.mammoth = mammoth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void observe() {
|
||||
LOGGER.info("{} is calm and peaceful.", mammoth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnterState() {
|
||||
LOGGER.info("{} calms down.", mammoth);
|
||||
}
|
||||
}
|
||||
|
||||
public class AngryState implements State {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AngryState.class);
|
||||
|
||||
private final Mammoth mammoth;
|
||||
|
||||
public AngryState(Mammoth mammoth) {
|
||||
this.mammoth = mammoth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void observe() {
|
||||
LOGGER.info("{} is furious!", mammoth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnterState() {
|
||||
LOGGER.info("{} gets angry!", mammoth);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后这里是包含状态的长毛象。
|
||||
|
||||
```java
|
||||
public class Mammoth {
|
||||
|
||||
private State state;
|
||||
|
||||
public Mammoth() {
|
||||
state = new PeacefulState(this);
|
||||
}
|
||||
|
||||
public void timePasses() {
|
||||
if (state.getClass().equals(PeacefulState.class)) {
|
||||
changeStateTo(new AngryState(this));
|
||||
} else {
|
||||
changeStateTo(new PeacefulState(this));
|
||||
}
|
||||
}
|
||||
|
||||
private void changeStateTo(State newState) {
|
||||
this.state = newState;
|
||||
this.state.onEnterState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "The mammoth";
|
||||
}
|
||||
|
||||
public void observe() {
|
||||
this.state.observe();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后这里是长毛象随着时间的推移后的整个行为示例。
|
||||
|
||||
```java
|
||||
var mammoth = new Mammoth();
|
||||
mammoth.observe();
|
||||
mammoth.timePasses();
|
||||
mammoth.observe();
|
||||
mammoth.timePasses();
|
||||
mammoth.observe();
|
||||
|
||||
// The mammoth gets angry!
|
||||
// The mammoth is furious!
|
||||
// The mammoth calms down.
|
||||
// The mammoth is calm and peaceful.
|
||||
```
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
在以下两种情况下,请使用State模式
|
||||
|
||||
* 对象的行为取决于它的状态,并且它必须在运行时根据状态更改其行为。
|
||||
* 根据对象状态的不同,操作有大量的条件语句。此状态通常由一个或多个枚举常量表示。通常,几个操作将包含此相同的条件结构。状态模式把条件语句的分支分别放入单独的类中。这样一来,你就可以将对象的状态视为独立的对象,该对象可以独立于其他对象而变化。
|
||||
|
||||
## Java中例子
|
||||
|
||||
* [javax.faces.lifecycle.Lifecycle#execute()](http://docs.oracle.com/javaee/7/api/javax/faces/lifecycle/Lifecycle.html#execute-javax.faces.context.FacesContext-) controlled by [FacesServlet](http://docs.oracle.com/javaee/7/api/javax/faces/webapp/FacesServlet.html), the behavior is dependent on current phase of lifecycle.
|
||||
* [JDiameter - Diameter State Machine](https://github.com/npathai/jdiameter/blob/master/core/jdiameter/api/src/main/java/org/jdiameter/api/app/State.java)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
145
zh/template-method/README.md
Normal file
@ -0,0 +1,145 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Template method
|
||||
folder: template-method
|
||||
permalink: /patterns/template-method/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## 目的
|
||||
在一个操作中定义算法的骨架,将某些步骤推迟到子类。模板方法允许子类重新定义算法的某些步骤,而无需更改算法的结构。
|
||||
|
||||
## 解释
|
||||
真实世界例子
|
||||
|
||||
> 偷东西的一般步骤是相同的。 首先,选择目标,然后以某种方式使其迷惑,最后,你偷走了该物品。然而这些步骤有很多实现方式。
|
||||
|
||||
通俗的说
|
||||
|
||||
> 模板方法模式在父类中列出一般的步骤然后让具体的子类定义实现细节。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 在面向对象的编程中,模板方法是Gamma等人确定的行为设计模式之一。在《设计模式》一书中。模板方法是父类中一个方法,通常是一个抽象父类,根据许多高级步骤定义了操作的骨架。这些步骤本身由与模板方法在同一类中的其他帮助程序方法实现。
|
||||
|
||||
**编程示例**
|
||||
|
||||
让我们首先介绍模板方法类及其具体实现。
|
||||
|
||||
```java
|
||||
public abstract class StealingMethod {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(StealingMethod.class);
|
||||
|
||||
protected abstract String pickTarget();
|
||||
|
||||
protected abstract void confuseTarget(String target);
|
||||
|
||||
protected abstract void stealTheItem(String target);
|
||||
|
||||
public void steal() {
|
||||
var target = pickTarget();
|
||||
LOGGER.info("The target has been chosen as {}.", target);
|
||||
confuseTarget(target);
|
||||
stealTheItem(target);
|
||||
}
|
||||
}
|
||||
|
||||
public class SubtleMethod extends StealingMethod {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SubtleMethod.class);
|
||||
|
||||
@Override
|
||||
protected String pickTarget() {
|
||||
return "shop keeper";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void confuseTarget(String target) {
|
||||
LOGGER.info("Approach the {} with tears running and hug him!", target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stealTheItem(String target) {
|
||||
LOGGER.info("While in close contact grab the {}'s wallet.", target);
|
||||
}
|
||||
}
|
||||
|
||||
public class HitAndRunMethod extends StealingMethod {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HitAndRunMethod.class);
|
||||
|
||||
@Override
|
||||
protected String pickTarget() {
|
||||
return "old goblin woman";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void confuseTarget(String target) {
|
||||
LOGGER.info("Approach the {} from behind.", target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stealTheItem(String target) {
|
||||
LOGGER.info("Grab the handbag and run away fast!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是包含模板方法的半身贼类。
|
||||
|
||||
```java
|
||||
public class HalflingThief {
|
||||
|
||||
private StealingMethod method;
|
||||
|
||||
public HalflingThief(StealingMethod method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public void steal() {
|
||||
method.steal();
|
||||
}
|
||||
|
||||
public void changeMethod(StealingMethod method) {
|
||||
this.method = method;
|
||||
}
|
||||
}
|
||||
```
|
||||
最后,我们展示半身人贼如何利用不同的偷窃方法。
|
||||
|
||||
```java
|
||||
var thief = new HalflingThief(new HitAndRunMethod());
|
||||
thief.steal();
|
||||
thief.changeMethod(new SubtleMethod());
|
||||
thief.steal();
|
||||
```
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
使用模板方法模式可以
|
||||
|
||||
* 一次性实现一个算法中不变的部分并将其留给子类来实现可能变化的行为。
|
||||
* 子类之间的共同行为应分解并集中在一个共同类中,以避免代码重复。如Opdyke和Johnson所描述的,这是“重构概括”的一个很好的例子。你首先要确定现有代码中的差异,然后将差异拆分为新的操作。最后,将不同的代码替换为调用这些新操作之一的模板方法。
|
||||
* 控制子类扩展。您可以定义一个模板方法,该方法在特定点调用“ 钩子”操作,从而仅允许在这些点进行扩展
|
||||
|
||||
## 教程
|
||||
|
||||
* [Template-method Pattern Tutorial](https://www.journaldev.com/1763/template-method-design-pattern-in-java)
|
||||
|
||||
## Java例子
|
||||
|
||||
* [javax.servlet.GenericServlet.init](https://jakarta.ee/specifications/servlet/4.0/apidocs/javax/servlet/GenericServlet.html#init--):
|
||||
Method `GenericServlet.init(ServletConfig config)` calls the parameterless method `GenericServlet.init()` which is intended to be overridden in subclasses.
|
||||
Method `GenericServlet.init(ServletConfig config)` is the template method in this example.
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|