Merge remote-tracking branch 'iluwatar/master'

# Conflicts:
#	pom.xml
This commit is contained in:
cfarrugia 2016-01-02 11:48:01 +01:00
commit 40042ae392
549 changed files with 11854 additions and 1579 deletions

4
CONTRIBUTING.MD Normal file
View 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.

View File

@ -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
[![Build status](https://travis-ci.org/iluwatar/java-design-patterns.svg?branch=master)](https://travis-ci.org/iluwatar/java-design-patterns) [![Build status](https://travis-ci.org/iluwatar/java-design-patterns.svg?branch=master)](https://travis-ci.org/iluwatar/java-design-patterns)
[![Coverage Status](https://coveralls.io/repos/iluwatar/java-design-patterns/badge.svg?branch=master)](https://coveralls.io/r/iluwatar/java-design-patterns?branch=master) [![Coverage Status](https://coveralls.io/repos/iluwatar/java-design-patterns/badge.svg?branch=master)](https://coveralls.io/r/iluwatar/java-design-patterns?branch=master)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/5634/badge.svg)](https://scan.coverity.com/projects/5634)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# Introduction # Introduction
@ -40,7 +39,7 @@ patterns by any of the following approaches
# 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

View File

@ -7,6 +7,7 @@ categories: Creational
tags: tags:
- Java - Java
- Gang Of Four - Gang Of Four
- Difficulty-Intermediate
--- ---
**Also known as:** Kit **Also known as:** Kit

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>abstract-factory</artifactId> <artifactId>abstract-factory</artifactId>
<dependencies> <dependencies>

View File

@ -1,20 +1,18 @@
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /**
* *
* The Abstract Factory pattern provides a way to encapsulate a group of individual factories that * The Abstract Factory pattern provides a way to encapsulate a group of individual factories that have a common theme
* have a common theme without specifying their concrete classes. In normal usage, the client * without specifying their concrete classes. In normal usage, the client software creates a concrete implementation of
* software creates a concrete implementation of the abstract factory and then uses the generic * the abstract factory and then uses the generic interface of the factory to create the concrete objects that are part
* interface of the factory to create the concrete objects that are part of the theme. The client * of the theme. The client does not know (or care) which concrete objects it gets from each of these internal
* does not know (or care) which concrete objects it gets from each of these internal factories, * factories, since it uses only the generic interfaces of their products. This pattern separates the details of
* since it uses only the generic interfaces of their products. This pattern separates the details * implementation of a set of objects from their general usage and relies on object composition, as object creation is
* of implementation of a set of objects from their general usage and relies on object composition, * implemented in methods exposed in the factory interface.
* as object creation is implemented in methods exposed in the factory interface.
* <p> * <p>
* The essence of the Abstract Factory pattern is a factory interface ({@link KingdomFactory}) and * The essence of the Abstract Factory pattern is a factory interface ({@link KingdomFactory}) and its implementations (
* its implementations ({@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses both * {@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses both concrete implementations to create a
* concrete implementations to create a king, a castle and an army. * king, a castle and an army.
* *
*/ */
public class App { public class App {
@ -23,11 +21,8 @@ public class App {
private Castle castle; private Castle castle;
private Army army; private Army army;
/** /**
* Creates kingdom * Creates kingdom
*
* @param factory
*/ */
public void createKingdom(final KingdomFactory factory) { public void createKingdom(final KingdomFactory factory) {
setKing(factory.createKing()); setKing(factory.createKing());
@ -47,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;
} }
@ -63,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;
} }
@ -71,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;
} }
@ -79,11 +74,11 @@ public class App {
this.army = army; this.army = army;
} }
/** /**
* 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) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

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

View File

@ -7,6 +7,7 @@ categories: Structural
tags: tags:
- Java - Java
- Gang Of Four - Gang Of Four
- Difficulty-Beginner
--- ---
**Also known as:** Wrapper **Also known as:** Wrapper
@ -15,7 +16,7 @@ tags:
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.
![alt text](./etc/adapter_1.png "Adapter") ![alt text](./etc/adapter.png "Adapter")
**Applicability:** Use the Adapter pattern when **Applicability:** Use the Adapter pattern when

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>adapter</artifactId> <artifactId>adapter</artifactId>
<dependencies> <dependencies>

View File

@ -6,13 +6,22 @@ package com.iluwatar.adapter;
* The Adapter design pattern allows otherwise incompatible classes to work together by converting * 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. * the interface of one class into an interface expected by the clients.
* *
* <p>There are two variations of the Adapter pattern: The class adapter implements the adaptee's * <p>
* 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 * interface whereas the object adapter uses composition to contain the adaptee in the adapter
* object. This example uses the object adapter approach. * object. This example uses the object adapter approach.
* *
* <p>The Adapter ({@link GnomeEngineer}) converts the interface of the target class ( * <p>
* {@link GoblinGlider}) into a suitable one expected by the client ({@link GnomeEngineeringManager} * 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 {
@ -22,7 +31,8 @@ public class App {
* @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(new GnomeEngineer()); Captain captain = new Captain(new BattleFishingBoat());
manager.operateDevice(); captain.move();
captain.fire();
} }
} }

View File

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

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

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

View File

@ -1,11 +0,0 @@
package com.iluwatar.adapter;
/**
*
* Engineers can operate devices.
*
*/
public interface Engineer {
void operateDevice();
}

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

View File

@ -1,23 +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();
}
}

View File

@ -1,26 +0,0 @@
package com.iluwatar.adapter;
/**
* GnomeEngineering manager uses {@link Engineer} to operate devices.
*/
public class GnomeEngineeringManager implements Engineer {
private Engineer engineer;
public GnomeEngineeringManager() {
}
public GnomeEngineeringManager(Engineer engineer) {
this.engineer = engineer;
}
@Override
public void operateDevice() {
engineer.operateDevice();
}
public void setEngineer(Engineer engineer) {
this.engineer = engineer;
}
}

View File

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

View File

