Compare commits

...

67 Commits

Author SHA1 Message Date
allcontributors[bot]
4b18e223cd docs: update .all-contributorsrc [skip ci] 2020-11-28 12:24:58 +00:00
allcontributors[bot]
13c6de036f docs: update README.md [skip ci] 2020-11-28 12:24:57 +00:00
Ilkka Seppälä
6f979d0cb2 Fix version number class diagram not showing 2020-11-24 08:10:34 +02:00
Ilkka Seppälä
f084f8bf41 Merge pull request #1599 from iluwatar/all-contributors/add-manannikov
docs: add manannikov as a contributor
2020-11-23 22:38:24 +02:00
allcontributors[bot]
6c95868b8d docs: update .all-contributorsrc [skip ci] 2020-11-23 20:37:49 +00:00
allcontributors[bot]
4c7f1b7822 docs: update README.md [skip ci] 2020-11-23 20:37:48 +00:00
Ilkka Seppälä
0c44b53909 Merge pull request #1563 from manannikov/Issue#1284
#1284 Implement Version Number pattern
2020-11-23 22:35:43 +02:00
Pavel Manannikov
9ead3adf73 #1284 Divide tests 2020-11-23 20:59:00 +02:00
Pavel Manannikov
97e3a3debc #1284 Use local variable inference 2020-11-23 19:42:50 +02:00
Ilkka Seppälä
43ed09015d Add missing license header 2020-11-22 21:14:53 +02:00
Ilkka Seppälä
7eee546208 Update README.md 2020-11-08 19:39:21 +02:00
Ilkka Seppälä
dc31960710 Remove unused logger objects 2020-11-08 19:37:15 +02:00
Ilkka Seppälä
7c0fdad5a2 Merge pull request #1531 from ravening/command-functional
Refactor the command pattern to use lambda functions
2020-11-08 19:33:48 +02:00
Ilkka Seppälä
5a8933ea17 Merge pull request #1534 from swarajsaaj/#1510-Circuit-Breaker-Refactoring
#1510 Improvements done in Circuit Breaker
2020-11-07 20:04:47 +02:00
Ilkka Seppälä
d02233f0b7 Merge pull request #1594 from iluwatar/all-contributors/add-akashchandwani
docs: add akashchandwani as a contributor
2020-11-07 14:36:54 +02:00
allcontributors[bot]
d42bcab9fc docs: update .all-contributorsrc [skip ci] 2020-11-07 12:34:36 +00:00
allcontributors[bot]
77b2ff2150 docs: update README.md [skip ci] 2020-11-07 12:34:35 +00:00
Ilkka Seppälä
cff072d1ca Merge pull request #1593 from iluwatar/all-contributors/add-dsibilio
docs: add dsibilio as a contributor
2020-11-07 14:34:08 +02:00
allcontributors[bot]
80ba0407db docs: update .all-contributorsrc [skip ci] 2020-11-07 12:33:11 +00:00
allcontributors[bot]
6a09b909f2 docs: update README.md [skip ci] 2020-11-07 12:33:10 +00:00
Ilkka Seppälä
53ccc0e7e6 Merge pull request #1548 from dsibilio/patch-1
Add missing word to README.md
2020-11-07 14:31:26 +02:00
Ilkka Seppälä
a7a8e23b01 Merge pull request #1591 from iluwatar/all-contributors/add-Ascenio
docs: add Ascenio as a contributor
2020-11-07 09:49:20 +02:00
allcontributors[bot]
2783251d00 docs: update .all-contributorsrc [skip ci] 2020-11-07 07:47:42 +00:00
allcontributors[bot]
ac432968ae docs: update README.md [skip ci] 2020-11-07 07:47:41 +00:00
Ilkka Seppälä
f006782805 Merge pull request #1529 from mookkiah/issue_1500
issue 1500 - using JUnit 5 and resolved a build issue
2020-10-17 18:02:56 +03:00
Pavel Manannikov
2332520d67 #1284 Implement Version Number pattern 2020-10-13 20:17:25 +03:00
Rakesh Venkatesh
af1b611136 code refactor 2020-10-13 17:36:59 +02:00
Rakesh Venkatesh
4ff196ce35 Refactor the command pattern to use lambda functions
We can leverage the lambda expressins of Java 8 onwards
to implement command design pattern instead of traditional
non functional way
2020-10-13 16:45:19 +02:00
swarajsaaj
242ae6a412 #1510 Update class diagram 2020-10-11 00:22:28 +05:30
swarajsaaj
b689fe0a26 #1510 Fix comment length 2020-10-11 00:11:06 +05:30
swarajsaaj
7aea765dd1 #1510 Fix review comments 2020-10-10 23:57:53 +05:30
swarajsaaj
ea49cbfe94 Merge branch 'master' into #1510-Circuit-Breaker-Refactoring 2020-10-10 22:19:46 +05:30
Ilkka Seppälä
1f4a412e70 Merge pull request #1535 from ohbus/master
Cache SonarCloud packages
2020-10-10 18:51:00 +03:00
Domenico Sibilio
36d0a3718c Update README.md
Missing word in sentence.
2020-10-06 18:24:47 +02:00
Ilkka Seppälä
195a735814 #508 fix SpaceStationMir logging 2020-10-04 14:50:48 +03:00
Ilkka Seppälä
633d45aa67 Fix typo 2020-10-04 14:33:01 +03:00
Ilkka Seppälä
adc267e48e Merge pull request #1526 from xiaod-dev/translation-zh
Translation zh
2020-10-04 14:26:39 +03:00
Ilkka Seppälä
cc8d209c22 Merge pull request #1545 from iluwatar/all-contributors/add-ChFlick
docs: add ChFlick as a contributor
2020-10-04 14:24:48 +03:00
allcontributors[bot]
578b3528d5 docs: update .all-contributorsrc [skip ci] 2020-10-04 11:23:50 +00:00
allcontributors[bot]
5674f3c52e docs: update README.md [skip ci] 2020-10-04 11:23:49 +00:00
Ilkka Seppälä
4ad2bfefad Merge pull request #1525 from ChFlick/patch-1
Fix links on the event sourcing page
2020-10-04 14:21:25 +03:00
Ilkka Seppälä
e5d219609d Add missing license headers 2020-10-04 14:17:28 +03:00
Ilkka Seppälä
21ca47a22e Fix build 2020-10-04 14:17:10 +03:00
Ilkka Seppälä
ebd38bcfaa Update license header 2020-10-04 14:09:36 +03:00
Subhrodip Mohanta
a3753807ae add Caching of SonarCloud packages 2020-10-01 22:23:43 +05:30
Subhrodip Mohanta
64266d63fd Merge pull request #1 from iluwatar/master
Update README.md
2020-10-01 22:18:53 +05:30
swarajsaaj
4f62070eb2 #1510 Revert pom to include all modules 2020-10-01 21:13:30 +05:30
swarajsaaj
b29bd66369 #1510 Improvments done in Circuit Breaker 2020-10-01 21:09:39 +05:30
Mahendran Mookkiah
ad435dd2fd issue 1500 - using Junit 5 and resolved a build issue 2020-09-27 09:26:19 -04:00
Ilkka Seppälä
a125879d15 Update README.md 2020-09-26 13:01:58 +03:00
Mike
dac8e659ce add facade pattern 2020-09-22 16:50:46 +08:00
Mike
a1515b3b52 finish builder and chain pattern 2020-09-22 11:13:25 +08:00
Christoph Flick
d2e070d21d Fix links on the event sourcing page 2020-09-16 13:44:50 +02:00
Ilkka Seppälä
82eb41b641 Update README.md 2020-09-13 18:35:23 +03:00
Ilkka Seppälä
24e8fa1bad Update README.md 2020-09-13 18:32:12 +03:00
Ilkka Seppälä
9017975276 Update README.md 2020-09-13 18:26:52 +03:00
Ilkka Seppälä
87093cf221 Update README.md 2020-09-13 18:23:54 +03:00
Ilkka Seppälä
6cb5b4a683 Update README.md 2020-09-13 18:19:35 +03:00
Ilkka Seppälä
e6ddff5f25 Update README.md 2020-09-13 18:15:17 +03:00
Ilkka Seppälä
b5fddd469a Update README.md 2020-09-13 18:09:41 +03:00
Ilkka Seppälä
9cbc509c3a Update README.md 2020-09-13 18:09:18 +03:00
Ilkka Seppälä
9c648cbdb8 Update README.md 2020-09-13 18:04:30 +03:00
Ilkka Seppälä
e6cca86e25 Update README.md 2020-09-13 17:59:12 +03:00
Ilkka Seppälä
c204b242df Update README.md 2020-09-13 17:53:50 +03:00
Ilkka Seppälä
2e98dcf217 Update README.md 2020-09-13 17:39:10 +03:00
Ilkka Seppälä
9d44eea64f Merge pull request #1524 from iluwatar/all-contributors/add-swarajsaaj
docs: add swarajsaaj as a contributor
2020-09-13 17:24:32 +03:00
Mike
93aa1046aa translate decorator and factory method pattern in Chinese 2020-09-11 09:34:11 +08:00
95 changed files with 2980 additions and 1208 deletions

