Compare commits
255 Commits
Author | SHA1 | Date | |
---|---|---|---|
e4458c1b17 | |||
30e09a8dd9 | |||
f987c7253b | |||
ad714294dd | |||
623c2081cd | |||
542a832a66 | |||
444eb07e26 | |||
fd8c05846f | |||
47709e24b9 | |||
09d3a82884 | |||
997bfba3b2 | |||
6326c1742d | |||
2e2707862a | |||
1d9aff4071 | |||
5611f26c77 | |||
d0cdf84936 | |||
a375b2d28b | |||
fcfdbe71f5 | |||
52c483f1d0 | |||
c72faeb67e | |||
299d612b9b | |||
9d4c3154b1 | |||
a3b1265921 | |||
dca68511e8 | |||
42a1dc69ca | |||
47e1cd710c | |||
b3d1c2b2ed | |||
3e20a2afa8 | |||
4c938ab8a5 | |||
8f6f171a3f | |||
b4dcec45ef | |||
dec5ff22fc | |||
7b323e9cb4 | |||
a0af0a1a76 | |||
7c6e1fc3d8 | |||
45cecf68c4 | |||
d7580f5530 | |||
58b8a06561 | |||
befe509582 | |||
17b889c31d | |||
52192de909 | |||
8b020837ea | |||
b369812511 | |||
52d6e20ad9 | |||
749880e3b9 | |||
41593774c6 | |||
0bc722f797 | |||
fcadb223ce | |||
c842f88eb7 | |||
6d516d5124 | |||
9544dd321a | |||
d623e9c8fd | |||
e27de33f75 | |||
7ac7e3b097 | |||
fefb51c46e | |||
58547fae8f | |||
30676f8d4f | |||
483db04cef | |||
fac10dc454 | |||
982f9f5e31 | |||
74d1823078 | |||
f59f18091e | |||
5f033be54f | |||
df911baf36 | |||
32e7181478 | |||
c8a750df49 | |||
191078735f | |||
fa6a6006c3 | |||
a49dbefb56 | |||
fb0617e9c5 | |||
84fd2348ea | |||
f0ff8ad4b3 | |||
c3184255bf | |||
bdacfe30c1 | |||
30363cbb7f | |||
3d8c64d76d | |||
78fcd63271 | |||
30bc25b5bf | |||
6b1356a160 | |||
cec9a99410 | |||
9fbb085985 | |||
6e496e7c86 | |||
e3e0e32e92 | |||
531158c836 | |||
6fe01e73b2 | |||
69c9374669 | |||
a57a71b09c | |||
719f80a0d6 | |||
2c82bd9450 | |||
5948a82cf2 | |||
a0151826ad | |||
33fe90d177 | |||
25cacdbbc9 | |||
323e4c8751 | |||
de78490d29 | |||
9059d2b96c | |||
dbca06a9e7 | |||
ca14e8ddad | |||
3dc370e2d1 | |||
4181514c65 | |||
df69a8f986 | |||
885d5bb7dd | |||
1b74e0ff67 | |||
94f80d1868 | |||
c837ffe234 | |||
2edc1898b1 | |||
29fc56002a | |||
0643289c74 | |||
2c0a5e8703 | |||
a3372febd0 | |||
7df65adbfc | |||
d7dd8d42af | |||
4e7a8fdaca | |||
6e59d9554f | |||
e85308fbc4 | |||
10a94cc91c | |||
e2d8079b36 | |||
8367c83aca | |||
875e5b872c | |||
1fa617d08d | |||
6c1f025d0f | |||
601964a2d1 | |||
1884df525b | |||
507b89d5e4 | |||
02d6754804 | |||
e709a196bd | |||
f018d13c39 | |||
33b41f872e | |||
119d264779 | |||
9a85dfe9df | |||
5bec63659f | |||
9d4fff6029 | |||
092d48d150 | |||
6b99f2669e | |||
ced317bc9d | |||
e5614e5a20 | |||
012b638023 | |||
b577890db4 | |||
8ba0192864 | |||
899b9617c9 | |||
d0fca6d318 | |||
7885380633 | |||
230c02fb24 | |||
b97e9207a7 | |||
8519e13de6 | |||
ba3f583467 | |||
142274f3f7 | |||
5b06c52fff | |||
033f8e740d | |||
34528173b7 | |||
fdbfa9e8ee | |||
bf65168776 | |||
043a610754 | |||
27199325ec | |||
960b568fbb | |||
c02e65fa3a | |||
fe63c9cec4 | |||
7e4f04602e | |||
306b1f3d31 | |||
449340bd2b | |||
3af06a3a3a | |||
e7b6542134 | |||
16a8c85af6 | |||
95c16200e7 | |||
c0c21ebd91 | |||
25c1f87d46 | |||
c6ca80b25f | |||
a2dd87d28d | |||
998ba7e6e0 | |||
6418a6c2b6 | |||
37cfa4b295 | |||
7d3483daad | |||
44b7b94da6 | |||
87baa98d2b | |||
3dc9b2f97e | |||
d0f5009996 | |||
9891c2e17b | |||
07faa2f625 | |||
a869294851 | |||
ac5c1967df | |||
a2b8359ab5 | |||
21a4d7b4c9 | |||
83c2fbdcd3 | |||
8eff279233 | |||
b961c57919 | |||
b028f66b08 | |||
339db2a0bf | |||
9cbc4a20b9 | |||
07a77ba11d | |||
e09dda6037 | |||
7ab799c452 | |||
3c8b83748a | |||
5d970438bf | |||
0d068a35d8 | |||
64e3e1a9e8 | |||
31e2940eb1 | |||
0107b24976 | |||
7cf5b32086 | |||
45b0ac386e | |||
6ba7f5ea04 | |||
271ce09e26 | |||
9d0673af0a | |||
eb22febdfc | |||
9a824c7d21 | |||
81b5bc99ce | |||
19f5966cb9 | |||
1e988c10f9 | |||
2c45f73254 | |||
c8fd9f3a0d | |||
0a9879a277 | |||
2234a25c76 | |||
74e32259be | |||
8db2bb2ac8 | |||
c3a827b475 | |||
9a4f83e7b8 | |||
40e378c1f3 | |||
98aa28d94e | |||
4d1aae21f7 | |||
ca6bb7a3a8 | |||
fc66ae8084 | |||
e4ff39e080 | |||
44d7be9c94 | |||
b4118bb866 | |||
c27291fd27 | |||
c989f6cb21 | |||
516b127d21 | |||
584a22238d | |||
e32b440a38 | |||
8cf35fc315 | |||
621793ed2d | |||
807478ab3d | |||
d5f52edecf | |||
a2f3d58709 | |||
9a08e35101 | |||
cdd586ec7c | |||
8c6caa29b7 | |||
0a61d7b067 | |||
6413c4d2be | |||
11cdd20f6f | |||
621c3498f6 | |||
83fed6dd34 | |||
793b1cc172 | |||
2ad361f2c3 | |||
e077aa34d7 | |||
c1fda3ad6c | |||
60f9b71278 | |||
ba6511fe5d | |||
36809537d9 | |||
6735c81b52 | |||
ec9af416ba | |||
b3e4e8a47b | |||
d0af12b1ee | |||
51dca28fe7 | |||
9c43827004 | |||
3da48d970c |
@ -1,20 +1,18 @@
|
|||||||
language: java
|
language: java
|
||||||
|
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- GH_REF: github.com/iluwatar/java-design-patterns.git
|
- GH_REF: github.com/iluwatar/java-design-patterns.git
|
||||||
- secure: "LxTDuNS/rBWIvKkaEqr79ImZAe48mCdoYCF41coxNXgNoippo4GIBArknqtv+XvdkiuRZ1yGyj6pn8GU33c/yn+krddTUkVCwTbVatbalW5jhQjDbHYym/JcxaK9ZS/3JTeGcWrBgiPqHEEDhCf26vPZsXoMSeVCEORVKTp1BSg="
|
- secure: LxTDuNS/rBWIvKkaEqr79ImZAe48mCdoYCF41coxNXgNoippo4GIBArknqtv+XvdkiuRZ1yGyj6pn8GU33c/yn+krddTUkVCwTbVatbalW5jhQjDbHYym/JcxaK9ZS/3JTeGcWrBgiPqHEEDhCf26vPZsXoMSeVCEORVKTp1BSg=
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- "export DISPLAY=:99.0"
|
- export DISPLAY=:99.0
|
||||||
- "sh -e /etc/init.d/xvfb start"
|
- sh -e /etc/init.d/xvfb start
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- mvn clean test jacoco:report coveralls:report
|
- mvn clean test jacoco:report coveralls:report
|
||||||
- bash update-ghpages.sh
|
- bash update-ghpages.sh
|
||||||
|
|
||||||
# Migration to container-based infrastructure
|
|
||||||
sudo: false
|
sudo: false
|
||||||
|
13
CODE_COVERAGE.md
Normal file
13
CODE_COVERAGE.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Code Coverage Report generation
|
||||||
|
|
||||||
|
To generate the code coverage report, execute the following command:
|
||||||
|
> mvn clean verify
|
||||||
|
|
||||||
|
This will generate code coverage report in each of the modules. In order to view the same, open the following file in your browser.
|
||||||
|
> target/site/jacoco/index.html
|
||||||
|
|
||||||
|
Please note that the above folder is created under each of the modules. For example:
|
||||||
|
* adapter/target/site/jacoco/index.html
|
||||||
|
* busniess-delegate/target/site/jacoco/index.html
|
||||||
|
|
||||||
|
|
4
CONTRIBUTING.MD
Normal file
4
CONTRIBUTING.MD
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
This is great you have something to contribute!
|
||||||
|
|
||||||
|
Before going any further please read the [wiki](https://github.com/iluwatar/java-design-patterns/wiki)
|
||||||
|
with conventions and rules we used for this project.
|
15
README.md
15
README.md
@ -2,11 +2,10 @@
|
|||||||
that smart and dearly wants an empty line before a heading to be able to
|
that smart and dearly wants an empty line before a heading to be able to
|
||||||
display it as such, e.g. website) -->
|
display it as such, e.g. website) -->
|
||||||
|
|
||||||
# Design pattern samples in Java
|
# Design patterns implemented in Java
|
||||||
|
|
||||||
[](https://travis-ci.org/iluwatar/java-design-patterns)
|
[](https://travis-ci.org/iluwatar/java-design-patterns)
|
||||||
[](https://coveralls.io/r/iluwatar/java-design-patterns?branch=master)
|
[](https://coveralls.io/r/iluwatar/java-design-patterns?branch=master)
|
||||||
[](https://scan.coverity.com/projects/5634)
|
|
||||||
[](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)
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
@ -26,7 +25,13 @@ are familiar with the patterns.
|
|||||||
Before you dive into the material, you should be familiar with various
|
Before you dive into the material, you should be familiar with various
|
||||||
[Programming/Software Design Principles](http://webpro.github.io/programming-principles/).
|
[Programming/Software Design Principles](http://webpro.github.io/programming-principles/).
|
||||||
|
|
||||||
Once you are familiar with these concepts you can start drilling down into patterns by any of the following approaches
|
All designs should be as simple as possible. You should start with KISS, YAGNI,
|
||||||
|
and Do The Simplest Thing That Could Possibly Work principles. Complexity and
|
||||||
|
patterns should only be introduced when they are needed for practical
|
||||||
|
extensibility.
|
||||||
|
|
||||||
|
Once you are familiar with these concepts you can start drilling down into
|
||||||
|
patterns by any of the following approaches
|
||||||
|
|
||||||
- Using difficulty tags, `Difficulty-Beginner`, `Difficulty-Intermediate` & `Difficulty-Expert`.
|
- Using difficulty tags, `Difficulty-Beginner`, `Difficulty-Intermediate` & `Difficulty-Expert`.
|
||||||
- Using pattern categories, `Creational`, `Behavioral` and others.
|
- Using pattern categories, `Creational`, `Behavioral` and others.
|
||||||
@ -34,11 +39,10 @@ Once you are familiar with these concepts you can start drilling down into patte
|
|||||||
|
|
||||||
# How to contribute
|
# How to contribute
|
||||||
|
|
||||||
If you are willing to contribute to the project you will find the relevant information in our [developer wiki](https://github.com/iluwatar/java-design-patterns/wiki).
|
If you are willing to contribute to the project you will find the relevant information in our [developer wiki](https://github.com/iluwatar/java-design-patterns/wiki). We will help you and answer your questions in the [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns).
|
||||||
|
|
||||||
# Credits
|
# Credits
|
||||||
|
|
||||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
|
|
||||||
* [Effective Java (2nd Edition)](http://www.amazon.com/Effective-Java-Edition-Joshua-Bloch/dp/0321356683)
|
* [Effective Java (2nd Edition)](http://www.amazon.com/Effective-Java-Edition-Joshua-Bloch/dp/0321356683)
|
||||||
* [Java Generics and Collections](http://www.amazon.com/Java-Generics-Collections-Maurice-Naftalin/dp/0596527756/)
|
* [Java Generics and Collections](http://www.amazon.com/Java-Generics-Collections-Maurice-Naftalin/dp/0596527756/)
|
||||||
* [Let's Modify the Objects-First Approach into Design-Patterns-First](http://edu.pecinovsky.cz/papers/2006_ITiCSE_Design_Patterns_First.pdf)
|
* [Let's Modify the Objects-First Approach into Design-Patterns-First](http://edu.pecinovsky.cz/papers/2006_ITiCSE_Design_Patterns_First.pdf)
|
||||||
@ -48,7 +52,6 @@ If you are willing to contribute to the project you will find the relevant infor
|
|||||||
* [Patterns of Enterprise Application Architecture](http://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420)
|
* [Patterns of Enterprise Application Architecture](http://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420)
|
||||||
* [Spring Data](http://www.amazon.com/Spring-Data-Mark-Pollack/dp/1449323952/ref=sr_1_1)
|
* [Spring Data](http://www.amazon.com/Spring-Data-Mark-Pollack/dp/1449323952/ref=sr_1_1)
|
||||||
* [J2EE Design Patterns](http://www.amazon.com/J2EE-Design-Patterns-William-Crawford/dp/0596004273/ref=sr_1_2)
|
* [J2EE Design Patterns](http://www.amazon.com/J2EE-Design-Patterns-William-Crawford/dp/0596004273/ref=sr_1_2)
|
||||||
* [Pattern Oriented Software Architecture Vol I-V](http://www.amazon.com/Pattern-Oriented-Software-Architecture-Volume-Patterns/dp/0471958697)
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
|
@ -4,9 +4,14 @@ title: Abstract Factory
|
|||||||
folder: abstract-factory
|
folder: abstract-factory
|
||||||
permalink: /patterns/abstract-factory/
|
permalink: /patterns/abstract-factory/
|
||||||
categories: Creational
|
categories: Creational
|
||||||
tags: Java
|
tags:
|
||||||
|
- Java
|
||||||
|
- Gang Of Four
|
||||||
|
- Difficulty-Intermediate
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Also known as:** Kit
|
||||||
|
|
||||||
**Intent:** Provide an interface for creating families of related or dependent
|
**Intent:** Provide an interface for creating families of related or dependent
|
||||||
objects without specifying their concrete classes.
|
objects without specifying their concrete classes.
|
||||||
|
|
||||||
@ -22,3 +27,7 @@ objects without specifying their concrete classes.
|
|||||||
**Real world examples:**
|
**Real world examples:**
|
||||||
|
|
||||||
* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html)
|
* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html)
|
||||||
|
|
||||||
|
**Credits**
|
||||||
|
|
||||||
|
* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project
|
<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"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.iluwatar</groupId>
|
<groupId>com.iluwatar</groupId>
|
||||||
<artifactId>java-design-patterns</artifactId>
|
<artifactId>java-design-patterns</artifactId>
|
||||||
<version>1.6.0</version>
|
<version>1.9.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>abstract-factory</artifactId>
|
<artifactId>abstract-factory</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
package com.iluwatar.abstractfactory;
|
package com.iluwatar.abstractfactory;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* The essence of the Abstract Factory pattern is a factory interface
|
* The Abstract Factory pattern provides a way to encapsulate a group of individual factories that have a common theme
|
||||||
* ({@link KingdomFactory}) and its implementations ({@link ElfKingdomFactory},
|
* without specifying their concrete classes. In normal usage, the client software creates a concrete implementation of
|
||||||
* {@link OrcKingdomFactory}).
|
* the abstract factory and then uses the generic interface of the factory to create the concrete objects that are part
|
||||||
|
* of the theme. The client does not know (or care) which concrete objects it gets from each of these internal
|
||||||
|
* factories, since it uses only the generic interfaces of their products. This pattern separates the details of
|
||||||
|
* implementation of a set of objects from their general usage and relies on object composition, as object creation is
|
||||||
|
* implemented in methods exposed in the factory interface.
|
||||||
* <p>
|
* <p>
|
||||||
* The example uses both concrete implementations to create a king, a castle and
|
* The essence of the Abstract Factory pattern is a factory interface ({@link KingdomFactory}) and its implementations (
|
||||||
* an army.
|
* {@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses both concrete implementations to create a
|
||||||
|
* king, a castle and an army.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class App {
|
public class App {
|
||||||
@ -19,7 +23,6 @@ public class App {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates kingdom
|
* Creates kingdom
|
||||||
* @param factory
|
|
||||||
*/
|
*/
|
||||||
public void createKingdom(final KingdomFactory factory) {
|
public void createKingdom(final KingdomFactory factory) {
|
||||||
setKing(factory.createKing());
|
setKing(factory.createKing());
|
||||||
@ -39,14 +42,6 @@ public class App {
|
|||||||
return factory.createKing();
|
return factory.createKing();
|
||||||
}
|
}
|
||||||
|
|
||||||
Castle getCastle(final KingdomFactory factory) {
|
|
||||||
return factory.createCastle();
|
|
||||||
}
|
|
||||||
|
|
||||||
Army getArmy(final KingdomFactory factory) {
|
|
||||||
return factory.createArmy();
|
|
||||||
}
|
|
||||||
|
|
||||||
public King getKing() {
|
public King getKing() {
|
||||||
return king;
|
return king;
|
||||||
}
|
}
|
||||||
@ -55,6 +50,10 @@ public class App {
|
|||||||
this.king = king;
|
this.king = king;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Castle getCastle(final KingdomFactory factory) {
|
||||||
|
return factory.createCastle();
|
||||||
|
}
|
||||||
|
|
||||||
public Castle getCastle() {
|
public Castle getCastle() {
|
||||||
return castle;
|
return castle;
|
||||||
}
|
}
|
||||||
@ -63,6 +62,10 @@ public class App {
|
|||||||
this.castle = castle;
|
this.castle = castle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Army getArmy(final KingdomFactory factory) {
|
||||||
|
return factory.createArmy();
|
||||||
|
}
|
||||||
|
|
||||||
public Army getArmy() {
|
public Army getArmy() {
|
||||||
return army;
|
return army;
|
||||||
}
|
}
|
||||||
@ -70,4 +73,33 @@ public class App {
|
|||||||
private void setArmy(final Army army) {
|
private void setArmy(final Army army) {
|
||||||
this.army = army;
|
this.army = army;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program entry point
|
||||||
|
*
|
||||||
|
* @param args
|
||||||
|
* command line args
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
App app = new App();
|
||||||
|
|
||||||
|
System.out.println("Elf Kingdom");
|
||||||
|
KingdomFactory elfKingdomFactory;
|
||||||
|
elfKingdomFactory = app.getElfKingdomFactory();
|
||||||
|
app.createKingdom(elfKingdomFactory);
|
||||||
|
System.out.println(app.getArmy().getDescription());
|
||||||
|
System.out.println(app.getCastle().getDescription());
|
||||||
|
System.out.println(app.getKing().getDescription());
|
||||||
|
|
||||||
|
System.out.println("\nOrc Kingdom");
|
||||||
|
KingdomFactory orcKingdomFactory;
|
||||||
|
orcKingdomFactory = app.getOrcKingdomFactory();
|
||||||
|
app.createKingdom(orcKingdomFactory);
|
||||||
|
System.out.println(app.getArmy().getDescription());
|
||||||
|
System.out.println(app.getCastle().getDescription());
|
||||||
|
System.out.println(app.getKing().getDescription());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,5 +18,4 @@ public class OrcKingdomFactory implements KingdomFactory {
|
|||||||
public Army createArmy() {
|
public Army createArmy() {
|
||||||
return new OrcArmy();
|
return new OrcArmy();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
package com.iluwatar.abstractfactory;
|
package com.iluwatar.abstractfactory;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@ -7,7 +8,7 @@ import org.junit.Test;
|
|||||||
|
|
||||||
public class AppTest {
|
public class AppTest {
|
||||||
|
|
||||||
private App app = new App();;
|
private App app = new App();
|
||||||
private KingdomFactory elfFactory;
|
private KingdomFactory elfFactory;
|
||||||
private KingdomFactory orcFactory;
|
private KingdomFactory orcFactory;
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@ -1,61 +1,61 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
<class-diagram version="1.1.9" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
|
||||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
|
||||||
<class id="1" language="java" name="com.iluwatar.adapter.GnomeEngineeringManager" project="adapter"
|
<class id="1" language="java" name="com.iluwatar.adapter.FishingBoat" project="adapter"
|
||||||
file="/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineeringManager.java" binary="false" corner="BOTTOM_RIGHT">
|
file="/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
<position height="106" width="224" x="110" y="210"/>
|
<position height="-1" width="-1" x="656" y="355"/>
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
sort-features="false" accessors="true" visibility="true">
|
sort-features="false" accessors="true" visibility="true">
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
</display>
|
</display>
|
||||||
</class>
|
</class>
|
||||||
<interface id="2" language="java" name="com.iluwatar.adapter.Engineer" project="adapter"
|
<class id="2" language="java" name="com.iluwatar.adapter.Captain" project="adapter"
|
||||||
file="/adapter/src/main/java/com/iluwatar/adapter/Engineer.java" binary="false" corner="BOTTOM_RIGHT">
|
file="/adapter/src/main/java/com/iluwatar/adapter/Captain.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
<position height="88" width="141" x="110" y="356"/>
|
<position height="-1" width="-1" x="228" y="185"/>
|
||||||
|
<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.adapter.BattleFishingBoat" project="adapter"
|
||||||
|
file="/adapter/src/main/java/com/iluwatar/adapter/BattleFishingBoat.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="463" y="357"/>
|
||||||
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
</display>
|
||||||
|
</class>
|
||||||
|
<interface id="4" language="java" name="com.iluwatar.adapter.BattleShip" project="adapter"
|
||||||
|
file="/adapter/src/main/java/com/iluwatar/adapter/BattleShip.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="466" y="170"/>
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
sort-features="false" accessors="true" visibility="true">
|
sort-features="false" accessors="true" visibility="true">
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
</display>
|
</display>
|
||||||
</interface>
|
</interface>
|
||||||
<class id="3" language="java" name="com.iluwatar.adapter.GnomeEngineer" project="adapter"
|
|
||||||
file="/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineer.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="106" width="141" x="374" 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="4" language="java" name="com.iluwatar.adapter.GoblinGlider" project="adapter"
|
|
||||||
file="/adapter/src/main/java/com/iluwatar/adapter/GoblinGlider.java" binary="false" corner="BOTTOM_RIGHT">
|
|
||||||
<position height="142" width="130" x="374" y="356"/>
|
|
||||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
|
||||||
sort-features="false" accessors="true" visibility="true">
|
|
||||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
|
||||||
</display>
|
|
||||||
</class>
|
|
||||||
<realization id="5">
|
<realization id="5">
|
||||||
<end type="SOURCE" refId="1"/>
|
<end type="SOURCE" refId="2"/>
|
||||||
<end type="TARGET" refId="2"/>
|
<end type="TARGET" refId="4"/>
|
||||||
</realization>
|
</realization>
|
||||||
<realization id="6">
|
<association id="6">
|
||||||
<end type="SOURCE" refId="3"/>
|
<end type="SOURCE" refId="3" navigable="false">
|
||||||
<end type="TARGET" refId="2"/>
|
<attribute id="7" name="boat"/>
|
||||||
</realization>
|
<multiplicity id="8" minimum="0" maximum="1"/>
|
||||||
<association id="7">
|
|
||||||
<end type="SOURCE" refId="1" navigable="false">
|
|
||||||
<attribute id="8" name="engineer"/>
|
|
||||||
<multiplicity id="9" minimum="0" maximum="1"/>
|
|
||||||
</end>
|
</end>
|
||||||
<end type="TARGET" refId="2" navigable="true"/>
|
<end type="TARGET" refId="1" navigable="true"/>
|
||||||
<display labels="true" multiplicity="true"/>
|
<display labels="true" multiplicity="true"/>
|
||||||
</association>
|
</association>
|
||||||
|
<realization id="9">
|
||||||
|
<end type="SOURCE" refId="3"/>
|
||||||
|
<end type="TARGET" refId="4"/>
|
||||||
|
</realization>
|
||||||
<association id="10">
|
<association id="10">
|
||||||
<end type="SOURCE" refId="3" navigable="false">
|
<end type="SOURCE" refId="2" navigable="false">
|
||||||
<attribute id="11" name="glider"/>
|
<attribute id="11" name="battleship"/>
|
||||||
<multiplicity id="12" minimum="0" maximum="1"/>
|
<multiplicity id="12" minimum="0" maximum="1"/>
|
||||||
</end>
|
</end>
|
||||||
<end type="TARGET" refId="4" navigable="true"/>
|
<end type="TARGET" refId="4" navigable="true"/>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 29 KiB |
@ -4,14 +4,19 @@ title: Adapter
|
|||||||
folder: adapter
|
folder: adapter
|
||||||
permalink: /patterns/adapter/
|
permalink: /patterns/adapter/
|
||||||
categories: Structural
|
categories: Structural
|
||||||
tags: Java
|
tags:
|
||||||
|
- Java
|
||||||
|
- Gang Of Four
|
||||||
|
- Difficulty-Beginner
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Also known as:** Wrapper
|
||||||
|
|
||||||
**Intent:** Convert the interface of a class into another interface the clients
|
**Intent:** Convert the interface of a class into another interface the clients
|
||||||
expect. Adapter lets classes work together that couldn't otherwise because of
|
expect. Adapter lets classes work together that couldn't otherwise because of
|
||||||
incompatible interfaces.
|
incompatible interfaces.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**Applicability:** Use the Adapter pattern when
|
**Applicability:** Use the Adapter pattern when
|
||||||
|
|
||||||
@ -22,3 +27,7 @@ incompatible interfaces.
|
|||||||
**Real world examples:**
|
**Real world examples:**
|
||||||
|
|
||||||
* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29)
|
* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29)
|
||||||
|
|
||||||
|
**Credits**
|
||||||
|
|
||||||
|
* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project
|
<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"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.iluwatar</groupId>
|
<groupId>com.iluwatar</groupId>
|
||||||
<artifactId>java-design-patterns</artifactId>
|
<artifactId>java-design-patterns</artifactId>
|
||||||
<version>1.6.0</version>
|
<version>1.9.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>adapter</artifactId>
|
<artifactId>adapter</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -15,5 +14,10 @@
|
|||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -1,25 +1,38 @@
|
|||||||
package com.iluwatar.adapter;
|
package com.iluwatar.adapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* An adapter helps two incompatible interfaces to work together. This is the real world definition
|
||||||
|
* for an adapter. Interfaces may be incompatible but the inner functionality should suit the need.
|
||||||
|
* The Adapter design pattern allows otherwise incompatible classes to work together by converting
|
||||||
|
* the interface of one class into an interface expected by the clients.
|
||||||
*
|
*
|
||||||
* There are two variations of the Adapter pattern: The class adapter implements
|
|
||||||
* the adaptee's interface whereas the object adapter uses composition to
|
|
||||||
* contain the adaptee in the adapter object. This example uses the object
|
|
||||||
* adapter approach.
|
|
||||||
* <p>
|
* <p>
|
||||||
* The Adapter ({@link GnomeEngineer}) converts the interface of the target class
|
* There are two variations of the Adapter pattern: The class adapter implements the adaptee's
|
||||||
* ({@link GoblinGlider}) into a suitable one expected by the client
|
* interface whereas the object adapter uses composition to contain the adaptee in the adapter
|
||||||
* ({@link GnomeEngineeringManager}).
|
* object. This example uses the object adapter approach.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The Adapter ({@link BattleFishingBoat}) converts the interface of the adaptee class (
|
||||||
|
* {@link FishingBoat}) into a suitable one expected by the client ( {@link BattleShip} ).
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The story of this implementation is this. <br>
|
||||||
|
* Pirates are coming! we need a {@link BattleShip} to fight! We have a {@link FishingBoat} and our
|
||||||
|
* captain. We have no time to make up a new ship! we need to reuse this {@link FishingBoat}. The
|
||||||
|
* captain needs a battleship which can fire and move. The spec is in {@link BattleShip}. We will
|
||||||
|
* use the Adapter pattern to reuse {@link FishingBoat}.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point
|
* Program entry point.
|
||||||
|
*
|
||||||
* @param args command line args
|
* @param args command line args
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Engineer manager = new GnomeEngineeringManager();
|
Captain captain = new Captain(new BattleFishingBoat());
|
||||||
manager.operateDevice();
|
captain.move();
|
||||||
|
captain.fire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.iluwatar.adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Adapter class. Adapts the interface of the device ({@link FishingBoat}) into {@link BattleShip}
|
||||||
|
* interface expected by the client ({@link Captain}). <br>
|
||||||
|
* In this case we added a new function fire to suit the interface. We are reusing the
|
||||||
|
* {@link FishingBoat} without changing itself. The Adapter class can just map the functions of the
|
||||||
|
* Adaptee or add, delete features of the Adaptee.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class BattleFishingBoat implements BattleShip {
|
||||||
|
|
||||||
|
private FishingBoat boat;
|
||||||
|
|
||||||
|
public BattleFishingBoat() {
|
||||||
|
boat = new FishingBoat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fire() {
|
||||||
|
System.out.println("fire!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void move() {
|
||||||
|
boat.sail();
|
||||||
|
}
|
||||||
|
}
|
14
adapter/src/main/java/com/iluwatar/adapter/BattleShip.java
Normal file
14
adapter/src/main/java/com/iluwatar/adapter/BattleShip.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package com.iluwatar.adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface expected by the client.<br>
|
||||||
|
* A Battleship can fire and move.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface BattleShip {
|
||||||
|
|
||||||
|
void fire();
|
||||||
|
|
||||||
|
void move();
|
||||||
|
|
||||||
|
}
|
33
adapter/src/main/java/com/iluwatar/adapter/Captain.java
Normal file
33
adapter/src/main/java/com/iluwatar/adapter/Captain.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package com.iluwatar.adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Captain uses {@link BattleShip} to fight. <br>
|
||||||
|
* This is the client in the pattern.
|
||||||
|
*/
|
||||||
|
public class Captain implements BattleShip {
|
||||||
|
|
||||||
|
private BattleShip battleship;
|
||||||
|
|
||||||
|
public Captain() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Captain(BattleShip battleship) {
|
||||||
|
this.battleship = battleship;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBattleship(BattleShip battleship) {
|
||||||
|
this.battleship = battleship;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fire() {
|
||||||
|
battleship.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void move() {
|
||||||
|
battleship.move();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
package com.iluwatar.adapter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Engineers can operate devices.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface Engineer {
|
|
||||||
|
|
||||||
void operateDevice();
|
|
||||||
|
|
||||||
}
|
|
18
adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java
Normal file
18
adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package com.iluwatar.adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Device class (adaptee in the pattern). We want to reuse this class
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class FishingBoat {
|
||||||
|
|
||||||
|
public void sail() {
|
||||||
|
System.out.println("The Boat is moving to that place");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fish() {
|
||||||
|
System.out.println("fishing ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
package com.iluwatar.adapter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Adapter class. Adapts the interface of the device ({@link GoblinGlider}) into
|
|
||||||
* {@link Engineer} interface expected by the client ({@link GnomeEngineeringManager}).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class GnomeEngineer implements Engineer {
|
|
||||||
|
|
||||||
private GoblinGlider glider;
|
|
||||||
|
|
||||||
public GnomeEngineer() {
|
|
||||||
glider = new GoblinGlider();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void operateDevice() {
|
|
||||||
glider.attachGlider();
|
|
||||||
glider.gainSpeed();
|
|
||||||
glider.takeOff();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package com.iluwatar.adapter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* GnomeEngineering manager uses {@link Engineer} to operate devices.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class GnomeEngineeringManager implements Engineer {
|
|
||||||
|
|
||||||
private Engineer engineer;
|
|
||||||
|
|
||||||
public GnomeEngineeringManager() {
|
|
||||||
engineer = new GnomeEngineer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void operateDevice() {
|
|
||||||
engineer.operateDevice();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package com.iluwatar.adapter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Device class (adaptee in the pattern).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class GoblinGlider {
|
|
||||||
|
|
||||||
public void attachGlider() {
|
|
||||||
System.out.println("Glider attached.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void gainSpeed() {
|
|
||||||
System.out.println("Gaining speed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void takeOff() {
|
|
||||||
System.out.println("Lift-off!");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,61 @@
|
|||||||
|
package com.iluwatar.adapter;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test class
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class AdapterPatternTest {
|
||||||
|
|
||||||
|
private Map<String, Object> beans;
|
||||||
|
|
||||||
|
private static final String BATTLESHIP_BEAN = "engineer";
|
||||||
|
|
||||||
|
private static final String CAPTAIN_BEAN = "captain";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method runs before the test execution and sets the bean objects in the beans Map.
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
beans = new HashMap<>();
|
||||||
|
|
||||||
|
BattleFishingBoat battleFishingBoat = spy(new BattleFishingBoat());
|
||||||
|
beans.put(BATTLESHIP_BEAN, battleFishingBoat);
|
||||||
|
|
||||||
|
Captain captain = new Captain();
|
||||||
|
captain.setBattleship((BattleFishingBoat) beans.get(BATTLESHIP_BEAN));
|
||||||
|
beans.put(CAPTAIN_BEAN, captain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test asserts that when we use the move() method on a captain bean(client), it is
|
||||||
|
* internally calling move method on the battleship object. The Adapter ({@link BattleFishingBoat}
|
||||||
|
* ) converts the interface of the target class ( {@link FishingBoat}) into a suitable one
|
||||||
|
* expected by the client ({@link Captain} ).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAdapter() {
|
||||||
|
BattleShip captain = (BattleShip) beans.get(CAPTAIN_BEAN);
|
||||||
|
|
||||||
|
// when captain moves
|
||||||
|
captain.move();
|
||||||
|
|
||||||
|
// the captain internally calls the battleship object to move
|
||||||
|
BattleShip battleship = (BattleShip) beans.get(BATTLESHIP_BEAN);
|
||||||
|
verify(battleship).move();
|
||||||
|
|
||||||
|
// same with above with firing
|
||||||
|
captain.fire();
|
||||||
|
verify(battleship).fire();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
package com.iluwatar.adapter;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.iluwatar.adapter.App;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Application test
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class AppTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
String[] args = {};
|
|
||||||
App.main(args);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,10 @@ title: Async Method Invocation
|
|||||||
folder: async-method-invocation
|
folder: async-method-invocation
|
||||||
permalink: /patterns/async-method-invocation/
|
permalink: /patterns/async-method-invocation/
|
||||||
categories: Concurrency
|
categories: Concurrency
|
||||||
tags: Java
|
tags:
|
||||||
|
- Java
|
||||||
|
- Difficulty-Intermediate
|
||||||
|
- Functional
|
||||||
---
|
---
|
||||||
|
|
||||||
**Intent:** Asynchronous method invocation is pattern where the calling thread
|
**Intent:** Asynchronous method invocation is pattern where the calling thread
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project
|
<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"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.iluwatar</groupId>
|
<groupId>com.iluwatar</groupId>
|
||||||
<artifactId>java-design-patterns</artifactId>
|
<artifactId>java-design-patterns</artifactId>
|
||||||
<version>1.6.0</version>
|
<version>1.9.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>async-method-invocation</artifactId>
|
<artifactId>async-method-invocation</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -15,5 +14,10 @@
|
|||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -5,22 +5,22 @@ import java.util.concurrent.Callable;
|
|||||||
/**
|
/**
|
||||||
* This application demonstrates the async method invocation pattern. Key parts of the pattern are
|
* This application demonstrates the async method invocation pattern. Key parts of the pattern are
|
||||||
* <code>AsyncResult</code> which is an intermediate container for an asynchronously evaluated value,
|
* <code>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>AsyncCallback</code> which can be provided to be executed on task completion and <code>AsyncExecutor</code>
|
||||||
* <code>AsyncExecutor</code> that manages the execution of the async tasks.
|
* that manages the execution of the async tasks.
|
||||||
* <p>
|
* <p>
|
||||||
* The main method shows example flow of async invocations. The main thread starts multiple tasks with
|
* The main method shows example flow of async invocations. The main thread starts multiple tasks with variable
|
||||||
* variable durations and then continues its own work. When the main thread has done it's job it collects
|
* durations and then continues its own work. When the main thread has done it's job it collects the results of the
|
||||||
* the results of the async tasks. Two of the tasks are handled with callbacks, meaning the callbacks are
|
* async tasks. Two of the tasks are handled with callbacks, meaning the callbacks are executed immediately when the
|
||||||
* executed immediately when the tasks complete.
|
* tasks complete.
|
||||||
* <p>
|
* <p>
|
||||||
* Noteworthy difference of thread usage between the async results and callbacks is that the async results
|
* Noteworthy difference of thread usage between the async results and callbacks is that the async results are collected
|
||||||
* are collected in the main thread but the callbacks are executed within the worker threads. This should be
|
* in the main thread but the callbacks are executed within the worker threads. This should be noted when working with
|
||||||
* noted when working with thread pools.
|
* thread pools.
|
||||||
* <p>
|
* <p>
|
||||||
* Java provides its own implementations of async method invocation pattern. FutureTask, CompletableFuture
|
* Java provides its own implementations of async method invocation pattern. FutureTask, CompletableFuture and
|
||||||
* and ExecutorService are the real world implementations of this pattern. But due to the nature of parallel
|
* ExecutorService are the real world implementations of this pattern. But due to the nature of parallel programming,
|
||||||
* programming, the implementations are not trivial. This example does not take all possible scenarios into
|
* the implementations are not trivial. This example does not take all possible scenarios into account but rather
|
||||||
* account but rather provides a simple version that helps to understand the pattern.
|
* provides a simple version that helps to understand the pattern.
|
||||||
*
|
*
|
||||||
* @see AsyncResult
|
* @see AsyncResult
|
||||||
* @see AsyncCallback
|
* @see AsyncCallback
|
||||||
@ -32,6 +32,9 @@ import java.util.concurrent.Callable;
|
|||||||
*/
|
*/
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program entry point
|
||||||
|
*/
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
// construct a new executor that will run async tasks
|
// construct a new executor that will run async tasks
|
||||||
AsyncExecutor executor = new ThreadAsyncExecutor();
|
AsyncExecutor executor = new ThreadAsyncExecutor();
|
||||||
@ -63,8 +66,10 @@ public class App {
|
|||||||
/**
|
/**
|
||||||
* Creates a callable that lazily evaluates to given value with artificial delay.
|
* Creates a callable that lazily evaluates to given value with artificial delay.
|
||||||
*
|
*
|
||||||
* @param value value to evaluate
|
* @param value
|
||||||
* @param delayMillis artificial delay in milliseconds
|
* value to evaluate
|
||||||
|
* @param delayMillis
|
||||||
|
* artificial delay in milliseconds
|
||||||
* @return new callable for lazy evaluation
|
* @return new callable for lazy evaluation
|
||||||
*/
|
*/
|
||||||
private static <T> Callable<T> lazyval(T value, long delayMillis) {
|
private static <T> Callable<T> lazyval(T value, long delayMillis) {
|
||||||
@ -78,7 +83,8 @@ public class App {
|
|||||||
/**
|
/**
|
||||||
* Creates a simple callback that logs the complete status of the async result.
|
* Creates a simple callback that logs the complete status of the async result.
|
||||||
*
|
*
|
||||||
* @param name callback name
|
* @param name
|
||||||
|
* callback name
|
||||||
* @return new async callback
|
* @return new async callback
|
||||||
*/
|
*/
|
||||||
private static <T> AsyncCallback<T> callback(String name) {
|
private static <T> AsyncCallback<T> callback(String name) {
|
||||||
|
@ -18,5 +18,4 @@ public interface AsyncCallback<T> {
|
|||||||
* @param ex empty value if execution succeeds, some exception if executions fails
|
* @param ex empty value if execution succeeds, some exception if executions fails
|
||||||
*/
|
*/
|
||||||
void onComplete(T value, Optional<Exception> ex);
|
void onComplete(T value, Optional<Exception> ex);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,5 +38,4 @@ public interface AsyncExecutor {
|
|||||||
* @throws InterruptedException if the execution is interrupted
|
* @throws InterruptedException if the execution is interrupted
|
||||||
*/
|
*/
|
||||||
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
|
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import java.util.concurrent.ExecutionException;
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* AsyncResult interface
|
* AsyncResult interface
|
||||||
*
|
|
||||||
* @param <T>
|
|
||||||
*/
|
*/
|
||||||
public interface AsyncResult<T> {
|
public interface AsyncResult<T> {
|
||||||
|
|
||||||
|
@ -44,9 +44,8 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple implementation of async result that allows completing it successfully with a value
|
* Simple implementation of async result that allows completing it successfully with a value or exceptionally with an
|
||||||
* or exceptionally with an exception. A really simplified version from its real life cousins
|
* exception. A really simplified version from its real life cousins FutureTask and CompletableFuture.
|
||||||
* FutureTask and CompletableFuture.
|
|
||||||
*
|
*
|
||||||
* @see java.util.concurrent.FutureTask
|
* @see java.util.concurrent.FutureTask
|
||||||
* @see java.util.concurrent.CompletableFuture
|
* @see java.util.concurrent.CompletableFuture
|
||||||
@ -70,10 +69,11 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value from successful execution and executes callback if available. Notifies
|
* Sets the value from successful execution and executes callback if available. Notifies any thread waiting for
|
||||||
* any thread waiting for completion.
|
* completion.
|
||||||
*
|
*
|
||||||
* @param value value of the evaluated task
|
* @param value
|
||||||
|
* value of the evaluated task
|
||||||
*/
|
*/
|
||||||
void setValue(T value) {
|
void setValue(T value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@ -85,10 +85,11 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the exception from failed execution and executes callback if available. Notifies
|
* Sets the exception from failed execution and executes callback if available. Notifies any thread waiting for
|
||||||
* any thread waiting for completion.
|
* completion.
|
||||||
*
|
*
|
||||||
* @param exception exception of the failed task
|
* @param exception
|
||||||
|
* exception of the failed task
|
||||||
*/
|
*/
|
||||||
void setException(Exception exception) {
|
void setException(Exception exception) {
|
||||||
this.exception = exception;
|
this.exception = exception;
|
||||||
@ -101,7 +102,7 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCompleted() {
|
public boolean isCompleted() {
|
||||||
return (state > RUNNING);
|
return state > RUNNING;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -14,5 +14,4 @@ public class AppTest {
|
|||||||
String[] args = {};
|
String[] args = {};
|
||||||
App.main(args);
|
App.main(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,290 @@
|
|||||||
|
package com.iluwatar.async.method.invocation;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Matchers;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
import static org.mockito.internal.verification.VerificationModeFactory.times;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date: 12/6/15 - 10:49 AM
|
||||||
|
*
|
||||||
|
* @author Jeroen Meulemeester
|
||||||
|
*/
|
||||||
|
public class ThreadAsyncExecutorTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)}
|
||||||
|
*/
|
||||||
|
@Test(timeout = 3000)
|
||||||
|
public void testSuccessfulTaskWithoutCallback() throws Exception {
|
||||||
|
// Instantiate a new executor and start a new 'null' task ...
|
||||||
|
final ThreadAsyncExecutor executor = new ThreadAsyncExecutor();
|
||||||
|
|
||||||
|
final Object result = new Object();
|
||||||
|
final Callable<Object> task = mock(Callable.class);
|
||||||
|
when(task.call()).thenReturn(result);
|
||||||
|
|
||||||
|
final AsyncResult<Object> asyncResult = executor.startProcess(task);
|
||||||
|
assertNotNull(asyncResult);
|
||||||
|
asyncResult.await(); // Prevent timing issues, and wait until the result is available
|
||||||
|
assertTrue(asyncResult.isCompleted());
|
||||||
|
|
||||||
|
// Our task should only execute once ...
|
||||||
|
verify(task, times(1)).call();
|
||||||
|
|
||||||
|
// ... and the result should be exactly the same object
|
||||||
|
assertSame(result, asyncResult.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)}
|
||||||
|
*/
|
||||||
|
@Test(timeout = 3000)
|
||||||
|
public void testSuccessfulTaskWithCallback() throws Exception {
|
||||||
|
// Instantiate a new executor and start a new 'null' task ...
|
||||||
|
final ThreadAsyncExecutor executor = new ThreadAsyncExecutor();
|
||||||
|
|
||||||
|
final Object result = new Object();
|
||||||
|
final Callable<Object> task = mock(Callable.class);
|
||||||
|
when(task.call()).thenReturn(result);
|
||||||
|
|
||||||
|
final AsyncCallback callback = mock(AsyncCallback.class);
|
||||||
|
final AsyncResult<Object> asyncResult = executor.startProcess(task, callback);
|
||||||
|
assertNotNull(asyncResult);
|
||||||
|
asyncResult.await(); // Prevent timing issues, and wait until the result is available
|
||||||
|
assertTrue(asyncResult.isCompleted());
|
||||||
|
|
||||||
|
// Our task should only execute once ...
|
||||||
|
verify(task, times(1)).call();
|
||||||
|
|
||||||
|
// ... same for the callback, we expect our object
|
||||||
|
final ArgumentCaptor<Optional<Exception>> optionalCaptor = ArgumentCaptor.forClass((Class) Optional.class);
|
||||||
|
verify(callback, times(1)).onComplete(eq(result), optionalCaptor.capture());
|
||||||
|
|
||||||
|
final Optional<Exception> optionalException = optionalCaptor.getValue();
|
||||||
|
assertNotNull(optionalException);
|
||||||
|
assertFalse(optionalException.isPresent());
|
||||||
|
|
||||||
|
// ... and the result should be exactly the same object
|
||||||
|
assertSame(result, asyncResult.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} when a task takes a while
|
||||||
|
* to execute
|
||||||
|
*/
|
||||||
|
@Test(timeout = 5000)
|
||||||
|
public void testLongRunningTaskWithoutCallback() throws Exception {
|
||||||
|
// Instantiate a new executor and start a new 'null' task ...
|
||||||
|
final ThreadAsyncExecutor executor = new ThreadAsyncExecutor();
|
||||||
|
|
||||||
|
final Object result = new Object();
|
||||||
|
final Callable<Object> task = mock(Callable.class);
|
||||||
|
when(task.call()).thenAnswer(i -> {
|
||||||
|
Thread.sleep(1500);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
final AsyncResult<Object> asyncResult = executor.startProcess(task);
|
||||||
|
assertNotNull(asyncResult);
|
||||||
|
assertFalse(asyncResult.isCompleted());
|
||||||
|
|
||||||
|
try {
|
||||||
|
asyncResult.getValue();
|
||||||
|
fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task");
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertNotNull(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our task should only execute once, but it can take a while ...
|
||||||
|
verify(task, timeout(3000).times(1)).call();
|
||||||
|
|
||||||
|
// Prevent timing issues, and wait until the result is available
|
||||||
|
asyncResult.await();
|
||||||
|
assertTrue(asyncResult.isCompleted());
|
||||||
|
verifyNoMoreInteractions(task);
|
||||||
|
|
||||||
|
// ... and the result should be exactly the same object
|
||||||
|
assertSame(result, asyncResult.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} when a task
|
||||||
|
* takes a while to execute
|
||||||
|
*/
|
||||||
|
@Test(timeout = 5000)
|
||||||
|
public void testLongRunningTaskWithCallback() throws Exception {
|
||||||
|
// Instantiate a new executor and start a new 'null' task ...
|
||||||
|
final ThreadAsyncExecutor executor = new ThreadAsyncExecutor();
|
||||||
|
|
||||||
|
final Object result = new Object();
|
||||||
|
final Callable<Object> task = mock(Callable.class);
|
||||||
|
when(task.call()).thenAnswer(i -> {
|
||||||
|
Thread.sleep(1500);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
final AsyncCallback<Object> callback = mock(AsyncCallback.class);
|
||||||
|
final AsyncResult<Object> asyncResult = executor.startProcess(task, callback);
|
||||||
|
assertNotNull(asyncResult);
|
||||||
|
assertFalse(asyncResult.isCompleted());
|
||||||
|
|
||||||
|
verifyZeroInteractions(callback);
|
||||||
|
|
||||||
|
try {
|
||||||
|
asyncResult.getValue();
|
||||||
|
fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task");
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertNotNull(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our task should only execute once, but it can take a while ...
|
||||||
|
verify(task, timeout(3000).times(1)).call();
|
||||||
|
|
||||||
|
final ArgumentCaptor<Optional<Exception>> optionalCaptor = ArgumentCaptor.forClass((Class) Optional.class);
|
||||||
|
verify(callback, timeout(3000).times(1)).onComplete(eq(result), optionalCaptor.capture());
|
||||||
|
|
||||||
|
final Optional<Exception> optionalException = optionalCaptor.getValue();
|
||||||
|
assertNotNull(optionalException);
|
||||||
|
assertFalse(optionalException.isPresent());
|
||||||
|
|
||||||
|
// Prevent timing issues, and wait until the result is available
|
||||||
|
asyncResult.await();
|
||||||
|
assertTrue(asyncResult.isCompleted());
|
||||||
|
verifyNoMoreInteractions(task, callback);
|
||||||
|
|
||||||
|
// ... and the result should be exactly the same object
|
||||||
|
assertSame(result, asyncResult.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} when a task takes a while
|
||||||
|
* to execute, while waiting on the result using {@link ThreadAsyncExecutor#endProcess(AsyncResult)}
|
||||||
|
*/
|
||||||
|
@Test(timeout = 5000)
|
||||||
|
public void testEndProcess() throws Exception {
|
||||||
|
// Instantiate a new executor and start a new 'null' task ...
|
||||||
|
final ThreadAsyncExecutor executor = new ThreadAsyncExecutor();
|
||||||
|
|
||||||
|
final Object result = new Object();
|
||||||
|
final Callable<Object> task = mock(Callable.class);
|
||||||
|
when(task.call()).thenAnswer(i -> {
|
||||||
|
Thread.sleep(1500);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
final AsyncResult<Object> asyncResult = executor.startProcess(task);
|
||||||
|
assertNotNull(asyncResult);
|
||||||
|
assertFalse(asyncResult.isCompleted());
|
||||||
|
|
||||||
|
try {
|
||||||
|
asyncResult.getValue();
|
||||||
|
fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task");
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertNotNull(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSame(result, executor.endProcess(asyncResult));
|
||||||
|
verify(task, times(1)).call();
|
||||||
|
assertTrue(asyncResult.isCompleted());
|
||||||
|
|
||||||
|
// Calling end process a second time while already finished should give the same result
|
||||||
|
assertSame(result, executor.endProcess(asyncResult));
|
||||||
|
verifyNoMoreInteractions(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable)} when the callable is 'null'
|
||||||
|
*/
|
||||||
|
@Test(timeout = 3000)
|
||||||
|
public void testNullTask() throws Exception {
|
||||||
|
// Instantiate a new executor and start a new 'null' task ...
|
||||||
|
final ThreadAsyncExecutor executor = new ThreadAsyncExecutor();
|
||||||
|
final AsyncResult<Object> asyncResult = executor.startProcess(null);
|
||||||
|
|
||||||
|
assertNotNull("The AsyncResult should not be 'null', even though the task was 'null'.", asyncResult);
|
||||||
|
asyncResult.await(); // Prevent timing issues, and wait until the result is available
|
||||||
|
assertTrue(asyncResult.isCompleted());
|
||||||
|
|
||||||
|
try {
|
||||||
|
asyncResult.getValue();
|
||||||
|
fail("Expected ExecutionException with NPE as cause");
|
||||||
|
} catch (final ExecutionException e) {
|
||||||
|
assertNotNull(e.getMessage());
|
||||||
|
assertNotNull(e.getCause());
|
||||||
|
assertEquals(NullPointerException.class, e.getCause().getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} when the
|
||||||
|
* callable is 'null', but the asynchronous callback is provided
|
||||||
|
*/
|
||||||
|
@Test(timeout = 3000)
|
||||||
|
public void testNullTaskWithCallback() throws Exception {
|
||||||
|
// Instantiate a new executor and start a new 'null' task ...
|
||||||
|
final ThreadAsyncExecutor executor = new ThreadAsyncExecutor();
|
||||||
|
final AsyncCallback<Object> callback = mock(AsyncCallback.class);
|
||||||
|
final AsyncResult<Object> asyncResult = executor.startProcess(null, callback);
|
||||||
|
|
||||||
|
assertNotNull("The AsyncResult should not be 'null', even though the task was 'null'.", asyncResult);
|
||||||
|
asyncResult.await(); // Prevent timing issues, and wait until the result is available
|
||||||
|
assertTrue(asyncResult.isCompleted());
|
||||||
|
|
||||||
|
final ArgumentCaptor<Optional<Exception>> optionalCaptor = ArgumentCaptor.forClass((Class) Optional.class);
|
||||||
|
verify(callback, times(1)).onComplete(Matchers.isNull(), optionalCaptor.capture());
|
||||||
|
|
||||||
|
final Optional<Exception> optionalException = optionalCaptor.getValue();
|
||||||
|
assertNotNull(optionalException);
|
||||||
|
assertTrue(optionalException.isPresent());
|
||||||
|
|
||||||
|
final Exception exception = optionalException.get();
|
||||||
|
assertNotNull(exception);
|
||||||
|
assertEquals(NullPointerException.class, exception.getClass());
|
||||||
|
|
||||||
|
try {
|
||||||
|
asyncResult.getValue();
|
||||||
|
fail("Expected ExecutionException with NPE as cause");
|
||||||
|
} catch (final ExecutionException e) {
|
||||||
|
assertNotNull(e.getMessage());
|
||||||
|
assertNotNull(e.getCause());
|
||||||
|
assertEquals(NullPointerException.class, e.getCause().getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} when both
|
||||||
|
* the callable and the asynchronous callback are 'null'
|
||||||
|
*/
|
||||||
|
@Test(timeout = 3000)
|
||||||
|
public void testNullTaskWithNullCallback() throws Exception {
|
||||||
|
// Instantiate a new executor and start a new 'null' task ...
|
||||||
|
final ThreadAsyncExecutor executor = new ThreadAsyncExecutor();
|
||||||
|
final AsyncResult<Object> asyncResult = executor.startProcess(null, null);
|
||||||
|
|
||||||
|
assertNotNull("The AsyncResult should not be 'null', even though the task and callback were 'null'.", asyncResult);
|
||||||
|
asyncResult.await(); // Prevent timing issues, and wait until the result is available
|
||||||
|
assertTrue(asyncResult.isCompleted());
|
||||||
|
|
||||||
|
try {
|
||||||
|
asyncResult.getValue();
|
||||||
|
fail("Expected ExecutionException with NPE as cause");
|
||||||
|
} catch (final ExecutionException e) {
|
||||||
|
assertNotNull(e.getMessage());
|
||||||
|
assertNotNull(e.getCause());
|
||||||
|
assertEquals(NullPointerException.class, e.getCause().getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,9 +4,14 @@ title: Bridge
|
|||||||
folder: bridge
|
folder: bridge
|
||||||
permalink: /patterns/bridge/
|
permalink: /patterns/bridge/
|
||||||
categories: Structural
|
categories: Structural
|
||||||
tags: Java
|
tags:
|
||||||
|
- Java
|
||||||
|
- Gang Of Four
|
||||||
|
- Difficulty-Intermediate
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Also known as:** Handle/Body
|
||||||
|
|
||||||
**Intent:** Decouple an abstraction from its implementation so that the two can
|
**Intent:** Decouple an abstraction from its implementation so that the two can
|
||||||
vary independently.
|
vary independently.
|
||||||
|
|
||||||
@ -20,3 +25,7 @@ vary independently.
|
|||||||
* changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled.
|
* changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled.
|
||||||
* you have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies
|
* you have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies
|
||||||
* you want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien's String class, in which multiple objects can share the same string representation.
|
* you want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien's String class, in which multiple objects can share the same string representation.
|
||||||
|
|
||||||
|
**Credits**
|
||||||
|
|
||||||
|
* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project
|
<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"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.iluwatar</groupId>
|
<groupId>com.iluwatar</groupId>
|
||||||
<artifactId>java-design-patterns</artifactId>
|
<artifactId>java-design-patterns</artifactId>
|
||||||
<version>1.6.0</version>
|
<version>1.9.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>bridge</artifactId>
|
<artifactId>bridge</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -15,5 +14,10 @@
|
|||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -2,38 +2,38 @@ package com.iluwatar.bridge;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* In Bridge pattern both abstraction ({@link MagicWeapon}) and implementation
|
* The Bridge pattern can also be thought of as two layers of abstraction. With Bridge, you can
|
||||||
* ({@link MagicWeaponImpl}) have their own class hierarchies. The interface of the
|
* decouple an abstraction from its implementation so that the two can vary independently.
|
||||||
* implementations can be changed without affecting the clients.
|
* <p>
|
||||||
|
* In Bridge pattern both abstraction ({@link MagicWeapon}) and implementation (
|
||||||
|
* {@link MagicWeaponImpl}) have their own class hierarchies. The interface of the implementations
|
||||||
|
* can be changed without affecting the clients.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point
|
* Program entry point
|
||||||
|
*
|
||||||
* @param args command line args
|
* @param args command line args
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
BlindingMagicWeapon blindingMagicWeapon = new BlindingMagicWeapon(
|
BlindingMagicWeapon blindingMagicWeapon = new BlindingMagicWeapon(new Excalibur());
|
||||||
new Excalibur());
|
|
||||||
blindingMagicWeapon.wield();
|
blindingMagicWeapon.wield();
|
||||||
blindingMagicWeapon.blind();
|
blindingMagicWeapon.blind();
|
||||||
blindingMagicWeapon.swing();
|
blindingMagicWeapon.swing();
|
||||||
blindingMagicWeapon.unwield();
|
blindingMagicWeapon.unwield();
|
||||||
|
|
||||||
FlyingMagicWeapon flyingMagicWeapon = new FlyingMagicWeapon(
|
FlyingMagicWeapon flyingMagicWeapon = new FlyingMagicWeapon(new Mjollnir());
|
||||||
new Mjollnir());
|
|
||||||
flyingMagicWeapon.wield();
|
flyingMagicWeapon.wield();
|
||||||
flyingMagicWeapon.fly();
|
flyingMagicWeapon.fly();
|
||||||
flyingMagicWeapon.swing();
|
flyingMagicWeapon.swing();
|
||||||
flyingMagicWeapon.unwield();
|
flyingMagicWeapon.unwield();
|
||||||
|
|
||||||
SoulEatingMagicWeapon soulEatingMagicWeapon = new SoulEatingMagicWeapon(
|
SoulEatingMagicWeapon soulEatingMagicWeapon = new SoulEatingMagicWeapon(new Stormbringer());
|
||||||
new Stormbringer());
|
|
||||||
soulEatingMagicWeapon.wield();
|
soulEatingMagicWeapon.wield();
|
||||||
soulEatingMagicWeapon.swing();
|
soulEatingMagicWeapon.swing();
|
||||||
soulEatingMagicWeapon.eatSoul();
|
soulEatingMagicWeapon.eatSoul();
|
||||||
soulEatingMagicWeapon.unwield();
|
soulEatingMagicWeapon.unwield();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,5 +34,4 @@ public class BlindingMagicWeapon extends MagicWeapon {
|
|||||||
public void blind() {
|
public void blind() {
|
||||||
getImp().blindImp();
|
getImp().blindImp();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,6 @@ public class Excalibur extends BlindingMagicWeaponImpl {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void blindImp() {
|
public void blindImp() {
|
||||||
System.out
|
System.out.println("bright light streams from Excalibur blinding the enemy");
|
||||||
.println("bright light streams from Excalibur blinding the enemy");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,5 +22,4 @@ public abstract class MagicWeapon {
|
|||||||
public MagicWeaponImpl getImp() {
|
public MagicWeaponImpl getImp() {
|
||||||
return imp;
|
return imp;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,6 @@ public class Mjollnir extends FlyingMagicWeaponImpl {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flyImp() {
|
public void flyImp() {
|
||||||
System.out
|
System.out.println("Mjollnir hits the enemy in the air and returns back to the owner's hand");
|
||||||
.println("Mjollnir hits the enemy in the air and returns back to the owner's hand");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,5 +26,4 @@ public class Stormbringer extends SoulEatingMagicWeaponImpl {
|
|||||||
public void eatSoulImp() {
|
public void eatSoulImp() {
|
||||||
System.out.println("Stormbringer devours the enemy's soul");
|
System.out.println("Stormbringer devours the enemy's soul");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@ package com.iluwatar.bridge;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.iluwatar.bridge.App;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Application test
|
* Application test
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.iluwatar.bridge;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.internal.verification.VerificationModeFactory.times;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date: 12/6/15 - 11:15 PM
|
||||||
|
*
|
||||||
|
* @author Jeroen Meulemeester
|
||||||
|
*/
|
||||||
|
public class BlindingMagicWeaponTest extends MagicWeaponTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke all possible actions on the weapon and check if the actions are executed on the actual
|
||||||
|
* underlying weapon implementation.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testExcalibur() throws Exception {
|
||||||
|
final Excalibur excalibur = spy(new Excalibur());
|
||||||
|
final BlindingMagicWeapon blindingMagicWeapon = new BlindingMagicWeapon(excalibur);
|
||||||
|
|
||||||
|
testBasicWeaponActions(blindingMagicWeapon, excalibur);
|
||||||
|
|
||||||
|
blindingMagicWeapon.blind();
|
||||||
|
verify(excalibur, times(1)).blindImp();
|
||||||
|
verifyNoMoreInteractions(excalibur);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.iluwatar.bridge;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.internal.verification.VerificationModeFactory.times;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date: 12/6/15 - 11:26 PM
|
||||||
|
*
|
||||||
|
* @author Jeroen Meulemeester
|
||||||
|
*/
|
||||||
|
public class FlyingMagicWeaponTest extends MagicWeaponTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke all possible actions on the weapon and check if the actions are executed on the actual
|
||||||
|
* underlying weapon implementation.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testMjollnir() throws Exception {
|
||||||
|
final Mjollnir mjollnir = spy(new Mjollnir());
|
||||||
|
final FlyingMagicWeapon flyingMagicWeapon = new FlyingMagicWeapon(mjollnir);
|
||||||
|
|
||||||
|
testBasicWeaponActions(flyingMagicWeapon, mjollnir);
|
||||||
|
|
||||||
|
flyingMagicWeapon.fly();
|
||||||
|
verify(mjollnir, times(1)).flyImp();
|
||||||
|
verifyNoMoreInteractions(mjollnir);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.iluwatar.bridge;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.internal.verification.VerificationModeFactory.times;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date: 12/6/15 - 11:28 PM
|
||||||
|
*
|
||||||
|
* @author Jeroen Meulemeester
|
||||||
|
*/
|
||||||
|
public abstract class MagicWeaponTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke the basic actions of the given weapon, and test if the underlying weapon implementation
|
||||||
|
* is invoked
|
||||||
|
*
|
||||||
|
* @param weaponImpl The spied weapon implementation where actions are bridged to
|
||||||
|
* @param weapon The weapon, handled by the app
|
||||||
|
*/
|
||||||
|
protected final void testBasicWeaponActions(final MagicWeapon weapon,
|
||||||
|
final MagicWeaponImpl weaponImpl) {
|
||||||
|
assertNotNull(weapon);
|
||||||
|
assertNotNull(weaponImpl);
|
||||||
|
assertNotNull(weapon.getImp());
|
||||||
|
|
||||||
|
weapon.swing();
|
||||||
|
verify(weaponImpl, times(1)).swingImp();
|
||||||
|
verifyNoMoreInteractions(weaponImpl);
|
||||||
|
|
||||||
|
weapon.wield();
|
||||||
|
verify(weaponImpl, times(1)).wieldImp();
|
||||||
|
verifyNoMoreInteractions(weaponImpl);
|
||||||
|
|
||||||
|
weapon.unwield();
|
||||||
|
verify(weaponImpl, times(1)).unwieldImp();
|
||||||
|
verifyNoMoreInteractions(weaponImpl);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.iluwatar.bridge;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.internal.verification.VerificationModeFactory.times;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date: 12/6/15 - 11:43 PM
|
||||||
|
*
|
||||||
|
* @author Jeroen Meulemeester
|
||||||
|
*/
|
||||||
|
public class SoulEatingMagicWeaponTest extends MagicWeaponTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke all possible actions on the weapon and check if the actions are executed on the actual
|
||||||
|
* underlying weapon implementation.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testStormBringer() throws Exception {
|
||||||
|
final Stormbringer stormbringer = spy(new Stormbringer());
|
||||||
|
final SoulEatingMagicWeapon soulEatingMagicWeapon = new SoulEatingMagicWeapon(stormbringer);
|
||||||
|
|
||||||
|
testBasicWeaponActions(soulEatingMagicWeapon, stormbringer);
|
||||||
|
|
||||||
|
soulEatingMagicWeapon.eatSoul();
|
||||||
|
verify(stormbringer, times(1)).eatSoulImp();
|
||||||
|
verifyNoMoreInteractions(stormbringer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,7 +4,10 @@ title: Builder
|
|||||||
folder: builder
|
folder: builder
|
||||||
permalink: /patterns/builder/
|
permalink: /patterns/builder/
|
||||||
categories: Creational
|
categories: Creational
|
||||||
tags: Java
|
tags:
|
||||||
|
- Java
|
||||||
|
- Gang Of Four
|
||||||
|
- Difficulty-Intermediate
|
||||||
---
|
---
|
||||||
|
|
||||||
**Intent:** Separate the construction of a complex object from its
|
**Intent:** Separate the construction of a complex object from its
|
||||||
@ -22,3 +25,7 @@ representations.
|
|||||||
|
|
||||||
* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html)
|
* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html)
|
||||||
* [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder)
|
* [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder)
|
||||||
|
|
||||||
|
**Credits**
|
||||||
|
|
||||||
|
* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project
|
<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"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.iluwatar</groupId>
|
<groupId>com.iluwatar</groupId>
|
||||||
<artifactId>java-design-patterns</artifactId>
|
<artifactId>java-design-patterns</artifactId>
|
||||||
<version>1.6.0</version>
|
<version>1.9.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>builder</artifactId>
|
<artifactId>builder</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -4,38 +4,51 @@ import com.iluwatar. builder.Hero.HeroBuilder;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* This is the Builder pattern variation as described by Joshua Bloch in
|
* The intention of the Builder pattern is to find a solution to the telescoping constructor
|
||||||
* Effective Java 2nd Edition.
|
* anti-pattern. The telescoping constructor anti-pattern occurs when the increase of object
|
||||||
|
* constructor parameter combination leads to an exponential list of constructors. Instead of using
|
||||||
|
* numerous constructors, the builder pattern uses another object, a builder, that receives each
|
||||||
|
* initialization parameter step by step and then returns the resulting constructed object at once.
|
||||||
* <p>
|
* <p>
|
||||||
* We want to build {@link Hero} objects, but its construction is complex because of the
|
* The Builder pattern has another benefit. It can be used for objects that contain flat data (html
|
||||||
* many parameters needed. To aid the user we introduce {@link HeroBuilder} class.
|
* code, SQL query, X.509 certificate...), that is to say, data that can't be easily edited. This
|
||||||
* {@link HeroBuilder} takes the minimum parameters to build {@link Hero} object in its
|
* type of data cannot be edited step by step and must be edited at once. The best way to construct
|
||||||
* constructor. After that additional configuration for the {@link Hero} object can be
|
* such an object is to use a builder class.
|
||||||
* done using the fluent {@link HeroBuilder} interface. When configuration is ready the
|
* <p>
|
||||||
* build method is called to receive the final {@link Hero} object.
|
* In this example we have the Builder pattern variation as described by Joshua Bloch in Effective
|
||||||
|
* Java 2nd Edition.
|
||||||
|
* <p>
|
||||||
|
* We want to build {@link Hero} objects, but its construction is complex because of the many
|
||||||
|
* parameters needed. To aid the user we introduce {@link HeroBuilder} class. {@link HeroBuilder}
|
||||||
|
* takes the minimum parameters to build {@link Hero} object in its constructor. After that
|
||||||
|
* additional configuration for the {@link Hero} object can be done using the fluent
|
||||||
|
* {@link HeroBuilder} interface. When configuration is ready the build method is called to receive
|
||||||
|
* the final {@link Hero} object.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point
|
* Program entry point
|
||||||
|
*
|
||||||
* @param args command line args
|
* @param args command line args
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
Hero mage = new HeroBuilder(Profession.MAGE, "Riobard")
|
Hero mage =
|
||||||
.withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER)
|
new HeroBuilder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK)
|
||||||
.build();
|
.withWeapon(Weapon.DAGGER).build();
|
||||||
System.out.println(mage);
|
System.out.println(mage);
|
||||||
|
|
||||||
Hero warrior = new HeroBuilder(Profession.WARRIOR, "Amberjill")
|
Hero warrior =
|
||||||
.withHairColor(HairColor.BLOND)
|
new HeroBuilder(Profession.WARRIOR, "Amberjill").withHairColor(HairColor.BLOND)
|
||||||
.withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL)
|
.withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL).withWeapon(Weapon.SWORD)
|
||||||
.withWeapon(Weapon.SWORD).build();
|
.build();
|
||||||
System.out.println(warrior);
|
System.out.println(warrior);
|
||||||
|
|
||||||
Hero thief = new HeroBuilder(Profession.THIEF, "Desmond")
|
Hero thief =
|
||||||
.withHairType(HairType.BALD).withWeapon(Weapon.BOW).build();
|
new HeroBuilder(Profession.THIEF, "Desmond").withHairType(HairType.BALD)
|
||||||
|
.withWeapon(Weapon.BOW).build();
|
||||||
System.out.println(thief);
|
System.out.println(thief);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ package com.iluwatar.builder;
|
|||||||
*/
|
*/
|
||||||
public enum HairType {
|
public enum HairType {
|
||||||
|
|
||||||
BALD("bald"), SHORT("short"), CURLY("curly"), LONG_STRAIGHT("long straight"), LONG_CURLY("long curly");
|
BALD("bald"), SHORT("short"), CURLY("curly"), LONG_STRAIGHT("long straight"), LONG_CURLY(
|
||||||
|
"long curly");
|
||||||
|
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
|
@ -93,10 +93,12 @@ public class Hero {
|
|||||||
private Armor armor;
|
private Armor armor;
|
||||||
private Weapon weapon;
|
private Weapon weapon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
public HeroBuilder(Profession profession, String name) {
|
public HeroBuilder(Profession profession, String name) {
|
||||||
if (profession == null || name == null) {
|
if (profession == null || name == null) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException("profession and name can not be null");
|
||||||
"profession and name can not be null");
|
|
||||||
}
|
}
|
||||||
this.profession = profession;
|
this.profession = profession;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@ -13,5 +13,4 @@ public enum Profession {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return name().toLowerCase();
|
return name().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,5 +13,4 @@ public enum Weapon {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return name().toLowerCase();
|
return name().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@ package com.iluwatar.builder;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.iluwatar. builder.App;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Application test
|
* Application test
|
||||||
|
56
builder/src/test/java/com/iluwatar/builder/HeroTest.java
Normal file
56
builder/src/test/java/com/iluwatar/builder/HeroTest.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package com.iluwatar.builder;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date: 12/6/15 - 11:01 PM
|
||||||
|
*
|
||||||
|
* @author Jeroen Meulemeester
|
||||||
|
*/
|
||||||
|
public class HeroTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if we get the expected exception when trying to create a hero without a profession
|
||||||
|
*/
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testMissingProfession() throws Exception {
|
||||||
|
new Hero.HeroBuilder(null, "Sir without a job");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if we get the expected exception when trying to create a hero without a name
|
||||||
|
*/
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testMissingName() throws Exception {
|
||||||
|
new Hero.HeroBuilder(Profession.THIEF, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if the hero build by the builder has the correct attributes, as requested
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testBuildHero() throws Exception {
|
||||||
|
final String heroName = "Sir Lancelot";
|
||||||
|
|
||||||
|
final Hero hero = new Hero.HeroBuilder(Profession.WARRIOR, heroName)
|
||||||
|
.withArmor(Armor.CHAIN_MAIL)
|
||||||
|
.withWeapon(Weapon.SWORD)
|
||||||
|
.withHairType(HairType.LONG_CURLY)
|
||||||
|
.withHairColor(HairColor.BLOND)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertNotNull(hero);
|
||||||
|
assertNotNull(hero.toString());
|
||||||
|
assertEquals(Profession.WARRIOR, hero.getProfession());
|
||||||
|
assertEquals(heroName, hero.getName());
|
||||||
|
assertEquals(Armor.CHAIN_MAIL, hero.getArmor());
|
||||||
|
assertEquals(Weapon.SWORD, hero.getWeapon());
|
||||||
|
assertEquals(HairType.LONG_CURLY, hero.getHairType());
|
||||||
|
assertEquals(HairColor.BLOND, hero.getHairColor());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,7 +4,9 @@ title: Business Delegate
|
|||||||
folder: business-delegate
|
folder: business-delegate
|
||||||
permalink: /patterns/business-delegate/
|
permalink: /patterns/business-delegate/
|
||||||
categories: Business Tier
|
categories: Business Tier
|
||||||
tags: Java
|
tags:
|
||||||
|
- Java
|
||||||
|
- Difficulty-Intermediate
|
||||||
---
|
---
|
||||||
|
|
||||||
**Intent:** The Business Delegate pattern adds an abstraction layer between
|
**Intent:** The Business Delegate pattern adds an abstraction layer between
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.iluwatar</groupId>
|
<groupId>com.iluwatar</groupId>
|
||||||
<artifactId>java-design-patterns</artifactId>
|
<artifactId>java-design-patterns</artifactId>
|
||||||
<version>1.6.0</version>
|
<version>1.9.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>business-delegate</artifactId>
|
<artifactId>business-delegate</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -15,5 +15,10 @@
|
|||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -1,29 +1,34 @@
|
|||||||
package com.iluwatar.business.delegate;
|
package com.iluwatar.business.delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The Business Delegate pattern adds an abstraction layer between the presentation and business
|
||||||
|
* tiers. By using the pattern we gain loose coupling between the tiers. The Business Delegate
|
||||||
|
* encapsulates knowledge about how to locate, connect to, and interact with the business objects
|
||||||
|
* that make up the application.
|
||||||
*
|
*
|
||||||
* The Business Delegate pattern adds an abstraction layer between the presentation and business tiers.
|
* <p>Some of the services the Business Delegate uses are instantiated directly, and some can be
|
||||||
* By using the pattern we gain loose coupling between the tiers. The Business Delegate encapsulates
|
* retrieved through service lookups. The Business Delegate itself may contain business logic too
|
||||||
* knowledge about how to locate, connect to, and interact with the business objects that make up
|
* potentially tying together multiple service calls, exception handling, retrying etc.
|
||||||
* 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.
|
|
||||||
* <p>
|
|
||||||
* In this example the client ({@link Client}) utilizes a business delegate ({@link BusinessDelegate}) to execute a task.
|
|
||||||
* The Business Delegate then selects the appropriate service and makes the service call.
|
|
||||||
*
|
*
|
||||||
|
* <p>In this example the client ({@link Client}) utilizes a business delegate (
|
||||||
|
* {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
|
||||||
|
* service and makes the service call.
|
||||||
*/
|
*/
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point
|
* Program entry point.
|
||||||
|
*
|
||||||
* @param args command line args
|
* @param args command line args
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
BusinessDelegate businessDelegate = new BusinessDelegate();
|
BusinessDelegate businessDelegate = new BusinessDelegate();
|
||||||
|
BusinessLookup businessLookup = new BusinessLookup();
|
||||||
|
businessLookup.setEjbService(new EjbService());
|
||||||
|
businessLookup.setJmsService(new JmsService());
|
||||||
|
|
||||||
|
businessDelegate.setLookupService(businessLookup);
|
||||||
businessDelegate.setServiceType(ServiceType.EJB);
|
businessDelegate.setServiceType(ServiceType.EJB);
|
||||||
|
|
||||||
Client client = new Client(businessDelegate);
|
Client client = new Client(businessDelegate);
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
package com.iluwatar.business.delegate;
|
package com.iluwatar.business.delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* BusinessDelegate separates the presentation and business tiers
|
* BusinessDelegate separates the presentation and business tiers
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class BusinessDelegate {
|
public class BusinessDelegate {
|
||||||
|
|
||||||
private BusinessLookup lookupService = new BusinessLookup();
|
private BusinessLookup lookupService;
|
||||||
private BusinessService businessService;
|
private BusinessService businessService;
|
||||||
private ServiceType serviceType;
|
private ServiceType serviceType;
|
||||||
|
|
||||||
|
public void setLookupService(BusinessLookup businessLookup) {
|
||||||
|
this.lookupService = businessLookup;
|
||||||
|
}
|
||||||
|
|
||||||
public void setServiceType(ServiceType serviceType) {
|
public void setServiceType(ServiceType serviceType) {
|
||||||
this.serviceType = serviceType;
|
this.serviceType = serviceType;
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,31 @@
|
|||||||
package com.iluwatar.business.delegate;
|
package com.iluwatar.business.delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Class for performing service lookups.
|
||||||
* Class for performing service lookups
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class BusinessLookup {
|
public class BusinessLookup {
|
||||||
|
|
||||||
|
private EjbService ejbService;
|
||||||
|
|
||||||
|
private JmsService jmsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param serviceType Type of service instance to be returned.
|
||||||
|
* @return Service instance.
|
||||||
|
*/
|
||||||
public BusinessService getBusinessService(ServiceType serviceType) {
|
public BusinessService getBusinessService(ServiceType serviceType) {
|
||||||
if (serviceType.equals(ServiceType.EJB)) {
|
if (serviceType.equals(ServiceType.EJB)) {
|
||||||
return new EjbService();
|
return ejbService;
|
||||||
} else {
|
} else {
|
||||||
return new JmsService();
|
return jmsService;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setJmsService(JmsService jmsService) {
|
||||||
|
this.jmsService = jmsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEjbService(EjbService ejbService) {
|
||||||
|
this.ejbService = ejbService;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
package com.iluwatar.business.delegate;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.iluwatar.business.delegate.App;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Application test
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class AppTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
String[] args = {};
|
|
||||||
App.main(args);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,78 @@
|
|||||||
|
package com.iluwatar.business.delegate;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Business Delegate pattern adds an abstraction layer between the presentation and business
|
||||||
|
* tiers. By using the pattern we gain loose coupling between the tiers. The Business Delegate
|
||||||
|
* encapsulates knowledge about how to locate, connect to, and interact with the business objects
|
||||||
|
* that make up the application.
|
||||||
|
*
|
||||||
|
* <p>Some of the services the Business Delegate uses are instantiated directly, and some can be
|
||||||
|
* retrieved through service lookups. The Business Delegate itself may contain business logic too
|
||||||
|
* potentially tying together multiple service calls, exception handling, retrying etc.
|
||||||
|
*/
|
||||||
|
public class BusinessDelegateTest {
|
||||||
|
|
||||||
|
private EjbService ejbService;
|
||||||
|
|
||||||
|
private JmsService jmsService;
|
||||||
|
|
||||||
|
private BusinessLookup businessLookup;
|
||||||
|
|
||||||
|
private BusinessDelegate businessDelegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method sets up the instance variables of this test class. It is executed before the
|
||||||
|
* execution of every test.
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
ejbService = spy(new EjbService());
|
||||||
|
jmsService = spy(new JmsService());
|
||||||
|
|
||||||
|
businessLookup = spy(new BusinessLookup());
|
||||||
|
businessLookup.setEjbService(ejbService);
|
||||||
|
businessLookup.setJmsService(jmsService);
|
||||||
|
|
||||||
|
businessDelegate = spy(new BusinessDelegate());
|
||||||
|
businessDelegate.setLookupService(businessLookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In this example the client ({@link Client}) utilizes a business delegate (
|
||||||
|
* {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
|
||||||
|
* service and makes the service call.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testBusinessDelegate() {
|
||||||
|
|
||||||
|
// setup a client object
|
||||||
|
Client client = new Client(businessDelegate);
|
||||||
|
|
||||||
|
// set the service type
|
||||||
|
businessDelegate.setServiceType(ServiceType.EJB);
|
||||||
|
|
||||||
|
// action
|
||||||
|
client.doTask();
|
||||||
|
|
||||||
|
// verifying that the businessDelegate was used by client during doTask() method.
|
||||||
|
verify(businessDelegate).doTask();
|
||||||
|
verify(ejbService).doProcessing();
|
||||||
|
|
||||||
|
// set the service type
|
||||||
|
businessDelegate.setServiceType(ServiceType.JMS);
|
||||||
|
|
||||||
|
// action
|
||||||
|
client.doTask();
|
||||||
|
|
||||||
|
// verifying that the businessDelegate was used by client during doTask() method.
|
||||||
|
verify(businessDelegate, times(2)).doTask();
|
||||||
|
verify(jmsService).doProcessing();
|
||||||
|
}
|
||||||
|
}
|
1
caching/.gitignore
vendored
Normal file
1
caching/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target/
|
BIN
caching/etc/caching.png
Normal file
BIN
caching/etc/caching.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
106
caching/etc/caching.ucls
Normal file
106
caching/etc/caching.ucls
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<?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="main.java.com.wssia.caching.App" project="CachingPatterns"
|
||||||
|
file="/CachingPatterns/src/main/java/com/wssia/caching/App.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="249" y="150"/>
|
||||||
|
<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="main.java.com.wssia.caching.AppManager" project="CachingPatterns"
|
||||||
|
file="/CachingPatterns/src/main/java/com/wssia/caching/AppManager.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="502" y="163"/>
|
||||||
|
<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="main.java.com.wssia.caching.CacheStore" project="CachingPatterns"
|
||||||
|
file="/CachingPatterns/src/main/java/com/wssia/caching/CacheStore.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="537" y="436"/>
|
||||||
|
<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="4" language="java" name="main.java.com.wssia.caching.CachingPolicy" project="CachingPatterns"
|
||||||
|
file="/CachingPatterns/src/main/java/com/wssia/caching/CachingPolicy.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="789" y="162"/>
|
||||||
|
<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>
|
||||||
|
<class id="5" language="java" name="main.java.com.wssia.caching.DBManager" project="CachingPatterns"
|
||||||
|
file="/CachingPatterns/src/main/java/com/wssia/caching/DBManager.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="1137" y="134"/>
|
||||||
|
<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="main.java.com.wssia.caching.LRUCache" project="CachingPatterns"
|
||||||
|
file="/CachingPatterns/src/main/java/com/wssia/caching/LRUCache.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="884" y="435"/>
|
||||||
|
<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="main.java.com.wssia.caching.UserAccount" project="CachingPatterns"
|
||||||
|
file="/CachingPatterns/src/main/java/com/wssia/caching/UserAccount.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="1140" y="405"/>
|
||||||
|
<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="8" language="java" name="test.java.com.wssia.caching.AppTest" project="CachingPatterns"
|
||||||
|
file="/CachingPatterns/src/test/java/com/wssia/caching/AppTest.java" binary="false" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="251" y="374"/>
|
||||||
|
<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="9">
|
||||||
|
<end type="SOURCE" refId="2" navigable="false">
|
||||||
|
<attribute id="10" name="cachingPolicy"/>
|
||||||
|
<multiplicity id="11" minimum="0" maximum="1"/>
|
||||||
|
</end>
|
||||||
|
<end type="TARGET" refId="4" navigable="true"/>
|
||||||
|
<display labels="true" multiplicity="true"/>
|
||||||
|
</association>
|
||||||
|
<association id="12">
|
||||||
|
<end type="SOURCE" refId="8" navigable="false">
|
||||||
|
<attribute id="13" name="app"/>
|
||||||
|
<multiplicity id="14" minimum="0" maximum="1"/>
|
||||||
|
</end>
|
||||||
|
<end type="TARGET" refId="1" navigable="true"/>
|
||||||
|
<display labels="true" multiplicity="true"/>
|
||||||
|
</association>
|
||||||
|
<association id="15">
|
||||||
|
<end type="SOURCE" refId="3" navigable="false">
|
||||||
|
<attribute id="16" name="cache"/>
|
||||||
|
<multiplicity id="17" minimum="0" maximum="1"/>
|
||||||
|
</end>
|
||||||
|
<end type="TARGET" refId="6" navigable="true"/>
|
||||||
|
<display labels="true" multiplicity="true"/>
|
||||||
|
</association>
|
||||||
|
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
</classifier-display>
|
||||||
|
<association-display labels="true" multiplicity="true"/>
|
||||||
|
</class-diagram>
|
26
caching/index.md
Normal file
26
caching/index.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
layout: pattern
|
||||||
|
title: Caching
|
||||||
|
folder: caching
|
||||||
|
permalink: /patterns/caching/
|
||||||
|
categories: Other
|
||||||
|
tags:
|
||||||
|
- Java
|
||||||
|
- Difficulty-Intermediate
|
||||||
|
- Performance
|
||||||
|
---
|
||||||
|
|
||||||
|
**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
|
||||||
|
fast-access storage, and are re-used to avoid having to acquire them again.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Applicability:** Use the Caching pattern(s) when
|
||||||
|
|
||||||
|
* Repetitious acquisition, initialization, and release of the same resource causes unnecessary performance overhead.
|
||||||
|
|
||||||
|
**Credits**
|
||||||
|
|
||||||
|
* [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)
|
51
caching/pom.xml
Normal file
51
caching/pom.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<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.9.0</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>caching</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>mongodb-driver</artifactId>
|
||||||
|
<version>3.0.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>mongodb-driver-core</artifactId>
|
||||||
|
<version>3.0.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>bson</artifactId>
|
||||||
|
<version>3.0.4</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<!--
|
||||||
|
Due to the use of MongoDB in the test of this pattern, TRAVIS and/or MAVEN might fail if the DB connection is
|
||||||
|
not open for the JUnit test. To avoid disrupting the compilation process, the surefire plug-in was used
|
||||||
|
to SKIP the running of the JUnit tests for this pattern. To ACTIVATE the running of the tests, change the
|
||||||
|
skipTests (below) flag to 'false' and vice-versa.
|
||||||
|
-->
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.19</version>
|
||||||
|
<configuration>
|
||||||
|
<skipTests>false</skipTests>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
117
caching/src/main/java/com/iluwatar/caching/App.java
Normal file
117
caching/src/main/java/com/iluwatar/caching/App.java
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package com.iluwatar.caching;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* The Caching pattern describes how 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
|
||||||
|
* fast-access storage, and are re-used to avoid having to acquire them again. There are three main
|
||||||
|
* caching strategies/techniques in this pattern; each with their own pros and cons. They are:
|
||||||
|
* <code>write-through</code> which writes data to the cache and DB in a single transaction,
|
||||||
|
* <code>write-around</code> which writes data immediately into the DB instead of the cache, and
|
||||||
|
* <code>write-behind</code> which writes data into the cache initially whilst the data is only
|
||||||
|
* written into the DB when the cache is full. The <code>read-through</code> strategy is also
|
||||||
|
* included in the mentioned three strategies -- returns data from the cache to the caller <b>if</b>
|
||||||
|
* it exists <b>else</b> queries from DB and stores it into the cache for future use. These
|
||||||
|
* strategies determine when the data in the cache should be written back to the backing store (i.e.
|
||||||
|
* Database) and help keep both data sources synchronized/up-to-date. This pattern can improve
|
||||||
|
* performance and also helps to maintain consistency between data held in the cache and the data in
|
||||||
|
* the underlying data store.
|
||||||
|
* <p>
|
||||||
|
* In this example, the user account ({@link UserAccount}) entity is used as the underlying
|
||||||
|
* application data. The cache itself is implemented as an internal (Java) data structure. It adopts
|
||||||
|
* a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The three
|
||||||
|
* strategies are individually tested. The testing of the cache is restricted towards saving and
|
||||||
|
* querying of user accounts from the underlying data store ( {@link DbManager}). The main class (
|
||||||
|
* {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and
|
||||||
|
* whether the data is coming from the cache or the DB (i.e. separation of concern). The AppManager
|
||||||
|
* ({@link AppManager}) handles the transaction of data to-and-from the underlying data store
|
||||||
|
* (depending on the preferred caching policy/strategy).
|
||||||
|
*
|
||||||
|
* <i>App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager</i>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see CacheStore
|
||||||
|
* @See LRUCache
|
||||||
|
* @see CachingPolicy
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class App {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program entry point
|
||||||
|
*
|
||||||
|
* @param args command line args
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
AppManager.initDb(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests
|
||||||
|
// and the App class to avoid Maven compilation errors. Set flag to
|
||||||
|
// true to run the tests with MongoDB (provided that MongoDB is
|
||||||
|
// installed and socket connection is open).
|
||||||
|
AppManager.initCacheCapacity(3);
|
||||||
|
App app = new App();
|
||||||
|
app.useReadAndWriteThroughStrategy();
|
||||||
|
app.useReadThroughAndWriteAroundStrategy();
|
||||||
|
app.useReadThroughAndWriteBehindStrategy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-through and write-through
|
||||||
|
*/
|
||||||
|
public void useReadAndWriteThroughStrategy() {
|
||||||
|
System.out.println("# CachingPolicy.THROUGH");
|
||||||
|
AppManager.initCachingPolicy(CachingPolicy.THROUGH);
|
||||||
|
|
||||||
|
UserAccount userAccount1 = new UserAccount("001", "John", "He is a boy.");
|
||||||
|
|
||||||
|
AppManager.save(userAccount1);
|
||||||
|
System.out.println(AppManager.printCacheContent());
|
||||||
|
AppManager.find("001");
|
||||||
|
AppManager.find("001");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-through and write-around
|
||||||
|
*/
|
||||||
|
public void useReadThroughAndWriteAroundStrategy() {
|
||||||
|
System.out.println("# CachingPolicy.AROUND");
|
||||||
|
AppManager.initCachingPolicy(CachingPolicy.AROUND);
|
||||||
|
|
||||||
|
UserAccount userAccount2 = new UserAccount("002", "Jane", "She is a girl.");
|
||||||
|
|
||||||
|
AppManager.save(userAccount2);
|
||||||
|
System.out.println(AppManager.printCacheContent());
|
||||||
|
AppManager.find("002");
|
||||||
|
System.out.println(AppManager.printCacheContent());
|
||||||
|
userAccount2 = AppManager.find("002");
|
||||||
|
userAccount2.setUserName("Jane G.");
|
||||||
|
AppManager.save(userAccount2);
|
||||||
|
System.out.println(AppManager.printCacheContent());
|
||||||
|
AppManager.find("002");
|
||||||
|
System.out.println(AppManager.printCacheContent());
|
||||||
|
AppManager.find("002");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-through and write-behind
|
||||||
|
*/
|
||||||
|
public void useReadThroughAndWriteBehindStrategy() {
|
||||||
|
System.out.println("# CachingPolicy.BEHIND");
|
||||||
|
AppManager.initCachingPolicy(CachingPolicy.BEHIND);
|
||||||
|
|
||||||
|
UserAccount userAccount3 = new UserAccount("003", "Adam", "He likes food.");
|
||||||
|
UserAccount userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
|
||||||
|
UserAccount userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
|
||||||
|
|
||||||
|
AppManager.save(userAccount3);
|
||||||
|
AppManager.save(userAccount4);
|
||||||
|
AppManager.save(userAccount5);
|
||||||
|
System.out.println(AppManager.printCacheContent());
|
||||||
|
AppManager.find("003");
|
||||||
|
System.out.println(AppManager.printCacheContent());
|
||||||
|
UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child.");
|
||||||
|
AppManager.save(userAccount6);
|
||||||
|
System.out.println(AppManager.printCacheContent());
|
||||||
|
AppManager.find("004");
|
||||||
|
System.out.println(AppManager.printCacheContent());
|
||||||
|
}
|
||||||
|
}
|
87
caching/src/main/java/com/iluwatar/caching/AppManager.java
Normal file
87
caching/src/main/java/com/iluwatar/caching/AppManager.java
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package com.iluwatar.caching;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class AppManager {
|
||||||
|
|
||||||
|
private static CachingPolicy cachingPolicy;
|
||||||
|
|
||||||
|
private AppManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Developer/Tester is able to choose whether the application should use MongoDB as its underlying
|
||||||
|
* data storage or a simple Java data structure to (temporarily) store the data/objects during
|
||||||
|
* runtime.
|
||||||
|
*/
|
||||||
|
public static void initDb(boolean useMongoDb) {
|
||||||
|
if (useMongoDb) {
|
||||||
|
try {
|
||||||
|
DbManager.connect();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DbManager.createVirtualDb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize caching policy
|
||||||
|
*/
|
||||||
|
public static void initCachingPolicy(CachingPolicy policy) {
|
||||||
|
cachingPolicy = policy;
|
||||||
|
if (cachingPolicy == CachingPolicy.BEHIND) {
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
CacheStore.flushCache();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
CacheStore.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initCacheCapacity(int capacity) {
|
||||||
|
CacheStore.initCapacity(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find user account
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save user account
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String printCacheContent() {
|
||||||
|
return CacheStore.print();
|
||||||
|
}
|
||||||
|
}
|
134
caching/src/main/java/com/iluwatar/caching/CacheStore.java
Normal file
134
caching/src/main/java/com/iluwatar/caching/CacheStore.java
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package com.iluwatar.caching;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* The caching strategies are implemented in this class.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class CacheStore {
|
||||||
|
|
||||||
|
static LruCache cache = null;
|
||||||
|
|
||||||
|
private CacheStore() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init cache capacity
|
||||||
|
*/
|
||||||
|
public static void initCapacity(int capacity) {
|
||||||
|
if (null == cache) {
|
||||||
|
cache = new LruCache(capacity);
|
||||||
|
} else {
|
||||||
|
cache.setCapacity(capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user account using read-through cache
|
||||||
|
*/
|
||||||
|
public static UserAccount readThrough(String userId) {
|
||||||
|
if (cache.contains(userId)) {
|
||||||
|
System.out.println("# Cache Hit!");
|
||||||
|
return cache.get(userId);
|
||||||
|
}
|
||||||
|
System.out.println("# Cache Miss!");
|
||||||
|
UserAccount userAccount = DbManager.readFromDb(userId);
|
||||||
|
cache.set(userId, userAccount);
|
||||||
|
return userAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user account using write-through cache
|
||||||
|
*/
|
||||||
|
public static void writeThrough(UserAccount userAccount) {
|
||||||
|
if (cache.contains(userAccount.getUserId())) {
|
||||||
|
DbManager.updateDb(userAccount);
|
||||||
|
} else {
|
||||||
|
DbManager.writeToDb(userAccount);
|
||||||
|
}
|
||||||
|
cache.set(userAccount.getUserId(), userAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user account using write-around cache
|
||||||
|
*/
|
||||||
|
public static void writeAround(UserAccount userAccount) {
|
||||||
|
if (cache.contains(userAccount.getUserId())) {
|
||||||
|
DbManager.updateDb(userAccount);
|
||||||
|
cache.invalidate(userAccount.getUserId()); // Cache data has been updated -- remove older
|
||||||
|
// version from cache.
|
||||||
|
} else {
|
||||||
|
DbManager.writeToDb(userAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user account using read-through cache with write-back policy
|
||||||
|
*/
|
||||||
|
public static UserAccount readThroughWithWriteBackPolicy(String userId) {
|
||||||
|
if (cache.contains(userId)) {
|
||||||
|
System.out.println("# Cache Hit!");
|
||||||
|
return cache.get(userId);
|
||||||
|
}
|
||||||
|
System.out.println("# Cache Miss!");
|
||||||
|
UserAccount userAccount = DbManager.readFromDb(userId);
|
||||||
|
if (cache.isFull()) {
|
||||||
|
System.out.println("# Cache is FULL! Writing LRU data to DB...");
|
||||||
|
UserAccount toBeWrittenToDb = cache.getLruData();
|
||||||
|
DbManager.upsertDb(toBeWrittenToDb);
|
||||||
|
}
|
||||||
|
cache.set(userId, userAccount);
|
||||||
|
return userAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user account
|
||||||
|
*/
|
||||||
|
public static void writeBehind(UserAccount userAccount) {
|
||||||
|
if (cache.isFull() && !cache.contains(userAccount.getUserId())) {
|
||||||
|
System.out.println("# Cache is FULL! Writing LRU data to DB...");
|
||||||
|
UserAccount toBeWrittenToDb = cache.getLruData();
|
||||||
|
DbManager.upsertDb(toBeWrittenToDb);
|
||||||
|
}
|
||||||
|
cache.set(userAccount.getUserId(), userAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears cache
|
||||||
|
*/
|
||||||
|
public static void clearCache() {
|
||||||
|
if (null != cache) {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes remaining content in the cache into the DB.
|
||||||
|
*/
|
||||||
|
public static void flushCache() {
|
||||||
|
System.out.println("# flushCache...");
|
||||||
|
if (null == cache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ArrayList<UserAccount> listOfUserAccounts = cache.getCacheDataInListForm();
|
||||||
|
for (UserAccount userAccount : listOfUserAccounts) {
|
||||||
|
DbManager.upsertDb(userAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print user accounts
|
||||||
|
*/
|
||||||
|
public static String print() {
|
||||||
|
ArrayList<UserAccount> listOfUserAccounts = cache.getCacheDataInListForm();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("\n--CACHE CONTENT--\n");
|
||||||
|
for (UserAccount userAccount : listOfUserAccounts) {
|
||||||
|
sb.append(userAccount.toString() + "\n");
|
||||||
|
}
|
||||||
|
sb.append("----\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.iluwatar.caching;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Enum class containing the three caching strategies implemented in the pattern.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public enum CachingPolicy {
|
||||||
|
THROUGH("through"), AROUND("around"), BEHIND("behind");
|
||||||
|
|
||||||
|
private String policy;
|
||||||
|
|
||||||
|
private CachingPolicy(String policy) {
|
||||||
|
this.policy = policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPolicy() {
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
}
|
143
caching/src/main/java/com/iluwatar/caching/DbManager.java
Normal file
143
caching/src/main/java/com/iluwatar/caching/DbManager.java
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package com.iluwatar.caching;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import org.bson.Document;
|
||||||
|
|
||||||
|
import com.mongodb.MongoClient;
|
||||||
|
import com.mongodb.client.FindIterable;
|
||||||
|
import com.mongodb.client.MongoDatabase;
|
||||||
|
import com.mongodb.client.model.UpdateOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* <p>DBManager handles the communication with the underlying data store i.e. Database. It contains the
|
||||||
|
* implemented methods for querying, inserting, and updating data. MongoDB was used as the database
|
||||||
|
* for the application.</p>
|
||||||
|
*
|
||||||
|
* <p>Developer/Tester is able to choose whether the application should use MongoDB as its underlying
|
||||||
|
* data storage (connect()) or a simple Java data structure to (temporarily) store the data/objects
|
||||||
|
* during runtime (createVirtualDB()).</p>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class DbManager {
|
||||||
|
|
||||||
|
private static MongoClient mongoClient;
|
||||||
|
private static MongoDatabase db;
|
||||||
|
private static boolean useMongoDB;
|
||||||
|
|
||||||
|
private static HashMap<String, UserAccount> virtualDB;
|
||||||
|
|
||||||
|
private DbManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create DB
|
||||||
|
*/
|
||||||
|
public static void createVirtualDb() {
|
||||||
|
useMongoDB = false;
|
||||||
|
virtualDB = new HashMap<String, UserAccount>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to DB
|
||||||
|
*/
|
||||||
|
public static void connect() throws ParseException {
|
||||||
|
useMongoDB = true;
|
||||||
|
mongoClient = new MongoClient();
|
||||||
|
db = mongoClient.getDatabase("test");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read user account from DB
|
||||||
|
*/
|
||||||
|
public static UserAccount readFromDb(String userId) {
|
||||||
|
if (!useMongoDB) {
|
||||||
|
if (virtualDB.containsKey(userId)) {
|
||||||
|
return virtualDB.get(userId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (null == db) {
|
||||||
|
try {
|
||||||
|
connect();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FindIterable<Document> iterable =
|
||||||
|
db.getCollection("user_accounts").find(new Document("userID", userId));
|
||||||
|
if (iterable == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Document doc = iterable.first();
|
||||||
|
UserAccount userAccount =
|
||||||
|
new UserAccount(userId, doc.getString("userName"), doc.getString("additionalInfo"));
|
||||||
|
return userAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write user account to DB
|
||||||
|
*/
|
||||||
|
public static void writeToDb(UserAccount userAccount) {
|
||||||
|
if (!useMongoDB) {
|
||||||
|
virtualDB.put(userAccount.getUserId(), userAccount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (null == db) {
|
||||||
|
try {
|
||||||
|
connect();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.getCollection("user_accounts").insertOne(
|
||||||
|
new Document("userID", userAccount.getUserId()).append("userName",
|
||||||
|
userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update DB
|
||||||
|
*/
|
||||||
|
public static void updateDb(UserAccount userAccount) {
|
||||||
|
if (!useMongoDB) {
|
||||||
|
virtualDB.put(userAccount.getUserId(), userAccount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (null == db) {
|
||||||
|
try {
|
||||||
|
connect();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.getCollection("user_accounts").updateOne(
|
||||||
|
new Document("userID", userAccount.getUserId()),
|
||||||
|
new Document("$set", new Document("userName", userAccount.getUserName()).append(
|
||||||
|
"additionalInfo", userAccount.getAdditionalInfo())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Insert data into DB if it does not exist. Else, update it.
|
||||||
|
*/
|
||||||
|
public static void upsertDb(UserAccount userAccount) {
|
||||||
|
if (!useMongoDB) {
|
||||||
|
virtualDB.put(userAccount.getUserId(), userAccount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (null == db) {
|
||||||
|
try {
|
||||||
|
connect();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.getCollection("user_accounts").updateOne(
|
||||||
|
new Document("userID", userAccount.getUserId()),
|
||||||
|
new Document("$set", new Document("userID", userAccount.getUserId()).append("userName",
|
||||||
|
userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())),
|
||||||
|
new UpdateOptions().upsert(true));
|
||||||
|
}
|
||||||
|
}
|
163
caching/src/main/java/com/iluwatar/caching/LruCache.java
Normal file
163
caching/src/main/java/com/iluwatar/caching/LruCache.java
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package com.iluwatar.caching;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Data structure/implementation of the application's cache. The data structure consists of a hash
|
||||||
|
* table attached with a doubly linked-list. The linked-list helps in capturing and maintaining the
|
||||||
|
* LRU data in the cache. When a 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class LruCache {
|
||||||
|
|
||||||
|
class Node {
|
||||||
|
String userId;
|
||||||
|
UserAccount userAccount;
|
||||||
|
Node previous;
|
||||||
|
Node next;
|
||||||
|
|
||||||
|
public Node(String userId, UserAccount userAccount) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.userAccount = userAccount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int capacity;
|
||||||
|
HashMap<String, Node> cache = new HashMap<String, Node>();
|
||||||
|
Node head = null;
|
||||||
|
Node end = null;
|
||||||
|
|
||||||
|
public LruCache(int capacity) {
|
||||||
|
this.capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user account
|
||||||
|
*/
|
||||||
|
public UserAccount get(String userId) {
|
||||||
|
if (cache.containsKey(userId)) {
|
||||||
|
Node node = cache.get(userId);
|
||||||
|
remove(node);
|
||||||
|
setHead(node);
|
||||||
|
return node.userAccount;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Remove node from linked list.
|
||||||
|
*/
|
||||||
|
public void remove(Node node) {
|
||||||
|
if (node.previous != null) {
|
||||||
|
node.previous.next = node.next;
|
||||||
|
} else {
|
||||||
|
head = node.next;
|
||||||
|
}
|
||||||
|
if (node.next != null) {
|
||||||
|
node.next.previous = node.previous;
|
||||||
|
} else {
|
||||||
|
end = node.previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Move node to the front of the list.
|
||||||
|
*/
|
||||||
|
public void setHead(Node node) {
|
||||||
|
node.next = head;
|
||||||
|
node.previous = null;
|
||||||
|
if (head != null) {
|
||||||
|
head.previous = node;
|
||||||
|
}
|
||||||
|
head = node;
|
||||||
|
if (end == null) {
|
||||||
|
end = head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user account
|
||||||
|
*/
|
||||||
|
public void set(String userId, UserAccount userAccount) {
|
||||||
|
if (cache.containsKey(userId)) {
|
||||||
|
Node old = cache.get(userId);
|
||||||
|
old.userAccount = userAccount;
|
||||||
|
remove(old);
|
||||||
|
setHead(old);
|
||||||
|
} else {
|
||||||
|
Node newNode = new Node(userId, userAccount);
|
||||||
|
if (cache.size() >= capacity) {
|
||||||
|
System.out.println("# Cache is FULL! Removing " + end.userId + " from cache...");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate cache for user
|
||||||
|
*/
|
||||||
|
public void invalidate(String userId) {
|
||||||
|
System.out.println("# " + userId + " has been updated! Removing older version from cache...");
|
||||||
|
Node toBeRemoved = cache.get(userId);
|
||||||
|
remove(toBeRemoved);
|
||||||
|
cache.remove(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFull() {
|
||||||
|
return cache.size() >= capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccount getLruData() {
|
||||||
|
return end.userAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear cache
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
head = null;
|
||||||
|
end = null;
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Returns cache data in list form.
|
||||||
|
*/
|
||||||
|
public ArrayList<UserAccount> getCacheDataInListForm() {
|
||||||
|
ArrayList<UserAccount> listOfCacheData = new ArrayList<UserAccount>();
|
||||||
|
Node temp = head;
|
||||||
|
while (temp != null) {
|
||||||
|
listOfCacheData.add(temp.userAccount);
|
||||||
|
temp = temp.next;
|
||||||
|
}
|
||||||
|
return listOfCacheData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set cache capacity
|
||||||
|
*/
|
||||||
|
public void setCapacity(int newCapacity) {
|
||||||
|
if (capacity > newCapacity) {
|
||||||
|
clear(); // Behavior can be modified to accommodate for decrease in cache size. For now, we'll
|
||||||
|
// just clear the cache.
|
||||||
|
} else {
|
||||||
|
this.capacity = newCapacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
caching/src/main/java/com/iluwatar/caching/UserAccount.java
Normal file
50
caching/src/main/java/com/iluwatar/caching/UserAccount.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package com.iluwatar.caching;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Entity class (stored in cache and DB) used in the application.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class UserAccount {
|
||||||
|
private String userId;
|
||||||
|
private String userName;
|
||||||
|
private String additionalInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public UserAccount(String userId, String userName, String additionalInfo) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.userName = userName;
|
||||||
|
this.additionalInfo = additionalInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userName) {
|
||||||
|
this.userName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAdditionalInfo() {
|
||||||
|
return additionalInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdditionalInfo(String additionalInfo) {
|
||||||
|
this.additionalInfo = additionalInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return userId + ", " + userName + ", " + additionalInfo;
|
||||||
|
}
|
||||||
|
}
|
41
caching/src/test/java/com/iluwatar/caching/AppTest.java
Normal file
41
caching/src/test/java/com/iluwatar/caching/AppTest.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package com.iluwatar.caching;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Application test
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class AppTest {
|
||||||
|
App app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup of application test includes: initializing DB connection and cache size/capacity.
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
AppManager.initDb(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests
|
||||||
|
// to avoid Maven compilation errors. Set flag to true to run the
|
||||||
|
// tests with MongoDB (provided that MongoDB is installed and socket
|
||||||
|
// connection is open).
|
||||||
|
AppManager.initCacheCapacity(3);
|
||||||
|
app = new App();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadAndWriteThroughStrategy() {
|
||||||
|
app.useReadAndWriteThroughStrategy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadThroughAndWriteAroundStrategy() {
|
||||||
|
app.useReadThroughAndWriteAroundStrategy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadThroughAndWriteBehindStrategy() {
|
||||||
|
app.useReadThroughAndWriteBehindStrategy();
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,11 @@ title: Callback
|
|||||||
folder: callback
|
folder: callback
|
||||||
permalink: /patterns/callback/
|
permalink: /patterns/callback/
|
||||||
categories: Other
|
categories: Other
|
||||||
tags: Java
|
tags:
|
||||||
|
- Java
|
||||||
|
- Difficulty-Beginner
|
||||||
|
- Functional
|
||||||
|
- Idiom
|
||||||
---
|
---
|
||||||
|
|
||||||
**Intent:** Callback is a piece of executable code that is passed as an
|
**Intent:** Callback is a piece of executable code that is passed as an
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project
|
<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"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.iluwatar</groupId>
|
<groupId>com.iluwatar</groupId>
|
||||||
<artifactId>java-design-patterns</artifactId>
|
<artifactId>java-design-patterns</artifactId>
|
||||||
<version>1.6.0</version>
|
<version>1.9.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>callback</artifactId>
|
<artifactId>callback</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -2,12 +2,16 @@ package com.iluwatar.callback;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Callback pattern is more native for functional languages where functions are treated as first-class citizens.
|
* Callback pattern is more native for functional languages where functions are treated as
|
||||||
* Prior to Java 8 callbacks can be simulated using simple (alike command) interfaces.
|
* first-class citizens. Prior to Java 8 callbacks can be simulated using simple (alike command)
|
||||||
|
* interfaces.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program entry point
|
||||||
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Task task = new SimpleTask();
|
Task task = new SimpleTask();
|
||||||
Callback callback = new Callback() {
|
Callback callback = new Callback() {
|
||||||
|
@ -7,5 +7,5 @@ package com.iluwatar.callback;
|
|||||||
*/
|
*/
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
|
|
||||||
public void call();
|
void call();
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ public class SimpleTask extends Task {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() {
|
public void execute() {
|
||||||
System.out.println("Perform some important activity.");
|
System.out.println("Perform some important activity and after call the callback method.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,9 @@ package com.iluwatar.callback;
|
|||||||
*/
|
*/
|
||||||
public abstract class Task {
|
public abstract class Task {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute with callback
|
||||||
|
*/
|
||||||
public final void executeWith(Callback callback) {
|
public final void executeWith(Callback callback) {
|
||||||
execute();
|
execute();
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
|
@ -2,18 +2,38 @@ package com.iluwatar.callback;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.iluwatar.callback.App;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Add a field as a counter. Every time the callback method is called increment this field. Unit
|
||||||
|
* test checks that the field is being incremented.
|
||||||
*
|
*
|
||||||
* Application test
|
* Could be done with mock objects as well where the call method call is verified.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class AppTest {
|
public class AppTest {
|
||||||
|
|
||||||
|
private Integer callingCount = 0;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
String[] args = {};
|
Callback callback = new Callback() {
|
||||||
App.main(args);
|
@Override
|
||||||
|
public void call() {
|
||||||
|
callingCount++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Task task = new SimpleTask();
|
||||||
|
|
||||||
|
assertEquals("Initial calling count of 0", new Integer(0), callingCount);
|
||||||
|
|
||||||
|
task.executeWith(callback);
|
||||||
|
|
||||||
|
assertEquals("Callback called once", new Integer(1), callingCount);
|
||||||
|
|
||||||
|
task.executeWith(callback);
|
||||||
|
|
||||||
|
assertEquals("Callback called twice", new Integer(2), callingCount);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,10 @@ title: Chain of responsibility
|
|||||||
folder: chain
|
folder: chain
|
||||||
permalink: /patterns/chain/
|
permalink: /patterns/chain/
|
||||||
categories: Behavioral
|
categories: Behavioral
|
||||||
tags: Java
|
tags:
|
||||||
|
- Java
|
||||||
|
- Gang Of Four
|
||||||
|
- Difficulty-Intermediate
|
||||||
---
|
---
|
||||||
|
|
||||||
**Intent:** Avoid coupling the sender of a request to its receiver by giving
|
**Intent:** Avoid coupling the sender of a request to its receiver by giving
|
||||||
@ -23,3 +26,7 @@ objects and pass the request along the chain until an object handles it.
|
|||||||
|
|
||||||
* [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29)
|
* [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29)
|
||||||
* [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html)
|
* [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html)
|
||||||
|
|
||||||
|
**Credits**
|
||||||
|
|
||||||
|
* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project
|
<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"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.iluwatar</groupId>
|
<groupId>com.iluwatar</groupId>
|
||||||
<artifactId>java-design-patterns</artifactId>
|
<artifactId>java-design-patterns</artifactId>
|
||||||
<version>1.6.0</version>
|
<version>1.9.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>chain</artifactId>
|
<artifactId>chain</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -2,24 +2,29 @@ package com.iluwatar.chain;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Chain of Responsibility organizes request handlers ({@link RequestHandler}) into a
|
* The Chain of Responsibility pattern is a design pattern consisting of command objects and a
|
||||||
* chain where each handler has a chance to act on the request on its turn. In
|
* series of processing objects. Each processing object contains logic that defines the types of
|
||||||
* this example the king ({@link OrcKing}) makes requests and the military orcs
|
* command objects that it can handle; the rest are passed to the next processing object in the
|
||||||
* ({@link OrcCommander}, {@link OrcOfficer}, {@link OrcSoldier}) form the handler chain.
|
* chain. A mechanism also exists for adding new processing objects to the end of this chain.
|
||||||
|
* <p>
|
||||||
|
* In this example we organize the request handlers ({@link RequestHandler}) into a chain where each
|
||||||
|
* handler has a chance to act on the request on its turn. Here the king ({@link OrcKing}) makes
|
||||||
|
* requests and the military orcs ({@link OrcCommander}, {@link OrcOfficer}, {@link OrcSoldier})
|
||||||
|
* form the handler chain.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class App {
|
public class App {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point
|
* Program entry point
|
||||||
|
*
|
||||||
* @param args command line args
|
* @param args command line args
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
OrcKing king = new OrcKing();
|
OrcKing king = new OrcKing();
|
||||||
king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle"));
|
king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle"));
|
||||||
king.makeRequest(new Request(RequestType.TORTURE_PRISONER,
|
king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner"));
|
||||||
"torture prisoner"));
|
|
||||||
king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax"));
|
king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ public class OrcCommander extends RequestHandler {
|
|||||||
public void handleRequest(Request req) {
|
public void handleRequest(Request req) {
|
||||||
if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) {
|
if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) {
|
||||||
printHandling(req);
|
printHandling(req);
|
||||||
|
req.markHandled();
|
||||||
} else {
|
} else {
|
||||||
super.handleRequest(req);
|
super.handleRequest(req);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ public class OrcOfficer extends RequestHandler {
|
|||||||
public void handleRequest(Request req) {
|
public void handleRequest(Request req) {
|
||||||
if (req.getRequestType().equals(RequestType.TORTURE_PRISONER)) {
|
if (req.getRequestType().equals(RequestType.TORTURE_PRISONER)) {
|
||||||
printHandling(req);
|
printHandling(req);
|
||||||
|
req.markHandled();
|
||||||
} else {
|
} else {
|
||||||
super.handleRequest(req);
|
super.handleRequest(req);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ public class OrcSoldier extends RequestHandler {
|
|||||||
public void handleRequest(Request req) {
|
public void handleRequest(Request req) {
|
||||||
if (req.getRequestType().equals(RequestType.COLLECT_TAX)) {
|
if (req.getRequestType().equals(RequestType.COLLECT_TAX)) {
|
||||||
printHandling(req);
|
printHandling(req);
|
||||||
|
req.markHandled();
|
||||||
} else {
|
} else {
|
||||||
super.handleRequest(req);
|
super.handleRequest(req);
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,78 @@
|
|||||||
package com.iluwatar.chain;
|
package com.iluwatar.chain;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* Request
|
* Request
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class Request {
|
public class Request {
|
||||||
|
|
||||||
private String requestDescription;
|
/**
|
||||||
private RequestType requestType;
|
* The type of this request, used by each item in the chain to see if they should or can handle
|
||||||
|
* this particular request
|
||||||
|
*/
|
||||||
|
private final RequestType requestType;
|
||||||
|
|
||||||
public Request(RequestType requestType, String requestDescription) {
|
/**
|
||||||
this.setRequestType(requestType);
|
* A description of the request
|
||||||
this.setRequestDescription(requestDescription);
|
*/
|
||||||
|
private final String requestDescription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the request is handled or not. A request can only switch state from unhandled to
|
||||||
|
* handled, there's no way to 'unhandle' a request
|
||||||
|
*/
|
||||||
|
private boolean handled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new request of the given type and accompanied description.
|
||||||
|
*
|
||||||
|
* @param requestType The type of request
|
||||||
|
* @param requestDescription The description of the request
|
||||||
|
*/
|
||||||
|
public Request(final RequestType requestType, final String requestDescription) {
|
||||||
|
this.requestType = Objects.requireNonNull(requestType);
|
||||||
|
this.requestDescription = Objects.requireNonNull(requestDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a description of the request
|
||||||
|
*
|
||||||
|
* @return A human readable description of the request
|
||||||
|
*/
|
||||||
public String getRequestDescription() {
|
public String getRequestDescription() {
|
||||||
return requestDescription;
|
return requestDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRequestDescription(String requestDescription) {
|
/**
|
||||||
this.requestDescription = requestDescription;
|
* Get the type of this request, used by each person in the chain of command to see if they should
|
||||||
}
|
* or can handle this particular request
|
||||||
|
*
|
||||||
|
* @return The request type
|
||||||
|
*/
|
||||||
public RequestType getRequestType() {
|
public RequestType getRequestType() {
|
||||||
return requestType;
|
return requestType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRequestType(RequestType requestType) {
|
/**
|
||||||
this.requestType = requestType;
|
* Mark the request as handled
|
||||||
|
*/
|
||||||
|
public void markHandled() {
|
||||||
|
this.handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this request is handled or not
|
||||||
|
*
|
||||||
|
* @return <tt>true</tt> when the request is handled, <tt>false</tt> if not
|
||||||
|
*/
|
||||||
|
public boolean isHandled() {
|
||||||
|
return this.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getRequestDescription();
|
return getRequestDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,9 @@ public abstract class RequestHandler {
|
|||||||
this.next = next;
|
this.next = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request handler
|
||||||
|
*/
|
||||||
public void handleRequest(Request req) {
|
public void handleRequest(Request req) {
|
||||||
if (next != null) {
|
if (next != null) {
|
||||||
next.handleRequest(req);
|
next.handleRequest(req);
|
||||||
|
@ -2,8 +2,6 @@ package com.iluwatar.chain;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.iluwatar.chain.App;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Application test
|
* Application test
|
||||||
|
37
chain/src/test/java/com/iluwatar/chain/OrcKingTest.java
Normal file
37
chain/src/test/java/com/iluwatar/chain/OrcKingTest.java
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package com.iluwatar.chain;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date: 12/6/15 - 9:29 PM
|
||||||
|
*
|
||||||
|
* @author Jeroen Meulemeester
|
||||||
|
*/
|
||||||
|
public class OrcKingTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All possible requests
|
||||||
|
*/
|
||||||
|
private static final Request[] REQUESTS = new Request[]{
|
||||||
|
new Request(RequestType.DEFEND_CASTLE, "Don't let the barbarians enter my castle!!"),
|
||||||
|
new Request(RequestType.TORTURE_PRISONER, "Don't just stand there, tickle him!"),
|
||||||
|
new Request(RequestType.COLLECT_TAX, "Don't steal, the King hates competition ..."),
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMakeRequest() throws Exception {
|
||||||
|
final OrcKing king = new OrcKing();
|
||||||
|
|
||||||
|
for (final Request request : REQUESTS) {
|
||||||
|
king.makeRequest(request);
|
||||||
|
assertTrue(
|
||||||
|
"Expected all requests from King to be handled, but [" + request + "] was not!",
|
||||||
|
request.isHandled()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
checkstyle-suppressions.xml
Normal file
9
checkstyle-suppressions.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN"
|
||||||
|
"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
|
||||||
|
<suppressions>
|
||||||
|
<suppress checks="AvoidStarImport" files="[\\/]src[\\/]test[\\/]java[\\/]"/>
|
||||||
|
<suppress checks="[a-zA-Z0-9]*" files="[\\/]src[\\/]test[\\/]resources[\\/]"/>
|
||||||
|
<suppress checks="[a-zA-Z0-9]*" files="[\\/]build[\\/]generated-sources[\\/]"/>
|
||||||
|
<suppress checks="[a-zA-Z0-9]*" files="[\\/]src[\\/]main[\\/]resources[\\/]"/>
|
||||||
|
</suppressions>
|
@ -26,7 +26,9 @@
|
|||||||
<module name="Checker">
|
<module name="Checker">
|
||||||
<property name="charset" value="UTF-8"/>
|
<property name="charset" value="UTF-8"/>
|
||||||
|
|
||||||
<property name="severity" value="warning"/>
|
<property name="fileExtensions" value="java, xml, properties"/>
|
||||||
|
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
|
||||||
<!-- Checks for whitespace -->
|
<!-- Checks for whitespace -->
|
||||||
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
|
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
|
||||||
@ -48,7 +50,7 @@
|
|||||||
<property name="allowNonPrintableEscapes" value="true"/>
|
<property name="allowNonPrintableEscapes" value="true"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="LineLength">
|
<module name="LineLength">
|
||||||
<property name="max" value="100"/>
|
<property name="max" value="120"/>
|
||||||
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
|
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="AvoidStarImport"/>
|
<module name="AvoidStarImport"/>
|
||||||
@ -61,7 +63,7 @@
|
|||||||
</module>
|
</module>
|
||||||
<module name="NeedBraces"/>
|
<module name="NeedBraces"/>
|
||||||
<module name="LeftCurly">
|
<module name="LeftCurly">
|
||||||
<property name="maxLineLength" value="100"/>
|
<property name="maxLineLength" value="120"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="RightCurly"/>
|
<module name="RightCurly"/>
|
||||||
<module name="RightCurly">
|
<module name="RightCurly">
|
||||||
@ -86,9 +88,6 @@
|
|||||||
<module name="FallThrough"/>
|
<module name="FallThrough"/>
|
||||||
<module name="UpperEll"/>
|
<module name="UpperEll"/>
|
||||||
<module name="ModifierOrder"/>
|
<module name="ModifierOrder"/>
|
||||||
<module name="EmptyLineSeparator">
|
|
||||||
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
|
||||||
</module>
|
|
||||||
<module name="SeparatorWrap">
|
<module name="SeparatorWrap">
|
||||||
<property name="tokens" value="DOT"/>
|
<property name="tokens" value="DOT"/>
|
||||||
<property name="option" value="nl"/>
|
<property name="option" value="nl"/>
|
||||||
@ -97,42 +96,19 @@
|
|||||||
<property name="tokens" value="COMMA"/>
|
<property name="tokens" value="COMMA"/>
|
||||||
<property name="option" value="EOL"/>
|
<property name="option" value="EOL"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="PackageName">
|
|
||||||
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
|
<!-- Checks for Naming Conventions. -->
|
||||||
<message key="name.invalidPattern"
|
<!-- See http://checkstyle.sf.net/config_naming.html -->
|
||||||
value="Package name ''{0}'' must match pattern ''{1}''."/>
|
<module name="ConstantName"/>
|
||||||
</module>
|
<module name="LocalFinalVariableName"/>
|
||||||
<module name="TypeName">
|
<module name="LocalVariableName"/>
|
||||||
<message key="name.invalidPattern"
|
<module name="MemberName"/>
|
||||||
value="Type name ''{0}'' must match pattern ''{1}''."/>
|
<module name="MethodName"/>
|
||||||
</module>
|
<module name="PackageName"/>
|
||||||
<module name="MemberName">
|
<module name="ParameterName"/>
|
||||||
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
|
<module name="StaticVariableName"/>
|
||||||
<message key="name.invalidPattern"
|
<module name="TypeName"/>
|
||||||
value="Member name ''{0}'' must match pattern ''{1}''."/>
|
|
||||||
</module>
|
|
||||||
<module name="ParameterName">
|
|
||||||
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
|
|
||||||
<message key="name.invalidPattern"
|
|
||||||
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
|
|
||||||
</module>
|
|
||||||
<module name="LocalVariableName">
|
|
||||||
<property name="tokens" value="VARIABLE_DEF"/>
|
|
||||||
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
|
|
||||||
<property name="allowOneCharVarInForLoop" value="true"/>
|
|
||||||
<message key="name.invalidPattern"
|
|
||||||
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
|
|
||||||
</module>
|
|
||||||
<module name="ClassTypeParameterName">
|
|
||||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
|
||||||
<message key="name.invalidPattern"
|
|
||||||
value="Class type name ''{0}'' must match pattern ''{1}''."/>
|
|
||||||
</module>
|
|
||||||
<module name="MethodTypeParameterName">
|
|
||||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
|
||||||
<message key="name.invalidPattern"
|
|
||||||
value="Method type name ''{0}'' must match pattern ''{1}''."/>
|
|
||||||
</module>
|
|
||||||
<module name="NoFinalizer"/>
|
<module name="NoFinalizer"/>
|
||||||
<module name="GenericWhitespace">
|
<module name="GenericWhitespace">
|
||||||
<message key="ws.followed"
|
<message key="ws.followed"
|
||||||
@ -157,14 +133,6 @@
|
|||||||
<property name="allowedAbbreviationLength" value="1"/>
|
<property name="allowedAbbreviationLength" value="1"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="OverloadMethodsDeclarationOrder"/>
|
<module name="OverloadMethodsDeclarationOrder"/>
|
||||||
<module name="VariableDeclarationUsageDistance"/>
|
|
||||||
<module name="CustomImportOrder">
|
|
||||||
<property name="thirdPartyPackageRegExp" value=".*"/>
|
|
||||||
<property name="specialImportsRegExp" value="com.google"/>
|
|
||||||
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
|
||||||
<property name="customImportOrderRules"
|
|
||||||
value="STATIC###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE"/>
|
|
||||||
</module>
|
|
||||||
<module name="MethodParamPad"/>
|
<module name="MethodParamPad"/>
|
||||||
<module name="OperatorWrap">
|
<module name="OperatorWrap">
|
||||||
<property name="option" value="NL"/>
|
<property name="option" value="NL"/>
|
||||||
@ -180,11 +148,6 @@
|
|||||||
</module>
|
</module>
|
||||||
<module name="NonEmptyAtclauseDescription"/>
|
<module name="NonEmptyAtclauseDescription"/>
|
||||||
<module name="JavadocTagContinuationIndentation"/>
|
<module name="JavadocTagContinuationIndentation"/>
|
||||||
<module name="SummaryJavadocCheck">
|
|
||||||
<property name="forbiddenSummaryFragments"
|
|
||||||
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
|
|
||||||
</module>
|
|
||||||
<module name="JavadocParagraph"/>
|
|
||||||
<module name="AtclauseOrder">
|
<module name="AtclauseOrder">
|
||||||
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
||||||
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||||
@ -195,7 +158,7 @@
|
|||||||
<property name="allowMissingThrowsTags" value="true"/>
|
<property name="allowMissingThrowsTags" value="true"/>
|
||||||
<property name="allowMissingReturnTag" value="true"/>
|
<property name="allowMissingReturnTag" value="true"/>
|
||||||
<property name="minLineCount" value="2"/>
|
<property name="minLineCount" value="2"/>
|
||||||
<property name="allowedAnnotations" value="Override, Test"/>
|
<property name="allowedAnnotations" value="Override, Test, Before, After, Parameters, Given, When, BeforeClass, AfterClass, Parameterized"/>
|
||||||
<property name="allowThrowsTagsForSubclasses" value="true"/>
|
<property name="allowThrowsTagsForSubclasses" value="true"/>
|
||||||
</module>
|
</module>
|
||||||
<module name="MethodName">
|
<module name="MethodName">
|
||||||
@ -205,4 +168,5 @@
|
|||||||
</module>
|
</module>
|
||||||
<module name="SingleLineJavadoc"/>
|
<module name="SingleLineJavadoc"/>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
</module>
|
</module>
|
||||||
|
@ -4,9 +4,14 @@ title: Command
|
|||||||
folder: command
|
folder: command
|
||||||
permalink: /patterns/command/
|
permalink: /patterns/command/
|
||||||
categories: Behavioral
|
categories: Behavioral
|
||||||
tags: Java
|
tags:
|
||||||
|
- Java
|
||||||
|
- Gang Of Four
|
||||||
|
- Difficulty-Intermediate
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Also known as:** Action, Transaction
|
||||||
|
|
||||||
**Intent:** Encapsulate a request as an object, thereby letting you
|
**Intent:** Encapsulate a request as an object, thereby letting you
|
||||||
parameterize clients with different requests, queue or log requests, and
|
parameterize clients with different requests, queue or log requests, and
|
||||||
support undoable operations.
|
support undoable operations.
|
||||||
@ -30,3 +35,7 @@ support undoable operations.
|
|||||||
**Real world examples:**
|
**Real world examples:**
|
||||||
|
|
||||||
* [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html)
|
* [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html)
|
||||||
|
|
||||||
|
**Credits**
|
||||||
|
|
||||||
|
* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project
|
<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"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.iluwatar</groupId>
|
<groupId>com.iluwatar</groupId>
|
||||||
<artifactId>java-design-patterns</artifactId>
|
<artifactId>java-design-patterns</artifactId>
|
||||||
<version>1.6.0</version>
|
<version>1.9.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>command</artifactId>
|
<artifactId>command</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -15,5 +14,10 @@
|
|||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -2,19 +2,22 @@ package com.iluwatar.command;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* In Command pattern actions are objects that can be executed and undone.
|
* The Command pattern is a behavioral design pattern in which an object is used to encapsulate all
|
||||||
|
* information needed to perform an action or trigger an event at a later time. This information
|
||||||
|
* includes the method name, the object that owns the method and values for the method parameters.
|
||||||
* <p>
|
* <p>
|
||||||
* Four terms always associated with the command pattern are command, receiver, invoker and client. A command
|
* Four terms always associated with the command pattern are command, receiver, invoker and client.
|
||||||
* object (spell) knows about the receiver (target) and invokes a method of the receiver. Values for parameters of
|
* A command object (spell) knows about the receiver (target) and invokes a method of the receiver.
|
||||||
* the receiver method are stored in the command. The receiver then does the work. An invoker object (wizard)
|
* Values for parameters of the receiver method are stored in the command. The receiver then does
|
||||||
* knows how to execute a command, and optionally does bookkeeping about the command execution. The invoker
|
* the work. An invoker object (wizard) knows how to execute a command, and optionally does
|
||||||
* does not know anything about a concrete command, it knows only about command interface. Both an invoker object
|
* bookkeeping about the command execution. The invoker does not know anything about a concrete
|
||||||
* and several command objects are held by a client object (app). The client decides which commands to execute at
|
* command, it knows only about command interface. Both an invoker object and several command
|
||||||
* which points. To execute a command, it passes the command object to the invoker object.
|
* objects are held by a client object (app). The client decides which commands to execute at which
|
||||||
|
* points. To execute a command, it passes the command object to the invoker object.
|
||||||
* <p>
|
* <p>
|
||||||
* In other words, in this example the wizard casts spells on the goblin. The wizard keeps track of the previous
|
* In other words, in this example the wizard casts spells on the goblin. The wizard keeps track of
|
||||||
* spells cast, so it is easy to undo them. In addition, the wizard keeps track of the spells undone, so they
|
* the previous spells cast, so it is easy to undo them. In addition, the wizard keeps track of the
|
||||||
* can be redone.
|
* spells undone, so they can be redone.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@ -22,6 +25,7 @@ public class App {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Program entry point
|
* Program entry point
|
||||||
|
*
|
||||||
* @param args command line args
|
* @param args command line args
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
@ -30,9 +30,12 @@ public abstract class Target {
|
|||||||
@Override
|
@Override
|
||||||
public abstract String toString();
|
public abstract String toString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print status
|
||||||
|
*/
|
||||||
public void printStatus() {
|
public void printStatus() {
|
||||||
System.out.println(String.format("%s, [size=%s] [visibility=%s]", this,
|
System.out.println(String.format("%s, [size=%s] [visibility=%s]", this, getSize(),
|
||||||
getSize(), getVisibility()));
|
getVisibility()));
|
||||||
System.out.println();
|
System.out.println();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,15 +13,20 @@ public class Wizard {
|
|||||||
private Deque<Command> undoStack = new LinkedList<>();
|
private Deque<Command> undoStack = new LinkedList<>();
|
||||||
private Deque<Command> redoStack = new LinkedList<>();
|
private Deque<Command> redoStack = new LinkedList<>();
|
||||||
|
|
||||||
public Wizard() {
|
public Wizard() {}
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast spell
|
||||||
|
*/
|
||||||
public void castSpell(Command command, Target target) {
|
public void castSpell(Command command, Target target) {
|
||||||
System.out.println(this + " casts " + command + " at " + target);
|
System.out.println(this + " casts " + command + " at " + target);
|
||||||
command.execute(target);
|
command.execute(target);
|
||||||
undoStack.offerLast(command);
|
undoStack.offerLast(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo last spell
|
||||||
|
*/
|
||||||
public void undoLastSpell() {
|
public void undoLastSpell() {
|
||||||
if (!undoStack.isEmpty()) {
|
if (!undoStack.isEmpty()) {
|
||||||
Command previousSpell = undoStack.pollLast();
|
Command previousSpell = undoStack.pollLast();
|
||||||
@ -31,6 +36,9 @@ public class Wizard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redo last spell
|
||||||
|
*/
|
||||||
public void redoLastSpell() {
|
public void redoLastSpell() {
|
||||||
if (!redoStack.isEmpty()) {
|
if (!redoStack.isEmpty()) {
|
||||||
Command previousSpell = redoStack.pollLast();
|
Command previousSpell = redoStack.pollLast();
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user