@ -1,37 +1,25 @@
package com.iluwatar.adapter; package com.iluwatar.adapter;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/** /**
* An adapter helps two incompatible interfaces to work together. This is the real world definition * Test class
* 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.
* *
* <p>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>The Adapter ({@link GnomeEngineer}) converts the interface
* of the target class ({@link GoblinGlider}) into a suitable one expected by
* the client ({@link GnomeEngineeringManager}
* ).
*/ */
public class AdapterPatternTest { public class AdapterPatternTest {
private Map<String, Object> beans; private Map<String, Object> beans;
private static final String ENGINEER_BEAN = "engineer"; private static final String BATTLESHIP_BEAN = "engineer";
private static final String MANAGER_BEAN = "manager"; private static final String CAPTAIN_BEAN = "captain";
/** /**
* This method runs before the test execution and sets the bean objects in the beans Map. * This method runs before the test execution and sets the bean objects in the beans Map.
@ -40,29 +28,34 @@ public class AdapterPatternTest {
public void setup() { public void setup() {
beans = new HashMap<>(); beans = new HashMap<>();
GnomeEngineer gnomeEngineer = spy(new GnomeEngineer()); BattleFishingBoat battleFishingBoat = spy(new BattleFishingBoat());
beans.put(ENGINEER_BEAN, gnomeEngineer); beans.put(BATTLESHIP_BEAN, battleFishingBoat);
GnomeEngineeringManager manager = new GnomeEngineeringManager(); Captain captain = new Captain();
manager.setEngineer((GnomeEngineer) beans.get(ENGINEER_BEAN)); captain.setBattleship((BattleFishingBoat) beans.get(BATTLESHIP_BEAN));
beans.put(MANAGER_BEAN, manager); beans.put(CAPTAIN_BEAN, captain);
} }
/** /**
* This test asserts that when we call operateDevice() method on a manager bean, it is internally * This test asserts that when we use the move() method on a captain bean(client), it is
* calling operateDevice method on the engineer object. The Adapter ({@link GnomeEngineer}) * internally calling move method on the battleship object. The Adapter ({@link BattleFishingBoat}
* converts the interface of the target class ( {@link GoblinGlider}) into a suitable one expected * ) converts the interface of the target class ( {@link FishingBoat}) into a suitable one
* by the client ({@link GnomeEngineeringManager} ). * expected by the client ({@link Captain} ).
*/ */
@Test @Test
public void testAdapter() { public void testAdapter() {
Engineer manager = (Engineer) beans.get(MANAGER_BEAN); BattleShip captain = (BattleShip) beans.get(CAPTAIN_BEAN);
// when manager is asked to operate device // when captain moves
manager.operateDevice(); 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();
// Manager internally calls the engineer object to operateDevice
Engineer engineer = (Engineer) beans.get(ENGINEER_BEAN);
verify(engineer).operateDevice();
} }
} }

View File

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

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>async-method-invocation</artifactId> <artifactId>async-method-invocation</artifactId>
<dependencies> <dependencies>
@ -14,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>

View File