View File

@@ -1213,6 +1213,60 @@
"contributions": [
"code"
]
},
{
"login": "ChFlick",
"name": "Christoph Flick",
"avatar_url": "https://avatars0.githubusercontent.com/u/4465376?v=4",
"profile": "http://christophflick.de",
"contributions": [
"doc"
]
},
{
"login": "Ascenio",
"name": "Ascênio",
"avatar_url": "https://avatars1.githubusercontent.com/u/7662016?v=4",
"profile": "https://github.com/Ascenio",
"contributions": [
"review"
]
},
{
"login": "dsibilio",
"name": "Domenico Sibilio",
"avatar_url": "https://avatars2.githubusercontent.com/u/24280982?v=4",
"profile": "https://www.linkedin.com/in/domenico-sibilio/",
"contributions": [
"doc"
]
},
{
"login": "akashchandwani",
"name": "Akash Chandwani",
"avatar_url": "https://avatars2.githubusercontent.com/u/3483277?v=4",
"profile": "https://github.com/akashchandwani",
"contributions": [
"review"
]
},
{
"login": "manannikov",
"name": "Pavlo Manannikov",
"avatar_url": "https://avatars2.githubusercontent.com/u/7019769?v=4",
"profile": "http://www.linkedin.com/in/manannikov",
"contributions": [
"code"
]
},
{
"login": "eimanip",
"name": "Eiman",
"avatar_url": "https://avatars0.githubusercontent.com/u/20307301?v=4",
"profile": "https://github.com/eimanip",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 4,

View File

@@ -30,29 +30,43 @@ on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
# Disabling shallow clone for improving relevancy of SonarQube reporting
fetch-depth: 0
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Cache SonarCloud packages
uses: actions/cache@v2
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- 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

View File

@@ -10,7 +10,7 @@
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-133-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-139-orange.svg?style=flat-square)](#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
@@ -269,6 +269,14 @@ This project is licensed under the terms of the MIT license.
</tr>
<tr>
<td align="center"><a href="https://github.com/swarajsaaj"><img src="https://avatars2.githubusercontent.com/u/6285049?v=4" width="100px;" alt=""/><br /><sub><b>Swaraj</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=swarajsaaj" title="Code">💻</a></td>
<td align="center"><a href="http://christophflick.de"><img src="https://avatars0.githubusercontent.com/u/4465376?v=4" width="100px;" alt=""/><br /><sub><b>Christoph Flick</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=ChFlick" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/Ascenio"><img src="https://avatars1.githubusercontent.com/u/7662016?v=4" width="100px;" alt=""/><br /><sub><b>Ascênio</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AAscenio" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://www.linkedin.com/in/domenico-sibilio/"><img src="https://avatars2.githubusercontent.com/u/24280982?v=4" width="100px;" alt=""/><br /><sub><b>Domenico Sibilio</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=dsibilio" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/akashchandwani"><img src="https://avatars2.githubusercontent.com/u/3483277?v=4" width="100px;" alt=""/><br /><sub><b>Akash Chandwani</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Aakashchandwani" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="http://www.linkedin.com/in/manannikov"><img src="https://avatars2.githubusercontent.com/u/7019769?v=4" width="100px;" alt=""/><br /><sub><b>Pavlo Manannikov</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=manannikov" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/eimanip"><img src="https://avatars0.githubusercontent.com/u/20307301?v=4" width="100px;" alt=""/><br /><sub><b>Eiman</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=eimanip" title="Code">💻</a></td>
</tr>
</table>

View File

@@ -1,3 +1,26 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.abstractfactory;
public class Kingdom {

View File

@@ -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;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}
}

