Compare commits

..

1 Commits

Author SHA1 Message Date
1ebb55cd19 format readme.md 2021-02-14 22:49:42 +02:00
798 changed files with 5942 additions and 6501 deletions

View File

@ -1404,52 +1404,6 @@
"contributions": [
"translation"
]
},
{
"login": "richardmr36",
"name": "Martel Richard",
"avatar_url": "https://avatars.githubusercontent.com/u/19147333?v=4",
"profile": "https://github.com/richardmr36",
"contributions": [
"code"
]
},
{
"login": "va1m",
"name": "va1m",
"avatar_url": "https://avatars.githubusercontent.com/u/17025445?v=4",
"profile": "https://github.com/va1m",
"contributions": [
"code"
]
},
{
"login": "noamgrinch",
"name": "Noam Greenshtain",
"avatar_url": "https://avatars.githubusercontent.com/u/31648669?v=4",
"profile": "https://github.com/noamgrinch",
"contributions": [
"code"
]
},
{
"login": "qfxl",
"name": "yonghong Xu",
"avatar_url": "https://avatars.githubusercontent.com/u/14086462?v=4",
"profile": "https://xuyonghong.cn/",
"contributions": [
"doc"
]
},
{
"login": "jinishavora",
"name": "jinishavora",
"avatar_url": "https://avatars.githubusercontent.com/u/40777762?v=4",
"profile": "https://www.linkedin.com/in/jinisha-vora",
"contributions": [
"review",
"code"
]
}
],
"contributorsPerLine": 4,

View File

@ -1,52 +0,0 @@
#
# The MIT License
# Copyright © 2014-2021 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.
#
version: 2
jobs:
sonar-pr:
docker:
- image: circleci/openjdk:11-node
steps:
- checkout
- restore_cache:
key: jdp-sonar-pr-{{ checksum "pom.xml" }}
- run: |
if [ -n "${CIRCLE_PR_NUMBER}" ]; then
MAVEN_OPTS="-Xmx3000m" xvfb-run ./mvnw -B clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
-Dsonar.pullrequest.key=${CIRCLE_PR_NUMBER} \
-Dsonar.pullrequest.branch=${CIRCLE_BRANCH} \
-Dsonar.pullrequest.base=master
else
echo "No Sonar PR analysis as this is not a pull request"
fi
- save_cache:
key: jdp-sonar-pr-{{ checksum "pom.xml" }}
paths:
- ~/.m2
workflows:
version: 2
all:
jobs:
- sonar-pr

View File

@ -40,25 +40,25 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@master
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@master
uses: actions/setup-java@v1
with:
java-version: 11
- name: Cache SonarCloud packages
uses: actions/cache@master
uses: actions/cache@v2
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Maven dependencies
uses: actions/cache@master
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
@ -69,8 +69,11 @@ jobs:
- 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
- name: Build with Maven and run SonarQube analysis
run: xvfb-run ./mvnw clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
run: xvfb-run mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
env:
# These two env variables are needed for sonar analysis
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -38,15 +38,15 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@master
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@master
uses: actions/setup-java@v1
with:
java-version: 11
- name: Cache Maven Dependecies
uses: actions/cache@master
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
@ -57,5 +57,8 @@ jobs:
- name: Install xvfb
run: sudo apt-get install -y xvfb
# This worflow is only for building Pull Requests, the master branch runs Sonar analysis on the main repository.
# SonarQube scan does not work for forked repositories.
# See https://jira.sonarsource.com/browse/MMF-1371
- name: Build with Maven
run: xvfb-run ./mvnw clean verify
run: xvfb-run mvn clean verify

View File

@ -1,125 +0,0 @@
/*
* The MIT License
* Copyright © 2014-2021 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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

View File

@ -1,25 +0,0 @@
#
# The MIT License
# Copyright © 2014-2021 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.
#
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

View File

@ -4,18 +4,18 @@
# Design patterns implemented in Java
![Java CI](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI/badge.svg)
![Java CI with Maven](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI%20with%20Maven/badge.svg)
[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-159-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-154-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<br/>
Read in different language : [![CN](/assets/flags/CN.png)**CN**](/zh/README.md), [![KR](/assets/flags/KR.png)**KR**](/ko/README.md), [![FR](/assets/flags/FR.png)**FR**](/fr/README.md), [![TR](/assets/flags/TR.png)**TR**](/tr/README.md), [![AR](/assets/flags/AR.png)**AR**](/ar/README.md)
Read in different language : [![CN](/assets/flags/CN.png)**CN**](/zh/README.md),[![KR](/assets/flags/KR.png)**KR**](/ko/README.md),[![FR](/assets/flags/FR.png)**FR**](/fr/README.md),[![TR](/assets/flags/TR.png)**TR**](/tr/README.md),[![AR](/assets/flags/AR.png)**AR**](/ar/README.md),
<br/>
@ -306,13 +306,6 @@ This project is licensed under the terms of the MIT license.
<tr>
<td align="center"><a href="https://github.com/byoungju94"><img src="https://avatars.githubusercontent.com/u/42516378?v=4?s=100" width="100px;" alt=""/><br /><sub><b>byoungju94</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=byoungju94" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/moustafafarhat"><img src="https://avatars.githubusercontent.com/u/38836727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Moustafa Farhat</b></sub></a><br /><a href="#translation-moustafafarhat" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/richardmr36"><img src="https://avatars.githubusercontent.com/u/19147333?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Martel Richard</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=richardmr36" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/va1m"><img src="https://avatars.githubusercontent.com/u/17025445?v=4?s=100" width="100px;" alt=""/><br /><sub><b>va1m</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=va1m" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/noamgrinch"><img src="https://avatars.githubusercontent.com/u/31648669?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Noam Greenshtain</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=noamgrinch" title="Code">💻</a></td>
<td align="center"><a href="https://xuyonghong.cn/"><img src="https://avatars.githubusercontent.com/u/14086462?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yonghong Xu</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=qfxl" title="Documentation">📖</a></td>
<td align="center"><a href="https://www.linkedin.com/in/jinisha-vora"><img src="https://avatars.githubusercontent.com/u/40777762?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jinishavora</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Ajinishavora" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=jinishavora" title="Code">💻</a></td>
</tr>
</table>

View File

@ -27,7 +27,8 @@ import com.iluwatar.abstractdocument.domain.Car;
import com.iluwatar.abstractdocument.domain.enums.Property;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Abstract Document pattern enables handling additional, non-static properties. This pattern
@ -37,9 +38,10 @@ import lombok.extern.slf4j.Slf4j;
* <p>In Abstract Document pattern,({@link AbstractDocument}) fully implements {@link Document})
* interface. Traits are then defined to enable access to properties in usual, static way.
*/
@Slf4j
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Program entry point.
*

View File