@ -4,24 +4,23 @@ 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 * <code>AsyncResult</code> which is an intermediate container for an asynchronously evaluated value,
* 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 * The main method shows example flow of async invocations. The main thread starts multiple tasks with variable
* with variable durations and then continues its own work. When the main thread has done it's job * durations and then continues its own work. When the main thread has done it's job it collects the results of the
* it collects the results of the async tasks. Two of the tasks are handled with callbacks, meaning * async tasks. Two of the tasks are handled with callbacks, meaning the callbacks are executed immediately when the
* the callbacks are 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 * Noteworthy difference of thread usage between the async results and callbacks is that the async results are collected
* results are collected in the main thread but the callbacks are executed within the worker * in the main thread but the callbacks are executed within the worker threads. This should be noted when working with
* threads. This should be noted when working with thread pools. * thread pools.
* <p> * <p>
* Java provides its own implementations of async method invocation pattern. FutureTask, * Java provides its own implementations of async method invocation pattern. FutureTask, CompletableFuture and
* CompletableFuture and ExecutorService are the real world implementations of this pattern. But due * ExecutorService are the real world implementations of this pattern. But due to the nature of parallel programming,
* to the nature of parallel programming, the implementations are not trivial. This example does not * the implementations are not trivial. This example does not take all possible scenarios into account but rather
* take all possible scenarios into account but rather provides a simple version that helps to * provides a simple version that helps to understand the pattern.
* understand the pattern.
* *
* @see AsyncResult * @see AsyncResult
* @see AsyncCallback * @see AsyncCallback
@ -33,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();
@ -41,10 +43,8 @@ public class App {
AsyncResult<Integer> asyncResult1 = executor.startProcess(lazyval(10, 500)); AsyncResult<Integer> asyncResult1 = executor.startProcess(lazyval(10, 500));
AsyncResult<String> asyncResult2 = executor.startProcess(lazyval("test", 300)); AsyncResult<String> asyncResult2 = executor.startProcess(lazyval("test", 300));
AsyncResult<Long> asyncResult3 = executor.startProcess(lazyval(50L, 700)); AsyncResult<Long> asyncResult3 = executor.startProcess(lazyval(50L, 700));
AsyncResult<Integer> asyncResult4 = AsyncResult<Integer> asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4"));
executor.startProcess(lazyval(20, 400), callback("Callback result 4")); AsyncResult<String> asyncResult5 = executor.startProcess(lazyval("callback", 600), callback("Callback result 5"));
AsyncResult<String> asyncResult5 =
executor.startProcess(lazyval("callback", 600), callback("Callback result 5"));
// emulate processing in the current thread while async tasks are running in their own threads // emulate processing in the current thread while async tasks are running in their own threads
Thread.sleep(350); // Oh boy I'm working hard here Thread.sleep(350); // Oh boy I'm working hard here
@ -66,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) {
@ -81,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) {

View File

@ -5,8 +5,6 @@ import java.util.concurrent.ExecutionException;
/** /**
* *
* AsyncResult interface * AsyncResult interface
*
* @param <T>
*/ */
public interface AsyncResult<T> { public interface AsyncResult<T> {

View File

@ -34,8 +34,7 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
} }
@Override @Override
public <T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, public <T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException {
InterruptedException {
if (asyncResult.isCompleted()) { if (asyncResult.isCompleted()) {
return asyncResult.getValue(); return asyncResult.getValue();
} else { } else {
@ -45,9 +44,8 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
} }
/** /**
* Simple implementation of async result that allows completing it successfully with a value or * Simple implementation of async result that allows completing it successfully with a value or exceptionally with an
* 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
@ -71,10 +69,11 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
} }
/** /**
* Sets the value from successful execution and executes callback if available. Notifies any * Sets the value from successful execution and executes callback if available. Notifies any thread waiting for
* 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;
@ -86,10 +85,11 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
} }
/** /**
* Sets the exception from failed execution and executes callback if available. Notifies any * Sets the exception from failed execution and executes callback if available. Notifies any thread waiting for
* 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;
@ -102,7 +102,7 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
@Override @Override
public boolean isCompleted() { public boolean isCompleted() {
return (state > RUNNING); return state > RUNNING;
} }
@Override @Override

View File

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

View File

@ -7,6 +7,7 @@ categories: Structural
tags: tags:
- Java - Java
- Gang Of Four - Gang Of Four
- Difficulty-Intermediate
--- ---
**Also known as:** Handle/Body **Also known as:** Handle/Body

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>bridge</artifactId> <artifactId>bridge</artifactId>
<dependencies> <dependencies>
@ -14,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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ categories: Creational
tags: tags:
- Java - Java
- Gang Of Four - 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

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>builder</artifactId> <artifactId>builder</artifactId>
<dependencies> <dependencies>

View File

@ -93,6 +93,9 @@ 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("profession and name can not be null"); throw new IllegalArgumentException("profession and name can not be null");

View File

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

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

View File

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

View File

@ -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.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>business-delegate</artifactId> <artifactId>business-delegate</artifactId>
<dependencies> <dependencies>

View File

@ -6,6 +6,8 @@ permalink: /patterns/caching/
categories: Other categories: Other
tags: tags:
- Java - Java
- Difficulty-Intermediate
- Performance
--- ---
**Intent:** To avoid expensive re-acquisition of resources by not releasing **Intent:** To avoid expensive re-acquisition of resources by not releasing

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>caching</artifactId> <artifactId>caching</artifactId>
<dependencies> <dependencies>

View File

@ -21,7 +21,7 @@ package com.iluwatar.caching;
* application data. The cache itself is implemented as an internal (Java) data structure. It adopts * 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 * 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 * 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 ( * 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 * {@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 * 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 * ({@link AppManager}) handles the transaction of data to-and-from the underlying data store
@ -43,7 +43,7 @@ public class App {
* @param args command line args * @param args command line args
*/ */
public static void main(String[] args) { public static void main(String[] args) {
AppManager.initDB(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests 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 // and the App class to avoid Maven compilation errors. Set flag to
// true to run the tests with MongoDB (provided that MongoDB is // true to run the tests with MongoDB (provided that MongoDB is
// installed and socket connection is open). // installed and socket connection is open).
@ -65,8 +65,8 @@ public class App {
AppManager.save(userAccount1); AppManager.save(userAccount1);
System.out.println(AppManager.printCacheContent()); System.out.println(AppManager.printCacheContent());
userAccount1 = AppManager.find("001"); AppManager.find("001");
userAccount1 = AppManager.find("001"); AppManager.find("001");
} }
/** /**
@ -80,15 +80,15 @@ public class App {
AppManager.save(userAccount2); AppManager.save(userAccount2);
System.out.println(AppManager.printCacheContent()); System.out.println(AppManager.printCacheContent());
userAccount2 = AppManager.find("002"); AppManager.find("002");
System.out.println(AppManager.printCacheContent()); System.out.println(AppManager.printCacheContent());
userAccount2 = AppManager.find("002"); userAccount2 = AppManager.find("002");
userAccount2.setUserName("Jane G."); userAccount2.setUserName("Jane G.");
AppManager.save(userAccount2); AppManager.save(userAccount2);
System.out.println(AppManager.printCacheContent()); System.out.println(AppManager.printCacheContent());
userAccount2 = AppManager.find("002"); AppManager.find("002");
System.out.println(AppManager.printCacheContent()); System.out.println(AppManager.printCacheContent());
userAccount2 = AppManager.find("002"); AppManager.find("002");
} }
/** /**
@ -106,12 +106,12 @@ public class App {
AppManager.save(userAccount4); AppManager.save(userAccount4);
AppManager.save(userAccount5); AppManager.save(userAccount5);
System.out.println(AppManager.printCacheContent()); System.out.println(AppManager.printCacheContent());
userAccount3 = AppManager.find("003"); AppManager.find("003");
System.out.println(AppManager.printCacheContent()); System.out.println(AppManager.printCacheContent());
UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child."); UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child.");
AppManager.save(userAccount6); AppManager.save(userAccount6);
System.out.println(AppManager.printCacheContent()); System.out.println(AppManager.printCacheContent());
userAccount4 = AppManager.find("004"); AppManager.find("004");
System.out.println(AppManager.printCacheContent()); System.out.println(AppManager.printCacheContent());
} }
} }

View File

@ -15,24 +15,30 @@ public class AppManager {
private static CachingPolicy cachingPolicy; private static CachingPolicy cachingPolicy;
private AppManager() {
}
/** /**
* *
* Developer/Tester is able to choose whether the application should use MongoDB as its underlying * 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 * data storage or a simple Java data structure to (temporarily) store the data/objects during
* runtime. * runtime.
*/ */
public static void initDB(boolean useMongoDB) { public static void initDb(boolean useMongoDb) {
if (useMongoDB) { if (useMongoDb) {
try { try {
DBManager.connect(); DbManager.connect();
} catch (ParseException e) { } catch (ParseException e) {
e.printStackTrace(); e.printStackTrace();
} }
} else { } else {
DBManager.createVirtualDB(); DbManager.createVirtualDb();
} }
} }
/**
* Initialize caching policy
*/
public static void initCachingPolicy(CachingPolicy policy) { public static void initCachingPolicy(CachingPolicy policy) {
cachingPolicy = policy; cachingPolicy = policy;
if (cachingPolicy == CachingPolicy.BEHIND) { if (cachingPolicy == CachingPolicy.BEHIND) {
@ -50,15 +56,21 @@ public class AppManager {
CacheStore.initCapacity(capacity); CacheStore.initCapacity(capacity);
} }
public static UserAccount find(String userID) { /**
* Find user account
*/
public static UserAccount find(String userId) {
if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) { if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
return CacheStore.readThrough(userID); return CacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) { } else if (cachingPolicy == CachingPolicy.BEHIND) {
return CacheStore.readThroughWithWriteBackPolicy(userID); return CacheStore.readThroughWithWriteBackPolicy(userId);
} }
return null; return null;
} }
/**
* Save user account
*/
public static void save(UserAccount userAccount) { public static void save(UserAccount userAccount) {
if (cachingPolicy == CachingPolicy.THROUGH) { if (cachingPolicy == CachingPolicy.THROUGH) {
CacheStore.writeThrough(userAccount); CacheStore.writeThrough(userAccount);

View File

@ -9,88 +9,118 @@ import java.util.ArrayList;
*/ */
public class CacheStore { public class CacheStore {
static LRUCache cache = null; static LruCache cache = null;
private CacheStore() {
}
/**
* Init cache capacity
*/
public static void initCapacity(int capacity) { public static void initCapacity(int capacity) {
if (null == cache) if (null == cache) {
cache = new LRUCache(capacity); cache = new LruCache(capacity);
else } else {
cache.setCapacity(capacity); cache.setCapacity(capacity);
} }
}
public static UserAccount readThrough(String userID) { /**
if (cache.contains(userID)) { * Get user account using read-through cache
*/
public static UserAccount readThrough(String userId) {
if (cache.contains(userId)) {
System.out.println("# Cache Hit!"); System.out.println("# Cache Hit!");
return cache.get(userID); return cache.get(userId);
} }
System.out.println("# Cache Miss!"); System.out.println("# Cache Miss!");
UserAccount userAccount = DBManager.readFromDB(userID); UserAccount userAccount = DbManager.readFromDb(userId);
cache.set(userID, userAccount); cache.set(userId, userAccount);
return userAccount; return userAccount;
} }
/**
* Get user account using write-through cache
*/
public static void writeThrough(UserAccount userAccount) { public static void writeThrough(UserAccount userAccount) {
if (cache.contains(userAccount.getUserID())) { if (cache.contains(userAccount.getUserId())) {
DBManager.updateDB(userAccount); DbManager.updateDb(userAccount);
} else { } else {
DBManager.writeToDB(userAccount); DbManager.writeToDb(userAccount);
} }
cache.set(userAccount.getUserID(), userAccount); cache.set(userAccount.getUserId(), userAccount);
} }
/**
* Get user account using write-around cache
*/
public static void writeAround(UserAccount userAccount) { public static void writeAround(UserAccount userAccount) {
if (cache.contains(userAccount.getUserID())) { if (cache.contains(userAccount.getUserId())) {
DBManager.updateDB(userAccount); DbManager.updateDb(userAccount);
cache.invalidate(userAccount.getUserID()); // Cache data has been updated -- remove older cache.invalidate(userAccount.getUserId()); // Cache data has been updated -- remove older
// version from cache. // version from cache.
} else { } else {
DBManager.writeToDB(userAccount); DbManager.writeToDb(userAccount);
} }
} }
public static UserAccount readThroughWithWriteBackPolicy(String userID) { /**
if (cache.contains(userID)) { * 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!"); System.out.println("# Cache Hit!");
return cache.get(userID); return cache.get(userId);
} }
System.out.println("# Cache Miss!"); System.out.println("# Cache Miss!");
UserAccount userAccount = DBManager.readFromDB(userID); UserAccount userAccount = DbManager.readFromDb(userId);
if (cache.isFull()) { if (cache.isFull()) {
System.out.println("# Cache is FULL! Writing LRU data to DB..."); System.out.println("# Cache is FULL! Writing LRU data to DB...");
UserAccount toBeWrittenToDB = cache.getLRUData(); UserAccount toBeWrittenToDb = cache.getLruData();
DBManager.upsertDB(toBeWrittenToDB); DbManager.upsertDb(toBeWrittenToDb);
} }
cache.set(userID, userAccount); cache.set(userId, userAccount);
return userAccount; return userAccount;
} }
/**
* Set user account
*/
public static void writeBehind(UserAccount userAccount) { public static void writeBehind(UserAccount userAccount) {
if (cache.isFull() && !cache.contains(userAccount.getUserID())) { if (cache.isFull() && !cache.contains(userAccount.getUserId())) {
System.out.println("# Cache is FULL! Writing LRU data to DB..."); System.out.println("# Cache is FULL! Writing LRU data to DB...");
UserAccount toBeWrittenToDB = cache.getLRUData(); UserAccount toBeWrittenToDb = cache.getLruData();
DBManager.upsertDB(toBeWrittenToDB); DbManager.upsertDb(toBeWrittenToDb);
} }
cache.set(userAccount.getUserID(), userAccount); cache.set(userAccount.getUserId(), userAccount);
} }
/**
* Clears cache
*/
public static void clearCache() { public static void clearCache() {
if (null != cache) if (null != cache) {
cache.clear(); cache.clear();
} }
}
/** /**
* Writes remaining content in the cache into the DB. * Writes remaining content in the cache into the DB.
*/ */
public static void flushCache() { public static void flushCache() {
System.out.println("# flushCache..."); System.out.println("# flushCache...");
if (null == cache) if (null == cache) {
return; return;
}
ArrayList<UserAccount> listOfUserAccounts = cache.getCacheDataInListForm(); ArrayList<UserAccount> listOfUserAccounts = cache.getCacheDataInListForm();
for (UserAccount userAccount : listOfUserAccounts) { for (UserAccount userAccount : listOfUserAccounts) {
DBManager.upsertDB(userAccount); DbManager.upsertDb(userAccount);
} }
} }
/**
* Print user accounts
*/
public static String print() { public static String print() {
ArrayList<UserAccount> listOfUserAccounts = cache.getCacheDataInListForm(); ArrayList<UserAccount> listOfUserAccounts = cache.getCacheDataInListForm();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();

View File

@ -21,7 +21,7 @@ import com.mongodb.client.model.UpdateOptions;
* during runtime (createVirtualDB()).</p> * during runtime (createVirtualDB()).</p>
* *
*/ */
public class DBManager { public class DbManager {
private static MongoClient mongoClient; private static MongoClient mongoClient;
private static MongoDatabase db; private static MongoDatabase db;
@ -29,21 +29,34 @@ public class DBManager {
private static HashMap<String, UserAccount> virtualDB; private static HashMap<String, UserAccount> virtualDB;
public static void createVirtualDB() { private DbManager() {
}
/**
* Create DB
*/
public static void createVirtualDb() {
useMongoDB = false; useMongoDB = false;
virtualDB = new HashMap<String, UserAccount>(); virtualDB = new HashMap<String, UserAccount>();
} }
/**
* Connect to DB
*/
public static void connect() throws ParseException { public static void connect() throws ParseException {
useMongoDB = true; useMongoDB = true;
mongoClient = new MongoClient(); mongoClient = new MongoClient();
db = mongoClient.getDatabase("test"); db = mongoClient.getDatabase("test");
} }
public static UserAccount readFromDB(String userID) { /**
* Read user account from DB
*/
public static UserAccount readFromDb(String userId) {
if (!useMongoDB) { if (!useMongoDB) {
if (virtualDB.containsKey(userID)) if (virtualDB.containsKey(userId)) {
return virtualDB.get(userID); return virtualDB.get(userId);
}
return null; return null;
} }
if (null == db) { if (null == db) {
@ -54,18 +67,22 @@ public class DBManager {
} }
} }
FindIterable<Document> iterable = FindIterable<Document> iterable =
db.getCollection("user_accounts").find(new Document("userID", userID)); db.getCollection("user_accounts").find(new Document("userID", userId));
if (iterable == null) if (iterable == null) {
return null; return null;
}
Document doc = iterable.first(); Document doc = iterable.first();
UserAccount userAccount = UserAccount userAccount =
new UserAccount(userID, doc.getString("userName"), doc.getString("additionalInfo")); new UserAccount(userId, doc.getString("userName"), doc.getString("additionalInfo"));
return userAccount; return userAccount;
} }
public static void writeToDB(UserAccount userAccount) { /**
* Write user account to DB
*/
public static void writeToDb(UserAccount userAccount) {
if (!useMongoDB) { if (!useMongoDB) {
virtualDB.put(userAccount.getUserID(), userAccount); virtualDB.put(userAccount.getUserId(), userAccount);
return; return;
} }
if (null == db) { if (null == db) {
@ -76,13 +93,16 @@ public class DBManager {
} }
} }
db.getCollection("user_accounts").insertOne( db.getCollection("user_accounts").insertOne(
new Document("userID", userAccount.getUserID()).append("userName", new Document("userID", userAccount.getUserId()).append("userName",
userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())); userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo()));
} }
public static void updateDB(UserAccount userAccount) { /**
* Update DB
*/
public static void updateDb(UserAccount userAccount) {
if (!useMongoDB) { if (!useMongoDB) {
virtualDB.put(userAccount.getUserID(), userAccount); virtualDB.put(userAccount.getUserId(), userAccount);
return; return;
} }
if (null == db) { if (null == db) {
@ -93,7 +113,7 @@ public class DBManager {
} }
} }
db.getCollection("user_accounts").updateOne( db.getCollection("user_accounts").updateOne(
new Document("userID", userAccount.getUserID()), new Document("userID", userAccount.getUserId()),
new Document("$set", new Document("userName", userAccount.getUserName()).append( new Document("$set", new Document("userName", userAccount.getUserName()).append(
"additionalInfo", userAccount.getAdditionalInfo()))); "additionalInfo", userAccount.getAdditionalInfo())));
} }
@ -102,9 +122,9 @@ public class DBManager {
* *
* Insert data into DB if it does not exist. Else, update it. * Insert data into DB if it does not exist. Else, update it.
*/ */
public static void upsertDB(UserAccount userAccount) { public static void upsertDb(UserAccount userAccount) {
if (!useMongoDB) { if (!useMongoDB) {
virtualDB.put(userAccount.getUserID(), userAccount); virtualDB.put(userAccount.getUserId(), userAccount);
return; return;
} }
if (null == db) { if (null == db) {
@ -115,8 +135,8 @@ public class DBManager {
} }
} }
db.getCollection("user_accounts").updateOne( db.getCollection("user_accounts").updateOne(
new Document("userID", userAccount.getUserID()), new Document("userID", userAccount.getUserId()),
new Document("$set", new Document("userID", userAccount.getUserID()).append("userName", new Document("$set", new Document("userID", userAccount.getUserId()).append("userName",
userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())), userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())),
new UpdateOptions().upsert(true)); new UpdateOptions().upsert(true));
} }

View File

@ -12,16 +12,16 @@ import java.util.HashMap;
* LRU data is always at the end of the list. * LRU data is always at the end of the list.
* *
*/ */
public class LRUCache { public class LruCache {
class Node { class Node {
String userID; String userId;
UserAccount userAccount; UserAccount userAccount;
Node previous; Node previous;
Node next; Node next;
public Node(String userID, UserAccount userAccount) { public Node(String userId, UserAccount userAccount) {
this.userID = userID; this.userId = userId;
this.userAccount = userAccount; this.userAccount = userAccount;
} }
} }
@ -31,13 +31,16 @@ public class LRUCache {
Node head = null; Node head = null;
Node end = null; Node end = null;
public LRUCache(int capacity) { public LruCache(int capacity) {
this.capacity = capacity; this.capacity = capacity;
} }
public UserAccount get(String userID) { /**
if (cache.containsKey(userID)) { * Get user account
Node node = cache.get(userID); */
public UserAccount get(String userId) {
if (cache.containsKey(userId)) {
Node node = cache.get(userId);
remove(node); remove(node);
setHead(node); setHead(node);
return node.userAccount; return node.userAccount;
@ -69,52 +72,63 @@ public class LRUCache {
public void setHead(Node node) { public void setHead(Node node) {
node.next = head; node.next = head;
node.previous = null; node.previous = null;
if (head != null) if (head != null) {
head.previous = node; head.previous = node;
}
head = node; head = node;
if (end == null) if (end == null) {
end = head; end = head;
} }
}
public void set(String userID, UserAccount userAccount) { /**
if (cache.containsKey(userID)) { * Set user account
Node old = cache.get(userID); */
public void set(String userId, UserAccount userAccount) {
if (cache.containsKey(userId)) {
Node old = cache.get(userId);
old.userAccount = userAccount; old.userAccount = userAccount;
remove(old); remove(old);
setHead(old); setHead(old);
} else { } else {
Node newNode = new Node(userID, userAccount); Node newNode = new Node(userId, userAccount);
if (cache.size() >= capacity) { if (cache.size() >= capacity) {
System.out.println("# Cache is FULL! Removing " + end.userID + " from cache..."); System.out.println("# Cache is FULL! Removing " + end.userId + " from cache...");
cache.remove(end.userID); // remove LRU data from cache. cache.remove(end.userId); // remove LRU data from cache.
remove(end); remove(end);
setHead(newNode); setHead(newNode);
} else { } else {
setHead(newNode); setHead(newNode);
} }
cache.put(userID, newNode); cache.put(userId, newNode);
} }
} }
public boolean contains(String userID) { public boolean contains(String userId) {
return cache.containsKey(userID); return cache.containsKey(userId);
} }
public void invalidate(String userID) { /**
System.out.println("# " + userID + " has been updated! Removing older version from cache..."); * Invalidate cache for user
Node toBeRemoved = cache.get(userID); */
public void invalidate(String userId) {
System.out.println("# " + userId + " has been updated! Removing older version from cache...");
Node toBeRemoved = cache.get(userId);
remove(toBeRemoved); remove(toBeRemoved);
cache.remove(userID); cache.remove(userId);
} }
public boolean isFull() { public boolean isFull() {
return cache.size() >= capacity; return cache.size() >= capacity;
} }
public UserAccount getLRUData() { public UserAccount getLruData() {
return end.userAccount; return end.userAccount;
} }
/**
* Clear cache
*/
public void clear() { public void clear() {
head = null; head = null;
end = null; end = null;
@ -135,6 +149,9 @@ public class LRUCache {
return listOfCacheData; return listOfCacheData;
} }
/**
* Set cache capacity
*/
public void setCapacity(int newCapacity) { public void setCapacity(int newCapacity) {
if (capacity > newCapacity) { if (capacity > newCapacity) {
clear(); // Behavior can be modified to accommodate for decrease in cache size. For now, we'll clear(); // Behavior can be modified to accommodate for decrease in cache size. For now, we'll

View File

@ -6,22 +6,25 @@ package com.iluwatar.caching;
* *
*/ */
public class UserAccount { public class UserAccount {
private String userID; private String userId;
private String userName; private String userName;
private String additionalInfo; private String additionalInfo;
public UserAccount(String userID, String userName, String additionalInfo) { /**
this.userID = userID; * Constructor
*/
public UserAccount(String userId, String userName, String additionalInfo) {
this.userId = userId;
this.userName = userName; this.userName = userName;
this.additionalInfo = additionalInfo; this.additionalInfo = additionalInfo;
} }
public String getUserID() { public String getUserId() {
return userID; return userId;
} }
public void setUserID(String userID) { public void setUserId(String userId) {
this.userID = userID; this.userId = userId;
} }
public String getUserName() { public String getUserName() {
@ -42,6 +45,6 @@ public class UserAccount {
@Override @Override
public String toString() { public String toString() {
return userID + ", " + userName + ", " + additionalInfo; return userId + ", " + userName + ", " + additionalInfo;
} }
} }

View File

@ -16,7 +16,7 @@ public class AppTest {
*/ */
@Before @Before
public void setUp() { public void setUp() {
AppManager.initDB(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests 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 // to avoid Maven compilation errors. Set flag to true to run the
// tests with MongoDB (provided that MongoDB is installed and socket // tests with MongoDB (provided that MongoDB is installed and socket
// connection is open). // connection is open).

View File

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

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>callback</artifactId> <artifactId>callback</artifactId>
<dependencies> <dependencies>

View File

@ -9,6 +9,9 @@ package com.iluwatar.callback;
*/ */
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() {

View File

@ -7,5 +7,5 @@ package com.iluwatar.callback;
*/ */
public interface Callback { public interface Callback {
public void call(); void call();
} }

View File

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

View File

@ -7,6 +7,7 @@ categories: Behavioral
tags: tags:
- Java - Java
- Gang Of Four - 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

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>chain</artifactId> <artifactId>chain</artifactId>
<dependencies> <dependencies>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

@ -7,6 +7,7 @@ categories: Behavioral
tags: tags:
- Java - Java
- Gang Of Four - Gang Of Four
- Difficulty-Intermediate
--- ---
**Also known as:** Action, Transaction **Also known as:** Action, Transaction

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>command</artifactId> <artifactId>command</artifactId>
<dependencies> <dependencies>

View File

@ -30,6 +30,9 @@ 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, getSize(), System.out.println(String.format("%s, [size=%s] [visibility=%s]", this, getSize(),
getVisibility())); getVisibility()));

View File

@ -15,12 +15,18 @@ public class Wizard {
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();
@ -30,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();

View File

@ -7,6 +7,7 @@ categories: Structural
tags: tags:
- Java - Java
- Gang Of Four - Gang Of Four
- Difficulty-Intermediate
--- ---
**Intent:** Compose objects into tree structures to represent part-whole **Intent:** Compose objects into tree structures to represent part-whole

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>composite</artifactId> <artifactId>composite</artifactId>
<dependencies> <dependencies>

View File

@ -24,6 +24,9 @@ public abstract class LetterComposite {
protected abstract void printThisAfter(); protected abstract void printThisAfter();
/**
* Print
*/
public void print() { public void print() {
printThisBefore(); printThisBefore();
for (LetterComposite letter : children) { for (LetterComposite letter : children) {

View File

@ -9,6 +9,9 @@ import java.util.List;
*/ */
public class Sentence extends LetterComposite { public class Sentence extends LetterComposite {
/**
* Constructor
*/
public Sentence(List<Word> words) { public Sentence(List<Word> words) {
for (Word w : words) { for (Word w : words) {
this.add(w); this.add(w);

View File

@ -9,6 +9,9 @@ import java.util.List;
*/ */
public class Word extends LetterComposite { public class Word extends LetterComposite {
/**
* Constructor
*/
public Word(List<Letter> letters) { public Word(List<Letter> letters) {
for (Letter l : letters) { for (Letter l : letters) {
this.add(l); this.add(l);

View File

@ -2,8 +2,6 @@ package com.iluwatar.composite;
import org.junit.Test; import org.junit.Test;
import com.iluwatar.composite.App;
/** /**
* *
* Application test * Application test

View File

@ -0,0 +1,90 @@
package com.iluwatar.composite;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Date: 12/11/15 - 8:12 PM
*
* @author Jeroen Meulemeester
*/
public class MessengerTest {
/**
* The buffer used to capture every write to {@link System#out}
*/
private ByteArrayOutputStream stdOutBuffer = new ByteArrayOutputStream();
/**
* Keep the original std-out so it can be restored after the test
*/
private final PrintStream realStdOut = System.out;
/**
* Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test
*/
@Before
public void setUp() {
this.stdOutBuffer = new ByteArrayOutputStream();
System.setOut(new PrintStream(stdOutBuffer));
}
/**
* Removed the mocked std-out {@link PrintStream} again from the {@link System} class
*/
@After
public void tearDown() {
System.setOut(realStdOut);
}
/**
* Test the message from the orcs
*/
@Test
public void testMessageFromOrcs() {
final Messenger messenger = new Messenger();
testMessage(
messenger.messageFromOrcs(),
"Where there is a whip there is a way."
);
}
/**
* Test the message from the elves
*/
@Test
public void testMessageFromElves() {
final Messenger messenger = new Messenger();
testMessage(
messenger.messageFromElves(),
"Much wind pours from your mouth."
);
}
/**
* Test if the given composed message matches the expected message
*
* @param composedMessage The composed message, received from the messenger
* @param message The expected message
*/
private void testMessage(final LetterComposite composedMessage, final String message) {
// Test is the composed message has the correct number of words
final String[] words = message.split(" ");
assertNotNull(composedMessage);
assertEquals(words.length, composedMessage.count());
// Print the message to the mocked stdOut ...
composedMessage.print();
// ... and verify if the message matches with the expected one
assertEquals(message, new String(this.stdOutBuffer.toByteArray()).trim());
}
}

View File

@ -3,7 +3,7 @@ layout: pattern
title: Data Access Object title: Data Access Object
folder: dao folder: dao
permalink: /patterns/dao/ permalink: /patterns/dao/
categories: Architectural categories: Persistence Tier
tags: tags:
- Java - Java
- Difficulty-Beginner - Difficulty-Beginner

View File

@ -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.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>dao</artifactId> <artifactId>dao</artifactId>

View File

@ -21,7 +21,7 @@ import org.apache.log4j.Logger;
*/ */
public class App { public class App {
private static Logger LOGGER = Logger.getLogger(App.class); private static Logger log = Logger.getLogger(App.class);
/** /**
* Program entry point. * Program entry point.
@ -30,17 +30,17 @@ public class App {
*/ */
public static void main(final String[] args) { public static void main(final String[] args) {
final CustomerDaoImpl customerDao = new CustomerDaoImpl(generateSampleCustomers()); final CustomerDaoImpl customerDao = new CustomerDaoImpl(generateSampleCustomers());
LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers());
LOGGER.info("customerDao.getCusterById(2): " + customerDao.getCustomerById(2)); log.info("customerDao.getCusterById(2): " + customerDao.getCustomerById(2));
final Customer customer = new Customer(4, "Dan", "Danson"); final Customer customer = new Customer(4, "Dan", "Danson");
customerDao.addCustomer(customer); customerDao.addCustomer(customer);
LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers());
customer.setFirstName("Daniel"); customer.setFirstName("Daniel");
customer.setLastName("Danielson"); customer.setLastName("Danielson");
customerDao.updateCustomer(customer); customerDao.updateCustomer(customer);
LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers());
customerDao.deleteCustomer(customer); customerDao.deleteCustomer(customer);
LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers());
} }
/** /**

View File

@ -11,6 +11,9 @@ public class Customer {
private String firstName; private String firstName;
private String lastName; private String lastName;
/**
* Constructor
*/
public Customer(final int id, final String firstName, final String lastName) { public Customer(final int id, final String firstName, final String lastName) {
this.id = id; this.id = id;
this.firstName = firstName; this.firstName = firstName;
@ -52,11 +55,12 @@ public class Customer {
boolean isEqual = false; boolean isEqual = false;
if (this == o) { if (this == o) {
isEqual = true; isEqual = true;
} else if (o != null && (getClass() == o.getClass())) { } else if (o != null && getClass() == o.getClass()) {
final Customer customer = (Customer) o; final Customer customer = (Customer) o;
if (getId() == customer.getId()) if (getId() == customer.getId()) {
isEqual = true; isEqual = true;
} }
}
return isEqual; return isEqual;
} }

View File

@ -7,6 +7,7 @@ categories: Structural
tags: tags:
- Java - Java
- Gang Of Four - Gang Of Four
- Difficulty-Beginner
--- ---
**Also known as:** Wrapper **Also known as:** Wrapper

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>decorator</artifactId> <artifactId>decorator</artifactId>
<dependencies> <dependencies>
@ -14,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>

View File

@ -2,8 +2,6 @@ package com.iluwatar.decorator;
import org.junit.Test; import org.junit.Test;
import com.iluwatar.decorator.App;
/** /**
* *
* Application test * Application test

View File

@ -0,0 +1,36 @@
package com.iluwatar.decorator;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
import static org.mockito.internal.verification.VerificationModeFactory.times;
/**
* Date: 12/7/15 - 7:47 PM
*
* @author Jeroen Meulemeester
*/
public class SmartTrollTest {
@Test
public void testSmartTroll() throws Exception {
// Create a normal troll first, but make sure we can spy on it later on.
final Hostile simpleTroll = spy(new Troll());
// Now we want to decorate the troll to make it smarter ...
final Hostile smartTroll = new SmartTroll(simpleTroll);
assertEquals(30, smartTroll.getAttackPower());
verify(simpleTroll, times(1)).getAttackPower();
// Check if the smart troll actions are delegated to the decorated troll
smartTroll.attack();
verify(simpleTroll, times(1)).attack();
smartTroll.fleeBattle();
verify(simpleTroll, times(1)).fleeBattle();
verifyNoMoreInteractions(simpleTroll);
}
}

View File

@ -0,0 +1,62 @@
package com.iluwatar.decorator;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.PrintStream;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
import static org.mockito.internal.verification.VerificationModeFactory.times;
/**
* Date: 12/7/15 - 7:26 PM
*
* @author Jeroen Meulemeester
*/
public class TrollTest {
/**
* The mocked standard out stream, required since the actions don't have any influence on other
* objects, except for writing to the std-out using {@link System#out}
*/
private final PrintStream stdOutMock = mock(PrintStream.class);
/**
* Keep the original std-out so it can be restored after the test
*/
private final PrintStream stdOutOrig = System.out;
/**
* Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test
*/
@Before
public void setUp() {
System.setOut(this.stdOutMock);
}
/**
* Removed the mocked std-out {@link PrintStream} again from the {@link System} class
*/
@After
public void tearDown() {
System.setOut(this.stdOutOrig);
}
@Test
public void testTrollActions() throws Exception {
final Troll troll = new Troll();
assertEquals(10, troll.getAttackPower());
troll.attack();
verify(this.stdOutMock, times(1)).println(eq("The troll swings at you with a club!"));
troll.fleeBattle();
verify(this.stdOutMock, times(1)).println(eq("The troll shrieks in horror and runs away!"));
verifyNoMoreInteractions(this.stdOutMock);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.9" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="false" nesting-relationships="true" router="FAN">
<class id="1" language="java" name="com.iluwatar.delegation.simple.printers.HpPrinter" project="delegation"
file="/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="764" y="272"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="com.iluwatar.delegation.simple.printers.EpsonPrinter" project="delegation"
file="/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="511" y="270"/>
<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.delegation.simple.printers.CanonPrinter" project="delegation"
file="/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="252" y="275"/>
<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.delegation.simple.Printer" project="delegation"
file="/delegation/src/main/java/com/iluwatar/delegation/simple/Printer.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="524" y="89"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="5" language="java" name="com.iluwatar.delegation.simple.PrinterController" project="delegation"
file="/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="112" y="90"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<realization id="6">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="4"/>
</realization>
<association id="7">
<end type="SOURCE" refId="5" navigable="false">
<attribute id="8" name="printer"/>
<multiplicity id="9" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="10">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="4"/>
</realization>
<realization id="11">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="4"/>
</realization>
<realization id="12">
<end type="SOURCE" refId="1"/>
<end type="TARGET" refId="4"/>
</realization>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

27
delegation/index.md Normal file
View File

@ -0,0 +1,27 @@
---
layout: pattern
title: Delegation
folder: delegation
permalink: /patterns/delegation/
categories: Behavioral
tags:
- Java
- Difficulty-Beginner
---
**Also known as:** Proxy Pattern
**Intent:** It is a technique where an object expresses certain behavior to the outside but in
reality delegates responsibility for implementing that behaviour to an associated object.
![alt text](./etc/delegation.png "Delegate")
**Applicability:** Use the Delegate pattern in order to achieve the following
* Reduce the coupling of methods to their class
* Components that behave identically, but realize that this situation can change in the future.
**Credits**
* [Delegate Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Delegation_pattern)
* [Proxy Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Proxy_pattern)

27
delegation/pom.xml Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.10.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>delegation</artifactId>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,38 @@
package com.iluwatar.delegation.simple;
import com.iluwatar.delegation.simple.printers.CanonPrinter;
import com.iluwatar.delegation.simple.printers.EpsonPrinter;
import com.iluwatar.delegation.simple.printers.HpPrinter;
/**
* The delegate pattern provides a mechanism to abstract away the implementation and control of the desired action.
* The class being called in this case {@link PrinterController} is not responsible for the actual desired action,
* but is actually delegated to a helper class either {@link CanonPrinter}, {@link EpsonPrinter} or {@link HpPrinter}.
* The consumer does not have or require knowledge of the actual class carrying out the action, only the
* container on which they are calling.
*
* In this example the delegates are {@link EpsonPrinter}, {@link HpPrinter} and {@link CanonPrinter} they all implement
* {@link Printer}. The {@link PrinterController} class also implements {@link Printer}. However neither provide the
* functionality of {@link Printer} by printing to the screen, they actually call upon the instance of {@link Printer}
* that they were instantiated with. Therefore delegating the behaviour to another class.
*/
public class App {
public static final String MESSAGE_TO_PRINT = "hello world";
/**
* Program entry point
*
* @param args command line args
*/
public static void main(String[] args) {
PrinterController hpPrinterController = new PrinterController(new HpPrinter());
PrinterController canonPrinterController = new PrinterController(new CanonPrinter());
PrinterController epsonPrinterController = new PrinterController(new EpsonPrinter());
hpPrinterController.print(MESSAGE_TO_PRINT);
canonPrinterController.print(MESSAGE_TO_PRINT);
epsonPrinterController.print(MESSAGE_TO_PRINT);
}
}

View File

@ -0,0 +1,23 @@
package com.iluwatar.delegation.simple;
import com.iluwatar.delegation.simple.printers.CanonPrinter;
import com.iluwatar.delegation.simple.printers.EpsonPrinter;
import com.iluwatar.delegation.simple.printers.HpPrinter;
/**
* Interface that both the Controller and the Delegate will implement.
*
* @see CanonPrinter
* @see EpsonPrinter
* @see HpPrinter
*/
public interface Printer {
/**
* Method that takes a String to print to the screen. This will be implemented on both the
* controller and the delegate allowing the controller to call the same method on the delegate class.
*
* @param message to be printed to the screen
*/
void print(final String message);
}

View File

@ -0,0 +1,23 @@
package com.iluwatar.delegation.simple;
public class PrinterController implements Printer {
private final Printer printer;
public PrinterController(Printer printer) {
this.printer = printer;
}
/**
* This method is implemented from {@link Printer} however instead on providing an
* implementation, it instead calls upon the class passed through the constructor. This is the delegate,
* hence the pattern. Therefore meaning that the caller does not care of the implementing class only the owning
* controller.
*
* @param message to be printed to the screen
*/
@Override
public void print(String message) {
printer.print(message);
}
}

View File

@ -0,0 +1,21 @@
package com.iluwatar.delegation.simple.printers;
import com.iluwatar.delegation.simple.Printer;
/**
* Specialised Implementation of {@link Printer} for a Canon Printer, in
* this case the message to be printed is appended to "Canon Printer : "
*
* @see Printer
*/
public class CanonPrinter implements Printer {
/**
* {@inheritDoc}
*/
@Override
public void print(String message) {
System.out.print("Canon Printer : " + message);
}
}

View File

@ -0,0 +1,21 @@
package com.iluwatar.delegation.simple.printers;
import com.iluwatar.delegation.simple.Printer;
/**
* Specialised Implementation of {@link Printer} for a Epson Printer, in
* this case the message to be printed is appended to "Epson Printer : "
*
* @see Printer
*/
public class EpsonPrinter implements Printer {
/**
* {@inheritDoc}
*/
@Override
public void print(String message) {
System.out.print("Epson Printer : " + message);
}
}

View File

@ -0,0 +1,21 @@
package com.iluwatar.delegation.simple.printers;
import com.iluwatar.delegation.simple.Printer;
/**
* Specialised Implementation of {@link Printer} for a HP Printer, in
* this case the message to be printed is appended to "HP Printer : "
*
* @see Printer
*/
public class HpPrinter implements Printer {
/**
* {@inheritDoc}
*/
@Override
public void print(String message) {
System.out.print("HP Printer : " + message);
}
}

View File

@ -0,0 +1,13 @@
package com.iluwatar.delegation.simple;
import org.junit.Test;
public class AppTest {
@Test
public void test() {
String[] args = {};
App.main(args);
}
}

View File

@ -0,0 +1,43 @@
package com.iluwatar.delegation.simple;
import com.iluwatar.delegation.simple.printers.CanonPrinter;
import com.iluwatar.delegation.simple.printers.EpsonPrinter;
import com.iluwatar.delegation.simple.printers.HpPrinter;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import static org.junit.Assert.assertEquals;
public class DelegateTest {
private static final String MESSAGE = "Test Message Printed";
@Rule
public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();
@Test
public void testCanonPrinter() throws Exception {
PrinterController printerController = new PrinterController(new CanonPrinter());
printerController.print(MESSAGE);
assertEquals("Canon Printer : Test Message Printed", systemOutRule.getLog());
}
@Test
public void testHpPrinter() throws Exception {
PrinterController printerController = new PrinterController(new HpPrinter());
printerController.print(MESSAGE);
assertEquals("HP Printer : Test Message Printed", systemOutRule.getLog());
}
@Test
public void testEpsonPrinter() throws Exception {
PrinterController printerController = new PrinterController(new EpsonPrinter());
printerController.print(MESSAGE);
assertEquals("Epson Printer : Test Message Printed", systemOutRule.getLog());
}
}

View File

@ -4,7 +4,9 @@ title: Dependency Injection
folder: dependency-injection folder: dependency-injection
permalink: /patterns/dependency-injection/ permalink: /patterns/dependency-injection/
categories: Behavioral categories: Behavioral
tags: Java tags:
- Java
- Difficulty-Beginner
--- ---
**Intent:** Dependency Injection is a software design pattern in which one or **Intent:** Dependency Injection is a software design pattern in which one or

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.iluwatar</groupId> <groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId> <artifactId>java-design-patterns</artifactId>
<version>1.8.0-SNAPSHOT</version> <version>1.10.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>dependency-injection</artifactId> <artifactId>dependency-injection</artifactId>
<dependencies> <dependencies>
@ -13,6 +13,11 @@
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.inject</groupId> <groupId>com.google.inject</groupId>

View File

@ -0,0 +1,40 @@
package com.iluwatar.dependency.injection;
import org.junit.Test;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Date: 12/10/15 - 8:40 PM
*
* @author Jeroen Meulemeester
*/
public class AdvancedWizardTest extends StdOutTest {
/**
* Test if the {@link AdvancedWizard} smokes whatever instance of {@link Tobacco} is passed to him
* through the constructor parameter
*/
@Test
public void testSmokeEveryThing() throws Exception {
final Tobacco[] tobaccos = {
new OldTobyTobacco(), new RivendellTobacco(), new SecondBreakfastTobacco()
};
for (final Tobacco tobacco : tobaccos) {
final AdvancedWizard advancedWizard = new AdvancedWizard(tobacco);
advancedWizard.smoke();
// Verify if the wizard is smoking the correct tobacco ...
verify(getStdOutMock(), times(1)).println("AdvancedWizard smoking " + tobacco.getClass().getSimpleName());
// ... and nothing else is happening.
verifyNoMoreInteractions(getStdOutMock());
}
}
}

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