Compare commits
3 Commits
caching-pa
...
business-d
Author | SHA1 | Date | |
---|---|---|---|
a163a46e03 | |||
964f2c56ad | |||
e0f1376bd0 |
@ -1440,16 +1440,6 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"doc"
|
"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,
|
||||||
|
2
.github/workflows/maven-pr-builder.yml
vendored
2
.github/workflows/maven-pr-builder.yml
vendored
@ -29,7 +29,7 @@ name: Java PR Builder
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
types: [ opened, reopened, synchronize ]
|
types: [ opened, reopened, synchronize, labeled, unlabeled ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -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/>
|
||||||
@ -312,7 +312,6 @@ This project is licensed under the terms of the MIT license.
|
|||||||
<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://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://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>
|
||||||
|
|
||||||
|
@ -9,234 +9,18 @@ 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
|
||||||
|
|
||||||
|
BIN
bytecode/etc/bytecode.png
Normal file
BIN
bytecode/etc/bytecode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
49
bytecode/etc/bytecode.ucls
Normal file
49
bytecode/etc/bytecode.ucls
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<class-diagram version="1.2.3" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
|
||||||
|
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
|
||||||
|
<class id="1" language="java" name="com.iluwatar.bytecode.VirtualMachine" project="bytecode"
|
||||||
|
file="/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="455" y="173"/>
|
||||||
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
</display>
|
||||||
|
</class>
|
||||||
|
<class id="2" language="java" name="com.iluwatar.bytecode.App" project="bytecode"
|
||||||
|
file="/bytecode/src/main/java/com/iluwatar/bytecode/App.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="148" y="110"/>
|
||||||
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
</display>
|
||||||
|
</class>
|
||||||
|
<class id="3" language="java" name="com.iluwatar.bytecode.Wizard" project="bytecode"
|
||||||
|
file="/bytecode/src/main/java/com/iluwatar/bytecode/Wizard.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="148" y="416"/>
|
||||||
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
</display>
|
||||||
|
</class>
|
||||||
|
<association id="4">
|
||||||
|
<end type="SOURCE" refId="1" navigable="false" variant="ASSOCIATION">
|
||||||
|
<attribute id="5" name="wizards">
|
||||||
|
<position height="18" width="48" x="296" y="291"/>
|
||||||
|
</attribute>
|
||||||
|
<multiplicity id="6" minimum="0" maximum="2147483647">
|
||||||
|
<position height="0" width="0" x="-327" y="-27"/>
|
||||||
|
</multiplicity>
|
||||||
|
</end>
|
||||||
|
<end type="TARGET" refId="3" navigable="true" variant="ASSOCIATION"/>
|
||||||
|
<display labels="true" multiplicity="true"/>
|
||||||
|
</association>
|
||||||
|
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
</classifier-display>
|
||||||
|
<association-display labels="true" multiplicity="true"/>
|
||||||
|
</class-diagram>
|
Binary file not shown.
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 67 KiB |
@ -3,6 +3,7 @@ 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 {
|
||||||
@ -17,25 +18,22 @@ package com.iluwatar.bytecode {
|
|||||||
+ SET_HEALTH {static}
|
+ SET_HEALTH {static}
|
||||||
+ SET_WISDOM {static}
|
+ SET_WISDOM {static}
|
||||||
+ SPAWN_PARTICLES {static}
|
+ SPAWN_PARTICLES {static}
|
||||||
- intValue : int
|
- value : 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)
|
||||||
@ -47,7 +45,7 @@ package com.iluwatar.bytecode {
|
|||||||
- numberOfPlayedSounds : int
|
- numberOfPlayedSounds : int
|
||||||
- numberOfSpawnedParticles : int
|
- numberOfSpawnedParticles : int
|
||||||
- wisdom : int
|
- wisdom : int
|
||||||
+ Wizard(health : int, agility : int, wisdom : int, numberOfPlayedSounds : int, numberOfSpawnedParticles : int)
|
+ Wizard()
|
||||||
+ getAgility() : int
|
+ getAgility() : int
|
||||||
+ getHealth() : int
|
+ getHealth() : int
|
||||||
+ getNumberOfPlayedSounds() : int
|
+ getNumberOfPlayedSounds() : int
|
||||||
@ -56,8 +54,6 @@ 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()
|
||||||
}
|
}
|
||||||
|
@ -49,21 +49,33 @@ public class App {
|
|||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
var vm = new VirtualMachine(
|
var wizard = new Wizard();
|
||||||
new Wizard(45, 7, 11, 0, 0),
|
wizard.setHealth(45);
|
||||||
new Wizard(36, 18, 8, 0, 0));
|
wizard.setAgility(7);
|
||||||
|
wizard.setWisdom(11);
|
||||||
|
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
var vm = new VirtualMachine();
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
vm.getWizards()[0] = wizard;
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH"));
|
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
String literal = "LITERAL 0";
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY"));
|
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
interpretInstruction(literal, vm);
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM"));
|
interpretInstruction(literal, vm);
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
|
interpretInstruction("GET_HEALTH", vm);
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2"));
|
interpretInstruction(literal, vm);
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE"));
|
interpretInstruction("GET_AGILITY", vm);
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
|
interpretInstruction(literal, vm);
|
||||||
vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH"));
|
interpretInstruction("GET_WISDOM ", vm);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,17 +33,17 @@ import lombok.Getter;
|
|||||||
@Getter
|
@Getter
|
||||||
public enum Instruction {
|
public enum Instruction {
|
||||||
|
|
||||||
LITERAL(1), // e.g. "LITERAL 0", push 0 to stack
|
LITERAL(1),
|
||||||
SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health
|
SET_HEALTH(2),
|
||||||
SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom
|
SET_WISDOM(3),
|
||||||
SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility
|
SET_AGILITY(4),
|
||||||
PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound
|
PLAY_SOUND(5),
|
||||||
SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles
|
SPAWN_PARTICLES(6),
|
||||||
GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health
|
GET_HEALTH(7),
|
||||||
GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility
|
GET_AGILITY(8),
|
||||||
GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom
|
GET_WISDOM(9),
|
||||||
ADD(10), // e.g. "ADD", pop 2 values, push their sum
|
ADD(10),
|
||||||
DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division
|
DIVIDE(11);
|
||||||
|
|
||||||
private final int intValue;
|
private final int intValue;
|
||||||
|
|
||||||
|
@ -24,15 +24,12 @@
|
|||||||
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.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of virtual machine.
|
* Implementation of virtual machine.
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@Slf4j
|
|
||||||
public class VirtualMachine {
|
public class VirtualMachine {
|
||||||
|
|
||||||
private final Stack<Integer> stack = new Stack<>();
|
private final Stack<Integer> stack = new Stack<>();
|
||||||
@ -40,21 +37,12 @@ public class VirtualMachine {
|
|||||||
private final Wizard[] wizards = new Wizard[2];
|
private final Wizard[] wizards = new Wizard[2];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No-args constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
public VirtualMachine() {
|
public VirtualMachine() {
|
||||||
wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
|
for (var i = 0; i < wizards.length; i++) {
|
||||||
0, 0);
|
wizards[i] = new Wizard();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,7 +57,6 @@ 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:
|
||||||
@ -120,7 +107,6 @@ 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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +133,4 @@ public class VirtualMachine {
|
|||||||
public int getAgility(int wizard) {
|
public int getAgility(int wizard) {
|
||||||
return wizards[wizard].getAgility();
|
return wizards[wizard].getAgility();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int randomInt(int min, int max) {
|
|
||||||
return ThreadLocalRandom.current().nextInt(min, max + 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
|
|
||||||
package com.iluwatar.bytecode;
|
package com.iluwatar.bytecode;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -32,15 +31,16 @@ 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
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Wizard {
|
public class Wizard {
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@ -53,4 +53,5 @@ public class Wizard {
|
|||||||
LOGGER.info("Spawning particles");
|
LOGGER.info("Spawning particles");
|
||||||
numberOfSpawnedParticles++;
|
numberOfSpawnedParticles++;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -73,4 +73,6 @@ public class InstructionConverterUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,326 +10,20 @@ tags:
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
To avoid expensive re-acquisition of resources by not releasing
|
||||||
The caching pattern avoids expensive re-acquisition of resources by not releasing them immediately
|
the resources immediately after their use. The resources retain their identity, are kept in some
|
||||||
after use. The resources retain their identity, are kept in some fast-access storage, and are
|
fast-access storage, and are re-used to avoid having to acquire them again.
|
||||||
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 cause unnecessary
|
* Repetitious acquisition, initialization, and release of the same resource causes unnecessary performance overhead.
|
||||||
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)
|
|
||||||
|
@ -25,7 +25,6 @@ package com.iluwatar.caching;
|
|||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AppManager helps to bridge the gap in communication between the main class and the application's
|
* AppManager helps to bridge the gap in communication between the main class and the application's
|
||||||
@ -34,7 +33,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
* Depending on the chosen caching policy, AppManager will call the appropriate function in the
|
* Depending on the chosen caching policy, AppManager will call the appropriate function in the
|
||||||
* CacheStore class.
|
* CacheStore class.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
public final class AppManager {
|
public final class AppManager {
|
||||||
|
|
||||||
private static CachingPolicy cachingPolicy;
|
private static CachingPolicy cachingPolicy;
|
||||||
@ -52,7 +50,7 @@ public final class AppManager {
|
|||||||
try {
|
try {
|
||||||
DbManager.connect();
|
DbManager.connect();
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
LOGGER.error("Error connecting to MongoDB", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DbManager.createVirtualDb();
|
DbManager.createVirtualDb();
|
||||||
|
@ -30,7 +30,6 @@ import com.mongodb.client.model.UpdateOptions;
|
|||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.bson.Document;
|
import org.bson.Document;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,7 +41,6 @@ import org.bson.Document;
|
|||||||
* underlying data storage (connect()) or a simple Java data structure to (temporarily) store the
|
* underlying data storage (connect()) or a simple Java data structure to (temporarily) store the
|
||||||
* data/objects during runtime (createVirtualDB()).</p>
|
* data/objects during runtime (createVirtualDB()).</p>
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
public final class DbManager {
|
public final class DbManager {
|
||||||
|
|
||||||
private static MongoClient mongoClient;
|
private static MongoClient mongoClient;
|
||||||
@ -85,7 +83,7 @@ public final class DbManager {
|
|||||||
try {
|
try {
|
||||||
connect();
|
connect();
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
LOGGER.error("Error connecting to MongoDB", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var iterable = db
|
var iterable = db
|
||||||
@ -112,7 +110,7 @@ public final class DbManager {
|
|||||||
try {
|
try {
|
||||||
connect();
|
connect();
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
LOGGER.error("Error connecting to MongoDB", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.getCollection(CachingConstants.USER_ACCOUNT).insertOne(
|
db.getCollection(CachingConstants.USER_ACCOUNT).insertOne(
|
||||||
@ -134,7 +132,7 @@ public final class DbManager {
|
|||||||
try {
|
try {
|
||||||
connect();
|
connect();
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
LOGGER.error("Error connecting to MongoDB", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
|
db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
|
||||||
@ -155,7 +153,7 @@ public final class DbManager {
|
|||||||
try {
|
try {
|
||||||
connect();
|
connect();
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
LOGGER.error("Error connecting to MongoDB", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
|
db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
|
||||||
|
@ -99,11 +99,11 @@ public class BookViewModel {
|
|||||||
</zk>
|
</zk>
|
||||||
```
|
```
|
||||||
|
|
||||||
To deploy the example, go to model-view-viewmodel folder and run:
|
Note:
|
||||||
|
* To deploy this, go to model-view-viewmodel folder and run:
|
||||||
* `mvn clean install`
|
* mvn clean install
|
||||||
* `mvn jetty:run -Djetty.http.port=9911`
|
* mvn jetty:run -Djetty.http.port=9911
|
||||||
* Open browser to address: http://localhost:9911/model-view-viewmodel/
|
* In browser, http://localhost:9911/model-view-viewmodel/
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user