@ -23,18 +23,19 @@
package com.iluwatar.abstractdocument;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
/**
* AbstractDocument test class
*/
class AbstractDocumentTest {
public class AbstractDocumentTest {
private static final String KEY = "key";
private static final String VALUE = "value";
@ -49,13 +50,13 @@ class AbstractDocumentTest {
private final DocumentImplementation document = new DocumentImplementation(new HashMap<>());
@Test
void shouldPutAndGetValue() {
public void shouldPutAndGetValue() {
document.put(KEY, VALUE);
assertEquals(VALUE, document.get(KEY));
}
@Test
void shouldRetrieveChildren() {
public void shouldRetrieveChildren() {
var children = List.of(Map.of(), Map.of());
document.put(KEY, children);
@ -66,14 +67,14 @@ class AbstractDocumentTest {
}
@Test
void shouldRetrieveEmptyStreamForNonExistingChildren() {
public void shouldRetrieveEmptyStreamForNonExistingChildren() {
var children = document.children(KEY, DocumentImplementation::new);
assertNotNull(children);
assertEquals(0, children.count());
}
@Test
void shouldIncludePropsInToString() {
public void shouldIncludePropsInToString() {
var props = Map.of(KEY, (Object) VALUE);
var document = new DocumentImplementation(props);
assertTrue(document.toString().contains(KEY));

View File

@ -23,20 +23,19 @@
package com.iluwatar.abstractdocument;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.iluwatar.abstractdocument.domain.Car;
import com.iluwatar.abstractdocument.domain.Part;
import com.iluwatar.abstractdocument.domain.enums.Property;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
/**
* Test for Part and Car
*/
class DomainTest {
public class DomainTest {
private static final String TEST_PART_TYPE = "test-part-type";
private static final String TEST_PART_MODEL = "test-part-model";
@ -46,7 +45,7 @@ class DomainTest {
private static final long TEST_CAR_PRICE = 1L;
@Test
void shouldConstructPart() {
public void shouldConstructPart() {
var partProperties = Map.of(
Property.TYPE.toString(), TEST_PART_TYPE,
Property.MODEL.toString(), TEST_PART_MODEL,
@ -59,7 +58,7 @@ class DomainTest {
}
@Test
void shouldConstructCar() {
public void shouldConstructCar() {
var carProperties = Map.of(
Property.MODEL.toString(), TEST_CAR_MODEL,
Property.PRICE.toString(), TEST_CAR_PRICE,

View File

@ -23,7 +23,8 @@
package com.iluwatar.abstractfactory;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Abstract Factory pattern provides a way to encapsulate a group of individual factories that
@ -39,9 +40,10 @@ import lombok.extern.slf4j.Slf4j;
* and its implementations ( {@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses
* both concrete implementations to create a king, a castle and an army.
*/
@Slf4j
public class App implements Runnable {
private static Logger log = LoggerFactory.getLogger(App.class);
private final Kingdom kingdom = new Kingdom();
public Kingdom getKingdom() {
@ -60,17 +62,17 @@ public class App implements Runnable {
@Override
public void run() {
LOGGER.info("Elf Kingdom");
log.info("Elf Kingdom");
createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
LOGGER.info(kingdom.getArmy().getDescription());
LOGGER.info(kingdom.getCastle().getDescription());
LOGGER.info(kingdom.getKing().getDescription());
log.info(kingdom.getArmy().getDescription());
log.info(kingdom.getCastle().getDescription());
log.info(kingdom.getKing().getDescription());
LOGGER.info("Orc Kingdom");
log.info("Orc Kingdom");
createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
LOGGER.info(kingdom.getArmy().getDescription());
LOGGER.info(kingdom.getCastle().getDescription());
LOGGER.info(kingdom.getKing().getDescription());
log.info(kingdom.getArmy().getDescription());
log.info(kingdom.getCastle().getDescription());
log.info(kingdom.getKing().getDescription());
}
/**

View File

@ -23,17 +23,36 @@
package com.iluwatar.abstractfactory;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Kingdom {
private King king;
private Castle castle;
private Army army;
public King getKing() {
return king;
}
public Castle getCastle() {
return castle;
}
public Army getArmy() {
return army;
}
public void setKing(King king) {
this.king = king;
}
public void setCastle(Castle castle) {
this.castle = castle;
}
public void setArmy(Army army) {
this.army = army;
}
/**
* The factory of kingdom factories.
*/

View File

@ -31,12 +31,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Test for abstract factory.
*/
class AbstractFactoryTest {
public class AbstractFactoryTest {
private final App app = new App();
@Test
void king() {
public void king() {
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
final var kingdom = app.getKingdom();
@ -51,7 +51,7 @@ class AbstractFactoryTest {
}
@Test
void castle() {
public void castle() {
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
final var kingdom = app.getKingdom();
@ -66,7 +66,7 @@ class AbstractFactoryTest {
}
@Test
void army() {
public void army() {
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
final var kingdom = app.getKingdom();
@ -81,7 +81,7 @@ class AbstractFactoryTest {
}
@Test
void createElfKingdom() {
public void createElfKingdom() {
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
final var kingdom = app.getKingdom();
@ -97,7 +97,7 @@ class AbstractFactoryTest {
}
@Test
void createOrcKingdom() {
public void createOrcKingdom() {
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
final var kingdom = app.getKingdom();

View File

@ -28,16 +28,20 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
* Tests that Abstract Factory example runs without errors.
*/
class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -1,125 +0,0 @@
---
layout: pattern
title: Active Object
folder: active-object
permalink: /patterns/active-object/
categories: Concurrency
tags:
- Performance
---
## Intent
The active object design pattern decouples method execution from method invocation for objects that each reside in their thread of control. The goal is to introduce concurrency, by using asynchronous method invocation and a scheduler for handling requests.
## Explanation
The class that implements the active object pattern will contain a self-synchronization mechanism without using 'synchronized' methods.
Real-world example
>The Orcs are known for their wildness and untameable soul. It seems like they have their own thread of control based on previous behavior.
To implement a creature that has its own thread of control mechanism and expose its API only and not the execution itself, we can use the Active Object pattern.
**Programmatic Example**
```java
public abstract class ActiveCreature{
private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName());
private BlockingQueue<Runnable> requests;
private String name;
private Thread thread;
public ActiveCreature(String name) {
this.name = name;
this.requests = new LinkedBlockingQueue<Runnable>();
thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
requests.take().run();
} catch (InterruptedException e) {
logger.error(e.getMessage());
}
}
}
}
);
thread.start();
}
public void eat() throws InterruptedException {
requests.put(new Runnable() {
@Override
public void run() {
logger.info("{} is eating!",name());
logger.info("{} has finished eating!",name());
}
}
);
}
public void roam() throws InterruptedException {
requests.put(new Runnable() {
@Override
public void run() {
logger.info("{} has started to roam and the wastelands.",name());
}
}
);
}
public String name() {
return this.name;
}
}
```
We can see that any class that will extend the ActiveCreature class will have its own thread of control to execute and invocate methods.
For example, the Orc class:
```java
public class Orc extends ActiveCreature {
public Orc(String name) {
super(name);
}
}
```
Now, we can create multiple creatures such as Orcs, tell them to eat and roam and they will execute it on their own thread of control:
```java
public static void main(String[] args) {
var app = new App();
app.run();
}
@Override
public void run() {
ActiveCreature creature;
try {
for (int i = 0;i < creatures;i++) {
creature = new Orc(Orc.class.getSimpleName().toString() + i);
creature.eat();
creature.roam();
}
Thread.sleep(1000);
} catch (InterruptedException e) {
logger.error(e.getMessage());
}
Runtime.getRuntime().exit(1);
}
```
## Class diagram
![alt text](./etc/active-object.urm.PNG "Active Object class diagram")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,25 +0,0 @@
@startuml
package com.iluwatar.activeobject {
abstract class ActiveCreature {
- logger : Logger
- name : String
- requests : BlockingQueue<Runnable>
- thread : Thread
+ ActiveCreature(name : String)
+ eat()
+ name() : String
+ roam()
}
class App {
- creatures : Integer
- logger : Logger
+ App()
+ main(args : String[]) {static}
+ run()
}
class Orc {
+ Orc(name : String)
}
}
Orc --|> ActiveCreature
@enduml

View File

@ -1,65 +0,0 @@
<?xml version="1.0"?>
<!--
The MIT License
Copyright © 2014-2021 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>active-object</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven assembly plugin is invoked with default setting which we have
in parent pom and specifying the class having main method -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.activeobject.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,118 +0,0 @@
/*
* The MIT License
* Copyright © 2014-2021 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.activeobject;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ActiveCreature class is the base of the active object example.
* @author Noam Greenshtain
*
*/
public abstract class ActiveCreature {
private static final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName());
private BlockingQueue<Runnable> requests;
private String name;
private Thread thread; // Thread of execution.
private int status; // status of the thread of execution.
/**
* Constructor and initialization.
*/
protected ActiveCreature(String name) {
this.name = name;
this.status = 0;
this.requests = new LinkedBlockingQueue<>();
thread = new Thread(() -> {
boolean infinite = true;
while (infinite) {
try {
requests.take().run();
} catch (InterruptedException e) {
if (this.status != 0) {
logger.error("Thread was interrupted. --> {}", e.getMessage());
}
infinite = false;
Thread.currentThread().interrupt();
}
}
});
thread.start();
}
/**
* Eats the porridge.
* @throws InterruptedException due to firing a new Runnable.
*/
public void eat() throws InterruptedException {
requests.put(() -> {
logger.info("{} is eating!",name());
logger.info("{} has finished eating!",name());
});
}
/**
* Roam in the wastelands.
* @throws InterruptedException due to firing a new Runnable.
*/
public void roam() throws InterruptedException {
requests.put(() ->
logger.info("{} has started to roam in the wastelands.",name())
);
}
/**
* Returns the name of the creature.
* @return the name of the creature.
*/
public String name() {
return this.name;
}
/**
* Kills the thread of execution.
* @param status of the thread of execution. 0 == OK, the rest is logging an error.
*/
public void kill(int status) {
this.status = status;
this.thread.interrupt();
}
/**
* Returns the status of the thread of execution.
* @return the status of the thread of execution.
*/
public int getStatus() {
return this.status;
}
}

View File

@ -1,75 +0,0 @@
/*
* The MIT License
* Copyright © 2014-2021 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.activeobject;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Active Object pattern helps to solve synchronization difficulties without using
* 'synchronized' methods. The active object will contain a thread-safe data structure
* (such as BlockingQueue) and use to synchronize method calls by moving the logic of the method
* into an invocator(usually a Runnable) and store it in the DSA.
*
* <p>In this example, we fire 20 threads to modify a value in the target class.
*/
public class App implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(App.class.getName());
private static final int NUM_CREATURES = 3;
/**
* Program entry point.
*
* @param args command line arguments.
*/
public static void main(String[] args) {
var app = new App();
app.run();
}
@Override
public void run() {
List<ActiveCreature> creatures = new ArrayList<>();
try {
for (int i = 0;i < NUM_CREATURES;i++) {
creatures.add(new Orc(Orc.class.getSimpleName() + i));
creatures.get(i).eat();
creatures.get(i).roam();
}
Thread.sleep(1000);
} catch (InterruptedException e) {
logger.error(e.getMessage());
Thread.currentThread().interrupt();
} finally {
for (int i = 0;i < NUM_CREATURES;i++) {
creatures.get(i).kill(0);
}
}
}
}

View File

@ -1,42 +0,0 @@
/*
* The MIT License
* Copyright © 2014-2021 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.activeobject;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class ActiveCreatureTest {
@Test
void executionTest() throws InterruptedException {
ActiveCreature orc = new Orc("orc1");
assertEquals("orc1",orc.name());
assertEquals(0,orc.getStatus());
orc.eat();
orc.roam();
orc.kill(0);
}
}

View File

@ -1,37 +0,0 @@
/*
* The MIT License
* Copyright © 2014-2021 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.activeobject;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import org.junit.jupiter.api.Test;
class AppTest {
@Test
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -23,15 +23,17 @@
package com.iluwatar.acyclicvisitor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ConfigureForDosVisitor class implements both zoom's and hayes' visit method for Dos
* manufacturer.
*/
@Slf4j
public class ConfigureForDosVisitor implements AllModemVisitor {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForDosVisitor.class);
@Override
public void visit(Hayes hayes) {
LOGGER.info(hayes + " used with Dos configurator.");

View File

@ -23,15 +23,17 @@
package com.iluwatar.acyclicvisitor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ConfigureForUnixVisitor class implements zoom's visit method for Unix manufacturer, unlike
* traditional visitor pattern, this class may selectively implement visit for other modems.
*/
@Slf4j
public class ConfigureForUnixVisitor implements ZoomVisitor {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForUnixVisitor.class);
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Unix configurator.");

View File

@ -23,14 +23,16 @@
package com.iluwatar.acyclicvisitor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Hayes class implements its accept method.
*/
@Slf4j
public class Hayes extends Modem {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForDosVisitor.class);
/**
* Accepts all visitors but honors only HayesVisitor.
*/

View File

@ -23,14 +23,16 @@
package com.iluwatar.acyclicvisitor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Zoom class implements its accept method.
*/
@Slf4j
public class Zoom extends Modem {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForDosVisitor.class);
/**
* Accepts all visitors but honors only ZoomVisitor.
*/

View File

@ -35,34 +35,34 @@ import uk.org.lidalia.slf4jtest.TestLoggerFactory;
/**
* ConfigureForDosVisitor test class
*/
class ConfigureForDosVisitorTest {
public class ConfigureForDosVisitorTest {
private final TestLogger logger = TestLoggerFactory.getTestLogger(ConfigureForDosVisitor.class);
@Test
void testVisitForZoom() {
public void testVisitForZoom() {
var conDos = new ConfigureForDosVisitor();
var zoom = new Zoom();
conDos.visit(zoom);
assertThat(logger.getLoggingEvents())
.extracting("level", "message")
.contains(tuple(INFO, zoom + " used with Dos configurator."));
}
@Test
void testVisitForHayes() {
public void testVisitForHayes() {
var conDos = new ConfigureForDosVisitor();
var hayes = new Hayes();
conDos.visit(hayes);
assertThat(logger.getLoggingEvents())
.extracting("level", "message")
.contains(tuple(INFO, hayes + " used with Dos configurator."));
}
@AfterEach
public void clearLoggers() {
TestLoggerFactory.clear();

View File

@ -23,34 +23,35 @@
package com.iluwatar.acyclicvisitor;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import uk.org.lidalia.slf4jtest.TestLogger;
import uk.org.lidalia.slf4jtest.TestLoggerFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
import static uk.org.lidalia.slf4jext.Level.INFO;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import uk.org.lidalia.slf4jtest.TestLogger;
import uk.org.lidalia.slf4jtest.TestLoggerFactory;
/**
* ConfigureForUnixVisitor test class
*/
class ConfigureForUnixVisitorTest {
public class ConfigureForUnixVisitorTest {
private static final TestLogger LOGGER = TestLoggerFactory.getTestLogger(ConfigureForUnixVisitor.class);
@AfterEach
public void clearLoggers() {
TestLoggerFactory.clear();
}
@Test
void testVisitForZoom() {
public void testVisitForZoom() {
var conUnix = new ConfigureForUnixVisitor();
var zoom = new Zoom();
conUnix.visit(zoom);
assertThat(LOGGER.getLoggingEvents())
.extracting("level", "message")
.contains(tuple(INFO, zoom + " used with Unix configurator."));

View File

@ -23,32 +23,34 @@
package com.iluwatar.acyclicvisitor;
import org.junit.jupiter.api.Test;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import org.junit.jupiter.api.Test;
/**
* Hayes test class
*/
class HayesTest {
public class HayesTest {
@Test
void testAcceptForDos() {
public void testAcceptForDos() {
var hayes = new Hayes();
var mockVisitor = mock(ConfigureForDosVisitor.class);
hayes.accept(mockVisitor);
verify((HayesVisitor) mockVisitor).visit(eq(hayes));
verify((HayesVisitor)mockVisitor).visit(eq(hayes));
}
@Test
void testAcceptForUnix() {
public void testAcceptForUnix() {
var hayes = new Hayes();
var mockVisitor = mock(ConfigureForUnixVisitor.class);
hayes.accept(mockVisitor);
verifyZeroInteractions(mockVisitor);
}
}

View File

@ -24,32 +24,32 @@
package com.iluwatar.acyclicvisitor;
import org.junit.jupiter.api.Test;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.mock;
import org.junit.jupiter.api.Test;
/**
* Zoom test class
*/
class ZoomTest {
public class ZoomTest {
@Test
void testAcceptForDos() {
public void testAcceptForDos() {
var zoom = new Zoom();
var mockVisitor = mock(ConfigureForDosVisitor.class);
zoom.accept(mockVisitor);
verify((ZoomVisitor) mockVisitor).visit(eq(zoom));
verify((ZoomVisitor)mockVisitor).visit(eq(zoom));
}
@Test
void testAcceptForUnix() {
public void testAcceptForUnix() {
var zoom = new Zoom();
var mockVisitor = mock(ConfigureForUnixVisitor.class);
zoom.accept(mockVisitor);
verify((ZoomVisitor) mockVisitor).visit(eq(zoom));
verify((ZoomVisitor)mockVisitor).visit(eq(zoom));
}
}

View File

@ -42,8 +42,8 @@ public interface RowingBoat {
void row();
}
@Slf4j
public class FishingBoat {
private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoat.class);
public void sail() {
LOGGER.info("The fishing boat is sailing");
}
@ -70,9 +70,10 @@ public class Captain {
Now let's say the pirates are coming and our captain needs to escape but there is only fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills.
```java
@Slf4j
public class FishingBoatAdapter implements RowingBoat {
private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoatAdapter.class);
private final FishingBoat boat;
public FishingBoatAdapter() {

View File

@ -23,20 +23,24 @@
package com.iluwatar.adapter;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* The Captain uses {@link RowingBoat} to sail. <br> This is the client in the pattern.
*/
@Setter
@NoArgsConstructor
@AllArgsConstructor
public final class Captain {
private RowingBoat rowingBoat;
public Captain() {
}
public Captain(final RowingBoat boat) {
this.rowingBoat = boat;
}
void setRowingBoat(final RowingBoat boat) {
this.rowingBoat = boat;
}
void row() {
rowingBoat.row();
}

View File

@ -23,15 +23,18 @@
package com.iluwatar.adapter;
import lombok.extern.slf4j.Slf4j;
import static org.slf4j.LoggerFactory.getLogger;
import org.slf4j.Logger;
/**
* Device class (adaptee in the pattern). We want to reuse this class. Fishing boat moves by
* sailing.
*/
@Slf4j
final class FishingBoat {
private static final Logger LOGGER = getLogger(FishingBoat.class);
void sail() {
LOGGER.info("The fishing boat is sailing");
}

View File

@ -29,7 +29,11 @@ package com.iluwatar.adapter;
*/
public class FishingBoatAdapter implements RowingBoat {
private final FishingBoat boat = new FishingBoat();
private final FishingBoat boat;
public FishingBoatAdapter() {
boat = new FishingBoat();
}
public final void row() {
boat.sail();

View File

@ -23,19 +23,18 @@
package com.iluwatar.adapter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import java.util.HashMap;
import java.util.Map;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Test class
*/
class AdapterPatternTest {
public class AdapterPatternTest {
private Map<String, Object> beans;
@ -65,7 +64,7 @@ class AdapterPatternTest {
* by the client ({@link Captain} ).
*/
@Test
void testAdapter() {
public void testAdapter() {
var captain = (Captain) beans.get(ROWING_BEAN);
// when captain moves

View File

@ -26,7 +26,8 @@ package com.iluwatar.aggregator.microservices;
import static java.util.Objects.requireNonNullElse;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
@ -36,18 +37,20 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
public class Aggregator {
@Resource
private ProductInformationClient informationClient;
@Resource
private ProductInventoryClient inventoryClient;
/**
* Retrieves product data.
*
* @return a Product.
*/
@GetMapping("/product")
@RequestMapping(path = "/product", method = RequestMethod.GET)
public Product getProduct() {
var product = new Product();

View File

@ -23,14 +23,9 @@
package com.iluwatar.aggregator.microservices;
import lombok.Getter;
import lombok.Setter;
/**
* Encapsulates all the data for a Product that clients will request.
*/
@Getter
@Setter
public class Product {
/**
@ -44,4 +39,20 @@ public class Product {
*/
private int productInventories;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getProductInventories() {
return productInventories;
}
public void setProductInventories(int productInventories) {
this.productInventories = productInventories;
}
}

View File

@ -28,16 +28,18 @@ import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* An adapter to communicate with information micro-service.
*/
@Slf4j
@Component
public class ProductInformationClientImpl implements ProductInformationClient {
private static final Logger LOGGER = LoggerFactory.getLogger(ProductInformationClientImpl.class);
@Override
public String getProductTitle() {
var request = HttpRequest.newBuilder()
@ -52,7 +54,6 @@ public class ProductInformationClientImpl implements ProductInformationClient {
LOGGER.error("IOException Occurred", ioe);
} catch (InterruptedException ie) {
LOGGER.error("InterruptedException Occurred", ie);
Thread.currentThread().interrupt();
}
return null;
}

View File

@ -28,16 +28,18 @@ import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* An adapter to communicate with inventory micro-service.
*/
@Slf4j
@Component
public class ProductInventoryClientImpl implements ProductInventoryClient {
private static final Logger LOGGER = LoggerFactory.getLogger(ProductInventoryClientImpl.class);
@Override
public Integer getProductInventories() {
var response = "";
@ -54,7 +56,6 @@ public class ProductInventoryClientImpl implements ProductInventoryClient {
LOGGER.error("IOException Occurred", ioe);
} catch (InterruptedException ie) {
LOGGER.error("InterruptedException Occurred", ie);
Thread.currentThread().interrupt();
}
if ("".equalsIgnoreCase(response)) {
return null;

View File

@ -23,19 +23,19 @@
package com.iluwatar.aggregator.microservices;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
/**
* Test Aggregation of domain objects
*/
class AggregatorTest {
public class AggregatorTest {
@InjectMocks
private Aggregator aggregator;
@ -55,7 +55,7 @@ class AggregatorTest {
* Tests getting the data for a desktop client
*/
@Test
void testGetProduct() {
public void testGetProduct() {
var title = "The Product Title.";
var inventories = 5;

View File

@ -23,7 +23,8 @@
package com.iluwatar.information.microservice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
@ -37,7 +38,7 @@ public class InformationController {
*
* @return product inventory.
*/
@GetMapping("/information")
@RequestMapping(value = "/information", method = RequestMethod.GET)
public String getProductTitle() {
return "The Product Title.";
}

View File

@ -23,17 +23,17 @@
package com.iluwatar.information.microservice;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
/**
* Test for Information Rest Controller
*/
class InformationControllerTest {
public class InformationControllerTest {
@Test
void shouldGetProductTitle() {
public void shouldGetProductTitle() {
var infoController = new InformationController();
var title = infoController.getProductTitle();
assertEquals("The Product Title.", title);

View File

@ -23,7 +23,8 @@
package com.iluwatar.inventory.microservice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
@ -37,7 +38,7 @@ public class InventoryController {
*
* @return product inventory.
*/
@GetMapping("/inventories")
@RequestMapping(value = "/inventories", method = RequestMethod.GET)
public int getProductInventories() {
return 5;
}

View File

@ -23,17 +23,16 @@
package com.iluwatar.inventory.microservice;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
/**
* Test Inventory Rest Controller
*/
class InventoryControllerTest {
public class InventoryControllerTest {
@Test
void testGetProductInventories() {
public void testGetProductInventories() {
var inventoryController = new InventoryController();
var numberOfInventories = inventoryController.getProductInventories();
assertEquals(5, numberOfInventories);

View File

@ -48,8 +48,9 @@ interface RemoteServiceInterface {
A remote services represented as a singleton.
```java
@Slf4j
public class RemoteService implements RemoteServiceInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);
private static RemoteService service = null;
static synchronized RemoteService getRemoteService() {
@ -79,8 +80,9 @@ public class RemoteService implements RemoteServiceInterface {
A service ambassador adding additional features such as logging, latency checks
```java
@Slf4j
public class ServiceAmbassador implements RemoteServiceInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAmbassador.class);
private static final int RETRIES = 3;
private static final int DELAY_MS = 3000;
@ -130,8 +132,9 @@ public class ServiceAmbassador implements RemoteServiceInterface {
A client has a local service ambassador used to interact with the remote service:
```java
@Slf4j
public class Client {
private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
long useService(int value) {

View File

@ -23,19 +23,20 @@
package com.iluwatar.ambassador;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A simple Client.
*/
@Slf4j
public class Client {
private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
long useService(int value) {
var result = serviceAmbassador.doRemoteFunction(value);
LOGGER.info("Service result: {}", result);
LOGGER.info("Service result: " + result);
return result;
}
}

View File

@ -26,14 +26,15 @@ package com.iluwatar.ambassador;
import static java.lang.Thread.sleep;
import com.iluwatar.ambassador.util.RandomProvider;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A remote legacy application represented by a Singleton implementation.
*/
@Slf4j
public class RemoteService implements RemoteServiceInterface {
private static final int THRESHOLD = 200;
private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);
private static RemoteService service = null;
private final RandomProvider randomProvider;
@ -72,9 +73,8 @@ public class RemoteService implements RemoteServiceInterface {
sleep(waitTime);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e);
Thread.currentThread().interrupt();
}
return waitTime <= THRESHOLD ? value * 10
: RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue();
: RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue();
}
}

View File

@ -33,7 +33,8 @@ package com.iluwatar.ambassador;
*/
public enum RemoteServiceStatus {
FAILURE(-1);
FAILURE(-1)
;
private final long remoteServiceStatusValue;

View File

@ -26,16 +26,17 @@ package com.iluwatar.ambassador;
import static com.iluwatar.ambassador.RemoteServiceStatus.FAILURE;
import static java.lang.Thread.sleep;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ServiceAmbassador provides an interface for a ({@link Client}) to access ({@link RemoteService}).
* The interface adds logging, latency testing and usage of the service in a safe way that will not
* add stress to the remote service when connectivity issues occur.
*/
@Slf4j
public class ServiceAmbassador implements RemoteServiceInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAmbassador.class);
private static final int RETRIES = 3;
private static final int DELAY_MS = 3000;
@ -52,7 +53,7 @@ public class ServiceAmbassador implements RemoteServiceInterface {
var result = RemoteService.getRemoteService().doRemoteFunction(value);
var timeTaken = System.currentTimeMillis() - startTime;
LOGGER.info("Time taken (ms): {}", timeTaken);
LOGGER.info("Time taken (ms): " + timeTaken);
return result;
}
@ -66,13 +67,12 @@ public class ServiceAmbassador implements RemoteServiceInterface {
}
if ((result = checkLatency(value)) == FAILURE.getRemoteServiceStatusValue()) {
LOGGER.info("Failed to reach remote: ({})", i + 1);
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
retries++;
try {
sleep(DELAY_MS);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e);
Thread.currentThread().interrupt();
}
} else {
break;

View File

@ -24,7 +24,8 @@
package com.iluwatar.api.gateway;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
@ -44,7 +45,7 @@ public class ApiGateway {
*
* @return Product information for clients on a desktop
*/
@GetMapping("/desktop")
@RequestMapping(path = "/desktop", method = RequestMethod.GET)
public DesktopProduct getProductDesktop() {
var desktopProduct = new DesktopProduct();
desktopProduct.setImagePath(imageClient.getImagePath());
@ -57,7 +58,7 @@ public class ApiGateway {
*
* @return Product information for clients on a mobile device
*/
@GetMapping("/mobile")
@RequestMapping(path = "/mobile", method = RequestMethod.GET)
public MobileProduct getProductMobile() {
var mobileProduct = new MobileProduct();
mobileProduct.setPrice(priceClient.getPrice());

View File

@ -23,16 +23,10 @@
package com.iluwatar.api.gateway;
import lombok.Getter;
import lombok.Setter;
/**
* Encapsulates all of the information that a desktop client needs to display a product.
*/
@Getter
@Setter
public class DesktopProduct {
/**
* The price of the product.
*/
@ -43,4 +37,19 @@ public class DesktopProduct {
*/
private String imagePath;
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public String getImagePath() {
return imagePath;
}
public void setImagePath(String imagePath) {
this.imagePath = imagePath;
}
}

View File

@ -23,21 +23,24 @@
package com.iluwatar.api.gateway;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
/**
* An adapter to communicate with the Image microservice.
*/
@Slf4j
@Component
public class ImageClientImpl implements ImageClient {
private static final Logger LOGGER = getLogger(ImageClientImpl.class);
/**
* Makes a simple HTTP Get request to the Image microservice.
@ -57,11 +60,8 @@ public class ImageClientImpl implements ImageClient {
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
logResponse(httpResponse);
return httpResponse.body();
} catch (IOException ioe) {
LOGGER.error("Failure occurred while getting image path", ioe);
} catch (InterruptedException ie) {
LOGGER.error("Failure occurred while getting image path", ie);
Thread.currentThread().interrupt();
} catch (IOException | InterruptedException e) {
LOGGER.error("Failure occurred while getting image path", e);
}
return null;

View File

@ -23,17 +23,20 @@
package com.iluwatar.api.gateway;
import lombok.Getter;
import lombok.Setter;
/**
* Encapsulates all of the information that mobile client needs to display a product.
*/
@Getter
@Setter
public class MobileProduct {
/**
* The price of the product.
*/
private String price;
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}

View File

@ -23,22 +23,25 @@
package com.iluwatar.api.gateway;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
/**
* An adapter to communicate with the Price microservice.
*/
@Slf4j
@Component
public class PriceClientImpl implements PriceClient {
private static final Logger LOGGER = getLogger(PriceClientImpl.class);
/**
* Makes a simple HTTP Get request to the Price microservice.
@ -58,11 +61,8 @@ public class PriceClientImpl implements PriceClient {
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
logResponse(httpResponse);
return httpResponse.body();
} catch (IOException e) {
} catch (IOException | InterruptedException e) {
LOGGER.error("Failure occurred while getting price info", e);
} catch (InterruptedException e) {
LOGGER.error("Failure occurred while getting price info", e);
Thread.currentThread().interrupt();
}
return null;

View File

@ -35,7 +35,7 @@ import org.mockito.MockitoAnnotations;
/**
* Test API Gateway Pattern
*/
class ApiGatewayTest {
public class ApiGatewayTest {
@InjectMocks
private ApiGateway apiGateway;
@ -55,7 +55,7 @@ class ApiGatewayTest {
* Tests getting the data for a desktop client
*/
@Test
void testGetProductDesktop() {
public void testGetProductDesktop() {
var imagePath = "/product-image.png";
var price = "20";
when(imageClient.getImagePath()).thenReturn(imagePath);
@ -71,7 +71,7 @@ class ApiGatewayTest {
* Tests getting the data for a mobile client
*/
@Test
void testGetProductMobile() {
public void testGetProductMobile() {
var price = "20";
when(priceClient.getPrice()).thenReturn(price);

View File

@ -23,24 +23,27 @@
package com.iluwatar.image.microservice;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import static org.slf4j.LoggerFactory.getLogger;
import org.slf4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* Exposes the Image microservice's endpoints.
*/
@Slf4j
@RestController
public class ImageController {
private static final Logger LOGGER = getLogger(ImageController.class);
/**
* An endpoint for a user to retrieve an image path.
*
* @return An image path
*/
@GetMapping("/image-path")
@RequestMapping(value = "/image-path", method = RequestMethod.GET)
public String getImagePath() {
LOGGER.info("Successfully found image path");
return "/product-image.png";

View File

@ -23,17 +23,16 @@
package com.iluwatar.image.microservice;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
/**
* Test for Image Rest Controller
*/
class ImageControllerTest {
public class ImageControllerTest {
@Test
void testGetImagePath() {
public void testGetImagePath() {
var imageController = new ImageController();
var imagePath = imageController.getImagePath();
assertEquals("/product-image.png", imagePath);

View File

@ -23,24 +23,27 @@
package com.iluwatar.price.microservice;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import static org.slf4j.LoggerFactory.getLogger;
import org.slf4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* Exposes the Price microservice's endpoints.
*/
@Slf4j
@RestController
public class PriceController {
private static final Logger LOGGER = getLogger(PriceController.class);
/**
* An endpoint for a user to retrieve a product's price.
*
* @return A product's price
*/
@GetMapping("/price")
@RequestMapping(value = "/price", method = RequestMethod.GET)
public String getPrice() {
LOGGER.info("Successfully found price info");
return "20";

View File

@ -23,17 +23,16 @@
package com.iluwatar.price.microservice;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
/**
* Test for Price Rest Controller
*/
class PriceControllerTest {
public class PriceControllerTest {
@Test
void testgetPrice() {
public void testgetPrice() {
var priceController = new PriceController();
var price = priceController.getPrice();
assertEquals("20", price);

View File

@ -81,10 +81,10 @@ Then we write our unit tests according to Arrange/Act/Assert pattern. Notice the
separated steps for each unit test.
```java
class CashAAATest {
public class CashAAATest {
@Test
void testPlus() {
public void testPlus() {
//Arrange
var cash = new Cash(3);
//Act
@ -94,7 +94,7 @@ class CashAAATest {
}
@Test
void testMinus() {
public void testMinus() {
//Arrange
var cash = new Cash(8);
//Act
@ -105,7 +105,7 @@ class CashAAATest {
}
@Test
void testInsufficientMinus() {
public void testInsufficientMinus() {
//Arrange
var cash = new Cash(1);
//Act
@ -116,7 +116,7 @@ class CashAAATest {
}
@Test
void testUpdate() {
public void testUpdate() {
//Arrange
var cash = new Cash(5);
//Act

View File

@ -36,8 +36,8 @@
<artifactId>arrange-act-assert</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -23,17 +23,18 @@
package com.iluwatar.arrangeactassert;
import lombok.AllArgsConstructor;
/**
* Arrange/Act/Assert (AAA) is a unit test pattern. In this simple example, we have a ({@link Cash})
* object for plus, minus and counting amount.
*/
@AllArgsConstructor
public class Cash {
private int amount;
Cash(int amount) {
this.amount = amount;
}
//plus
void plus(int addend) {
amount += addend;

View File

@ -23,11 +23,11 @@
package com.iluwatar.arrangeactassert;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.jupiter.api.Test;
import org.junit.Test;
/**
* Arrange/Act/Assert (AAA) is a pattern for organizing unit tests. It is a way to structure your
@ -51,11 +51,10 @@ import org.junit.jupiter.api.Test;
* change and one reason to fail. In a large and complicated code base, tests that honor the single
* responsibility principle are much easier to troubleshoot.
*/
class CashAAATest {
public class CashAAATest {
@Test
void testPlus() {
public void testPlus() {
//Arrange
var cash = new Cash(3);
//Act
@ -65,7 +64,7 @@ class CashAAATest {
}
@Test
void testMinus() {
public void testMinus() {
//Arrange
var cash = new Cash(8);
//Act
@ -76,7 +75,7 @@ class CashAAATest {
}
@Test
void testInsufficientMinus() {
public void testInsufficientMinus() {
//Arrange
var cash = new Cash(1);
//Act
@ -87,7 +86,7 @@ class CashAAATest {
}
@Test
void testUpdate() {
public void testUpdate() {
//Arrange
var cash = new Cash(5);
//Act

View File

@ -23,24 +23,23 @@
package com.iluwatar.arrangeactassert;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.jupiter.api.Test;
import org.junit.Test;
/**
* ({@link CashAAATest}) is an anti-example of AAA pattern. This test is functionally correct, but
* with the addition of a new feature, it needs refactoring. There are an awful lot of steps in that
* with the addition of new feature, it needs refactoring. There are an awful lot of steps in that
* test method, but it verifies the class' important behavior in just eleven lines. It violates the
* single responsibility principle. If this test method failed after a small code change, it might
* take some digging to discover why.
*/
class CashAntiAAATest {
public class CashAntiAAATest {
@Test
void testCash() {
public void testCash() {
//initialize
var cash = new Cash(3);
//test plus

View File

@ -9,160 +9,21 @@ tags:
---
## Intent
Asynchronous method invocation is a pattern where the calling thread
Asynchronous method invocation is pattern where the calling thread
is not blocked while waiting results of tasks. The pattern provides parallel
processing of multiple independent tasks and retrieving the results via
callbacks or waiting until everything is done.
## Explanation
Real world example
> Launching space rockets is an exciting business. The mission command gives an order to launch and
> after some undetermined time, the rocket either launches successfully or fails miserably.
In plain words
> Asynchronous method invocation starts task processing and returns immediately before the task is
> ready. The results of the task processing are returned to the caller later.
Wikipedia says
> In multithreaded computer programming, asynchronous method invocation (AMI), also known as
> asynchronous method calls or the asynchronous pattern is a design pattern in which the call site
> is not blocked while waiting for the called code to finish. Instead, the calling thread is
> notified when the reply arrives. Polling for a reply is an undesired option.
**Programmatic Example**
In this example, we are launching space rockets and deploying lunar rovers.
The application demonstrates the async method invocation pattern. The key parts of the pattern are
`AsyncResult` which is an intermediate container for an asynchronously evaluated value,
`AsyncCallback` which can be provided to be executed on task completion and `AsyncExecutor` that
manages the execution of the async tasks.
```java
public interface AsyncResult<T> {
boolean isCompleted();
T getValue() throws ExecutionException;
void await() throws InterruptedException;
}
```
```java
public interface AsyncCallback<T> {
void onComplete(T value, Optional<Exception> ex);
}
```
```java
public interface AsyncExecutor {
<T> AsyncResult<T> startProcess(Callable<T> task);
<T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback);
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
}
```
`ThreadAsyncExecutor` is an implementation of `AsyncExecutor`. Some of its key parts are highlighted
next.
```java
public class ThreadAsyncExecutor implements AsyncExecutor {
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task) {
return startProcess(task, null);
}
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) {
var result = new CompletableResult<>(callback);
new Thread(
() -> {
try {
result.setValue(task.call());
} catch (Exception ex) {
result.setException(ex);
}
},
"executor-" + idx.incrementAndGet())
.start();
return result;
}
@Override
public <T> T endProcess(AsyncResult<T> asyncResult)
throws ExecutionException, InterruptedException {
if (!asyncResult.isCompleted()) {
asyncResult.await();
}
return asyncResult.getValue();
}
}
```
Then we are ready to launch some rockets to see how everything works together.
```java
public static void main(String[] args) throws Exception {
// construct a new executor that will run async tasks
var executor = new ThreadAsyncExecutor();
// start few async tasks with varying processing times, two last with callback handlers
final var asyncResult1 = executor.startProcess(lazyval(10, 500));
final var asyncResult2 = executor.startProcess(lazyval("test", 300));
final var asyncResult3 = executor.startProcess(lazyval(50L, 700));
final var asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Deploying lunar rover"));
final var asyncResult5 =
executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
// emulate processing in the current thread while async tasks are running in their own threads
Thread.sleep(350); // Oh boy, we are working hard here
log("Mission command is sipping coffee");
// wait for completion of the tasks
final var result1 = executor.endProcess(asyncResult1);
final var result2 = executor.endProcess(asyncResult2);
final var result3 = executor.endProcess(asyncResult3);
asyncResult4.await();
asyncResult5.await();
// log the results of the tasks, callbacks log immediately when complete
log("Space rocket <" + result1 + "> launch complete");
log("Space rocket <" + result2 + "> launch complete");
log("Space rocket <" + result3 + "> launch complete");
}
```
Here's the program console output.
```java
21:47:08.227 [executor-2] INFO com.iluwatar.async.method.invocation.App - Space rocket <test> launched successfully
21:47:08.269 [main] INFO com.iluwatar.async.method.invocation.App - Mission command is sipping coffee
21:47:08.318 [executor-4] INFO com.iluwatar.async.method.invocation.App - Space rocket <20> launched successfully
21:47:08.335 [executor-4] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover <20>
21:47:08.414 [executor-1] INFO com.iluwatar.async.method.invocation.App - Space rocket <10> launched successfully
21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Space rocket <callback> launched successfully
21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover <callback>
21:47:08.616 [executor-3] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launched successfully
21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <10> launch complete
21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <test> launch complete
21:47:08.618 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launch complete
```
# Class diagram
![alt text](./etc/async-method-invocation.png "Async Method Invocation")
## Applicability
Use the async method invocation pattern when
Use async method invocation pattern when
* You have multiple independent tasks that can run in parallel
* You need to improve the performance of a group of sequential tasks
* You have a limited amount of processing capacity or long-running tasks and the caller should not wait for the tasks to be ready
* You have limited amount of processing capacity or long running tasks and the
caller should not wait the tasks to be ready
## Real world examples

View File

@ -45,6 +45,11 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -24,15 +24,14 @@
package com.iluwatar.async.method.invocation;
import java.util.concurrent.Callable;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* In this example, we are launching space rockets and deploying lunar rovers.
*
* <p>The application demonstrates the async method invocation pattern. The key parts of the
* pattern are <code>AsyncResult</code> which is an intermediate container for an asynchronously
* evaluated value, <code>AsyncCallback</code> which can be provided to be executed on task
* completion and <code>AsyncExecutor</code> that manages the execution of the async tasks.
* This application demonstrates the async method invocation pattern. Key parts of the pattern are
* <code>AsyncResult</code> which is an intermediate container for an asynchronously evaluated
* value, <code>AsyncCallback</code> which can be provided to be executed on task completion and
* <code>AsyncExecutor</code> that manages the execution of the async tasks.
*
* <p>The main method shows example flow of async invocations. The main thread starts multiple
* tasks with variable durations and then continues its own work. When the main thread has done it's
@ -56,9 +55,10 @@ import lombok.extern.slf4j.Slf4j;
* @see java.util.concurrent.CompletableFuture
* @see java.util.concurrent.ExecutorService
*/
@Slf4j
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Program entry point.
*/
@ -70,14 +70,13 @@ public class App {
final var asyncResult1 = executor.startProcess(lazyval(10, 500));
final var asyncResult2 = executor.startProcess(lazyval("test", 300));
final var asyncResult3 = executor.startProcess(lazyval(50L, 700));
final var asyncResult4 = executor.startProcess(lazyval(20, 400),
callback("Deploying lunar rover"));
final var asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4"));
final var asyncResult5 =
executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
executor.startProcess(lazyval("callback", 600), callback("Callback result 5"));
// emulate processing in the current thread while async tasks are running in their own threads
Thread.sleep(350); // Oh boy, we are working hard here
log("Mission command is sipping coffee");
Thread.sleep(350); // Oh boy I'm working hard here
log("Some hard work done");
// wait for completion of the tasks
final var result1 = executor.endProcess(asyncResult1);
@ -87,9 +86,9 @@ public class App {
asyncResult5.await();
// log the results of the tasks, callbacks log immediately when complete
log("Space rocket <" + result1 + "> launch complete");
log("Space rocket <" + result2 + "> launch complete");
log("Space rocket <" + result3 + "> launch complete");
log("Result 1: " + result1);
log("Result 2: " + result2);
log("Result 3: " + result3);
}
/**
@ -102,7 +101,7 @@ public class App {
private static <T> Callable<T> lazyval(T value, long delayMillis) {
return () -> {
Thread.sleep(delayMillis);
log("Space rocket <" + value + "> launched successfully");
log("Task completed with: " + value);
return value;
};
}
@ -118,7 +117,7 @@ public class App {
if (ex.isPresent()) {
log(name + " failed: " + ex.map(Exception::getMessage).orElse(""));
} else {
log(name + " <" + value + ">");
log(name + ": " + value);
}
};
}

View File

@ -9,131 +9,19 @@ tags:
---
## Intent
Balking Pattern is used to prevent an object from executing a certain code if it is in an incomplete
or inappropriate state.
## Explanation
Real world example
> There's a start-button in a washing machine to initiate the laundry washing. When the washing
> machine is inactive the button works as expected, but if it's already washing the button does
> nothing.
In plain words
> Using the balking pattern, a certain code executes only if the object is in particular state.
Wikipedia says
> The balking pattern is a software design pattern that only executes an action on an object when
> the object is in a particular state. For example, if an object reads ZIP files and a calling
> method invokes a get method on the object when the ZIP file is not open, the object would "balk"
> at the request.
**Programmatic Example**
In this example implementation, `WashingMachine` is an object that has two states in which it can
be: ENABLED and WASHING. If the machine is ENABLED, the state changes to WASHING using a thread-safe
method. On the other hand, if it already has been washing and any other thread executes `wash()`
it won't do that and returns without doing anything.
Here are the relevant parts of the `WashingMachine` class.
```java
@Slf4j
public class WashingMachine {
private final DelayProvider delayProvider;
private WashingMachineState washingMachineState;
public WashingMachine(DelayProvider delayProvider) {
this.delayProvider = delayProvider;
this.washingMachineState = WashingMachineState.ENABLED;
}
public WashingMachineState getWashingMachineState() {
return washingMachineState;
}
public void wash() {
synchronized (this) {
var machineState = getWashingMachineState();
LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
if (this.washingMachineState == WashingMachineState.WASHING) {
LOGGER.error("Cannot wash if the machine has been already washing!");
return;
}
this.washingMachineState = WashingMachineState.WASHING;
}
LOGGER.info("{}: Doing the washing", Thread.currentThread().getName());
this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing);
}
public synchronized void endOfWashing() {
washingMachineState = WashingMachineState.ENABLED;
LOGGER.info("{}: Washing completed.", Thread.currentThread().getId());
}
}
```
Here's the simple `DelayProvider` interface used by the `WashingMachine`.
```java
public interface DelayProvider {
void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task);
}
```
Now we introduce the application using the `WashingMachine`.
```java
public static void main(String... args) {
final var washingMachine = new WashingMachine();
var executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
executorService.execute(washingMachine::wash);
}
executorService.shutdown();
try {
executorService.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException ie) {
LOGGER.error("ERROR: Waiting on executor service shutdown!");
Thread.currentThread().interrupt();
}
}
```
Here is the console output of the program.
```
14:02:52.268 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Actual machine state: ENABLED
14:02:52.272 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Doing the washing
14:02:52.272 [pool-1-thread-3] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-3: Actual machine state: WASHING
14:02:52.273 [pool-1-thread-3] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing!
14:02:52.273 [pool-1-thread-1] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-1: Actual machine state: WASHING
14:02:52.273 [pool-1-thread-1] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing!
14:02:52.324 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - 14: Washing completed.
```
Balking Pattern is used to prevent an object from executing certain code if it is an
incomplete or inappropriate state
## Class diagram
![alt text](./etc/balking.png "Balking")
## Applicability
Use the Balking pattern when
* You want to invoke an action on an object only when it is in a particular state
* Objects are generally only in a state that is prone to balking temporarily but for an unknown
amount of time
* you want to invoke an action on an object only when it is in a particular state
* objects are generally only in a state that is prone to balking temporarily
but for an unknown amount of time
## Related patterns
* [Guarded Suspension Pattern](https://java-design-patterns.com/patterns/guarded-suspension/)
* [Double Checked Locking Pattern](https://java-design-patterns.com/patterns/double-checked-locking/)
## Credits
* [Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML, 2nd Edition, Volume 1](https://www.amazon.com/gp/product/0471227293/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0471227293&linkId=0e39a59ffaab93fb476036fecb637b99)
* Guarded Suspension Pattern
* Double Checked Locking Pattern

View File

@ -23,24 +23,28 @@
package com.iluwatar.balking;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* In Balking Design Pattern if an objects method is invoked when it is in an inappropriate state,
* then the method will return without doing anything. Objects that use this pattern are generally
* only in a state that is prone to balking temporarily but for an unknown amount of time
*
* <p>In this example implementation, {@link WashingMachine} is an object that has two states in
* which it can be: ENABLED and WASHING. If the machine is ENABLED, the state changes to WASHING
* using a thread-safe method. On the other hand, if it already has been washing and any other
* thread executes {@link WashingMachine#wash()} it won't do that and returns without doing
* anything.
* <p>In this example implementation WashingMachine is an object that has two states in which it
* can be: ENABLED and WASHING. If the machine is ENABLED the state is changed into WASHING that any
* other thread can't invoke this action on this and then do the job. On the other hand if it have
* been already washing and any other thread execute wash() it can't do that once again and returns
* doing nothing.
*/
@Slf4j
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Entry Point.
*
@ -54,12 +58,10 @@ public class App {
}
executorService.shutdown();
try {
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
executorService.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException ie) {
LOGGER.error("ERROR: Waiting on executor service shutdown!");
Thread.currentThread().interrupt();
}
}
}

View File

@ -24,14 +24,15 @@
package com.iluwatar.balking;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Washing machine class.
*/
@Slf4j
public class WashingMachine {
private static final Logger LOGGER = LoggerFactory.getLogger(WashingMachine.class);
private final DelayProvider delayProvider;
private WashingMachineState washingMachineState;
@ -43,8 +44,7 @@ public class WashingMachine {
try {
Thread.sleep(timeUnit.toMillis(interval));
} catch (InterruptedException ie) {
LOGGER.error("", ie);
Thread.currentThread().interrupt();
ie.printStackTrace();
}
task.run();
});
@ -71,7 +71,7 @@ public class WashingMachine {
var machineState = getWashingMachineState();
LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
if (this.washingMachineState == WashingMachineState.WASHING) {
LOGGER.error("Cannot wash if the machine has been already washing!");
LOGGER.error("ERROR: Cannot wash if the machine has been already washing!");
return;
}
this.washingMachineState = WashingMachineState.WASHING;

View File

@ -28,6 +28,5 @@ package com.iluwatar.balking;
* as well as during washing.
*/
public enum WashingMachineState {
ENABLED,
WASHING
ENABLED, WASHING
}

View File

@ -23,7 +23,8 @@
package com.iluwatar.bridge;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Composition over inheritance. The Bridge pattern can also be thought of as two layers of
@ -38,9 +39,10 @@ import lombok.extern.slf4j.Slf4j;
* enchantments. We can easily combine any weapon with any enchantment using composition instead of
* creating deep class hierarchy.
*/
@Slf4j
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Program entry point.
*

View File

@ -23,14 +23,16 @@
package com.iluwatar.bridge;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* FlyingEnchantment.
*/
@Slf4j
public class FlyingEnchantment implements Enchantment {
private static final Logger LOGGER = LoggerFactory.getLogger(FlyingEnchantment.class);
@Override
public void onActivate() {
LOGGER.info("The item begins to glow faintly.");

View File

@ -23,18 +23,22 @@
package com.iluwatar.bridge;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Hammer.
*/
@Slf4j
@AllArgsConstructor
public class Hammer implements Weapon {
private static final Logger LOGGER = LoggerFactory.getLogger(Hammer.class);
private final Enchantment enchantment;
public Hammer(Enchantment enchantment) {
this.enchantment = enchantment;
}
@Override
public void wield() {
LOGGER.info("The hammer is wielded.");

View File

@ -23,14 +23,16 @@
package com.iluwatar.bridge;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SoulEatingEnchantment.
*/
@Slf4j
public class SoulEatingEnchantment implements Enchantment {
private static final Logger LOGGER = LoggerFactory.getLogger(SoulEatingEnchantment.class);
@Override
public void onActivate() {
LOGGER.info("The item spreads bloodlust.");

View File

@ -23,18 +23,22 @@
package com.iluwatar.bridge;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Sword.
*/
@Slf4j
@AllArgsConstructor
public class Sword implements Weapon {
private static final Logger LOGGER = LoggerFactory.getLogger(Sword.class);
private final Enchantment enchantment;
public Sword(Enchantment enchantment) {
this.enchantment = enchantment;
}
@Override
public void wield() {
LOGGER.info("The sword is wielded.");

View File

@ -24,7 +24,8 @@
package com.iluwatar.builder;
import com.iluwatar.builder.Hero.Builder;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The intention of the Builder pattern is to find a solution to the telescoping constructor
@ -47,9 +48,10 @@ import lombok.extern.slf4j.Slf4j;
* configuration for the {@link Hero} object can be done using the fluent {@link Builder} interface.
* When configuration is ready the build method is called to receive the final {@link Hero} object.
*/
@Slf4j
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Program entry point.
*
@ -74,5 +76,6 @@ public class App {
.withWeapon(Weapon.BOW)
.build();
LOGGER.info(thief.toString());
}
}

View File

@ -23,21 +23,19 @@
package com.iluwatar.builder;
import lombok.AllArgsConstructor;
/**
* Armor enumeration.
*/
@AllArgsConstructor
public enum Armor {
CLOTHES("clothes"),
LEATHER("leather"),
CHAIN_MAIL("chain mail"),
PLATE_MAIL("plate mail");
CLOTHES("clothes"), LEATHER("leather"), CHAIN_MAIL("chain mail"), PLATE_MAIL("plate mail");
private final String title;
Armor(String title) {
this.title = title;
}
@Override
public String toString() {
return title;

View File

@ -28,11 +28,7 @@ package com.iluwatar.builder;
*/
public enum HairColor {
WHITE,
BLOND,
RED,
BROWN,
BLACK;
WHITE, BLOND, RED, BROWN, BLACK;
@Override
public String toString() {

View File

@ -23,22 +23,20 @@
package com.iluwatar.builder;
import lombok.AllArgsConstructor;
/**
* HairType enumeration.
*/
@AllArgsConstructor
public enum HairType {
BALD("bald"),
SHORT("short"),
CURLY("curly"),
LONG_STRAIGHT("long straight"),
LONG_CURLY("long curly");
BALD("bald"), SHORT("short"), CURLY("curly"), LONG_STRAIGHT("long straight"), LONG_CURLY(
"long curly");
private final String title;
HairType(String title) {
this.title = title;
}
@Override
public String toString() {
return title;

View File

@ -41,6 +41,7 @@ class AppTest {
@Test
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -9,156 +9,21 @@ tags:
---
## Intent
The Business Delegate pattern adds an abstraction layer between
presentation and business tiers. By using the pattern we gain loose coupling
between the tiers and encapsulate knowledge about how to locate, connect to,
and interact with the business objects that make up the application.
## Explanation
Real world example
> A mobile phone application promises to stream any movie in existence to your phone. It captures
> the user's search string and passes this on to the business delegate. The business delegate
> selects the most suitable video streaming service and plays the video from there.
In Plain Words
> Business delegate adds an abstraction layer between the presentation and business tiers.
Wikipedia says
> Business delegate is a Java EE design pattern. This pattern is directing to reduce the coupling
> in between business services and the connected presentation tier, and to hide the implementation
> details of services (including lookup and accessibility of EJB architecture). Business delegates
> acts as an adaptor to invoke business objects from the presentation tier.
**Programmatic Example**
First, we have an abstraction for video streaming services and a couple of implementations.
```java
public interface VideoStreamingService {
void doProcessing();
}
@Slf4j
public class NetflixService implements VideoStreamingService {
@Override
public void doProcessing() {
LOGGER.info("NetflixService is now processing");
}
}
@Slf4j
public class YouTubeService implements VideoStreamingService {
@Override
public void doProcessing() {
LOGGER.info("YouTubeService is now processing");
}
}
```
Then we have a lookup service that decides which video streaming service is used.
```java
@Setter
public class BusinessLookup {
private NetflixService netflixService;
private YouTubeService youTubeService;
public VideoStreamingService getBusinessService(String movie) {
if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
return netflixService;
} else {
return youTubeService;
}
}
}
```
The business delegate uses a business lookup to route movie playback requests to a suitable
video streaming service.
```java
@Setter
public class BusinessDelegate {
private BusinessLookup lookupService;
public void playbackMovie(String movie) {
VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
videoStreamingService.doProcessing();
}
}
```
The mobile client utilizes business delegate to call the business tier.
```java
public class MobileClient {
private final BusinessDelegate businessDelegate;
public MobileClient(BusinessDelegate businessDelegate) {
this.businessDelegate = businessDelegate;
}
public void playbackMovie(String movie) {
businessDelegate.playbackMovie(movie);
}
}
```
Finally, we can show the full example in action.
```java
public static void main(String[] args) {
// prepare the objects
var businessDelegate = new BusinessDelegate();
var businessLookup = new BusinessLookup();
businessLookup.setNetflixService(new NetflixService());
businessLookup.setYouTubeService(new YouTubeService());
businessDelegate.setLookupService(businessLookup);
// create the client and use the business delegate
var client = new MobileClient(businessDelegate);
client.playbackMovie("Die Hard 2");
client.playbackMovie("Maradona: The Greatest Ever");
}
```
Here is the console output.
```
21:15:33.790 [main] INFO com.iluwatar.business.delegate.NetflixService - NetflixService is now processing
21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing
```
## Class diagram
![alt text](./etc/business-delegate.urm.png "Business Delegate")
## Related patterns
* [Service locator pattern](https://java-design-patterns.com/patterns/service-locator/)
![alt text](./etc/business-delegate.png "Business Delegate")
## Applicability
Use the Business Delegate pattern when
* You want loose coupling between presentation and business tiers
* You want to orchestrate calls to multiple business services
* You want to encapsulate service lookups and service calls
## Tutorials
* [Business Delegate Pattern at TutorialsPoint](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm)
* you want loose coupling between presentation and business tiers
* you want to orchestrate calls to multiple business services
* you want to encapsulate service lookups and service calls
## Credits
* [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)
* [Core J2EE Patterns: Best Practices and Design Strategies](https://www.amazon.com/gp/product/0130648841/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0130648841&linkId=a0100de2b28c71ede8db1757fb2b5947)

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,136 @@
<?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.business.delegate.BusinessDelegate" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="272" y="219"/>
<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.business.delegate.Client" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="272" y="64"/>
<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>
<enumeration id="3" language="java" name="com.iluwatar.business.delegate.ServiceType" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="89" y="383"/>
<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>
</enumeration>
<interface id="4" language="java" name="com.iluwatar.business.delegate.BusinessService" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessService.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="630" y="365"/>
<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.business.delegate.JmsService" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/JmsService.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="489" y="210"/>
<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.business.delegate.BusinessLookup" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="360" y="399"/>
<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="7" language="java" name="com.iluwatar.business.delegate.EjbService" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/EjbService.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="668" y="210"/>
<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="8">
<end type="SOURCE" refId="7"/>
<end type="TARGET" refId="4"/>
</realization>
<association id="9">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="10" name="lookupService">
<position height="0" width="0" x="0" y="0"/>
</attribute>
<multiplicity id="11" minimum="0" maximum="1">
<position height="0" width="0" x="0" y="0"/>
</multiplicity>
</end>
<end type="TARGET" refId="6" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="12">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="13" name="serviceType">
<position height="0" width="0" x="0" y="0"/>
</attribute>
<multiplicity id="14" minimum="0" maximum="1">
<position height="0" width="0" x="0" y="0"/>
</multiplicity>
</end>
<end type="TARGET" refId="3" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="15">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="4"/>
</realization>
<association id="16">
<end type="SOURCE" refId="2" navigable="false">
<attribute id="17" name="businessDelegate">
<position height="0" width="0" x="0" y="0"/>
</attribute>
<multiplicity id="18" minimum="0" maximum="1">
<position height="0" width="0" x="0" y="0"/>
</multiplicity>
</end>
<end type="TARGET" refId="1" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="19">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="20" name="businessService">
<position height="0" width="0" x="0" y="0"/>
</attribute>
<multiplicity id="21" minimum="0" maximum="1">
<position height="0" width="0" x="0" y="0"/>
</multiplicity>
</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: 49 KiB

View File

@ -5,42 +5,53 @@ package com.iluwatar.business.delegate {
+ main(args : String[]) {static}
}
class BusinessDelegate {
- businessService : BusinessService
- lookupService : BusinessLookup
- serviceType : ServiceType
+ BusinessDelegate()
+ playbackMovie(movie : String)
+ setLookupService(lookupService : BusinessLookup)
+ doTask()
+ setLookupService(businessLookup : BusinessLookup)
+ setServiceType(serviceType : ServiceType)
}
class BusinessLookup {
- netflixService : NetflixService
- youTubeService : YouTubeService
- ejbService : EjbService
- jmsService : JmsService
+ BusinessLookup()
+ getBusinessService(movie : String) : VideoStreamingService
+ setNetflixService(netflixService : NetflixService)
+ setYouTubeService(youTubeService : YouTubeService)
+ getBusinessService(serviceType : ServiceType) : BusinessService
+ setEjbService(ejbService : EjbService)
+ setJmsService(jmsService : JmsService)
}
class MobileClient {
- businessDelegate : BusinessDelegate
+ MobileClient(businessDelegate : BusinessDelegate)
+ playbackMovie(movie : String)
}
class NetflixService {
- LOGGER : Logger {static}
+ NetflixService()
+ doProcessing()
}
interface VideoStreamingService {
interface BusinessService {
+ doProcessing() {abstract}
}
class YouTubeService {
class Client {
- businessDelegate : BusinessDelegate
+ Client(businessDelegate : BusinessDelegate)
+ doTask()
}
class EjbService {
- LOGGER : Logger {static}
+ YouTubeService()
+ EjbService()
+ doProcessing()
}
class JmsService {
- LOGGER : Logger {static}
+ JmsService()
+ doProcessing()
}
enum ServiceType {
+ EJB {static}
+ JMS {static}
+ valueOf(name : String) : ServiceType {static}
+ values() : ServiceType[] {static}
}
}
BusinessLookup --> "-netflixService" NetflixService
BusinessLookup --> "-youTubeService" YouTubeService
MobileClient --> "-businessDelegate" BusinessDelegate
BusinessLookup --> "-ejbService" EjbService
BusinessDelegate --> "-serviceType" ServiceType
Client --> "-businessDelegate" BusinessDelegate
BusinessDelegate --> "-businessService" BusinessService
BusinessDelegate --> "-lookupService" BusinessLookup
NetflixService ..|> VideoStreamingService
YouTubeService ..|> VideoStreamingService
BusinessLookup --> "-jmsService" JmsService
EjbService ..|> BusinessService
JmsService ..|> BusinessService
@enduml

View File

@ -33,9 +33,9 @@ package com.iluwatar.business.delegate;
* retrieved through service lookups. The Business Delegate itself may contain business logic too
* potentially tying together multiple service calls, exception handling, retrying etc.
*
* <p>In this example the client ({@link MobileClient}) utilizes a business delegate (
* {@link BusinessDelegate}) to search for movies in video streaming services. The Business Delegate
* then selects the appropriate service and makes the service call.
* <p>In this example the client ({@link Client}) utilizes a business delegate (
* {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
* service and makes the service call.
*/
public class App {
@ -46,16 +46,18 @@ public class App {
*/
public static void main(String[] args) {
// prepare the objects
var businessDelegate = new BusinessDelegate();
var businessLookup = new BusinessLookup();
businessLookup.setNetflixService(new NetflixService());
businessLookup.setYouTubeService(new YouTubeService());
businessDelegate.setLookupService(businessLookup);
businessLookup.setEjbService(new EjbService());
businessLookup.setJmsService(new JmsService());
// create the client and use the business delegate
var client = new MobileClient(businessDelegate);
client.playbackMovie("Die Hard 2");
client.playbackMovie("Maradona: The Greatest Ever");
businessDelegate.setLookupService(businessLookup);
businessDelegate.setServiceType(ServiceType.EJB);
var client = new Client(businessDelegate);
client.doTask();
businessDelegate.setServiceType(ServiceType.JMS);
client.doTask();
}
}

View File

@ -23,18 +23,25 @@
package com.iluwatar.business.delegate;
import lombok.Setter;
/**
* BusinessDelegate separates the presentation and business tiers.
*/
@Setter
public class BusinessDelegate {
private BusinessLookup lookupService;
private BusinessService businessService;
private ServiceType serviceType;
public void playbackMovie(String movie) {
VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
videoStreamingService.doProcessing();
public void setLookupService(BusinessLookup businessLookup) {
this.lookupService = businessLookup;
}
public void setServiceType(ServiceType serviceType) {
this.serviceType = serviceType;
}
public void doTask() {
businessService = lookupService.getBusinessService(serviceType);
businessService.doProcessing();
}
}

View File

@ -23,30 +23,34 @@
package com.iluwatar.business.delegate;
import java.util.Locale;
import lombok.Setter;
/**
* Class for performing service lookups.
*/
@Setter
public class BusinessLookup {
private NetflixService netflixService;
private EjbService ejbService;
private YouTubeService youTubeService;
private JmsService jmsService;
/**
* Gets service instance based on given movie search string.
* Gets service instance based on service type.
*
* @param movie Search string for the movie.
* @param serviceType Type of service instance to be returned.
* @return Service instance.
*/
public VideoStreamingService getBusinessService(String movie) {
if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
return netflixService;
public BusinessService getBusinessService(ServiceType serviceType) {
if (serviceType.equals(ServiceType.EJB)) {
return ejbService;
} else {
return youTubeService;
return jmsService;
}
}
public void setJmsService(JmsService jmsService) {
this.jmsService = jmsService;
}
public void setEjbService(EjbService ejbService) {
this.ejbService = ejbService;
}
}

View File

@ -24,9 +24,9 @@
package com.iluwatar.business.delegate;
/**
* Interface for video streaming service implementations.
* Interface for service implementations.
*/
public interface VideoStreamingService {
public interface BusinessService {
void doProcessing();
}

View File

@ -24,17 +24,17 @@
package com.iluwatar.business.delegate;
/**
* MobileClient utilizes BusinessDelegate to call the business tier.
* Client utilizes BusinessDelegate to call the business tier.
*/
public class MobileClient {
public class Client {
private final BusinessDelegate businessDelegate;
public MobileClient(BusinessDelegate businessDelegate) {
public Client(BusinessDelegate businessDelegate) {
this.businessDelegate = businessDelegate;
}
public void playbackMovie(String movie) {
businessDelegate.playbackMovie(movie);
public void doTask() {
businessDelegate.doTask();
}
}

View File

@ -23,16 +23,18 @@
package com.iluwatar.business.delegate;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* NetflixService implementation.
* Service EJB implementation.
*/
@Slf4j
public class NetflixService implements VideoStreamingService {
public class EjbService implements BusinessService {
private static final Logger LOGGER = LoggerFactory.getLogger(EjbService.class);
@Override
public void doProcessing() {
LOGGER.info("NetflixService is now processing");
LOGGER.info("EjbService is now processing");
}
}

View File

@ -23,16 +23,18 @@
package com.iluwatar.business.delegate;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* YouTubeService implementation.
* Service JMS implementation.
*/
@Slf4j
public class YouTubeService implements VideoStreamingService {
public class JmsService implements BusinessService {
private static final Logger LOGGER = LoggerFactory.getLogger(JmsService.class);
@Override
public void doProcessing() {
LOGGER.info("YouTubeService is now processing");
LOGGER.info("JmsService is now processing");
}
}

View File

@ -21,17 +21,12 @@
* THE SOFTWARE.
*/
package com.iluwatar.activeobject;
package com.iluwatar.business.delegate;
/**
* An implementation of the ActiveCreature class.
* @author Noam Greenshtain
*
* Enumeration for service types.
*/
public class Orc extends ActiveCreature {
public Orc(String name) {
super(name);
}
public enum ServiceType {
EJB, JMS
}

View File

@ -26,20 +26,27 @@ package com.iluwatar.business.delegate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for the {@link BusinessDelegate}
* The Business Delegate pattern adds an abstraction layer between the presentation and business
* tiers. By using the pattern we gain loose coupling between the tiers. The Business Delegate
* encapsulates knowledge about how to locate, connect to, and interact with the business objects
* that make up the application.
*
* <p>Some of the services the Business Delegate uses are instantiated directly, and some can be
* retrieved through service lookups. The Business Delegate itself may contain business logic too
* potentially tying together multiple service calls, exception handling, retrying etc.
*/
class BusinessDelegateTest {
public class BusinessDelegateTest {
private NetflixService netflixService;
private EjbService ejbService;
private YouTubeService youTubeService;
private JmsService jmsService;
private BusinessLookup businessLookup;
private BusinessDelegate businessDelegate;
@ -49,40 +56,46 @@ class BusinessDelegateTest {
*/
@BeforeEach
public void setup() {
netflixService = spy(new NetflixService());
youTubeService = spy(new YouTubeService());
ejbService = spy(new EjbService());
jmsService = spy(new JmsService());
BusinessLookup businessLookup = spy(new BusinessLookup());
businessLookup.setNetflixService(netflixService);
businessLookup.setYouTubeService(youTubeService);
businessLookup = spy(new BusinessLookup());
businessLookup.setEjbService(ejbService);
businessLookup.setJmsService(jmsService);
businessDelegate = spy(new BusinessDelegate());
businessDelegate.setLookupService(businessLookup);
}
/**
* In this example the client ({@link MobileClient}) utilizes a business delegate (
* In this example the client ({@link Client}) utilizes a business delegate (
* {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
* service and makes the service call.
*/
@Test
void testBusinessDelegate() {
public void testBusinessDelegate() {
// setup a client object
var client = new MobileClient(businessDelegate);
var client = new Client(businessDelegate);
// set the service type
businessDelegate.setServiceType(ServiceType.EJB);
// action
client.playbackMovie("Die hard");
// verifying that the businessDelegate was used by client during playbackMovie() method.
verify(businessDelegate).playbackMovie(anyString());
verify(netflixService).doProcessing();
// action
client.playbackMovie("Maradona");
client.doTask();
// verifying that the businessDelegate was used by client during doTask() method.
verify(businessDelegate, times(2)).playbackMovie(anyString());
verify(youTubeService).doProcessing();
verify(businessDelegate).doTask();
verify(ejbService).doProcessing();
// set the service type
businessDelegate.setServiceType(ServiceType.JMS);
// action
client.doTask();
// verifying that the businessDelegate was used by client during doTask() method.
verify(businessDelegate, times(2)).doTask();
verify(jmsService).doProcessing();
}
}

View File

@ -9,234 +9,18 @@ tags:
---
## Intent
Allows encoding behavior as instructions for a virtual machine.
## Explanation
Real world example
> A team is working on a new game where wizards battle against each other. The wizard behavior
> needs to be carefully adjusted and iterated hundreds of times through playtesting. It's not
> optimal to ask the programmer to make changes each time the game designer wants to vary the
> behavior, so the wizard behavior is implemented as a data-driven virtual machine.
In plain words
> Bytecode pattern enables behavior driven by data instead of code.
[Gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/bytecode.html) documentation
states:
> An instruction set defines the low-level operations that can be performed. A series of
> instructions is encoded as a sequence of bytes. A virtual machine executes these instructions one
> at a time, using a stack for intermediate values. By combining instructions, complex high-level
> behavior can be defined.
**Programmatic Example**
One of the most important game objects is the `Wizard` class.
```java
@AllArgsConstructor
@Setter
@Getter
@Slf4j
public class Wizard {
private int health;
private int agility;
private int wisdom;
private int numberOfPlayedSounds;
private int numberOfSpawnedParticles;
public void playSound() {
LOGGER.info("Playing sound");
numberOfPlayedSounds++;
}
public void spawnParticles() {
LOGGER.info("Spawning particles");
numberOfSpawnedParticles++;
}
}
```
Next, we show the available instructions for our virtual machine. Each of the instructions has its
own semantics on how it operates with the stack data. For example, the ADD instruction takes the top
two items from the stack, adds them together and pushes the result to the stack.
```java
@AllArgsConstructor
@Getter
public enum Instruction {
LITERAL(1), // e.g. "LITERAL 0", push 0 to stack
SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health
SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom
SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility
PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound
SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles
GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health
GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility
GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom
ADD(10), // e.g. "ADD", pop 2 values, push their sum
DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division
// ...
}
```
At the heart of our example is the `VirtualMachine` class. It takes instructions as input and
executes them to provide the game object behavior.
```java
@Getter
@Slf4j
public class VirtualMachine {
private final Stack<Integer> stack = new Stack<>();
private final Wizard[] wizards = new Wizard[2];
public VirtualMachine() {
wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
0, 0);
wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
0, 0);
}
public VirtualMachine(Wizard wizard1, Wizard wizard2) {
wizards[0] = wizard1;
wizards[1] = wizard2;
}
public void execute(int[] bytecode) {
for (var i = 0; i < bytecode.length; i++) {
Instruction instruction = Instruction.getInstruction(bytecode[i]);
switch (instruction) {
case LITERAL:
// Read the next byte from the bytecode.
int value = bytecode[++i];
// Push the next value to stack
stack.push(value);
break;
case SET_AGILITY:
var amount = stack.pop();
var wizard = stack.pop();
setAgility(wizard, amount);
break;
case SET_WISDOM:
amount = stack.pop();
wizard = stack.pop();
setWisdom(wizard, amount);
break;
case SET_HEALTH:
amount = stack.pop();
wizard = stack.pop();
setHealth(wizard, amount);
break;
case GET_HEALTH:
wizard = stack.pop();
stack.push(getHealth(wizard));
break;
case GET_AGILITY:
wizard = stack.pop();
stack.push(getAgility(wizard));
break;
case GET_WISDOM:
wizard = stack.pop();
stack.push(getWisdom(wizard));
break;
case ADD:
var a = stack.pop();
var b = stack.pop();
stack.push(a + b);
break;
case DIVIDE:
a = stack.pop();
b = stack.pop();
stack.push(b / a);
break;
case PLAY_SOUND:
wizard = stack.pop();
getWizards()[wizard].playSound();
break;
case SPAWN_PARTICLES:
wizard = stack.pop();
getWizards()[wizard].spawnParticles();
break;
default:
throw new IllegalArgumentException("Invalid instruction value");
}
LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack());
}
}
public void setHealth(int wizard, int amount) {
wizards[wizard].setHealth(amount);
}
// other setters ->
// ...
}
```
Now we can show the full example utilizing the virtual machine.
```java
public static void main(String[] args) {
var vm = new VirtualMachine(
new Wizard(45, 7, 11, 0, 0),
new Wizard(36, 18, 8, 0, 0));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM"));
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2"));
vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE"));
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH"));
}
```
Here is the console output.
```
16:20:10.193 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0]
16:20:10.196 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 0]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_HEALTH, Stack contains [0, 45]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 0]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_AGILITY, Stack contains [0, 45, 7]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 7, 0]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_WISDOM, Stack contains [0, 45, 7, 11]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 45, 18]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 18, 2]
16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed DIVIDE, Stack contains [0, 45, 9]
16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 54]
16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed SET_HEALTH, Stack contains []
```
Allows to encode behaviour as instructions for virtual machine.
## Class diagram
![alt text](./etc/bytecode.urm.png "Bytecode class diagram")
## Applicability
Use the Bytecode pattern when you have a lot of behavior you need to define and your
games implementation language isnt a good fit because:
* Its too low-level, making it tedious or error-prone to program in.
* Iterating on it takes too long due to slow compile times or other tooling issues.
* It has too much trust. If you want to ensure the behavior being defined cant break the game, you need to sandbox it from the rest of the codebase.
## Related patterns
* [Interpreter](https://java-design-patterns.com/patterns/interpreter/)
* its too low-level, making it tedious or error-prone to program in.
* iterating on it takes too long due to slow compile times or other tooling issues.
* it has too much trust. If you want to ensure the behavior being defined cant break the game, you need to sandbox it from the rest of the codebase.
## Credits

BIN
bytecode/etc/bytecode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.2.3" 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.bytecode.VirtualMachine" project="bytecode"
file="/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="455" y="173"/>
<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.bytecode.App" project="bytecode"
file="/bytecode/src/main/java/com/iluwatar/bytecode/App.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="148" y="110"/>
<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.bytecode.Wizard" project="bytecode"
file="/bytecode/src/main/java/com/iluwatar/bytecode/Wizard.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="148" y="416"/>
<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="4">
<end type="SOURCE" refId="1" navigable="false" variant="ASSOCIATION">
<attribute id="5" name="wizards">
<position height="18" width="48" x="296" y="291"/>
</attribute>
<multiplicity id="6" minimum="0" maximum="2147483647">
<position height="0" width="0" x="-327" y="-27"/>
</multiplicity>
</end>
<end type="TARGET" refId="3" navigable="true" variant="ASSOCIATION"/>
<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: 84 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -3,6 +3,7 @@ package com.iluwatar.bytecode {
class App {
- LOGGER : Logger {static}
+ App()
- interpretInstruction(instruction : String, vm : VirtualMachine) {static}
+ main(args : String[]) {static}
}
enum Instruction {
@ -17,25 +18,22 @@ package com.iluwatar.bytecode {
+ SET_HEALTH {static}
+ SET_WISDOM {static}
+ SPAWN_PARTICLES {static}
- intValue : int
- value : int
+ getInstruction(value : int) : Instruction {static}
+ getIntValue() : int
+ valueOf(name : String) : Instruction {static}
+ values() : Instruction[] {static}
}
class VirtualMachine {
- LOGGER : Logger {static}
- stack : Stack<Integer>
- wizards : Wizard[]
+ VirtualMachine()
+ VirtualMachine(wizard1 : Wizard, wizard2 : Wizard)
+ execute(bytecode : int[])
+ getAgility(wizard : int) : int
+ getHealth(wizard : int) : int
+ getStack() : Stack<Integer>
+ getWisdom(wizard : int) : int
+ getWizards() : Wizard[]
- randomInt(min : int, max : int) : int
+ setAgility(wizard : int, amount : int)
+ setHealth(wizard : int, amount : int)
+ setWisdom(wizard : int, amount : int)
@ -47,7 +45,7 @@ package com.iluwatar.bytecode {
- numberOfPlayedSounds : int
- numberOfSpawnedParticles : int
- wisdom : int
+ Wizard(health : int, agility : int, wisdom : int, numberOfPlayedSounds : int, numberOfSpawnedParticles : int)
+ Wizard()
+ getAgility() : int
+ getHealth() : int
+ getNumberOfPlayedSounds() : int
@ -56,8 +54,6 @@ package com.iluwatar.bytecode {
+ playSound()
+ setAgility(agility : int)
+ setHealth(health : int)
+ setNumberOfPlayedSounds(numberOfPlayedSounds : int)
+ setNumberOfSpawnedParticles(numberOfSpawnedParticles : int)
+ setWisdom(wisdom : int)
+ spawnParticles()
}

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