Compare commits
19 Commits
zh
...
caching-pa
Author | SHA1 | Date | |
---|---|---|---|
a8355988b2 | |||
6d7084f18d | |||
74f5cfa670 | |||
b525d871b4 | |||
c413e0902e | |||
7ac468db20 | |||
794795acf5 | |||
2b7d181ac4 | |||
b5ed8b2278 | |||
8fc64a2d38 | |||
7e91322a43 | |||
e9106ccfc5 | |||
77ffae5ecc | |||
74abc7a0d6 | |||
ccf350b611 | |||
c8a2ef01d3 | |||
cbf1847425 | |||
5cf2fe009b | |||
0e26a6adb5 |
@ -1413,6 +1413,43 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"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,
|
"contributorsPerLine": 4,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
[](#contributors-)
|
[](#contributors-)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
@ -307,6 +307,12 @@ This project is licensed under the terms of the MIT license.
|
|||||||
<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/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/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/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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -27,8 +27,7 @@ import com.iluwatar.abstractdocument.domain.Car;
|
|||||||
import com.iluwatar.abstractdocument.domain.enums.Property;
|
import com.iluwatar.abstractdocument.domain.enums.Property;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Abstract Document pattern enables handling additional, non-static properties. This pattern
|
* The Abstract Document pattern enables handling additional, non-static properties. This pattern
|
||||||
@ -38,10 +37,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
* <p>In Abstract Document pattern,({@link AbstractDocument}) fully implements {@link Document})
|
* <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.
|
* interface. Traits are then defined to enable access to properties in usual, static way.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point.
|
* Program entry point.
|
||||||
*
|
*
|
||||||
|
@ -23,19 +23,18 @@
|
|||||||
|
|
||||||
package com.iluwatar.abstractdocument;
|
package com.iluwatar.abstractdocument;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AbstractDocument test class
|
* AbstractDocument test class
|
||||||
*/
|
*/
|
||||||
public class AbstractDocumentTest {
|
class AbstractDocumentTest {
|
||||||
|
|
||||||
private static final String KEY = "key";
|
private static final String KEY = "key";
|
||||||
private static final String VALUE = "value";
|
private static final String VALUE = "value";
|
||||||
@ -50,13 +49,13 @@ public class AbstractDocumentTest {
|
|||||||
private final DocumentImplementation document = new DocumentImplementation(new HashMap<>());
|
private final DocumentImplementation document = new DocumentImplementation(new HashMap<>());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldPutAndGetValue() {
|
void shouldPutAndGetValue() {
|
||||||
document.put(KEY, VALUE);
|
document.put(KEY, VALUE);
|
||||||
assertEquals(VALUE, document.get(KEY));
|
assertEquals(VALUE, document.get(KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldRetrieveChildren() {
|
void shouldRetrieveChildren() {
|
||||||
var children = List.of(Map.of(), Map.of());
|
var children = List.of(Map.of(), Map.of());
|
||||||
|
|
||||||
document.put(KEY, children);
|
document.put(KEY, children);
|
||||||
@ -67,14 +66,14 @@ public class AbstractDocumentTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldRetrieveEmptyStreamForNonExistingChildren() {
|
void shouldRetrieveEmptyStreamForNonExistingChildren() {
|
||||||
var children = document.children(KEY, DocumentImplementation::new);
|
var children = document.children(KEY, DocumentImplementation::new);
|
||||||
assertNotNull(children);
|
assertNotNull(children);
|
||||||
assertEquals(0, children.count());
|
assertEquals(0, children.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldIncludePropsInToString() {
|
void shouldIncludePropsInToString() {
|
||||||
var props = Map.of(KEY, (Object) VALUE);
|
var props = Map.of(KEY, (Object) VALUE);
|
||||||
var document = new DocumentImplementation(props);
|
var document = new DocumentImplementation(props);
|
||||||
assertTrue(document.toString().contains(KEY));
|
assertTrue(document.toString().contains(KEY));
|
||||||
|
@ -23,19 +23,20 @@
|
|||||||
|
|
||||||
package com.iluwatar.abstractdocument;
|
package com.iluwatar.abstractdocument;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import com.iluwatar.abstractdocument.domain.Car;
|
import com.iluwatar.abstractdocument.domain.Car;
|
||||||
import com.iluwatar.abstractdocument.domain.Part;
|
import com.iluwatar.abstractdocument.domain.Part;
|
||||||
import com.iluwatar.abstractdocument.domain.enums.Property;
|
import com.iluwatar.abstractdocument.domain.enums.Property;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for Part and Car
|
* Test for Part and Car
|
||||||
*/
|
*/
|
||||||
public class DomainTest {
|
class DomainTest {
|
||||||
|
|
||||||
private static final String TEST_PART_TYPE = "test-part-type";
|
private static final String TEST_PART_TYPE = "test-part-type";
|
||||||
private static final String TEST_PART_MODEL = "test-part-model";
|
private static final String TEST_PART_MODEL = "test-part-model";
|
||||||
@ -45,7 +46,7 @@ public class DomainTest {
|
|||||||
private static final long TEST_CAR_PRICE = 1L;
|
private static final long TEST_CAR_PRICE = 1L;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldConstructPart() {
|
void shouldConstructPart() {
|
||||||
var partProperties = Map.of(
|
var partProperties = Map.of(
|
||||||
Property.TYPE.toString(), TEST_PART_TYPE,
|
Property.TYPE.toString(), TEST_PART_TYPE,
|
||||||
Property.MODEL.toString(), TEST_PART_MODEL,
|
Property.MODEL.toString(), TEST_PART_MODEL,
|
||||||
@ -58,7 +59,7 @@ public class DomainTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldConstructCar() {
|
void shouldConstructCar() {
|
||||||
var carProperties = Map.of(
|
var carProperties = Map.of(
|
||||||
Property.MODEL.toString(), TEST_CAR_MODEL,
|
Property.MODEL.toString(), TEST_CAR_MODEL,
|
||||||
Property.PRICE.toString(), TEST_CAR_PRICE,
|
Property.PRICE.toString(), TEST_CAR_PRICE,
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
|
|
||||||
package com.iluwatar.abstractfactory;
|
package com.iluwatar.abstractfactory;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Abstract Factory pattern provides a way to encapsulate a group of individual factories that
|
* The Abstract Factory pattern provides a way to encapsulate a group of individual factories that
|
||||||
@ -40,10 +39,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
* and its implementations ( {@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses
|
* and its implementations ( {@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses
|
||||||
* both concrete implementations to create a king, a castle and an army.
|
* both concrete implementations to create a king, a castle and an army.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class App implements Runnable {
|
public class App implements Runnable {
|
||||||
|
|
||||||
private static Logger log = LoggerFactory.getLogger(App.class);
|
|
||||||
|
|
||||||
private final Kingdom kingdom = new Kingdom();
|
private final Kingdom kingdom = new Kingdom();
|
||||||
|
|
||||||
public Kingdom getKingdom() {
|
public Kingdom getKingdom() {
|
||||||
@ -62,17 +60,17 @@ public class App implements Runnable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
log.info("Elf Kingdom");
|
LOGGER.info("Elf Kingdom");
|
||||||
createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
||||||
log.info(kingdom.getArmy().getDescription());
|
LOGGER.info(kingdom.getArmy().getDescription());
|
||||||
log.info(kingdom.getCastle().getDescription());
|
LOGGER.info(kingdom.getCastle().getDescription());
|
||||||
log.info(kingdom.getKing().getDescription());
|
LOGGER.info(kingdom.getKing().getDescription());
|
||||||
|
|
||||||
log.info("Orc Kingdom");
|
LOGGER.info("Orc Kingdom");
|
||||||
createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
|
createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
|
||||||
log.info(kingdom.getArmy().getDescription());
|
LOGGER.info(kingdom.getArmy().getDescription());
|
||||||
log.info(kingdom.getCastle().getDescription());
|
LOGGER.info(kingdom.getCastle().getDescription());
|
||||||
log.info(kingdom.getKing().getDescription());
|
LOGGER.info(kingdom.getKing().getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,36 +23,17 @@
|
|||||||
|
|
||||||
package com.iluwatar.abstractfactory;
|
package com.iluwatar.abstractfactory;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
public class Kingdom {
|
public class Kingdom {
|
||||||
|
|
||||||
private King king;
|
private King king;
|
||||||
private Castle castle;
|
private Castle castle;
|
||||||
private Army army;
|
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.
|
* The factory of kingdom factories.
|
||||||
*/
|
*/
|
||||||
|
@ -31,12 +31,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||||||
/**
|
/**
|
||||||
* Test for abstract factory.
|
* Test for abstract factory.
|
||||||
*/
|
*/
|
||||||
public class AbstractFactoryTest {
|
class AbstractFactoryTest {
|
||||||
|
|
||||||
private final App app = new App();
|
private final App app = new App();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void king() {
|
void king() {
|
||||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
||||||
final var kingdom = app.getKingdom();
|
final var kingdom = app.getKingdom();
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ public class AbstractFactoryTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void castle() {
|
void castle() {
|
||||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
||||||
final var kingdom = app.getKingdom();
|
final var kingdom = app.getKingdom();
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ public class AbstractFactoryTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void army() {
|
void army() {
|
||||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
||||||
final var kingdom = app.getKingdom();
|
final var kingdom = app.getKingdom();
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ public class AbstractFactoryTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createElfKingdom() {
|
void createElfKingdom() {
|
||||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
|
||||||
final var kingdom = app.getKingdom();
|
final var kingdom = app.getKingdom();
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ public class AbstractFactoryTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createOrcKingdom() {
|
void createOrcKingdom() {
|
||||||
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
|
app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
|
||||||
final var kingdom = app.getKingdom();
|
final var kingdom = app.getKingdom();
|
||||||
|
|
||||||
|
@ -28,16 +28,12 @@ import org.junit.jupiter.api.Test;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests that Abstract Factory example runs without errors.
|
|
||||||
*/
|
|
||||||
class AppTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Issue: Add at least one assertion to this test case.
|
* 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}
|
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
|
||||||
* throws an exception.
|
* throws an exception.
|
||||||
*/
|
*/
|
||||||
|
class AppTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldExecuteApplicationWithoutException() {
|
void shouldExecuteApplicationWithoutException() {
|
||||||
|
125
active-object/README.md
Normal file
125
active-object/README.md
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|

|
BIN
active-object/etc/active-object.urm.PNG
Normal file
BIN
active-object/etc/active-object.urm.PNG
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
25
active-object/etc/active-object.urm.puml
Normal file
25
active-object/etc/active-object.urm.puml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
@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
|
65
active-object/pom.xml
Normal file
65
active-object/pom.xml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?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>
|
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,12 +21,17 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.iluwatar.business.delegate;
|
package com.iluwatar.activeobject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumeration for service types.
|
* An implementation of the ActiveCreature class.
|
||||||
|
* @author Noam Greenshtain
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public enum ServiceType {
|
public class Orc extends ActiveCreature {
|
||||||
|
|
||||||
|
public Orc(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
EJB, JMS
|
|
||||||
}
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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[]{}));
|
||||||
|
}
|
||||||
|
}
|
@ -23,17 +23,15 @@
|
|||||||
|
|
||||||
package com.iluwatar.acyclicvisitor;
|
package com.iluwatar.acyclicvisitor;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConfigureForDosVisitor class implements both zoom's and hayes' visit method for Dos
|
* ConfigureForDosVisitor class implements both zoom's and hayes' visit method for Dos
|
||||||
* manufacturer.
|
* manufacturer.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class ConfigureForDosVisitor implements AllModemVisitor {
|
public class ConfigureForDosVisitor implements AllModemVisitor {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForDosVisitor.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(Hayes hayes) {
|
public void visit(Hayes hayes) {
|
||||||
LOGGER.info(hayes + " used with Dos configurator.");
|
LOGGER.info(hayes + " used with Dos configurator.");
|
||||||
|
@ -23,17 +23,15 @@
|
|||||||
|
|
||||||
package com.iluwatar.acyclicvisitor;
|
package com.iluwatar.acyclicvisitor;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConfigureForUnixVisitor class implements zoom's visit method for Unix manufacturer, unlike
|
* ConfigureForUnixVisitor class implements zoom's visit method for Unix manufacturer, unlike
|
||||||
* traditional visitor pattern, this class may selectively implement visit for other modems.
|
* traditional visitor pattern, this class may selectively implement visit for other modems.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class ConfigureForUnixVisitor implements ZoomVisitor {
|
public class ConfigureForUnixVisitor implements ZoomVisitor {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForUnixVisitor.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(Zoom zoom) {
|
public void visit(Zoom zoom) {
|
||||||
LOGGER.info(zoom + " used with Unix configurator.");
|
LOGGER.info(zoom + " used with Unix configurator.");
|
||||||
|
@ -23,16 +23,14 @@
|
|||||||
|
|
||||||
package com.iluwatar.acyclicvisitor;
|
package com.iluwatar.acyclicvisitor;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hayes class implements its accept method.
|
* Hayes class implements its accept method.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class Hayes extends Modem {
|
public class Hayes extends Modem {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForDosVisitor.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accepts all visitors but honors only HayesVisitor.
|
* Accepts all visitors but honors only HayesVisitor.
|
||||||
*/
|
*/
|
||||||
|
@ -23,16 +23,14 @@
|
|||||||
|
|
||||||
package com.iluwatar.acyclicvisitor;
|
package com.iluwatar.acyclicvisitor;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom class implements its accept method.
|
* Zoom class implements its accept method.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class Zoom extends Modem {
|
public class Zoom extends Modem {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForDosVisitor.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accepts all visitors but honors only ZoomVisitor.
|
* Accepts all visitors but honors only ZoomVisitor.
|
||||||
*/
|
*/
|
||||||
|
@ -35,12 +35,12 @@ import uk.org.lidalia.slf4jtest.TestLoggerFactory;
|
|||||||
/**
|
/**
|
||||||
* ConfigureForDosVisitor test class
|
* ConfigureForDosVisitor test class
|
||||||
*/
|
*/
|
||||||
public class ConfigureForDosVisitorTest {
|
class ConfigureForDosVisitorTest {
|
||||||
|
|
||||||
private final TestLogger logger = TestLoggerFactory.getTestLogger(ConfigureForDosVisitor.class);
|
private final TestLogger logger = TestLoggerFactory.getTestLogger(ConfigureForDosVisitor.class);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVisitForZoom() {
|
void testVisitForZoom() {
|
||||||
var conDos = new ConfigureForDosVisitor();
|
var conDos = new ConfigureForDosVisitor();
|
||||||
var zoom = new Zoom();
|
var zoom = new Zoom();
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ public class ConfigureForDosVisitorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVisitForHayes() {
|
void testVisitForHayes() {
|
||||||
var conDos = new ConfigureForDosVisitor();
|
var conDos = new ConfigureForDosVisitor();
|
||||||
var hayes = new Hayes();
|
var hayes = new Hayes();
|
||||||
|
|
||||||
|
@ -23,20 +23,19 @@
|
|||||||
|
|
||||||
package com.iluwatar.acyclicvisitor;
|
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.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.groups.Tuple.tuple;
|
import static org.assertj.core.groups.Tuple.tuple;
|
||||||
import static uk.org.lidalia.slf4jext.Level.INFO;
|
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
|
* ConfigureForUnixVisitor test class
|
||||||
*/
|
*/
|
||||||
public class ConfigureForUnixVisitorTest {
|
class ConfigureForUnixVisitorTest {
|
||||||
|
|
||||||
private static final TestLogger LOGGER = TestLoggerFactory.getTestLogger(ConfigureForUnixVisitor.class);
|
private static final TestLogger LOGGER = TestLoggerFactory.getTestLogger(ConfigureForUnixVisitor.class);
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ public class ConfigureForUnixVisitorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVisitForZoom() {
|
void testVisitForZoom() {
|
||||||
var conUnix = new ConfigureForUnixVisitor();
|
var conUnix = new ConfigureForUnixVisitor();
|
||||||
var zoom = new Zoom();
|
var zoom = new Zoom();
|
||||||
|
|
||||||
|
@ -23,29 +23,27 @@
|
|||||||
|
|
||||||
package com.iluwatar.acyclicvisitor;
|
package com.iluwatar.acyclicvisitor;
|
||||||
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hayes test class
|
* Hayes test class
|
||||||
*/
|
*/
|
||||||
public class HayesTest {
|
class HayesTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAcceptForDos() {
|
void testAcceptForDos() {
|
||||||
var hayes = new Hayes();
|
var hayes = new Hayes();
|
||||||
var mockVisitor = mock(ConfigureForDosVisitor.class);
|
var mockVisitor = mock(ConfigureForDosVisitor.class);
|
||||||
|
|
||||||
hayes.accept(mockVisitor);
|
hayes.accept(mockVisitor);
|
||||||
verify((HayesVisitor)mockVisitor).visit(eq(hayes));
|
verify((HayesVisitor) mockVisitor).visit(eq(hayes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAcceptForUnix() {
|
void testAcceptForUnix() {
|
||||||
var hayes = new Hayes();
|
var hayes = new Hayes();
|
||||||
var mockVisitor = mock(ConfigureForUnixVisitor.class);
|
var mockVisitor = mock(ConfigureForUnixVisitor.class);
|
||||||
|
|
||||||
|
@ -24,32 +24,32 @@
|
|||||||
package com.iluwatar.acyclicvisitor;
|
package com.iluwatar.acyclicvisitor;
|
||||||
|
|
||||||
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom test class
|
* Zoom test class
|
||||||
*/
|
*/
|
||||||
public class ZoomTest {
|
class ZoomTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAcceptForDos() {
|
void testAcceptForDos() {
|
||||||
var zoom = new Zoom();
|
var zoom = new Zoom();
|
||||||
var mockVisitor = mock(ConfigureForDosVisitor.class);
|
var mockVisitor = mock(ConfigureForDosVisitor.class);
|
||||||
|
|
||||||
zoom.accept(mockVisitor);
|
zoom.accept(mockVisitor);
|
||||||
verify((ZoomVisitor)mockVisitor).visit(eq(zoom));
|
verify((ZoomVisitor) mockVisitor).visit(eq(zoom));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAcceptForUnix() {
|
void testAcceptForUnix() {
|
||||||
var zoom = new Zoom();
|
var zoom = new Zoom();
|
||||||
var mockVisitor = mock(ConfigureForUnixVisitor.class);
|
var mockVisitor = mock(ConfigureForUnixVisitor.class);
|
||||||
|
|
||||||
zoom.accept(mockVisitor);
|
zoom.accept(mockVisitor);
|
||||||
verify((ZoomVisitor)mockVisitor).visit(eq(zoom));
|
verify((ZoomVisitor) mockVisitor).visit(eq(zoom));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,8 @@ public interface RowingBoat {
|
|||||||
void row();
|
void row();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class FishingBoat {
|
public class FishingBoat {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoat.class);
|
|
||||||
public void sail() {
|
public void sail() {
|
||||||
LOGGER.info("The fishing boat is sailing");
|
LOGGER.info("The fishing boat is sailing");
|
||||||
}
|
}
|
||||||
@ -70,10 +70,9 @@ 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.
|
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
|
```java
|
||||||
|
@Slf4j
|
||||||
public class FishingBoatAdapter implements RowingBoat {
|
public class FishingBoatAdapter implements RowingBoat {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoatAdapter.class);
|
|
||||||
|
|
||||||
private final FishingBoat boat;
|
private final FishingBoat boat;
|
||||||
|
|
||||||
public FishingBoatAdapter() {
|
public FishingBoatAdapter() {
|
||||||
|
@ -23,24 +23,20 @@
|
|||||||
|
|
||||||
package com.iluwatar.adapter;
|
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.
|
* The Captain uses {@link RowingBoat} to sail. <br> This is the client in the pattern.
|
||||||
*/
|
*/
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
public final class Captain {
|
public final class Captain {
|
||||||
|
|
||||||
private RowingBoat rowingBoat;
|
private RowingBoat rowingBoat;
|
||||||
|
|
||||||
public Captain() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Captain(final RowingBoat boat) {
|
|
||||||
this.rowingBoat = boat;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setRowingBoat(final RowingBoat boat) {
|
|
||||||
this.rowingBoat = boat;
|
|
||||||
}
|
|
||||||
|
|
||||||
void row() {
|
void row() {
|
||||||
rowingBoat.row();
|
rowingBoat.row();
|
||||||
}
|
}
|
||||||
|
@ -23,18 +23,15 @@
|
|||||||
|
|
||||||
package com.iluwatar.adapter;
|
package com.iluwatar.adapter;
|
||||||
|
|
||||||
import static org.slf4j.LoggerFactory.getLogger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device class (adaptee in the pattern). We want to reuse this class. Fishing boat moves by
|
* Device class (adaptee in the pattern). We want to reuse this class. Fishing boat moves by
|
||||||
* sailing.
|
* sailing.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
final class FishingBoat {
|
final class FishingBoat {
|
||||||
|
|
||||||
private static final Logger LOGGER = getLogger(FishingBoat.class);
|
|
||||||
|
|
||||||
void sail() {
|
void sail() {
|
||||||
LOGGER.info("The fishing boat is sailing");
|
LOGGER.info("The fishing boat is sailing");
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,7 @@ package com.iluwatar.adapter;
|
|||||||
*/
|
*/
|
||||||
public class FishingBoatAdapter implements RowingBoat {
|
public class FishingBoatAdapter implements RowingBoat {
|
||||||
|
|
||||||
private final FishingBoat boat;
|
private final FishingBoat boat = new FishingBoat();
|
||||||
|
|
||||||
public FishingBoatAdapter() {
|
|
||||||
boat = new FishingBoat();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void row() {
|
public final void row() {
|
||||||
boat.sail();
|
boat.sail();
|
||||||
|
@ -23,18 +23,19 @@
|
|||||||
|
|
||||||
package com.iluwatar.adapter;
|
package com.iluwatar.adapter;
|
||||||
|
|
||||||
import static org.mockito.Mockito.spy;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import static org.mockito.Mockito.verify;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class
|
* Test class
|
||||||
*/
|
*/
|
||||||
public class AdapterPatternTest {
|
class AdapterPatternTest {
|
||||||
|
|
||||||
private Map<String, Object> beans;
|
private Map<String, Object> beans;
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ public class AdapterPatternTest {
|
|||||||
* by the client ({@link Captain} ).
|
* by the client ({@link Captain} ).
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testAdapter() {
|
void testAdapter() {
|
||||||
var captain = (Captain) beans.get(ROWING_BEAN);
|
var captain = (Captain) beans.get(ROWING_BEAN);
|
||||||
|
|
||||||
// when captain moves
|
// when captain moves
|
||||||
|
@ -26,8 +26,7 @@ package com.iluwatar.aggregator.microservices;
|
|||||||
import static java.util.Objects.requireNonNullElse;
|
import static java.util.Objects.requireNonNullElse;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,20 +36,18 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
@RestController
|
@RestController
|
||||||
public class Aggregator {
|
public class Aggregator {
|
||||||
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ProductInformationClient informationClient;
|
private ProductInformationClient informationClient;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ProductInventoryClient inventoryClient;
|
private ProductInventoryClient inventoryClient;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves product data.
|
* Retrieves product data.
|
||||||
*
|
*
|
||||||
* @return a Product.
|
* @return a Product.
|
||||||
*/
|
*/
|
||||||
@RequestMapping(path = "/product", method = RequestMethod.GET)
|
@GetMapping("/product")
|
||||||
public Product getProduct() {
|
public Product getProduct() {
|
||||||
|
|
||||||
var product = new Product();
|
var product = new Product();
|
||||||
|
@ -23,9 +23,14 @@
|
|||||||
|
|
||||||
package com.iluwatar.aggregator.microservices;
|
package com.iluwatar.aggregator.microservices;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates all the data for a Product that clients will request.
|
* Encapsulates all the data for a Product that clients will request.
|
||||||
*/
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
public class Product {
|
public class Product {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,20 +44,4 @@ public class Product {
|
|||||||
*/
|
*/
|
||||||
private int productInventories;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,18 +28,16 @@ import java.net.URI;
|
|||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter to communicate with information micro-service.
|
* An adapter to communicate with information micro-service.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class ProductInformationClientImpl implements ProductInformationClient {
|
public class ProductInformationClientImpl implements ProductInformationClient {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ProductInformationClientImpl.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getProductTitle() {
|
public String getProductTitle() {
|
||||||
var request = HttpRequest.newBuilder()
|
var request = HttpRequest.newBuilder()
|
||||||
@ -54,6 +52,7 @@ public class ProductInformationClientImpl implements ProductInformationClient {
|
|||||||
LOGGER.error("IOException Occurred", ioe);
|
LOGGER.error("IOException Occurred", ioe);
|
||||||
} catch (InterruptedException ie) {
|
} catch (InterruptedException ie) {
|
||||||
LOGGER.error("InterruptedException Occurred", ie);
|
LOGGER.error("InterruptedException Occurred", ie);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -28,18 +28,16 @@ import java.net.URI;
|
|||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter to communicate with inventory micro-service.
|
* An adapter to communicate with inventory micro-service.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class ProductInventoryClientImpl implements ProductInventoryClient {
|
public class ProductInventoryClientImpl implements ProductInventoryClient {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ProductInventoryClientImpl.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getProductInventories() {
|
public Integer getProductInventories() {
|
||||||
var response = "";
|
var response = "";
|
||||||
@ -56,6 +54,7 @@ public class ProductInventoryClientImpl implements ProductInventoryClient {
|
|||||||
LOGGER.error("IOException Occurred", ioe);
|
LOGGER.error("IOException Occurred", ioe);
|
||||||
} catch (InterruptedException ie) {
|
} catch (InterruptedException ie) {
|
||||||
LOGGER.error("InterruptedException Occurred", ie);
|
LOGGER.error("InterruptedException Occurred", ie);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
if ("".equalsIgnoreCase(response)) {
|
if ("".equalsIgnoreCase(response)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -23,19 +23,19 @@
|
|||||||
|
|
||||||
package com.iluwatar.aggregator.microservices;
|
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test Aggregation of domain objects
|
* Test Aggregation of domain objects
|
||||||
*/
|
*/
|
||||||
public class AggregatorTest {
|
class AggregatorTest {
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private Aggregator aggregator;
|
private Aggregator aggregator;
|
||||||
@ -55,7 +55,7 @@ public class AggregatorTest {
|
|||||||
* Tests getting the data for a desktop client
|
* Tests getting the data for a desktop client
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetProduct() {
|
void testGetProduct() {
|
||||||
var title = "The Product Title.";
|
var title = "The Product Title.";
|
||||||
var inventories = 5;
|
var inventories = 5;
|
||||||
|
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
|
|
||||||
package com.iluwatar.information.microservice;
|
package com.iluwatar.information.microservice;
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +37,7 @@ public class InformationController {
|
|||||||
*
|
*
|
||||||
* @return product inventory.
|
* @return product inventory.
|
||||||
*/
|
*/
|
||||||
@RequestMapping(value = "/information", method = RequestMethod.GET)
|
@GetMapping("/information")
|
||||||
public String getProductTitle() {
|
public String getProductTitle() {
|
||||||
return "The Product Title.";
|
return "The Product Title.";
|
||||||
}
|
}
|
||||||
|
@ -23,17 +23,17 @@
|
|||||||
|
|
||||||
package com.iluwatar.information.microservice;
|
package com.iluwatar.information.microservice;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for Information Rest Controller
|
* Test for Information Rest Controller
|
||||||
*/
|
*/
|
||||||
public class InformationControllerTest {
|
class InformationControllerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldGetProductTitle() {
|
void shouldGetProductTitle() {
|
||||||
var infoController = new InformationController();
|
var infoController = new InformationController();
|
||||||
var title = infoController.getProductTitle();
|
var title = infoController.getProductTitle();
|
||||||
assertEquals("The Product Title.", title);
|
assertEquals("The Product Title.", title);
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
|
|
||||||
package com.iluwatar.inventory.microservice;
|
package com.iluwatar.inventory.microservice;
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +37,7 @@ public class InventoryController {
|
|||||||
*
|
*
|
||||||
* @return product inventory.
|
* @return product inventory.
|
||||||
*/
|
*/
|
||||||
@RequestMapping(value = "/inventories", method = RequestMethod.GET)
|
@GetMapping("/inventories")
|
||||||
public int getProductInventories() {
|
public int getProductInventories() {
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
@ -23,16 +23,17 @@
|
|||||||
|
|
||||||
package com.iluwatar.inventory.microservice;
|
package com.iluwatar.inventory.microservice;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test Inventory Rest Controller
|
* Test Inventory Rest Controller
|
||||||
*/
|
*/
|
||||||
public class InventoryControllerTest {
|
class InventoryControllerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetProductInventories() {
|
void testGetProductInventories() {
|
||||||
var inventoryController = new InventoryController();
|
var inventoryController = new InventoryController();
|
||||||
var numberOfInventories = inventoryController.getProductInventories();
|
var numberOfInventories = inventoryController.getProductInventories();
|
||||||
assertEquals(5, numberOfInventories);
|
assertEquals(5, numberOfInventories);
|
||||||
|
@ -48,9 +48,8 @@ interface RemoteServiceInterface {
|
|||||||
A remote services represented as a singleton.
|
A remote services represented as a singleton.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
@Slf4j
|
||||||
public class RemoteService implements RemoteServiceInterface {
|
public class RemoteService implements RemoteServiceInterface {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);
|
|
||||||
private static RemoteService service = null;
|
private static RemoteService service = null;
|
||||||
|
|
||||||
static synchronized RemoteService getRemoteService() {
|
static synchronized RemoteService getRemoteService() {
|
||||||
@ -80,9 +79,8 @@ public class RemoteService implements RemoteServiceInterface {
|
|||||||
A service ambassador adding additional features such as logging, latency checks
|
A service ambassador adding additional features such as logging, latency checks
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
@Slf4j
|
||||||
public class ServiceAmbassador implements RemoteServiceInterface {
|
public class ServiceAmbassador implements RemoteServiceInterface {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAmbassador.class);
|
|
||||||
private static final int RETRIES = 3;
|
private static final int RETRIES = 3;
|
||||||
private static final int DELAY_MS = 3000;
|
private static final int DELAY_MS = 3000;
|
||||||
|
|
||||||
@ -132,9 +130,8 @@ public class ServiceAmbassador implements RemoteServiceInterface {
|
|||||||
A client has a local service ambassador used to interact with the remote service:
|
A client has a local service ambassador used to interact with the remote service:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
@Slf4j
|
||||||
public class Client {
|
public class Client {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
|
|
||||||
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
|
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
|
||||||
|
|
||||||
long useService(int value) {
|
long useService(int value) {
|
||||||
|
@ -23,20 +23,19 @@
|
|||||||
|
|
||||||
package com.iluwatar.ambassador;
|
package com.iluwatar.ambassador;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple Client.
|
* A simple Client.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class Client {
|
public class Client {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
|
|
||||||
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
|
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
|
||||||
|
|
||||||
long useService(int value) {
|
long useService(int value) {
|
||||||
var result = serviceAmbassador.doRemoteFunction(value);
|
var result = serviceAmbassador.doRemoteFunction(value);
|
||||||
LOGGER.info("Service result: " + result);
|
LOGGER.info("Service result: {}", result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,15 +26,14 @@ package com.iluwatar.ambassador;
|
|||||||
import static java.lang.Thread.sleep;
|
import static java.lang.Thread.sleep;
|
||||||
|
|
||||||
import com.iluwatar.ambassador.util.RandomProvider;
|
import com.iluwatar.ambassador.util.RandomProvider;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A remote legacy application represented by a Singleton implementation.
|
* A remote legacy application represented by a Singleton implementation.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class RemoteService implements RemoteServiceInterface {
|
public class RemoteService implements RemoteServiceInterface {
|
||||||
private static final int THRESHOLD = 200;
|
private static final int THRESHOLD = 200;
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);
|
|
||||||
private static RemoteService service = null;
|
private static RemoteService service = null;
|
||||||
private final RandomProvider randomProvider;
|
private final RandomProvider randomProvider;
|
||||||
|
|
||||||
@ -73,6 +72,7 @@ public class RemoteService implements RemoteServiceInterface {
|
|||||||
sleep(waitTime);
|
sleep(waitTime);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOGGER.error("Thread sleep state interrupted", e);
|
LOGGER.error("Thread sleep state interrupted", e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
return waitTime <= THRESHOLD ? value * 10
|
return waitTime <= THRESHOLD ? value * 10
|
||||||
: RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue();
|
: RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue();
|
||||||
|
@ -33,8 +33,7 @@ package com.iluwatar.ambassador;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public enum RemoteServiceStatus {
|
public enum RemoteServiceStatus {
|
||||||
FAILURE(-1)
|
FAILURE(-1);
|
||||||
;
|
|
||||||
|
|
||||||
private final long remoteServiceStatusValue;
|
private final long remoteServiceStatusValue;
|
||||||
|
|
||||||
|
@ -26,17 +26,16 @@ package com.iluwatar.ambassador;
|
|||||||
import static com.iluwatar.ambassador.RemoteServiceStatus.FAILURE;
|
import static com.iluwatar.ambassador.RemoteServiceStatus.FAILURE;
|
||||||
import static java.lang.Thread.sleep;
|
import static java.lang.Thread.sleep;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServiceAmbassador provides an interface for a ({@link Client}) to access ({@link RemoteService}).
|
* 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
|
* 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.
|
* add stress to the remote service when connectivity issues occur.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class ServiceAmbassador implements RemoteServiceInterface {
|
public class ServiceAmbassador implements RemoteServiceInterface {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAmbassador.class);
|
|
||||||
private static final int RETRIES = 3;
|
private static final int RETRIES = 3;
|
||||||
private static final int DELAY_MS = 3000;
|
private static final int DELAY_MS = 3000;
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ public class ServiceAmbassador implements RemoteServiceInterface {
|
|||||||
var result = RemoteService.getRemoteService().doRemoteFunction(value);
|
var result = RemoteService.getRemoteService().doRemoteFunction(value);
|
||||||
var timeTaken = System.currentTimeMillis() - startTime;
|
var timeTaken = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
LOGGER.info("Time taken (ms): " + timeTaken);
|
LOGGER.info("Time taken (ms): {}", timeTaken);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,12 +66,13 @@ public class ServiceAmbassador implements RemoteServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((result = checkLatency(value)) == FAILURE.getRemoteServiceStatusValue()) {
|
if ((result = checkLatency(value)) == FAILURE.getRemoteServiceStatusValue()) {
|
||||||
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
|
LOGGER.info("Failed to reach remote: ({})", i + 1);
|
||||||
retries++;
|
retries++;
|
||||||
try {
|
try {
|
||||||
sleep(DELAY_MS);
|
sleep(DELAY_MS);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOGGER.error("Thread sleep state interrupted", e);
|
LOGGER.error("Thread sleep state interrupted", e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
@ -24,8 +24,7 @@
|
|||||||
package com.iluwatar.api.gateway;
|
package com.iluwatar.api.gateway;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +44,7 @@ public class ApiGateway {
|
|||||||
*
|
*
|
||||||
* @return Product information for clients on a desktop
|
* @return Product information for clients on a desktop
|
||||||
*/
|
*/
|
||||||
@RequestMapping(path = "/desktop", method = RequestMethod.GET)
|
@GetMapping("/desktop")
|
||||||
public DesktopProduct getProductDesktop() {
|
public DesktopProduct getProductDesktop() {
|
||||||
var desktopProduct = new DesktopProduct();
|
var desktopProduct = new DesktopProduct();
|
||||||
desktopProduct.setImagePath(imageClient.getImagePath());
|
desktopProduct.setImagePath(imageClient.getImagePath());
|
||||||
@ -58,7 +57,7 @@ public class ApiGateway {
|
|||||||
*
|
*
|
||||||
* @return Product information for clients on a mobile device
|
* @return Product information for clients on a mobile device
|
||||||
*/
|
*/
|
||||||
@RequestMapping(path = "/mobile", method = RequestMethod.GET)
|
@GetMapping("/mobile")
|
||||||
public MobileProduct getProductMobile() {
|
public MobileProduct getProductMobile() {
|
||||||
var mobileProduct = new MobileProduct();
|
var mobileProduct = new MobileProduct();
|
||||||
mobileProduct.setPrice(priceClient.getPrice());
|
mobileProduct.setPrice(priceClient.getPrice());
|
||||||
|
@ -23,10 +23,16 @@
|
|||||||
|
|
||||||
package com.iluwatar.api.gateway;
|
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.
|
* Encapsulates all of the information that a desktop client needs to display a product.
|
||||||
*/
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
public class DesktopProduct {
|
public class DesktopProduct {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The price of the product.
|
* The price of the product.
|
||||||
*/
|
*/
|
||||||
@ -37,19 +43,4 @@ public class DesktopProduct {
|
|||||||
*/
|
*/
|
||||||
private String imagePath;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,24 +23,21 @@
|
|||||||
|
|
||||||
package com.iluwatar.api.gateway;
|
package com.iluwatar.api.gateway;
|
||||||
|
|
||||||
import static org.slf4j.LoggerFactory.getLogger;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.net.http.HttpResponse.BodyHandlers;
|
import java.net.http.HttpResponse.BodyHandlers;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter to communicate with the Image microservice.
|
* An adapter to communicate with the Image microservice.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class ImageClientImpl implements ImageClient {
|
public class ImageClientImpl implements ImageClient {
|
||||||
private static final Logger LOGGER = getLogger(ImageClientImpl.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a simple HTTP Get request to the Image microservice.
|
* Makes a simple HTTP Get request to the Image microservice.
|
||||||
@ -60,8 +57,11 @@ public class ImageClientImpl implements ImageClient {
|
|||||||
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
|
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
|
||||||
logResponse(httpResponse);
|
logResponse(httpResponse);
|
||||||
return httpResponse.body();
|
return httpResponse.body();
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException ioe) {
|
||||||
LOGGER.error("Failure occurred while getting image path", e);
|
LOGGER.error("Failure occurred while getting image path", ioe);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
LOGGER.error("Failure occurred while getting image path", ie);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -23,20 +23,17 @@
|
|||||||
|
|
||||||
package com.iluwatar.api.gateway;
|
package com.iluwatar.api.gateway;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates all of the information that mobile client needs to display a product.
|
* Encapsulates all of the information that mobile client needs to display a product.
|
||||||
*/
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
public class MobileProduct {
|
public class MobileProduct {
|
||||||
/**
|
/**
|
||||||
* The price of the product.
|
* The price of the product.
|
||||||
*/
|
*/
|
||||||
private String price;
|
private String price;
|
||||||
|
|
||||||
public String getPrice() {
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrice(String price) {
|
|
||||||
this.price = price;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,25 +23,22 @@
|
|||||||
|
|
||||||
package com.iluwatar.api.gateway;
|
package com.iluwatar.api.gateway;
|
||||||
|
|
||||||
import static org.slf4j.LoggerFactory.getLogger;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.net.http.HttpResponse.BodyHandlers;
|
import java.net.http.HttpResponse.BodyHandlers;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter to communicate with the Price microservice.
|
* An adapter to communicate with the Price microservice.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class PriceClientImpl implements PriceClient {
|
public class PriceClientImpl implements PriceClient {
|
||||||
private static final Logger LOGGER = getLogger(PriceClientImpl.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a simple HTTP Get request to the Price microservice.
|
* Makes a simple HTTP Get request to the Price microservice.
|
||||||
@ -61,8 +58,11 @@ public class PriceClientImpl implements PriceClient {
|
|||||||
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
|
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
|
||||||
logResponse(httpResponse);
|
logResponse(httpResponse);
|
||||||
return httpResponse.body();
|
return httpResponse.body();
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException e) {
|
||||||
LOGGER.error("Failure occurred while getting price info", 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;
|
return null;
|
||||||
|
@ -35,7 +35,7 @@ import org.mockito.MockitoAnnotations;
|
|||||||
/**
|
/**
|
||||||
* Test API Gateway Pattern
|
* Test API Gateway Pattern
|
||||||
*/
|
*/
|
||||||
public class ApiGatewayTest {
|
class ApiGatewayTest {
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private ApiGateway apiGateway;
|
private ApiGateway apiGateway;
|
||||||
@ -55,7 +55,7 @@ public class ApiGatewayTest {
|
|||||||
* Tests getting the data for a desktop client
|
* Tests getting the data for a desktop client
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetProductDesktop() {
|
void testGetProductDesktop() {
|
||||||
var imagePath = "/product-image.png";
|
var imagePath = "/product-image.png";
|
||||||
var price = "20";
|
var price = "20";
|
||||||
when(imageClient.getImagePath()).thenReturn(imagePath);
|
when(imageClient.getImagePath()).thenReturn(imagePath);
|
||||||
@ -71,7 +71,7 @@ public class ApiGatewayTest {
|
|||||||
* Tests getting the data for a mobile client
|
* Tests getting the data for a mobile client
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetProductMobile() {
|
void testGetProductMobile() {
|
||||||
var price = "20";
|
var price = "20";
|
||||||
when(priceClient.getPrice()).thenReturn(price);
|
when(priceClient.getPrice()).thenReturn(price);
|
||||||
|
|
||||||
|
@ -23,27 +23,24 @@
|
|||||||
|
|
||||||
package com.iluwatar.image.microservice;
|
package com.iluwatar.image.microservice;
|
||||||
|
|
||||||
import static org.slf4j.LoggerFactory.getLogger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
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;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exposes the Image microservice's endpoints.
|
* Exposes the Image microservice's endpoints.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
public class ImageController {
|
public class ImageController {
|
||||||
private static final Logger LOGGER = getLogger(ImageController.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An endpoint for a user to retrieve an image path.
|
* An endpoint for a user to retrieve an image path.
|
||||||
*
|
*
|
||||||
* @return An image path
|
* @return An image path
|
||||||
*/
|
*/
|
||||||
@RequestMapping(value = "/image-path", method = RequestMethod.GET)
|
@GetMapping("/image-path")
|
||||||
public String getImagePath() {
|
public String getImagePath() {
|
||||||
LOGGER.info("Successfully found image path");
|
LOGGER.info("Successfully found image path");
|
||||||
return "/product-image.png";
|
return "/product-image.png";
|
||||||
|
@ -23,16 +23,17 @@
|
|||||||
|
|
||||||
package com.iluwatar.image.microservice;
|
package com.iluwatar.image.microservice;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for Image Rest Controller
|
* Test for Image Rest Controller
|
||||||
*/
|
*/
|
||||||
public class ImageControllerTest {
|
class ImageControllerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetImagePath() {
|
void testGetImagePath() {
|
||||||
var imageController = new ImageController();
|
var imageController = new ImageController();
|
||||||
var imagePath = imageController.getImagePath();
|
var imagePath = imageController.getImagePath();
|
||||||
assertEquals("/product-image.png", imagePath);
|
assertEquals("/product-image.png", imagePath);
|
||||||
|
@ -23,27 +23,24 @@
|
|||||||
|
|
||||||
package com.iluwatar.price.microservice;
|
package com.iluwatar.price.microservice;
|
||||||
|
|
||||||
import static org.slf4j.LoggerFactory.getLogger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
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;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exposes the Price microservice's endpoints.
|
* Exposes the Price microservice's endpoints.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
public class PriceController {
|
public class PriceController {
|
||||||
private static final Logger LOGGER = getLogger(PriceController.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An endpoint for a user to retrieve a product's price.
|
* An endpoint for a user to retrieve a product's price.
|
||||||
*
|
*
|
||||||
* @return A product's price
|
* @return A product's price
|
||||||
*/
|
*/
|
||||||
@RequestMapping(value = "/price", method = RequestMethod.GET)
|
@GetMapping("/price")
|
||||||
public String getPrice() {
|
public String getPrice() {
|
||||||
LOGGER.info("Successfully found price info");
|
LOGGER.info("Successfully found price info");
|
||||||
return "20";
|
return "20";
|
||||||
|
@ -23,16 +23,17 @@
|
|||||||
|
|
||||||
package com.iluwatar.price.microservice;
|
package com.iluwatar.price.microservice;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for Price Rest Controller
|
* Test for Price Rest Controller
|
||||||
*/
|
*/
|
||||||
public class PriceControllerTest {
|
class PriceControllerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testgetPrice() {
|
void testgetPrice() {
|
||||||
var priceController = new PriceController();
|
var priceController = new PriceController();
|
||||||
var price = priceController.getPrice();
|
var price = priceController.getPrice();
|
||||||
assertEquals("20", price);
|
assertEquals("20", price);
|
||||||
|
@ -81,10 +81,10 @@ Then we write our unit tests according to Arrange/Act/Assert pattern. Notice the
|
|||||||
separated steps for each unit test.
|
separated steps for each unit test.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class CashAAATest {
|
class CashAAATest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPlus() {
|
void testPlus() {
|
||||||
//Arrange
|
//Arrange
|
||||||
var cash = new Cash(3);
|
var cash = new Cash(3);
|
||||||
//Act
|
//Act
|
||||||
@ -94,7 +94,7 @@ public class CashAAATest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMinus() {
|
void testMinus() {
|
||||||
//Arrange
|
//Arrange
|
||||||
var cash = new Cash(8);
|
var cash = new Cash(8);
|
||||||
//Act
|
//Act
|
||||||
@ -105,7 +105,7 @@ public class CashAAATest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInsufficientMinus() {
|
void testInsufficientMinus() {
|
||||||
//Arrange
|
//Arrange
|
||||||
var cash = new Cash(1);
|
var cash = new Cash(1);
|
||||||
//Act
|
//Act
|
||||||
@ -116,7 +116,7 @@ public class CashAAATest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdate() {
|
void testUpdate() {
|
||||||
//Arrange
|
//Arrange
|
||||||
var cash = new Cash(5);
|
var cash = new Cash(5);
|
||||||
//Act
|
//Act
|
||||||
|
@ -23,18 +23,17 @@
|
|||||||
|
|
||||||
package com.iluwatar.arrangeactassert;
|
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})
|
* 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.
|
* object for plus, minus and counting amount.
|
||||||
*/
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
public class Cash {
|
public class Cash {
|
||||||
|
|
||||||
private int amount;
|
private int amount;
|
||||||
|
|
||||||
Cash(int amount) {
|
|
||||||
this.amount = amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
//plus
|
//plus
|
||||||
void plus(int addend) {
|
void plus(int addend) {
|
||||||
amount += addend;
|
amount += addend;
|
||||||
|
@ -9,21 +9,160 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Asynchronous method invocation is pattern where the calling thread
|
|
||||||
|
Asynchronous method invocation is a pattern where the calling thread
|
||||||
is not blocked while waiting results of tasks. The pattern provides parallel
|
is not blocked while waiting results of tasks. The pattern provides parallel
|
||||||
processing of multiple independent tasks and retrieving the results via
|
processing of multiple independent tasks and retrieving the results via
|
||||||
callbacks or waiting until everything is done.
|
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
|
# Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
Use async method invocation pattern when
|
|
||||||
|
Use the async method invocation pattern when
|
||||||
|
|
||||||
* You have multiple independent tasks that can run in parallel
|
* You have multiple independent tasks that can run in parallel
|
||||||
* You need to improve the performance of a group of sequential tasks
|
* You need to improve the performance of a group of sequential tasks
|
||||||
* You have limited amount of processing capacity or long running tasks and the
|
* You have a limited amount of processing capacity or long-running tasks and the caller should not wait for the tasks to be ready
|
||||||
caller should not wait the tasks to be ready
|
|
||||||
|
|
||||||
## Real world examples
|
## Real world examples
|
||||||
|
|
||||||
|
@ -24,14 +24,15 @@
|
|||||||
package com.iluwatar.async.method.invocation;
|
package com.iluwatar.async.method.invocation;
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This application demonstrates the async method invocation pattern. Key parts of the pattern are
|
* In this example, we are launching space rockets and deploying lunar rovers.
|
||||||
* <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
|
* <p>The application demonstrates the async method invocation pattern. The key parts of the
|
||||||
* <code>AsyncExecutor</code> that manages the execution of the async tasks.
|
* 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
|
* <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
|
* tasks with variable durations and then continues its own work. When the main thread has done it's
|
||||||
@ -55,10 +56,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @see java.util.concurrent.CompletableFuture
|
* @see java.util.concurrent.CompletableFuture
|
||||||
* @see java.util.concurrent.ExecutorService
|
* @see java.util.concurrent.ExecutorService
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point.
|
* Program entry point.
|
||||||
*/
|
*/
|
||||||
@ -70,13 +70,14 @@ public class App {
|
|||||||
final var asyncResult1 = executor.startProcess(lazyval(10, 500));
|
final var asyncResult1 = executor.startProcess(lazyval(10, 500));
|
||||||
final var asyncResult2 = executor.startProcess(lazyval("test", 300));
|
final var asyncResult2 = executor.startProcess(lazyval("test", 300));
|
||||||
final var asyncResult3 = executor.startProcess(lazyval(50L, 700));
|
final var asyncResult3 = executor.startProcess(lazyval(50L, 700));
|
||||||
final var asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4"));
|
final var asyncResult4 = executor.startProcess(lazyval(20, 400),
|
||||||
|
callback("Deploying lunar rover"));
|
||||||
final var asyncResult5 =
|
final var asyncResult5 =
|
||||||
executor.startProcess(lazyval("callback", 600), callback("Callback result 5"));
|
executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
|
||||||
|
|
||||||
// emulate processing in the current thread while async tasks are running in their own threads
|
// emulate processing in the current thread while async tasks are running in their own threads
|
||||||
Thread.sleep(350); // Oh boy I'm working hard here
|
Thread.sleep(350); // Oh boy, we are working hard here
|
||||||
log("Some hard work done");
|
log("Mission command is sipping coffee");
|
||||||
|
|
||||||
// wait for completion of the tasks
|
// wait for completion of the tasks
|
||||||
final var result1 = executor.endProcess(asyncResult1);
|
final var result1 = executor.endProcess(asyncResult1);
|
||||||
@ -86,9 +87,9 @@ public class App {
|
|||||||
asyncResult5.await();
|
asyncResult5.await();
|
||||||
|
|
||||||
// log the results of the tasks, callbacks log immediately when complete
|
// log the results of the tasks, callbacks log immediately when complete
|
||||||
log("Result 1: " + result1);
|
log("Space rocket <" + result1 + "> launch complete");
|
||||||
log("Result 2: " + result2);
|
log("Space rocket <" + result2 + "> launch complete");
|
||||||
log("Result 3: " + result3);
|
log("Space rocket <" + result3 + "> launch complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,7 +102,7 @@ public class App {
|
|||||||
private static <T> Callable<T> lazyval(T value, long delayMillis) {
|
private static <T> Callable<T> lazyval(T value, long delayMillis) {
|
||||||
return () -> {
|
return () -> {
|
||||||
Thread.sleep(delayMillis);
|
Thread.sleep(delayMillis);
|
||||||
log("Task completed with: " + value);
|
log("Space rocket <" + value + "> launched successfully");
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -117,7 +118,7 @@ public class App {
|
|||||||
if (ex.isPresent()) {
|
if (ex.isPresent()) {
|
||||||
log(name + " failed: " + ex.map(Exception::getMessage).orElse(""));
|
log(name + " failed: " + ex.map(Exception::getMessage).orElse(""));
|
||||||
} else {
|
} else {
|
||||||
log(name + ": " + value);
|
log(name + " <" + value + ">");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -9,19 +9,131 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Balking Pattern is used to prevent an object from executing certain code if it is an
|
|
||||||
incomplete or inappropriate state
|
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.
|
||||||
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Balking pattern when
|
Use the Balking pattern when
|
||||||
|
|
||||||
* you want to invoke an action on an object only when it is in a particular state
|
* 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
|
* Objects are generally only in a state that is prone to balking temporarily but for an unknown
|
||||||
but for an unknown amount of time
|
amount of time
|
||||||
|
|
||||||
## Related patterns
|
## Related patterns
|
||||||
* Guarded Suspension Pattern
|
|
||||||
* Double Checked Locking Pattern
|
* [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)
|
||||||
|
@ -23,28 +23,24 @@
|
|||||||
|
|
||||||
package com.iluwatar.balking;
|
package com.iluwatar.balking;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In Balking Design Pattern if an object’s method is invoked when it is in an inappropriate state,
|
* In Balking Design Pattern if an object’s 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
|
* 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
|
* only in a state that is prone to balking temporarily but for an unknown amount of time
|
||||||
*
|
*
|
||||||
* <p>In this example implementation WashingMachine is an object that has two states in which it
|
* <p>In this example implementation, {@link WashingMachine} is an object that has two states in
|
||||||
* can be: ENABLED and WASHING. If the machine is ENABLED the state is changed into WASHING that any
|
* which it can be: ENABLED and WASHING. If the machine is ENABLED, the state changes to WASHING
|
||||||
* other thread can't invoke this action on this and then do the job. On the other hand if it have
|
* using a thread-safe method. On the other hand, if it already has been washing and any other
|
||||||
* been already washing and any other thread execute wash() it can't do that once again and returns
|
* thread executes {@link WashingMachine#wash()} it won't do that and returns without doing
|
||||||
* doing nothing.
|
* anything.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry Point.
|
* Entry Point.
|
||||||
*
|
*
|
||||||
@ -58,10 +54,12 @@ public class App {
|
|||||||
}
|
}
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
try {
|
try {
|
||||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||||
|
executorService.shutdownNow();
|
||||||
|
}
|
||||||
} catch (InterruptedException ie) {
|
} catch (InterruptedException ie) {
|
||||||
LOGGER.error("ERROR: Waiting on executor service shutdown!");
|
LOGGER.error("ERROR: Waiting on executor service shutdown!");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,15 +24,14 @@
|
|||||||
package com.iluwatar.balking;
|
package com.iluwatar.balking;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Washing machine class.
|
* Washing machine class.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class WashingMachine {
|
public class WashingMachine {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(WashingMachine.class);
|
|
||||||
private final DelayProvider delayProvider;
|
private final DelayProvider delayProvider;
|
||||||
private WashingMachineState washingMachineState;
|
private WashingMachineState washingMachineState;
|
||||||
|
|
||||||
@ -44,7 +43,8 @@ public class WashingMachine {
|
|||||||
try {
|
try {
|
||||||
Thread.sleep(timeUnit.toMillis(interval));
|
Thread.sleep(timeUnit.toMillis(interval));
|
||||||
} catch (InterruptedException ie) {
|
} catch (InterruptedException ie) {
|
||||||
ie.printStackTrace();
|
LOGGER.error("", ie);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
task.run();
|
task.run();
|
||||||
});
|
});
|
||||||
@ -71,7 +71,7 @@ public class WashingMachine {
|
|||||||
var machineState = getWashingMachineState();
|
var machineState = getWashingMachineState();
|
||||||
LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
|
LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
|
||||||
if (this.washingMachineState == WashingMachineState.WASHING) {
|
if (this.washingMachineState == WashingMachineState.WASHING) {
|
||||||
LOGGER.error("ERROR: Cannot wash if the machine has been already washing!");
|
LOGGER.error("Cannot wash if the machine has been already washing!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.washingMachineState = WashingMachineState.WASHING;
|
this.washingMachineState = WashingMachineState.WASHING;
|
||||||
|
@ -28,5 +28,6 @@ package com.iluwatar.balking;
|
|||||||
* as well as during washing.
|
* as well as during washing.
|
||||||
*/
|
*/
|
||||||
public enum WashingMachineState {
|
public enum WashingMachineState {
|
||||||
ENABLED, WASHING
|
ENABLED,
|
||||||
|
WASHING
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
|
|
||||||
package com.iluwatar.bridge;
|
package com.iluwatar.bridge;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composition over inheritance. The Bridge pattern can also be thought of as two layers of
|
* Composition over inheritance. The Bridge pattern can also be thought of as two layers of
|
||||||
@ -39,10 +38,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
* enchantments. We can easily combine any weapon with any enchantment using composition instead of
|
* enchantments. We can easily combine any weapon with any enchantment using composition instead of
|
||||||
* creating deep class hierarchy.
|
* creating deep class hierarchy.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point.
|
* Program entry point.
|
||||||
*
|
*
|
||||||
|
@ -23,16 +23,14 @@
|
|||||||
|
|
||||||
package com.iluwatar.bridge;
|
package com.iluwatar.bridge;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FlyingEnchantment.
|
* FlyingEnchantment.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class FlyingEnchantment implements Enchantment {
|
public class FlyingEnchantment implements Enchantment {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(FlyingEnchantment.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivate() {
|
public void onActivate() {
|
||||||
LOGGER.info("The item begins to glow faintly.");
|
LOGGER.info("The item begins to glow faintly.");
|
||||||
|
@ -23,22 +23,18 @@
|
|||||||
|
|
||||||
package com.iluwatar.bridge;
|
package com.iluwatar.bridge;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.AllArgsConstructor;
|
||||||
import org.slf4j.LoggerFactory;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hammer.
|
* Hammer.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
public class Hammer implements Weapon {
|
public class Hammer implements Weapon {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Hammer.class);
|
|
||||||
|
|
||||||
private final Enchantment enchantment;
|
private final Enchantment enchantment;
|
||||||
|
|
||||||
public Hammer(Enchantment enchantment) {
|
|
||||||
this.enchantment = enchantment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void wield() {
|
public void wield() {
|
||||||
LOGGER.info("The hammer is wielded.");
|
LOGGER.info("The hammer is wielded.");
|
||||||
|
@ -23,16 +23,14 @@
|
|||||||
|
|
||||||
package com.iluwatar.bridge;
|
package com.iluwatar.bridge;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SoulEatingEnchantment.
|
* SoulEatingEnchantment.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class SoulEatingEnchantment implements Enchantment {
|
public class SoulEatingEnchantment implements Enchantment {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(SoulEatingEnchantment.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivate() {
|
public void onActivate() {
|
||||||
LOGGER.info("The item spreads bloodlust.");
|
LOGGER.info("The item spreads bloodlust.");
|
||||||
|
@ -23,22 +23,18 @@
|
|||||||
|
|
||||||
package com.iluwatar.bridge;
|
package com.iluwatar.bridge;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.AllArgsConstructor;
|
||||||
import org.slf4j.LoggerFactory;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sword.
|
* Sword.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
public class Sword implements Weapon {
|
public class Sword implements Weapon {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Sword.class);
|
|
||||||
|
|
||||||
private final Enchantment enchantment;
|
private final Enchantment enchantment;
|
||||||
|
|
||||||
public Sword(Enchantment enchantment) {
|
|
||||||
this.enchantment = enchantment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void wield() {
|
public void wield() {
|
||||||
LOGGER.info("The sword is wielded.");
|
LOGGER.info("The sword is wielded.");
|
||||||
|
@ -24,8 +24,7 @@
|
|||||||
package com.iluwatar.builder;
|
package com.iluwatar.builder;
|
||||||
|
|
||||||
import com.iluwatar.builder.Hero.Builder;
|
import com.iluwatar.builder.Hero.Builder;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The intention of the Builder pattern is to find a solution to the telescoping constructor
|
* The intention of the Builder pattern is to find a solution to the telescoping constructor
|
||||||
@ -48,10 +47,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
* configuration for the {@link Hero} object can be done using the fluent {@link Builder} interface.
|
* 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.
|
* When configuration is ready the build method is called to receive the final {@link Hero} object.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point.
|
* Program entry point.
|
||||||
*
|
*
|
||||||
@ -76,6 +74,5 @@ public class App {
|
|||||||
.withWeapon(Weapon.BOW)
|
.withWeapon(Weapon.BOW)
|
||||||
.build();
|
.build();
|
||||||
LOGGER.info(thief.toString());
|
LOGGER.info(thief.toString());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,19 +23,21 @@
|
|||||||
|
|
||||||
package com.iluwatar.builder;
|
package com.iluwatar.builder;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Armor enumeration.
|
* Armor enumeration.
|
||||||
*/
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
public enum Armor {
|
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;
|
private final String title;
|
||||||
|
|
||||||
Armor(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return title;
|
return title;
|
||||||
|
@ -28,7 +28,11 @@ package com.iluwatar.builder;
|
|||||||
*/
|
*/
|
||||||
public enum HairColor {
|
public enum HairColor {
|
||||||
|
|
||||||
WHITE, BLOND, RED, BROWN, BLACK;
|
WHITE,
|
||||||
|
BLOND,
|
||||||
|
RED,
|
||||||
|
BROWN,
|
||||||
|
BLACK;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@ -23,20 +23,22 @@
|
|||||||
|
|
||||||
package com.iluwatar.builder;
|
package com.iluwatar.builder;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HairType enumeration.
|
* HairType enumeration.
|
||||||
*/
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
public enum HairType {
|
public enum HairType {
|
||||||
|
|
||||||
BALD("bald"), SHORT("short"), CURLY("curly"), LONG_STRAIGHT("long straight"), LONG_CURLY(
|
BALD("bald"),
|
||||||
"long curly");
|
SHORT("short"),
|
||||||
|
CURLY("curly"),
|
||||||
|
LONG_STRAIGHT("long straight"),
|
||||||
|
LONG_CURLY("long curly");
|
||||||
|
|
||||||
private final String title;
|
private final String title;
|
||||||
|
|
||||||
HairType(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return title;
|
return title;
|
||||||
|
@ -41,7 +41,6 @@ class AppTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldExecuteApplicationWithoutException() {
|
void shouldExecuteApplicationWithoutException() {
|
||||||
|
|
||||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,21 +9,156 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
The Business Delegate pattern adds an abstraction layer between
|
The Business Delegate pattern adds an abstraction layer between
|
||||||
presentation and business tiers. By using the pattern we gain loose coupling
|
presentation and business tiers. By using the pattern we gain loose coupling
|
||||||
between the tiers and encapsulate knowledge about how to locate, connect to,
|
between the tiers and encapsulate knowledge about how to locate, connect to,
|
||||||
and interact with the business objects that make up the application.
|
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
|
## Class diagram
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
|
## Related patterns
|
||||||
|
|
||||||
|
* [Service locator pattern](https://java-design-patterns.com/patterns/service-locator/)
|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Business Delegate pattern when
|
Use the Business Delegate pattern when
|
||||||
|
|
||||||
* you want loose coupling between presentation and business tiers
|
* You want loose coupling between presentation and business tiers
|
||||||
* you want to orchestrate calls to multiple business services
|
* You want to orchestrate calls to multiple business services
|
||||||
* you want to encapsulate service lookups and service calls
|
* 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)
|
||||||
|
|
||||||
## Credits
|
## 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)
|
* [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.
Before Width: | Height: | Size: 24 KiB |
@ -1,136 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
|
||||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
|
||||||
<class id="1" language="java" name="com.iluwatar.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>
|
|
BIN
business-delegate/etc/business-delegate.urm.png
Normal file
BIN
business-delegate/etc/business-delegate.urm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
@ -5,53 +5,42 @@ package com.iluwatar.business.delegate {
|
|||||||
+ main(args : String[]) {static}
|
+ main(args : String[]) {static}
|
||||||
}
|
}
|
||||||
class BusinessDelegate {
|
class BusinessDelegate {
|
||||||
- businessService : BusinessService
|
|
||||||
- lookupService : BusinessLookup
|
- lookupService : BusinessLookup
|
||||||
- serviceType : ServiceType
|
|
||||||
+ BusinessDelegate()
|
+ BusinessDelegate()
|
||||||
+ doTask()
|
+ playbackMovie(movie : String)
|
||||||
+ setLookupService(businessLookup : BusinessLookup)
|
+ setLookupService(lookupService : BusinessLookup)
|
||||||
+ setServiceType(serviceType : ServiceType)
|
|
||||||
}
|
}
|
||||||
class BusinessLookup {
|
class BusinessLookup {
|
||||||
- ejbService : EjbService
|
- netflixService : NetflixService
|
||||||
- jmsService : JmsService
|
- youTubeService : YouTubeService
|
||||||
+ BusinessLookup()
|
+ BusinessLookup()
|
||||||
+ getBusinessService(serviceType : ServiceType) : BusinessService
|
+ getBusinessService(movie : String) : VideoStreamingService
|
||||||
+ setEjbService(ejbService : EjbService)
|
+ setNetflixService(netflixService : NetflixService)
|
||||||
+ setJmsService(jmsService : JmsService)
|
+ setYouTubeService(youTubeService : YouTubeService)
|
||||||
}
|
}
|
||||||
interface BusinessService {
|
class MobileClient {
|
||||||
|
- businessDelegate : BusinessDelegate
|
||||||
|
+ MobileClient(businessDelegate : BusinessDelegate)
|
||||||
|
+ playbackMovie(movie : String)
|
||||||
|
}
|
||||||
|
class NetflixService {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
+ NetflixService()
|
||||||
|
+ doProcessing()
|
||||||
|
}
|
||||||
|
interface VideoStreamingService {
|
||||||
+ doProcessing() {abstract}
|
+ doProcessing() {abstract}
|
||||||
}
|
}
|
||||||
class Client {
|
class YouTubeService {
|
||||||
- businessDelegate : BusinessDelegate
|
|
||||||
+ Client(businessDelegate : BusinessDelegate)
|
|
||||||
+ doTask()
|
|
||||||
}
|
|
||||||
class EjbService {
|
|
||||||
- LOGGER : Logger {static}
|
- LOGGER : Logger {static}
|
||||||
+ EjbService()
|
+ YouTubeService()
|
||||||
+ doProcessing()
|
+ doProcessing()
|
||||||
}
|
}
|
||||||
class JmsService {
|
|
||||||
- LOGGER : Logger {static}
|
|
||||||
+ JmsService()
|
|
||||||
+ doProcessing()
|
|
||||||
}
|
|
||||||
enum ServiceType {
|
|
||||||
+ EJB {static}
|
|
||||||
+ JMS {static}
|
|
||||||
+ valueOf(name : String) : ServiceType {static}
|
|
||||||
+ values() : ServiceType[] {static}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
BusinessLookup --> "-ejbService" EjbService
|
BusinessLookup --> "-netflixService" NetflixService
|
||||||
BusinessDelegate --> "-serviceType" ServiceType
|
BusinessLookup --> "-youTubeService" YouTubeService
|
||||||
Client --> "-businessDelegate" BusinessDelegate
|
MobileClient --> "-businessDelegate" BusinessDelegate
|
||||||
BusinessDelegate --> "-businessService" BusinessService
|
|
||||||
BusinessDelegate --> "-lookupService" BusinessLookup
|
BusinessDelegate --> "-lookupService" BusinessLookup
|
||||||
BusinessLookup --> "-jmsService" JmsService
|
NetflixService ..|> VideoStreamingService
|
||||||
EjbService ..|> BusinessService
|
YouTubeService ..|> VideoStreamingService
|
||||||
JmsService ..|> BusinessService
|
|
||||||
@enduml
|
@enduml
|
@ -33,9 +33,9 @@ package com.iluwatar.business.delegate;
|
|||||||
* retrieved through service lookups. The Business Delegate itself may contain business logic too
|
* retrieved through service lookups. The Business Delegate itself may contain business logic too
|
||||||
* potentially tying together multiple service calls, exception handling, retrying etc.
|
* potentially tying together multiple service calls, exception handling, retrying etc.
|
||||||
*
|
*
|
||||||
* <p>In this example the client ({@link Client}) utilizes a business delegate (
|
* <p>In this example the client ({@link MobileClient}) utilizes a business delegate (
|
||||||
* {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
|
* {@link BusinessDelegate}) to search for movies in video streaming services. The Business Delegate
|
||||||
* service and makes the service call.
|
* then selects the appropriate service and makes the service call.
|
||||||
*/
|
*/
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
@ -46,18 +46,16 @@ public class App {
|
|||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
// prepare the objects
|
||||||
var businessDelegate = new BusinessDelegate();
|
var businessDelegate = new BusinessDelegate();
|
||||||
var businessLookup = new BusinessLookup();
|
var businessLookup = new BusinessLookup();
|
||||||
businessLookup.setEjbService(new EjbService());
|
businessLookup.setNetflixService(new NetflixService());
|
||||||
businessLookup.setJmsService(new JmsService());
|
businessLookup.setYouTubeService(new YouTubeService());
|
||||||
|
|
||||||
businessDelegate.setLookupService(businessLookup);
|
businessDelegate.setLookupService(businessLookup);
|
||||||
businessDelegate.setServiceType(ServiceType.EJB);
|
|
||||||
|
|
||||||
var client = new Client(businessDelegate);
|
// create the client and use the business delegate
|
||||||
client.doTask();
|
var client = new MobileClient(businessDelegate);
|
||||||
|
client.playbackMovie("Die Hard 2");
|
||||||
businessDelegate.setServiceType(ServiceType.JMS);
|
client.playbackMovie("Maradona: The Greatest Ever");
|
||||||
client.doTask();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,25 +23,18 @@
|
|||||||
|
|
||||||
package com.iluwatar.business.delegate;
|
package com.iluwatar.business.delegate;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BusinessDelegate separates the presentation and business tiers.
|
* BusinessDelegate separates the presentation and business tiers.
|
||||||
*/
|
*/
|
||||||
|
@Setter
|
||||||
public class BusinessDelegate {
|
public class BusinessDelegate {
|
||||||
|
|
||||||
private BusinessLookup lookupService;
|
private BusinessLookup lookupService;
|
||||||
private BusinessService businessService;
|
|
||||||
private ServiceType serviceType;
|
|
||||||
|
|
||||||
public void setLookupService(BusinessLookup businessLookup) {
|
public void playbackMovie(String movie) {
|
||||||
this.lookupService = businessLookup;
|
VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
|
||||||
}
|
videoStreamingService.doProcessing();
|
||||||
|
|
||||||
public void setServiceType(ServiceType serviceType) {
|
|
||||||
this.serviceType = serviceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doTask() {
|
|
||||||
businessService = lookupService.getBusinessService(serviceType);
|
|
||||||
businessService.doProcessing();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,34 +23,30 @@
|
|||||||
|
|
||||||
package com.iluwatar.business.delegate;
|
package com.iluwatar.business.delegate;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for performing service lookups.
|
* Class for performing service lookups.
|
||||||
*/
|
*/
|
||||||
|
@Setter
|
||||||
public class BusinessLookup {
|
public class BusinessLookup {
|
||||||
|
|
||||||
private EjbService ejbService;
|
private NetflixService netflixService;
|
||||||
|
|
||||||
private JmsService jmsService;
|
private YouTubeService youTubeService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets service instance based on service type.
|
* Gets service instance based on given movie search string.
|
||||||
*
|
*
|
||||||
* @param serviceType Type of service instance to be returned.
|
* @param movie Search string for the movie.
|
||||||
* @return Service instance.
|
* @return Service instance.
|
||||||
*/
|
*/
|
||||||
public BusinessService getBusinessService(ServiceType serviceType) {
|
public VideoStreamingService getBusinessService(String movie) {
|
||||||
if (serviceType.equals(ServiceType.EJB)) {
|
if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
|
||||||
return ejbService;
|
return netflixService;
|
||||||
} else {
|
} else {
|
||||||
return jmsService;
|
return youTubeService;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setJmsService(JmsService jmsService) {
|
|
||||||
this.jmsService = jmsService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEjbService(EjbService ejbService) {
|
|
||||||
this.ejbService = ejbService;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -24,17 +24,17 @@
|
|||||||
package com.iluwatar.business.delegate;
|
package com.iluwatar.business.delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client utilizes BusinessDelegate to call the business tier.
|
* MobileClient utilizes BusinessDelegate to call the business tier.
|
||||||
*/
|
*/
|
||||||
public class Client {
|
public class MobileClient {
|
||||||
|
|
||||||
private final BusinessDelegate businessDelegate;
|
private final BusinessDelegate businessDelegate;
|
||||||
|
|
||||||
public Client(BusinessDelegate businessDelegate) {
|
public MobileClient(BusinessDelegate businessDelegate) {
|
||||||
this.businessDelegate = businessDelegate;
|
this.businessDelegate = businessDelegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doTask() {
|
public void playbackMovie(String movie) {
|
||||||
businessDelegate.doTask();
|
businessDelegate.playbackMovie(movie);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,18 +23,16 @@
|
|||||||
|
|
||||||
package com.iluwatar.business.delegate;
|
package com.iluwatar.business.delegate;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service EJB implementation.
|
* NetflixService implementation.
|
||||||
*/
|
*/
|
||||||
public class EjbService implements BusinessService {
|
@Slf4j
|
||||||
|
public class NetflixService implements VideoStreamingService {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(EjbService.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doProcessing() {
|
public void doProcessing() {
|
||||||
LOGGER.info("EjbService is now processing");
|
LOGGER.info("NetflixService is now processing");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,9 +24,9 @@
|
|||||||
package com.iluwatar.business.delegate;
|
package com.iluwatar.business.delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for service implementations.
|
* Interface for video streaming service implementations.
|
||||||
*/
|
*/
|
||||||
public interface BusinessService {
|
public interface VideoStreamingService {
|
||||||
|
|
||||||
void doProcessing();
|
void doProcessing();
|
||||||
}
|
}
|
@ -23,18 +23,16 @@
|
|||||||
|
|
||||||
package com.iluwatar.business.delegate;
|
package com.iluwatar.business.delegate;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service JMS implementation.
|
* YouTubeService implementation.
|
||||||
*/
|
*/
|
||||||
public class JmsService implements BusinessService {
|
@Slf4j
|
||||||
|
public class YouTubeService implements VideoStreamingService {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(JmsService.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doProcessing() {
|
public void doProcessing() {
|
||||||
LOGGER.info("JmsService is now processing");
|
LOGGER.info("YouTubeService is now processing");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,27 +26,20 @@ package com.iluwatar.business.delegate;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Business Delegate pattern adds an abstraction layer between the presentation and business
|
* Tests for the {@link BusinessDelegate}
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
public class BusinessDelegateTest {
|
class BusinessDelegateTest {
|
||||||
|
|
||||||
private EjbService ejbService;
|
private NetflixService netflixService;
|
||||||
|
|
||||||
private JmsService jmsService;
|
private YouTubeService youTubeService;
|
||||||
|
|
||||||
private BusinessLookup businessLookup;
|
|
||||||
|
|
||||||
private BusinessDelegate businessDelegate;
|
private BusinessDelegate businessDelegate;
|
||||||
|
|
||||||
@ -56,46 +49,40 @@ public class BusinessDelegateTest {
|
|||||||
*/
|
*/
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setup() {
|
public void setup() {
|
||||||
ejbService = spy(new EjbService());
|
netflixService = spy(new NetflixService());
|
||||||
jmsService = spy(new JmsService());
|
youTubeService = spy(new YouTubeService());
|
||||||
|
|
||||||
businessLookup = spy(new BusinessLookup());
|
BusinessLookup businessLookup = spy(new BusinessLookup());
|
||||||
businessLookup.setEjbService(ejbService);
|
businessLookup.setNetflixService(netflixService);
|
||||||
businessLookup.setJmsService(jmsService);
|
businessLookup.setYouTubeService(youTubeService);
|
||||||
|
|
||||||
businessDelegate = spy(new BusinessDelegate());
|
businessDelegate = spy(new BusinessDelegate());
|
||||||
businessDelegate.setLookupService(businessLookup);
|
businessDelegate.setLookupService(businessLookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In this example the client ({@link Client}) utilizes a business delegate (
|
* In this example the client ({@link MobileClient}) utilizes a business delegate (
|
||||||
* {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
|
* {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
|
||||||
* service and makes the service call.
|
* service and makes the service call.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testBusinessDelegate() {
|
void testBusinessDelegate() {
|
||||||
|
|
||||||
// setup a client object
|
// setup a client object
|
||||||
var client = new Client(businessDelegate);
|
var client = new MobileClient(businessDelegate);
|
||||||
|
|
||||||
// set the service type
|
|
||||||
businessDelegate.setServiceType(ServiceType.EJB);
|
|
||||||
|
|
||||||
// action
|
// action
|
||||||
client.doTask();
|
client.playbackMovie("Die hard");
|
||||||
|
|
||||||
// verifying that the businessDelegate was used by client during doTask() method.
|
// verifying that the businessDelegate was used by client during playbackMovie() method.
|
||||||
verify(businessDelegate).doTask();
|
verify(businessDelegate).playbackMovie(anyString());
|
||||||
verify(ejbService).doProcessing();
|
verify(netflixService).doProcessing();
|
||||||
|
|
||||||
// set the service type
|
|
||||||
businessDelegate.setServiceType(ServiceType.JMS);
|
|
||||||
|
|
||||||
// action
|
// action
|
||||||
client.doTask();
|
client.playbackMovie("Maradona");
|
||||||
|
|
||||||
// verifying that the businessDelegate was used by client during doTask() method.
|
// verifying that the businessDelegate was used by client during doTask() method.
|
||||||
verify(businessDelegate, times(2)).doTask();
|
verify(businessDelegate, times(2)).playbackMovie(anyString());
|
||||||
verify(jmsService).doProcessing();
|
verify(youTubeService).doProcessing();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,18 +9,234 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Allows to encode behaviour as instructions for virtual machine.
|
|
||||||
|
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 []
|
||||||
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Bytecode pattern when you have a lot of behavior you need to define and your
|
Use the Bytecode pattern when you have a lot of behavior you need to define and your
|
||||||
game’s implementation language isn’t a good fit because:
|
game’s implementation language isn’t a good fit because:
|
||||||
|
|
||||||
* it’s too low-level, making it tedious or error-prone to program in.
|
* It’s 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.
|
* 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 can’t break the game, you need to sandbox it from the rest of the codebase.
|
* It has too much trust. If you want to ensure the behavior being defined can’t break the game, you need to sandbox it from the rest of the codebase.
|
||||||
|
|
||||||
|
## Related patterns
|
||||||
|
|
||||||
|
* [Interpreter](https://java-design-patterns.com/patterns/interpreter/)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
@ -1,49 +0,0 @@
|
|||||||
<?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: 67 KiB After Width: | Height: | Size: 84 KiB |
@ -3,7 +3,6 @@ package com.iluwatar.bytecode {
|
|||||||
class App {
|
class App {
|
||||||
- LOGGER : Logger {static}
|
- LOGGER : Logger {static}
|
||||||
+ App()
|
+ App()
|
||||||
- interpretInstruction(instruction : String, vm : VirtualMachine) {static}
|
|
||||||
+ main(args : String[]) {static}
|
+ main(args : String[]) {static}
|
||||||
}
|
}
|
||||||
enum Instruction {
|
enum Instruction {
|
||||||
@ -18,22 +17,25 @@ package com.iluwatar.bytecode {
|
|||||||
+ SET_HEALTH {static}
|
+ SET_HEALTH {static}
|
||||||
+ SET_WISDOM {static}
|
+ SET_WISDOM {static}
|
||||||
+ SPAWN_PARTICLES {static}
|
+ SPAWN_PARTICLES {static}
|
||||||
- value : int
|
- intValue : int
|
||||||
+ getInstruction(value : int) : Instruction {static}
|
+ getInstruction(value : int) : Instruction {static}
|
||||||
+ getIntValue() : int
|
+ getIntValue() : int
|
||||||
+ valueOf(name : String) : Instruction {static}
|
+ valueOf(name : String) : Instruction {static}
|
||||||
+ values() : Instruction[] {static}
|
+ values() : Instruction[] {static}
|
||||||
}
|
}
|
||||||
class VirtualMachine {
|
class VirtualMachine {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
- stack : Stack<Integer>
|
- stack : Stack<Integer>
|
||||||
- wizards : Wizard[]
|
- wizards : Wizard[]
|
||||||
+ VirtualMachine()
|
+ VirtualMachine()
|
||||||
|
+ VirtualMachine(wizard1 : Wizard, wizard2 : Wizard)
|
||||||
+ execute(bytecode : int[])
|
+ execute(bytecode : int[])
|
||||||
+ getAgility(wizard : int) : int
|
+ getAgility(wizard : int) : int
|
||||||
+ getHealth(wizard : int) : int
|
+ getHealth(wizard : int) : int
|
||||||
+ getStack() : Stack<Integer>
|
+ getStack() : Stack<Integer>
|
||||||
+ getWisdom(wizard : int) : int
|
+ getWisdom(wizard : int) : int
|
||||||
+ getWizards() : Wizard[]
|
+ getWizards() : Wizard[]
|
||||||
|
- randomInt(min : int, max : int) : int
|
||||||
+ setAgility(wizard : int, amount : int)
|
+ setAgility(wizard : int, amount : int)
|
||||||
+ setHealth(wizard : int, amount : int)
|
+ setHealth(wizard : int, amount : int)
|
||||||
+ setWisdom(wizard : int, amount : int)
|
+ setWisdom(wizard : int, amount : int)
|
||||||
@ -45,7 +47,7 @@ package com.iluwatar.bytecode {
|
|||||||
- numberOfPlayedSounds : int
|
- numberOfPlayedSounds : int
|
||||||
- numberOfSpawnedParticles : int
|
- numberOfSpawnedParticles : int
|
||||||
- wisdom : int
|
- wisdom : int
|
||||||
+ Wizard()
|
+ Wizard(health : int, agility : int, wisdom : int, numberOfPlayedSounds : int, numberOfSpawnedParticles : int)
|
||||||
+ getAgility() : int
|
+ getAgility() : int
|
||||||
+ getHealth() : int
|
+ getHealth() : int
|
||||||
+ getNumberOfPlayedSounds() : int
|
+ getNumberOfPlayedSounds() : int
|
||||||
@ -54,6 +56,8 @@ package com.iluwatar.bytecode {
|
|||||||
+ playSound()
|
+ playSound()
|
||||||
+ setAgility(agility : int)
|
+ setAgility(agility : int)
|
||||||
+ setHealth(health : int)
|
+ setHealth(health : int)
|
||||||
|
+ setNumberOfPlayedSounds(numberOfPlayedSounds : int)
|
||||||
|
+ setNumberOfSpawnedParticles(numberOfSpawnedParticles : int)
|
||||||
+ setWisdom(wisdom : int)
|
+ setWisdom(wisdom : int)
|
||||||
+ spawnParticles()
|
+ spawnParticles()
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,7 @@
|
|||||||
package com.iluwatar.bytecode;
|
package com.iluwatar.bytecode;
|
||||||
|
|
||||||
import com.iluwatar.bytecode.util.InstructionConverterUtil;
|
import com.iluwatar.bytecode.util.InstructionConverterUtil;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The intention of Bytecode pattern is to give behavior the flexibility of data by encoding it as
|
* The intention of Bytecode pattern is to give behavior the flexibility of data by encoding it as
|
||||||
@ -40,8 +39,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
* ensure the behavior being defined can’t break the game, you need to sandbox it from the rest of
|
* ensure the behavior being defined can’t break the game, you need to sandbox it from the rest of
|
||||||
* the codebase.
|
* the codebase.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class App {
|
public class App {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main app method.
|
* Main app method.
|
||||||
@ -50,33 +49,21 @@ public class App {
|
|||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
var wizard = new Wizard();
|
var vm = new VirtualMachine(
|
||||||
wizard.setHealth(45);
|
new Wizard(45, 7, 11, 0, 0),
|
||||||
wizard.setAgility(7);
|
new Wizard(36, 18, 8, 0, 0));
|
||||||
wizard.setWisdom(11);
|
|
||||||
|
|
||||||
var vm = new VirtualMachine();
|
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
||||||
vm.getWizards()[0] = wizard;
|
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
||||||
|
vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH"));
|
||||||
String literal = "LITERAL 0";
|
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
||||||
|
vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY"));
|
||||||
interpretInstruction(literal, vm);
|
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
||||||
interpretInstruction(literal, vm);
|
vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM"));
|
||||||
interpretInstruction("GET_HEALTH", vm);
|
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
|
||||||
interpretInstruction(literal, vm);
|
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2"));
|
||||||
interpretInstruction("GET_AGILITY", vm);
|
vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE"));
|
||||||
interpretInstruction(literal, vm);
|
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
|
||||||
interpretInstruction("GET_WISDOM ", vm);
|
vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH"));
|
||||||
interpretInstruction("ADD", vm);
|
|
||||||
interpretInstruction("LITERAL 2", vm);
|
|
||||||
interpretInstruction("DIVIDE", vm);
|
|
||||||
interpretInstruction("ADD", vm);
|
|
||||||
interpretInstruction("SET_HEALTH", vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void interpretInstruction(String instruction, VirtualMachine vm) {
|
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode(instruction));
|
|
||||||
var stack = vm.getStack();
|
|
||||||
LOGGER.info(instruction + String.format("%" + (12 - instruction.length()) + "s", "") + stack);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,32 +23,29 @@
|
|||||||
|
|
||||||
package com.iluwatar.bytecode;
|
package com.iluwatar.bytecode;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of instructions understandable by virtual machine.
|
* Representation of instructions understandable by virtual machine.
|
||||||
*/
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
public enum Instruction {
|
public enum Instruction {
|
||||||
|
|
||||||
LITERAL(1),
|
LITERAL(1), // e.g. "LITERAL 0", push 0 to stack
|
||||||
SET_HEALTH(2),
|
SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health
|
||||||
SET_WISDOM(3),
|
SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom
|
||||||
SET_AGILITY(4),
|
SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility
|
||||||
PLAY_SOUND(5),
|
PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound
|
||||||
SPAWN_PARTICLES(6),
|
SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles
|
||||||
GET_HEALTH(7),
|
GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health
|
||||||
GET_AGILITY(8),
|
GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility
|
||||||
GET_WISDOM(9),
|
GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom
|
||||||
ADD(10),
|
ADD(10), // e.g. "ADD", pop 2 values, push their sum
|
||||||
DIVIDE(11);
|
DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division
|
||||||
|
|
||||||
private final int value;
|
private final int intValue;
|
||||||
|
|
||||||
Instruction(int value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIntValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts integer value to Instruction.
|
* Converts integer value to Instruction.
|
||||||
|
@ -24,10 +24,15 @@
|
|||||||
package com.iluwatar.bytecode;
|
package com.iluwatar.bytecode;
|
||||||
|
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of virtual machine.
|
* Implementation of virtual machine.
|
||||||
*/
|
*/
|
||||||
|
@Getter
|
||||||
|
@Slf4j
|
||||||
public class VirtualMachine {
|
public class VirtualMachine {
|
||||||
|
|
||||||
private final Stack<Integer> stack = new Stack<>();
|
private final Stack<Integer> stack = new Stack<>();
|
||||||
@ -35,12 +40,21 @@ public class VirtualMachine {
|
|||||||
private final Wizard[] wizards = new Wizard[2];
|
private final Wizard[] wizards = new Wizard[2];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* No-args constructor.
|
||||||
*/
|
*/
|
||||||
public VirtualMachine() {
|
public VirtualMachine() {
|
||||||
for (var i = 0; i < wizards.length; i++) {
|
wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
|
||||||
wizards[i] = new Wizard();
|
0, 0);
|
||||||
|
wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
|
||||||
|
0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor taking the wizards as arguments.
|
||||||
|
*/
|
||||||
|
public VirtualMachine(Wizard wizard1, Wizard wizard2) {
|
||||||
|
wizards[0] = wizard1;
|
||||||
|
wizards[1] = wizard2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,6 +69,7 @@ public class VirtualMachine {
|
|||||||
case LITERAL:
|
case LITERAL:
|
||||||
// Read the next byte from the bytecode.
|
// Read the next byte from the bytecode.
|
||||||
int value = bytecode[++i];
|
int value = bytecode[++i];
|
||||||
|
// Push the next value to stack
|
||||||
stack.push(value);
|
stack.push(value);
|
||||||
break;
|
break;
|
||||||
case SET_AGILITY:
|
case SET_AGILITY:
|
||||||
@ -105,13 +120,10 @@ public class VirtualMachine {
|
|||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Invalid instruction value");
|
throw new IllegalArgumentException("Invalid instruction value");
|
||||||
}
|
}
|
||||||
|
LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stack<Integer> getStack() {
|
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHealth(int wizard, int amount) {
|
public void setHealth(int wizard, int amount) {
|
||||||
wizards[wizard].setHealth(amount);
|
wizards[wizard].setHealth(amount);
|
||||||
}
|
}
|
||||||
@ -136,7 +148,7 @@ public class VirtualMachine {
|
|||||||
return wizards[wizard].getAgility();
|
return wizards[wizard].getAgility();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Wizard[] getWizards() {
|
private int randomInt(int min, int max) {
|
||||||
return wizards;
|
return ThreadLocalRandom.current().nextInt(min, max + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,48 +23,27 @@
|
|||||||
|
|
||||||
package com.iluwatar.bytecode;
|
package com.iluwatar.bytecode;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.AllArgsConstructor;
|
||||||
import org.slf4j.LoggerFactory;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represent game objects which properties can be changed by instructions interpreted by
|
* This class represent game objects which properties can be changed by instructions interpreted by
|
||||||
* virtual machine.
|
* virtual machine.
|
||||||
*/
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@Slf4j
|
||||||
public class Wizard {
|
public class Wizard {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class);
|
|
||||||
|
|
||||||
private int health;
|
private int health;
|
||||||
|
|
||||||
private int agility;
|
private int agility;
|
||||||
private int wisdom;
|
private int wisdom;
|
||||||
|
|
||||||
private int numberOfPlayedSounds;
|
private int numberOfPlayedSounds;
|
||||||
private int numberOfSpawnedParticles;
|
private int numberOfSpawnedParticles;
|
||||||
|
|
||||||
public int getHealth() {
|
|
||||||
return health;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHealth(int health) {
|
|
||||||
this.health = health;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAgility() {
|
|
||||||
return agility;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAgility(int agility) {
|
|
||||||
this.agility = agility;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWisdom() {
|
|
||||||
return wisdom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWisdom(int wisdom) {
|
|
||||||
this.wisdom = wisdom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void playSound() {
|
public void playSound() {
|
||||||
LOGGER.info("Playing sound");
|
LOGGER.info("Playing sound");
|
||||||
numberOfPlayedSounds++;
|
numberOfPlayedSounds++;
|
||||||
@ -74,12 +53,4 @@ public class Wizard {
|
|||||||
LOGGER.info("Spawning particles");
|
LOGGER.info("Spawning particles");
|
||||||
numberOfSpawnedParticles++;
|
numberOfSpawnedParticles++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNumberOfPlayedSounds() {
|
|
||||||
return numberOfPlayedSounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNumberOfSpawnedParticles() {
|
|
||||||
return numberOfSpawnedParticles;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,4 @@ public class InstructionConverterUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,19 +23,19 @@
|
|||||||
|
|
||||||
package com.iluwatar.bytecode;
|
package com.iluwatar.bytecode;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static com.iluwatar.bytecode.Instruction.*;
|
import static com.iluwatar.bytecode.Instruction.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@Link VirtualMachine}
|
* Test for {@link VirtualMachine}
|
||||||
*/
|
*/
|
||||||
public class VirtualMachineTest {
|
class VirtualMachineTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLiteral() {
|
void testLiteral() {
|
||||||
var bytecode = new int[2];
|
var bytecode = new int[2];
|
||||||
bytecode[0] = LITERAL.getIntValue();
|
bytecode[0] = LITERAL.getIntValue();
|
||||||
bytecode[1] = 10;
|
bytecode[1] = 10;
|
||||||
@ -48,7 +48,7 @@ public class VirtualMachineTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetHealth() {
|
void testSetHealth() {
|
||||||
var wizardNumber = 0;
|
var wizardNumber = 0;
|
||||||
var bytecode = new int[5];
|
var bytecode = new int[5];
|
||||||
bytecode[0] = LITERAL.getIntValue();
|
bytecode[0] = LITERAL.getIntValue();
|
||||||
@ -64,7 +64,7 @@ public class VirtualMachineTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetAgility() {
|
void testSetAgility() {
|
||||||
var wizardNumber = 0;
|
var wizardNumber = 0;
|
||||||
var bytecode = new int[5];
|
var bytecode = new int[5];
|
||||||
bytecode[0] = LITERAL.getIntValue();
|
bytecode[0] = LITERAL.getIntValue();
|
||||||
@ -80,7 +80,7 @@ public class VirtualMachineTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetWisdom() {
|
void testSetWisdom() {
|
||||||
var wizardNumber = 0;
|
var wizardNumber = 0;
|
||||||
var bytecode = new int[5];
|
var bytecode = new int[5];
|
||||||
bytecode[0] = LITERAL.getIntValue();
|
bytecode[0] = LITERAL.getIntValue();
|
||||||
@ -96,7 +96,7 @@ public class VirtualMachineTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetHealth() {
|
void testGetHealth() {
|
||||||
var wizardNumber = 0;
|
var wizardNumber = 0;
|
||||||
var bytecode = new int[8];
|
var bytecode = new int[8];
|
||||||
bytecode[0] = LITERAL.getIntValue();
|
bytecode[0] = LITERAL.getIntValue();
|
||||||
@ -115,7 +115,7 @@ public class VirtualMachineTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPlaySound() {
|
void testPlaySound() {
|
||||||
var wizardNumber = 0;
|
var wizardNumber = 0;
|
||||||
var bytecode = new int[3];
|
var bytecode = new int[3];
|
||||||
bytecode[0] = LITERAL.getIntValue();
|
bytecode[0] = LITERAL.getIntValue();
|
||||||
@ -130,7 +130,7 @@ public class VirtualMachineTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSpawnParticles() {
|
void testSpawnParticles() {
|
||||||
var wizardNumber = 0;
|
var wizardNumber = 0;
|
||||||
var bytecode = new int[3];
|
var bytecode = new int[3];
|
||||||
bytecode[0] = LITERAL.getIntValue();
|
bytecode[0] = LITERAL.getIntValue();
|
||||||
@ -145,7 +145,7 @@ public class VirtualMachineTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidInstruction() {
|
void testInvalidInstruction() {
|
||||||
var bytecode = new int[1];
|
var bytecode = new int[1];
|
||||||
bytecode[0] = 999;
|
bytecode[0] = 999;
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
@ -24,16 +24,16 @@
|
|||||||
package com.iluwatar.bytecode.util;
|
package com.iluwatar.bytecode.util;
|
||||||
|
|
||||||
import com.iluwatar.bytecode.Instruction;
|
import com.iluwatar.bytecode.Instruction;
|
||||||
import com.iluwatar.bytecode.util.InstructionConverterUtil;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@Link InstructionConverterUtil}
|
* Test for {@link InstructionConverterUtil}
|
||||||
*/
|
*/
|
||||||
public class InstructionConverterUtilTest {
|
class InstructionConverterUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmptyInstruction() {
|
void testEmptyInstruction() {
|
||||||
var instruction = "";
|
var instruction = "";
|
||||||
|
|
||||||
var bytecode = InstructionConverterUtil.convertToByteCode(instruction);
|
var bytecode = InstructionConverterUtil.convertToByteCode(instruction);
|
||||||
@ -42,7 +42,7 @@ public class InstructionConverterUtilTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInstructions() {
|
void testInstructions() {
|
||||||
var instructions = "LITERAL 35 SET_HEALTH SET_WISDOM SET_AGILITY PLAY_SOUND"
|
var instructions = "LITERAL 35 SET_HEALTH SET_WISDOM SET_AGILITY PLAY_SOUND"
|
||||||
+ " SPAWN_PARTICLES GET_HEALTH ADD DIVIDE";
|
+ " SPAWN_PARTICLES GET_HEALTH ADD DIVIDE";
|
||||||
|
|
||||||
|
@ -10,20 +10,326 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
To avoid expensive re-acquisition of resources by not releasing
|
|
||||||
the resources immediately after their use. The resources retain their identity, are kept in some
|
The caching pattern avoids expensive re-acquisition of resources by not releasing them immediately
|
||||||
fast-access storage, and are re-used to avoid having to acquire them again.
|
after use. The resources retain their identity, are kept in some fast-access storage, and are
|
||||||
|
re-used to avoid having to acquire them again.
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
Real world example
|
||||||
|
|
||||||
|
> A team is working on a website that provides new homes for abandoned cats. People can post their
|
||||||
|
> cats on the website after registering, but all the new posts require approval from one of the
|
||||||
|
> site moderators. The user accounts of the site moderators contain a specific flag and the data
|
||||||
|
> is stored in a MongoDB database. Checking for the moderator flag each time a post is viewed
|
||||||
|
> becomes expensive and it's a good idea to utilize caching here.
|
||||||
|
|
||||||
|
In plain words
|
||||||
|
|
||||||
|
> Caching pattern keeps frequently needed data in fast-access storage to improve performance.
|
||||||
|
|
||||||
|
Wikipedia says:
|
||||||
|
|
||||||
|
> In computing, a cache is a hardware or software component that stores data so that future
|
||||||
|
> requests for that data can be served faster; the data stored in a cache might be the result of
|
||||||
|
> an earlier computation or a copy of data stored elsewhere. A cache hit occurs when the requested
|
||||||
|
> data can be found in a cache, while a cache miss occurs when it cannot. Cache hits are served by
|
||||||
|
> reading data from the cache, which is faster than recomputing a result or reading from a slower
|
||||||
|
> data store; thus, the more requests that can be served from the cache, the faster the system
|
||||||
|
> performs.
|
||||||
|
|
||||||
|
**Programmatic Example**
|
||||||
|
|
||||||
|
Let's first look at the data layer of our application. The interesting classes are `UserAccount`
|
||||||
|
which is a simple Java object containing the user account details, and `DbManager` which handles
|
||||||
|
reading and writing of these objects to/from MongoDB database.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString
|
||||||
|
public class UserAccount {
|
||||||
|
private String userId;
|
||||||
|
private String userName;
|
||||||
|
private String additionalInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public final class DbManager {
|
||||||
|
|
||||||
|
private static MongoClient mongoClient;
|
||||||
|
private static MongoDatabase db;
|
||||||
|
|
||||||
|
private DbManager() { /*...*/ }
|
||||||
|
|
||||||
|
public static void createVirtualDb() { /*...*/ }
|
||||||
|
|
||||||
|
public static void connect() throws ParseException { /*...*/ }
|
||||||
|
|
||||||
|
public static UserAccount readFromDb(String userId) { /*...*/ }
|
||||||
|
|
||||||
|
public static void writeToDb(UserAccount userAccount) { /*...*/ }
|
||||||
|
|
||||||
|
public static void updateDb(UserAccount userAccount) { /*...*/ }
|
||||||
|
|
||||||
|
public static void upsertDb(UserAccount userAccount) { /*...*/ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the example, we are demonstrating various different caching policies
|
||||||
|
|
||||||
|
* Write-through writes data to the cache and DB in a single transaction
|
||||||
|
* Write-around writes data immediately into the DB instead of the cache
|
||||||
|
* Write-behind writes data into the cache initially whilst the data is only written into the DB
|
||||||
|
when the cache is full
|
||||||
|
* Cache-aside pushes the responsibility of keeping the data synchronized in both data sources to
|
||||||
|
the application itself
|
||||||
|
* Read-through strategy is also included in the aforementioned strategies and it returns data from
|
||||||
|
the cache to the caller if it exists, otherwise queries from DB and stores it into the cache for
|
||||||
|
future use.
|
||||||
|
|
||||||
|
The cache implementation in `LruCache` is a hash table accompanied by a doubly
|
||||||
|
linked-list. The linked-list helps in capturing and maintaining the LRU data in the cache. When
|
||||||
|
data is queried (from the cache), added (to the cache), or updated, the data is moved to the front
|
||||||
|
of the list to depict itself as the most-recently-used data. The LRU data is always at the end of
|
||||||
|
the list.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Slf4j
|
||||||
|
public class LruCache {
|
||||||
|
|
||||||
|
static class Node {
|
||||||
|
String userId;
|
||||||
|
UserAccount userAccount;
|
||||||
|
Node previous;
|
||||||
|
Node next;
|
||||||
|
|
||||||
|
public Node(String userId, UserAccount userAccount) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.userAccount = userAccount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ... omitted details ... */
|
||||||
|
|
||||||
|
public LruCache(int capacity) {
|
||||||
|
this.capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccount get(String userId) {
|
||||||
|
if (cache.containsKey(userId)) {
|
||||||
|
var node = cache.get(userId);
|
||||||
|
remove(node);
|
||||||
|
setHead(node);
|
||||||
|
return node.userAccount;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String userId, UserAccount userAccount) {
|
||||||
|
if (cache.containsKey(userId)) {
|
||||||
|
var old = cache.get(userId);
|
||||||
|
old.userAccount = userAccount;
|
||||||
|
remove(old);
|
||||||
|
setHead(old);
|
||||||
|
} else {
|
||||||
|
var newNode = new Node(userId, userAccount);
|
||||||
|
if (cache.size() >= capacity) {
|
||||||
|
LOGGER.info("# Cache is FULL! Removing {} from cache...", end.userId);
|
||||||
|
cache.remove(end.userId); // remove LRU data from cache.
|
||||||
|
remove(end);
|
||||||
|
setHead(newNode);
|
||||||
|
} else {
|
||||||
|
setHead(newNode);
|
||||||
|
}
|
||||||
|
cache.put(userId, newNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(String userId) {
|
||||||
|
return cache.containsKey(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(Node node) { /* ... */ }
|
||||||
|
public void setHead(Node node) { /* ... */ }
|
||||||
|
public void invalidate(String userId) { /* ... */ }
|
||||||
|
public boolean isFull() { /* ... */ }
|
||||||
|
public UserAccount getLruData() { /* ... */ }
|
||||||
|
public void clear() { /* ... */ }
|
||||||
|
public List<UserAccount> getCacheDataInListForm() { /* ... */ }
|
||||||
|
public void setCapacity(int newCapacity) { /* ... */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The next layer we are going to look at is `CacheStore` which implements the different caching
|
||||||
|
strategies.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Slf4j
|
||||||
|
public class CacheStore {
|
||||||
|
|
||||||
|
private static LruCache cache;
|
||||||
|
|
||||||
|
/* ... details omitted ... */
|
||||||
|
|
||||||
|
public static UserAccount readThrough(String userId) {
|
||||||
|
if (cache.contains(userId)) {
|
||||||
|
LOGGER.info("# Cache Hit!");
|
||||||
|
return cache.get(userId);
|
||||||
|
}
|
||||||
|
LOGGER.info("# Cache Miss!");
|
||||||
|
UserAccount userAccount = DbManager.readFromDb(userId);
|
||||||
|
cache.set(userId, userAccount);
|
||||||
|
return userAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeThrough(UserAccount userAccount) {
|
||||||
|
if (cache.contains(userAccount.getUserId())) {
|
||||||
|
DbManager.updateDb(userAccount);
|
||||||
|
} else {
|
||||||
|
DbManager.writeToDb(userAccount);
|
||||||
|
}
|
||||||
|
cache.set(userAccount.getUserId(), userAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearCache() {
|
||||||
|
if (cache != null) {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void flushCache() {
|
||||||
|
LOGGER.info("# flushCache...");
|
||||||
|
Optional.ofNullable(cache)
|
||||||
|
.map(LruCache::getCacheDataInListForm)
|
||||||
|
.orElse(List.of())
|
||||||
|
.forEach(DbManager::updateDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ... omitted the implementation of other caching strategies ... */
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`AppManager` helps to bridge the gap in communication between the main class and the application's
|
||||||
|
back-end. DB connection is initialized through this class. The chosen caching strategy/policy is
|
||||||
|
also initialized here. Before the cache can be used, the size of the cache has to be set. Depending
|
||||||
|
on the chosen caching policy, `AppManager` will call the appropriate function in the `CacheStore`
|
||||||
|
class.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Slf4j
|
||||||
|
public final class AppManager {
|
||||||
|
|
||||||
|
private static CachingPolicy cachingPolicy;
|
||||||
|
|
||||||
|
private AppManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initDb(boolean useMongoDb) { /* ... */ }
|
||||||
|
|
||||||
|
public static void initCachingPolicy(CachingPolicy policy) { /* ... */ }
|
||||||
|
|
||||||
|
public static void initCacheCapacity(int capacity) { /* ... */ }
|
||||||
|
|
||||||
|
public static UserAccount find(String userId) {
|
||||||
|
if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
|
||||||
|
return CacheStore.readThrough(userId);
|
||||||
|
} else if (cachingPolicy == CachingPolicy.BEHIND) {
|
||||||
|
return CacheStore.readThroughWithWriteBackPolicy(userId);
|
||||||
|
} else if (cachingPolicy == CachingPolicy.ASIDE) {
|
||||||
|
return findAside(userId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void save(UserAccount userAccount) {
|
||||||
|
if (cachingPolicy == CachingPolicy.THROUGH) {
|
||||||
|
CacheStore.writeThrough(userAccount);
|
||||||
|
} else if (cachingPolicy == CachingPolicy.AROUND) {
|
||||||
|
CacheStore.writeAround(userAccount);
|
||||||
|
} else if (cachingPolicy == CachingPolicy.BEHIND) {
|
||||||
|
CacheStore.writeBehind(userAccount);
|
||||||
|
} else if (cachingPolicy == CachingPolicy.ASIDE) {
|
||||||
|
saveAside(userAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String printCacheContent() {
|
||||||
|
return CacheStore.print();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ... details omitted ... */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is what we do in the main class of the application.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Slf4j
|
||||||
|
public class App {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
AppManager.initDb(false);
|
||||||
|
AppManager.initCacheCapacity(3);
|
||||||
|
var app = new App();
|
||||||
|
app.useReadAndWriteThroughStrategy();
|
||||||
|
app.useReadThroughAndWriteAroundStrategy();
|
||||||
|
app.useReadThroughAndWriteBehindStrategy();
|
||||||
|
app.useCacheAsideStategy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useReadAndWriteThroughStrategy() {
|
||||||
|
LOGGER.info("# CachingPolicy.THROUGH");
|
||||||
|
AppManager.initCachingPolicy(CachingPolicy.THROUGH);
|
||||||
|
var userAccount1 = new UserAccount("001", "John", "He is a boy.");
|
||||||
|
AppManager.save(userAccount1);
|
||||||
|
LOGGER.info(AppManager.printCacheContent());
|
||||||
|
AppManager.find("001");
|
||||||
|
AppManager.find("001");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useReadThroughAndWriteAroundStrategy() { /* ... */ }
|
||||||
|
|
||||||
|
public void useReadThroughAndWriteBehindStrategy() { /* ... */ }
|
||||||
|
|
||||||
|
public void useCacheAsideStategy() { /* ... */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, here is some of the console output from the program.
|
||||||
|
|
||||||
|
```
|
||||||
|
12:32:53.845 [main] INFO com.iluwatar.caching.App - # CachingPolicy.THROUGH
|
||||||
|
12:32:53.900 [main] INFO com.iluwatar.caching.App -
|
||||||
|
--CACHE CONTENT--
|
||||||
|
UserAccount(userId=001, userName=John, additionalInfo=He is a boy.)
|
||||||
|
----
|
||||||
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Caching pattern(s) when
|
Use the Caching pattern(s) when
|
||||||
|
|
||||||
* Repetitious acquisition, initialization, and release of the same resource causes unnecessary performance overhead.
|
* Repetitious acquisition, initialization, and release of the same resource cause unnecessary
|
||||||
|
performance overhead.
|
||||||
|
|
||||||
|
## Related patterns
|
||||||
|
|
||||||
|
* [Proxy](https://java-design-patterns.com/patterns/proxy/)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* [Write-through, write-around, write-back: Cache explained](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained)
|
* [Write-through, write-around, write-back: Cache explained](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained)
|
||||||
* [Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching](https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177)
|
* [Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching](https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177)
|
||||||
* [Cache-Aside pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside)
|
* [Cache-Aside pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside)
|
||||||
|
* [Java EE 8 High Performance: Master techniques such as memory optimization, caching, concurrency, and multithreading to achieve maximum performance from your enterprise applications](https://www.amazon.com/gp/product/178847306X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=178847306X&linkId=e948720055599f248cdac47da9125ff4)
|
||||||
|
* [Java Performance: In-Depth Advice for Tuning and Programming Java 8, 11, and Beyond](https://www.amazon.com/gp/product/1492056111/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1492056111&linkId=7e553581559b9ec04221259e52004b08)
|
||||||
|
* [Effective Java](https://www.amazon.com/gp/product/B078H61SCH/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B078H61SCH&linkId=f06607a0b48c76541ef19c5b8b9e7882)
|
||||||
|
* [Java Performance: The Definitive Guide: Getting the Most Out of Your Code](https://www.amazon.com/gp/product/1449358454/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1449358454&linkId=475c18363e350630cc0b39ab681b2687)
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
|
|
||||||
package com.iluwatar.caching;
|
package com.iluwatar.caching;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Caching pattern describes how to avoid expensive re-acquisition of resources by not releasing
|
* The Caching pattern describes how to avoid expensive re-acquisition of resources by not releasing
|
||||||
@ -60,11 +59,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @see LruCache
|
* @see LruCache
|
||||||
* @see CachingPolicy
|
* @see CachingPolicy
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point.
|
* Program entry point.
|
||||||
*
|
*
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user