View File

@@ -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";
}
}

View File

@@ -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();
}
}

View File

@@ -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";
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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");
}
}

View File

@@ -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());
}
}

View File

@@ -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");
}
}

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -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>

View File

@@ -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

View File

@@ -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();

View File

@@ -37,5 +37,4 @@ public class Goblin extends Target {
public String toString() {
return "Goblin";
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -24,11 +24,11 @@ Use the Event Sourcing pattern when
## Real world examples
* [The Lmax Architecture] (https://martinfowler.com/articles/lmax.html)
* [The Lmax Architecture](https://martinfowler.com/articles/lmax.html)
## Credits
* [Martin Fowler - Event Sourcing] (https://martinfowler.com/eaaDev/EventSourcing.html)
* [Event Sourcing | Microsoft Docs] (https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
* [Reference 3: Introducing Event Sourcing] (https://msdn.microsoft.com/en-us/library/jj591559.aspx)
* [Martin Fowler - Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html)
* [Event Sourcing in Microsoft's documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
* [Reference 3: Introducing Event Sourcing](https://msdn.microsoft.com/en-us/library/jj591559.aspx)
* [Event Sourcing pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)

View File

@@ -1,3 +1,27 @@
<!--
The MIT License
Copyright © 2014-2019 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

View File

@@ -1,3 +1,26 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.factory;
/**

View File

@@ -1,3 +1,26 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.factory;
import java.util.function.Supplier;

View File

@@ -1,3 +1,26 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.factory;
/**

View File

@@ -1,3 +1,26 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.factory;
/**

View File

@@ -1,3 +1,26 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.factory;
/**

View File

@@ -1,3 +1,26 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.factory;
import static org.junit.jupiter.api.Assertions.*;

View File

@@ -1,3 +1,26 @@
/*
* The MIT License
* Copyright © 2014-2019 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.factory;
import static org.junit.jupiter.api.Assertions.*;

View File

@@ -9,6 +9,10 @@ tags:
- Performance
---
## Also known as
Resource Pool
## Intent
When objects are expensive to create and they are needed only for short periods of time it is

View File

@@ -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>

View File

@@ -10,23 +10,35 @@ tags:
## Intent
Separate the interface definition and implementation in different packages. This allows the client to be completely unaware of the implementation.
Separate the interface definition and implementation in different packages. This allows the client
to be completely unaware of the implementation.
## Explanation
Real world example
> An Invoice generator may be created with ability to use different Tax calculators that may be added in the invoice depending upon type of purchase, region etc.
> An Invoice generator may be created with ability to use different Tax calculators that may be
> added in the invoice depending upon type of purchase, region etc.
In plain words
> Separated interface pattern encourages to keep the implementations of an interface decoupled from the client and its definition, so the client is not dependent on the implementation.
> Separated interface pattern encourages to keep the implementations of an interface decoupled from
> the client and its definition, so the client is not dependent on the implementation.
A client code may abstract some specific functionality to an interface, and define the definition of the interface as an SPI ([Service Programming Interface](https://en.wikipedia.org/wiki/Service_provider_interface) is an API intended and open to be implemented or extended by a third party). Another package may implement this interface definition with a concrete logic, which will be injected into the client code at runtime (with a third class, injecting the implementation in the client) or at compile time (using Plugin pattern with some configurable file).
A client code may abstract some specific functionality to an interface, and define the definition of
the interface as an SPI ([Service Programming Interface](https://en.wikipedia.org/wiki/Service_provider_interface)
is an API intended and open to be implemented or extended by a third party). Another package may
implement this interface definition with a concrete logic, which will be injected into the client
code at runtime (with a third class, injecting the implementation in the client) or at compile time
(using Plugin pattern with some configurable file).
**Programmatic Example**
**Client** An Invoice generator class accepts the cost of the product and calculates the total amount payable inclusive of tax
**Client**
`InvoiceGenerator` class accepts the cost of the product and calculates the total
amount payable inclusive of tax.
```java
public class InvoiceGenerator {
@@ -46,21 +58,23 @@ public class InvoiceGenerator {
}
```
The tax calculation logic is delegated to the ```TaxCalculator``` interface
The tax calculation logic is delegated to the `TaxCalculator` interface.
```java
public interface TaxCalculator {
double calculate(double amount);
}
```
**Implementation package**
In another package (which the client is completely unaware of) there exist multiple implementations of the ```TaxCalculator``` interface
```ForeignTaxCalculator``` which levies 60% tax for international products.
In another package (which the client is completely unaware of) there exist multiple implementations
of the `TaxCalculator` interface. `ForeignTaxCalculator` is one of them which levies 60% tax
for international products.
```java
public class ForeignTaxCalculator implements TaxCalculator {
@@ -74,7 +88,8 @@ public class ForeignTaxCalculator implements TaxCalculator {
}
```
```DomesticTaxCalculator``` which levies 20% tax for international products.
Another is `DomesticTaxCalculator` which levies 20% tax for international products.
```java
public class DomesticTaxCalculator implements TaxCalculator {
@@ -88,7 +103,8 @@ public class DomesticTaxCalculator implements TaxCalculator {
}
```
These both implementations are instantiated and injected in the client class by the ```App.java``` class
These both implementations are instantiated and injected in the client class by the ```App.java```
class.
```java
var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, new ForeignTaxCalculator());
@@ -101,9 +117,11 @@ These both implementations are instantiated and injected in the client class by
```
## Class diagram
![alt text](./etc/class_diagram.png "Separated Interface")
## Applicability
Use the Separated interface pattern when
* You are developing a framework package, and your framework needs to call some application code through interfaces.
@@ -117,3 +135,4 @@ Use the Separated interface pattern when
## Credits
* [Martin Fowler](https://www.martinfowler.com/eaaCatalog/separatedInterface.html)
* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=e08dfb7f2cf6153542ef1b5a00b10abc)

View File

@@ -9,14 +9,16 @@ tags:
---
## Intent
Ensure a class only has one instance, and provide a global point of
access to it.
Ensure a class only has one instance, and provide a global point of access to it.
## Explanation
Real world example
> There can only be one ivory tower where the wizards study their magic. The same enchanted ivory tower is always used by the wizards. Ivory tower here is singleton.
> There can only be one ivory tower where the wizards study their magic. The same enchanted ivory
> tower is always used by the wizards. Ivory tower here is singleton.
In plain words
@@ -24,7 +26,9 @@ In plain words
Wikipedia says
> In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
> In software engineering, the singleton pattern is a software design pattern that restricts the
> instantiation of a class to one object. This is useful when exactly one object is needed to
> coordinate actions across the system.
**Programmatic Example**
@@ -38,7 +42,7 @@ public enum EnumIvoryTower {
}
```
Then in order to use
Then in order to use:
```java
var enumIvoryTower1 = EnumIvoryTower.INSTANCE;
@@ -47,9 +51,11 @@ assertEquals(enumIvoryTower1, enumIvoryTower2); // true
```
## Class diagram
![alt text](./etc/singleton.urm.png "Singleton pattern class diagram")
## Applicability
Use the Singleton pattern when
* There must be exactly one instance of a class, and it must be accessible to clients from a well-known access point

View File

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

View File

@@ -24,7 +24,6 @@
package com.iluwatar.spatialpartition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
/**

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,80 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
<class id="1" language="java" name="com.iluwatar.state.AngryState" project="state"
file="/state/src/main/java/com/iluwatar/state/AngryState.java" binary="false" corner="BOTTOM_RIGHT">
<position height="124" width="157" x="98" y="310"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="com.iluwatar.state.PeacefulState" project="state"
file="/state/src/main/java/com/iluwatar/state/PeacefulState.java" binary="false" corner="BOTTOM_RIGHT">
<position height="124" width="173" x="295" y="310"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="3" language="java" name="com.iluwatar.state.State" project="state"
file="/state/src/main/java/com/iluwatar/state/State.java" binary="false" corner="BOTTOM_RIGHT">
<position height="106" width="136" x="138" y="674"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="4" language="java" name="com.iluwatar.state.Mammoth" project="state"
file="/state/src/main/java/com/iluwatar/state/Mammoth.java" binary="false" corner="BOTTOM_RIGHT">
<position height="160" width="177" x="138" y="474"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<realization id="5">
<bendpoint x="107" y="474"/>
<end type="SOURCE" refId="1"/>
<end type="TARGET" refId="3"/>
</realization>
<association id="6">
<end type="SOURCE" refId="4" navigable="false">
<attribute id="7" name="state"/>
<multiplicity id="8" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="3" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="9">
<bendpoint x="345" y="474"/>
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="3"/>
</realization>
<association id="10">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="11" name="mammoth"/>
<multiplicity id="12" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="13">
<end type="SOURCE" refId="2" navigable="false">
<attribute id="14" name="mammoth"/>
<multiplicity id="15" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

BIN
state/etc/state_urm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -9,17 +9,20 @@ tags:
---
## Also known as
Policy
## Intent
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary
independently from clients that use it.
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets
the algorithm vary independently from clients that use it.
## Explanation
Real world example
> Slaying dragons is a dangerous profession. With experience it becomes easier. Veteran dragonslayers have developed different fighting strategies against different types of dragons.
> Slaying dragons is a dangerous job. With experience it becomes easier. Veteran
> dragonslayers have developed different fighting strategies against different types of dragons.
In plain words
@@ -27,7 +30,8 @@ In plain words
Wikipedia says
> In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime.
> In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral
> software design pattern that enables selecting an algorithm at runtime.
**Programmatic Example**
@@ -71,7 +75,8 @@ public class SpellStrategy implements DragonSlayingStrategy {
}
```
And here is the mighty dragonslayer who is able to pick his fighting strategy based on the opponent.
And here is the mighty dragonslayer, who is able to pick his fighting strategy based on the
opponent.
```java
public class DragonSlayer {
@@ -92,7 +97,7 @@ public class DragonSlayer {
}
```
Finally here's dragonslayer in action.
Finally here's the dragonslayer in action.
```java
LOGGER.info("Green dragon spotted ahead!");
@@ -104,19 +109,25 @@ Finally here's dragonslayer in action.
LOGGER.info("Black dragon lands before you.");
dragonSlayer.changeStrategy(new SpellStrategy());
dragonSlayer.goToBattle();
// Green dragon spotted ahead!
// With your Excalibur you sever the dragon's head!
// Red dragon emerges.
// You shoot the dragon with the magical crossbow and it falls dead on the ground!
// Black dragon lands before you.
// You cast the spell of disintegration and the dragon vaporizes in a pile of dust!
```
Program output:
```
Green dragon spotted ahead!
With your Excalibur you sever the dragon's head!
Red dragon emerges.
You shoot the dragon with the magical crossbow and it falls dead on the ground!
Black dragon lands before you.
You cast the spell of disintegration and the dragon vaporizes in a pile of dust!
```
## Class diagram
![alt text](./etc/strategy_1.png "Strategy")
![alt text](./etc/strategy_urm.png "Strategy")
## Applicability
Use the Strategy pattern when
* Many related classes differ only in their behavior. Strategies provide a way to configure a class either one of many behaviors

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,75 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
<interface id="1" language="java" name="com.iluwatar.strategy.DragonSlayingStrategy" project="strategy"
file="/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java" binary="false" corner="BOTTOM_RIGHT">
<position height="88" width="186" x="525" y="428"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="2" language="java" name="com.iluwatar.strategy.ProjectileStrategy" project="strategy"
file="/strategy/src/main/java/com/iluwatar/strategy/ProjectileStrategy.java" binary="false" corner="BOTTOM_RIGHT">
<position height="106" width="151" x="218" y="264"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="com.iluwatar.strategy.SpellStrategy" project="strategy"
file="/strategy/src/main/java/com/iluwatar/strategy/SpellStrategy.java" binary="false" corner="BOTTOM_RIGHT">
<position height="106" width="125" x="409" y="264"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.strategy.DragonSlayer" project="strategy"
file="/strategy/src/main/java/com/iluwatar/strategy/DragonSlayer.java" binary="false" corner="BOTTOM_RIGHT">
<position height="124" width="284" x="574" y="264"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="5" language="java" name="com.iluwatar.strategy.MeleeStrategy" project="strategy"
file="/strategy/src/main/java/com/iluwatar/strategy/MeleeStrategy.java" binary="false" corner="BOTTOM_RIGHT">
<position height="106" width="127" x="898" y="264"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<realization id="6">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="1"/>
</realization>
<realization id="7">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="1"/>
</realization>
<realization id="8">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="1"/>
</realization>
<association id="9">
<end type="SOURCE" refId="4" navigable="false">
<attribute id="10" name="strategy"/>
<multiplicity id="11" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="1" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -9,21 +9,30 @@ tags:
---
## Intent
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template method lets
subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template
Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's
structure.
## Explanation
Real world example
> The general steps in stealing an item are the same. First you pick the target, next you confuse him somehow and finally you steal the item. However there are many ways to implement these steps.
> The general steps in stealing an item are the same. First you pick the target, next you confuse
> him somehow and finally you steal the item. However there are many ways to implement these steps.
In plain words
> Template Method pattern outlines the general steps in the parent class and lets the concrete child implementations define the details.
> Template Method pattern outlines the general steps in the parent class and lets the concrete child
> implementations define the details.
Wikipedia says
> In object-oriented programming, the template method is one of the behavioral design patterns identified by Gamma et al. in the book Design Patterns. The template method is a method in a superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of a number of high-level steps. These steps are themselves implemented by additional helper methods in the same class as the template method.
> In object-oriented programming, the template method is one of the behavioral design patterns
> identified by Gamma et al. in the book Design Patterns. The template method is a method in a
> superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of
> a number of high-level steps. These steps are themselves implemented by additional helper methods
> in the same class as the template method.
**Programmatic Example**
@@ -120,9 +129,11 @@ And finally we show how the halfling thief utilizes the different stealing metho
```
## Class diagram
![alt text](./etc/template-method_1.png "Template Method")
![alt text](./etc/template_method_urm.png "Template Method")
## Applicability
The Template Method pattern should be used
* To implement the invariant parts of an algorithm once and leave it up to subclasses to implement the behavior that can vary

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
<class id="1" language="java" name="com.iluwatar.templatemethod.SubtleMethod" project="template-method"
file="/template-method/src/main/java/com/iluwatar/templatemethod/SubtleMethod.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="142" width="174" x="-90" y="683"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="com.iluwatar.templatemethod.StealingMethod" project="template-method"
file="/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="159" width="177" x="17" y="484"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="com.iluwatar.templatemethod.HalflingThief" project="template-method"
file="/template-method/src/main/java/com/iluwatar/templatemethod/HalflingThief.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="124" width="235" x="17" y="320"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.templatemethod.HitAndRunMethod" project="template-method"
file="/template-method/src/main/java/com/iluwatar/templatemethod/HitAndRunMethod.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="142" width="174" x="124" y="683"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<association id="5">
<end type="SOURCE" refId="3" navigable="false">
<attribute id="6" name="method"/>
<multiplicity id="7" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="2" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<generalization id="8">
<end type="SOURCE" refId="4"/>
<end type="TARGET" refId="2"/>
</generalization>
<generalization id="9">
<end type="SOURCE" refId="1"/>
<end type="TARGET" refId="2"/>
</generalization>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -9,16 +9,19 @@ tags:
---
## Intent
It is often the case that tasks to be executed are short-lived and
the number of tasks is large. Creating a new thread for each task would make
the system spend more time creating and destroying the threads than executing
the actual tasks. Thread Pool solves this problem by reusing existing threads
and eliminating the latency of creating new threads.
It is often the case that tasks to be executed are short-lived and the number of tasks is large.
Creating a new thread for each task would make the system spend more time creating and destroying
the threads than executing the actual tasks. Thread Pool solves this problem by reusing existing
threads and eliminating the latency of creating new threads.
## Explanation
Real world example
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we establish a thread pool.
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes
> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
> establish a thread pool.
In plain words
@@ -26,11 +29,18 @@ In plain words
Wikipedia says
> In computer programming, a thread pool is a software design pattern for achieving concurrency of execution in a computer program. Often also called a replicated workers or worker-crew model, a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program. By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks. The number of available threads is tuned to the computing resources available to the program, such as a parallel task queue after completion of execution.
> In computer programming, a thread pool is a software design pattern for achieving concurrency of
> execution in a computer program. Often also called a replicated workers or worker-crew model,
> a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent
> execution by the supervising program. By maintaining a pool of threads, the model increases
> performance and avoids latency in execution due to frequent creation and destruction of threads
> for short-lived tasks. The number of available threads is tuned to the computing resources
> available to the program, such as a parallel task queue after completion of execution.
**Programmatic Example**
Let's first look at our task hierarchy. We have a base class and then concrete CoffeeMakingTask and PotatoPeelingTask.
Let's first look at our task hierarchy. We have a base class and then concrete `CoffeeMakingTask`
and `PotatoPeelingTask`.
```java
public abstract class Task {
@@ -88,8 +98,8 @@ public class PotatoPeelingTask extends Task {
}
```
Next we present a runnable Worker class that the thread pool will utilize to handle all the potato peeling and coffee
making.
Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
peeling and coffee making.
```java
public class Worker implements Runnable {
@@ -156,9 +166,11 @@ Now we are ready to show the full example in action.
```
## Class diagram
![alt text](./etc/thread-pool.png "Thread Pool")
![alt text](./etc/thread_pool_urm.png "Thread Pool")
## Applicability
Use the Thread Pool pattern when
* You have a large number of short-lived tasks to be executed in parallel

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
<class id="1" language="java" name="com.iluwatar.Task" project="thread-pool"
file="/thread-pool/src/main/java/com/iluwatar/Task.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="343" y="579"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="com.iluwatar.Worker" project="thread-pool"
file="/thread-pool/src/main/java/com/iluwatar/Worker.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="344" y="389"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="com.iluwatar.CoffeeMakingTask" project="thread-pool"
file="/thread-pool/src/main/java/com/iluwatar/CoffeeMakingTask.java" binary="false" corner="BOTTOM_RIGHT">
<position height="124" width="156" x="194" y="717"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.PotatoPeelingTask" project="thread-pool"
file="/thread-pool/src/main/java/com/iluwatar/PotatoPeelingTask.java" binary="false" corner="BOTTOM_RIGHT">
<position height="124" width="169" x="390" y="717"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<association id="5">
<end type="SOURCE" refId="2" navigable="false">
<attribute id="6" name="task"/>
<multiplicity id="7" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="1" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<generalization id="8">
<end type="SOURCE" refId="4"/>
<end type="TARGET" refId="1"/>
</generalization>
<generalization id="9">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="1"/>
</generalization>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -10,12 +10,15 @@ tags:
---
## Intent
Ensure that a given client is not able to access service resources more than the assigned limit.
## Explanation
Real world example
> A large multinational corporation offers API to its customers. The API is rate-limited and each customer can only make certain amount of calls per second.
> A large multinational corporation offers API to its customers. The API is rate-limited and each
> customer can only make certain amount of calls per second.
In plain words
@@ -23,7 +26,9 @@ In plain words
[Microsoft documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling) says
> Control the consumption of resources used by an instance of an application, an individual tenant, or an entire service. This can allow the system to continue to function and meet service level agreements, even when an increase in demand places an extreme load on resources.
> Control the consumption of resources used by an instance of an application, an individual tenant,
> or an entire service. This can allow the system to continue to function and meet service level
> agreements, even when an increase in demand places an extreme load on resources.
**Programmatic Example**
@@ -77,7 +82,8 @@ public final class CallsCount {
}
```
Next we introduce the service that the tenants are calling. To track the call count we use the throttler timer.
Next we introduce the service that the tenants are calling. To track the call count we use the
throttler timer.
```java
public interface Throttler {
@@ -134,7 +140,8 @@ class B2BService {
}
```
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per second and Nike to 6.
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
second and Nike to 6.
```java
public static void main(String[] args) {
@@ -171,9 +178,11 @@ Now we are ready to see the full example in action. Tenant Adidas is rate-limite
## Class diagram
![alt text](./etc/throttling-pattern.png "Throttling pattern class diagram")
![alt text](./etc/throttling_urm.png "Throttling pattern class diagram")
## Applicability
The Throttling pattern should be used:
* When a service access needs to be restricted to not have high impacts on the performance of the service.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,88 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.2.2" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
<class id="1" language="java" name="com.iluwatar.throttling.CallsCount" project="throttling"
file="/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java" binary="false" corner="BOTTOM_RIGHT">
<position height="211" width="256" x="656" y="228"/>
<display autosize="false" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="com.iluwatar.throttling.Tenant" project="throttling"
file="/throttling/src/main/java/com/iluwatar/throttling/Tenant.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="465" y="524"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="com.iluwatar.throttling.B2BService" project="throttling"
file="/throttling/src/main/java/com/iluwatar/throttling/B2BService.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="464" y="192"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="4" language="java" name="com.iluwatar.throttling.timer.Throttler" project="throttling"
file="/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="167" y="174"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="5" language="java" name="com.iluwatar.throttling.timer.ThrottleTimerImpl" project="throttling"
file="/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="166" y="396"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<association id="6">
<end type="SOURCE" refId="3" navigable="false">
<attribute id="7" name="callsCount"/>
<multiplicity id="8" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="1" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="9">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="4"/>
</dependency>
<dependency id="10">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="2"/>
</dependency>
<association id="11">
<end type="SOURCE" refId="5" navigable="false">
<attribute id="12" name="callsCount"/>
<multiplicity id="13" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="1" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="14">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="1"/>
</dependency>
<realization id="15">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="4"/>
</realization>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -24,7 +24,6 @@
package com.iluwatar.throttling;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;

View File

@@ -9,14 +9,18 @@ tags:
---
## Intent
Tolerant Reader is an integration pattern that helps creating robust communication systems. The idea is to be as
tolerant as possible when reading data from another service. This way, when the communication schema changes, the
readers must not break.
Tolerant Reader is an integration pattern that helps creating robust communication systems. The idea
is to be as tolerant as possible when reading data from another service. This way, when the
communication schema changes, the readers must not break.
## Explanation
Real world example
> We are persisting rainbowfish objects to file and later on they need to be restored. What makes it problematic is that rainbowfish data structure is versioned and evolves over time. New version of rainbowfish needs to be able to restore old versions as well.
> We are persisting rainbowfish objects to file and later on they need to be restored. What makes it
> problematic is that rainbowfish data structure is versioned and evolves over time. New version of
> rainbowfish needs to be able to restore old versions as well.
In plain words
@@ -24,11 +28,11 @@ In plain words
[Robustness Principle](https://java-design-patterns.com/principles/#robustness-principle) says
> Be conservative in what you do, be liberal in what you accept from others
> Be conservative in what you do, be liberal in what you accept from others.
**Programmatic Example**
Here's the versioned rainbowfish. Notice how the second version introduces additional properties.
Here's the versioned `RainbowFish`. Notice how the second version introduces additional properties.
```java
public class RainbowFish implements Serializable {
@@ -104,7 +108,8 @@ public class RainbowFishV2 extends RainbowFish {
}
```
Next we introduce the rainbowfish serializer. This is the class that implements the Tolerant Reader pattern.
Next we introduce the `RainbowFishSerializer`. This is the class that implements the Tolerant Reader
pattern.
```java
public final class RainbowFishSerializer {
@@ -185,18 +190,23 @@ And finally here's the full example in action.
LOGGER.info("deserializedFishV2 name={} age={} length={} weight={}",
deserializedFishV2.getName(), deserializedFishV2.getAge(),
deserializedFishV2.getLengthMeters(), deserializedFishV2.getWeightTons());
// fishV1 name=Zed age=10 length=11 weight=12
// deserializedFishV1 name=Zed age=10 length=11 weight=12
// fishV2 name=Scar age=5 length=12 weight=15 sleeping=true hungry=true angry=true
// deserializedFishV2 name=Scar age=5 length=12 weight=15
```
Program output:
```
fishV1 name=Zed age=10 length=11 weight=12
deserializedFishV1 name=Zed age=10 length=11 weight=12
fishV2 name=Scar age=5 length=12 weight=15 sleeping=true hungry=true angry=true
deserializedFishV2 name=Scar age=5 length=12 weight=15
```
## Class diagram
![alt text](./etc/tolerant-reader.png "Tolerant Reader")
![alt text](./etc/tolerant_reader_urm.png "Tolerant Reader")
## Applicability
Use the Tolerant Reader pattern when
* The communication schema can evolve and change and yet the receiving side should not break

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
<class id="1" language="java" name="com.iluwatar.tolerantreader.RainbowFishSerializer" project="tolerant-reader"
file="/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="123" y="415"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="com.iluwatar.tolerantreader.RainbowFishV2" project="tolerant-reader"
file="/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="389" y="685"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="com.iluwatar.tolerantreader.RainbowFish" project="tolerant-reader"
file="/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="389" y="404"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<dependency id="4">
<end type="SOURCE" refId="1"/>
<end type="TARGET" refId="3"/>
</dependency>
<dependency id="5">
<end type="SOURCE" refId="1"/>
<end type="TARGET" refId="2"/>
</dependency>
<generalization id="6">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="3"/>
</generalization>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

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

View File

@@ -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>

View File

@@ -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();
}
};
}

View File

@@ -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
View 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
![alt text](./etc/version-number.urm.png "Version Number pattern 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View 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
View 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>

View File

@@ -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
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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[]{}));
}
}

View File

@@ -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());
}
}
}

139
zh/builder/README.md Normal file
View 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();
```
## 类图
![alt text](../../builder/etc/builder.urm.png "Builder class diagram")
## 适用性
使用建造者模式当
* 创建复杂对象的算法应独立于组成对象的零件及其组装方式
* 构造过程必须允许所构造的对象具有不同的表示形式
## 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)

159
zh/chain/README.md Normal file
View 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"
```
## 类图
![alt text](../../chain/etc/chain.urm.png "Chain of Responsibility class diagram")
## 适用性
使用责任链模式当
* 多于一个对象可能要处理请求,并且处理器并不知道一个优先级。处理器应自动确定。
* 你想对多个对象之一发出请求而无需明确指定接收者
* 处理请求的对象集合应该被动态指定时
## 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)

139
zh/decorator/README.md Normal file
View File

@@ -0,0 +1,139 @@
---
layout: pattern
title: Decorator
folder: decorator
permalink: /patterns/decorator/
categories: Structural
tags:
- Gang Of Four
- Extensibility
---
## 或称
包装器
## 目的
动态的为对象附加额外的职责。装饰器为子类提供了灵活的替代方案,以扩展功能。
## 解释
真实世界例子
> 附近的山丘上住着一个愤怒的巨魔。通常它是徒手的,但有时它有武器。为了武装巨魔不必创建新的巨魔,而是用合适的武器动态的装饰它。
通俗的说
> 装饰者模式让你可以在运行时通过把对象包装进一个装饰类对象中来动态的改变一个对象的行为。
维基百科说
> 在面向对象的编程中,装饰器模式是一种设计模式,它允许将行为静态或动态地添加到单个对象中,而不会影响同一类中其他对象的行为。装饰器模式通常对于遵守单一责任原则很有用,因为它允许将功能划分到具有唯一关注领域的类之间。
**程序示例**
以巨魔的为例。首先我有有一个简单的巨魔,实现了巨魔接口。
程序mple. First of all we have a simple troll implementing the troll interface
```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!
```
## 类图
![alt text](../../decorator/etc/decorator.urm.png "Decorator pattern class diagram")
## 适用性
使用装饰者
* 动态透明地向单个对象添加职责,即不影响其他对象
* 对于可以撤销的责任
* 当通过子类化进行扩展是不切实际的。有时可能会有大量的独立扩展,并且会产生大量的子类来支持每种组合。 否则类定义可能被隐藏或无法用于子类化。
## 教程
* [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)

207
zh/facade/README.md Normal file
View 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.
```
## 类图
![alt text](../../facade/etc/facade.urm.png "Facade pattern class diagram")
## 适用性
使用外观模式当
* 你想为一个复杂的子系统提供一个简单的接口。随着子系统的发展,它们通常会变得更加复杂。多数模式在应用时会导致更多和更少的类。这使子系统更可重用,更易于自定义,但是对于不需要自定义它的客户来说,使用它也变得更加困难。 外观可以提供子系统的简单默认视图,足以满足大多数客户端的需求。只有需要更多可定制性的客户才需要查看外观外的东西(原子系统提供的接口)。
* 客户端与抽象的实现类之间存在许多依赖关系。 引入外观以使子系统与客户端和其他子系统分离,从而提高子系统的独立性和可移植性。
* 您想对子系统进行分层。 使用外观来定义每个子系统级别的入口点。 如果子系统是相关的,则可以通过使子系统仅通过其外观相互通信来简化它们之间的依赖性。
## 鸣谢
* [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)

View 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
```
## 类图
![alt text](../../factory-method/etc/factory-method.urm.png "Factory Method pattern class diagram")
## 适用性
使用工厂方法模式当
* 一个类无法预料它所要必须创建的对象的类
* 一个类想要它的子类来指定它要创建的对象
* 类将责任委派给几个帮助子类中的一个,而你想定位了解是具体之中的哪一个
## 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)