diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD new file mode 100644 index 000000000000..39087fbf12d3 --- /dev/null +++ b/CONTRIBUTING.MD @@ -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. diff --git a/README.md b/README.md index ac3aadd6737f..f1ce0bfc7326 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,10 @@ that smart and dearly wants an empty line before a heading to be able to 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) [![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) # Introduction @@ -40,7 +39,7 @@ patterns by any of the following approaches # 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 diff --git a/abstract-factory/index.md b/abstract-factory/index.md index f824f7e0eba4..485599b984fb 100644 --- a/abstract-factory/index.md +++ b/abstract-factory/index.md @@ -7,26 +7,30 @@ categories: Creational tags: - Java - Gang Of Four + - Difficulty-Intermediate --- -**Also known as:** Kit +## Also known as +Kit -**Intent:** Provide an interface for creating families of related or dependent +## Intent +Provide an interface for creating families of related or dependent objects without specifying their concrete classes. ![alt text](./etc/abstract-factory_1.png "Abstract Factory") -**Applicability:** Use the Abstract Factory pattern when +## Applicability +Use the Abstract Factory pattern when * a system should be independent of how its products are created, composed and represented * a system should be configured with one of multiple families of products * a family of related product objects is designed to be used together, and you need to enforce this constraint * you want to provide a class library of products, and you want to reveal just their interfaces, not their implementations -**Real world examples:** +## Real world examples * [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/abstract-factory/pom.xml b/abstract-factory/pom.xml index f8c7fa106447..71ea6dc98f54 100644 --- a/abstract-factory/pom.xml +++ b/abstract-factory/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT abstract-factory diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java index 9a32a091a899..cdde3bd8f0d9 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java @@ -1,20 +1,18 @@ package com.iluwatar.abstractfactory; - /** * - * The Abstract Factory pattern provides a way to encapsulate a group of individual factories that - * have a common theme without specifying their concrete classes. In normal usage, the client - * software creates a concrete implementation of the abstract factory and then uses the generic - * interface of the factory to create the concrete objects that are part of the theme. The client - * does not know (or care) which concrete objects it gets from each of these internal factories, - * since it uses only the generic interfaces of their products. This pattern separates the details - * of implementation of a set of objects from their general usage and relies on object composition, - * as object creation is implemented in methods exposed in the factory interface. + * The Abstract Factory pattern provides a way to encapsulate a group of individual factories that have a common theme + * without specifying their concrete classes. In normal usage, the client software creates a concrete implementation of + * the abstract factory and then uses the generic interface of the factory to create the concrete objects that are part + * of the theme. The client does not know (or care) which concrete objects it gets from each of these internal + * factories, since it uses only the generic interfaces of their products. This pattern separates the details of + * implementation of a set of objects from their general usage and relies on object composition, as object creation is + * implemented in methods exposed in the factory interface. *

- * The essence of the Abstract Factory pattern is a factory interface ({@link KingdomFactory}) and - * its implementations ({@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses both - * concrete implementations to create a king, a castle and an army. + * The essence of the Abstract Factory pattern is a factory interface ({@link KingdomFactory}) and its implementations ( + * {@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses both concrete implementations to create a + * king, a castle and an army. * */ public class App { @@ -23,11 +21,8 @@ public class App { private Castle castle; private Army army; - /** * Creates kingdom - * - * @param factory */ public void createKingdom(final KingdomFactory factory) { setKing(factory.createKing()); @@ -47,14 +42,6 @@ King getKing(final KingdomFactory factory) { return factory.createKing(); } - Castle getCastle(final KingdomFactory factory) { - return factory.createCastle(); - } - - Army getArmy(final KingdomFactory factory) { - return factory.createArmy(); - } - public King getKing() { return king; } @@ -62,6 +49,10 @@ public King getKing() { private void setKing(final King king) { this.king = king; } + + Castle getCastle(final KingdomFactory factory) { + return factory.createCastle(); + } public Castle getCastle() { return castle; @@ -70,6 +61,10 @@ public Castle getCastle() { private void setCastle(final Castle castle) { this.castle = castle; } + + Army getArmy(final KingdomFactory factory) { + return factory.createArmy(); + } public Army getArmy() { return army; @@ -79,32 +74,32 @@ private void setArmy(final Army army) { this.army = army; } - /** * Program entry point * - * @param args command line args + * @param args + * command line args */ public static void main(String[] args) { - - App app = new App(); - - System.out.println("Elf Kingdom"); - KingdomFactory elfKingdomFactory; - elfKingdomFactory = app.getElfKingdomFactory(); - app.createKingdom(elfKingdomFactory); - System.out.println(app.getArmy().getDescription()); - System.out.println(app.getCastle().getDescription()); - System.out.println(app.getKing().getDescription()); - - System.out.println("\nOrc Kingdom"); - KingdomFactory orcKingdomFactory; - orcKingdomFactory = app.getOrcKingdomFactory(); - app.createKingdom(orcKingdomFactory); - System.out.println(app.getArmy().getDescription()); - System.out.println(app.getCastle().getDescription()); - System.out.println(app.getKing().getDescription()); - + + App app = new App(); + + System.out.println("Elf Kingdom"); + KingdomFactory elfKingdomFactory; + elfKingdomFactory = app.getElfKingdomFactory(); + app.createKingdom(elfKingdomFactory); + System.out.println(app.getArmy().getDescription()); + System.out.println(app.getCastle().getDescription()); + System.out.println(app.getKing().getDescription()); + + System.out.println("\nOrc Kingdom"); + KingdomFactory orcKingdomFactory; + orcKingdomFactory = app.getOrcKingdomFactory(); + app.createKingdom(orcKingdomFactory); + System.out.println(app.getArmy().getDescription()); + System.out.println(app.getCastle().getDescription()); + System.out.println(app.getKing().getDescription()); + } - + } diff --git a/adapter/etc/adapter.png b/adapter/etc/adapter.png index 511bb5880ee7..f43358b042e7 100644 Binary files a/adapter/etc/adapter.png and b/adapter/etc/adapter.png differ diff --git a/adapter/etc/adapter.ucls b/adapter/etc/adapter.ucls index 8c09f039930c..290ff544e5ad 100644 --- a/adapter/etc/adapter.ucls +++ b/adapter/etc/adapter.ucls @@ -1,61 +1,61 @@ - - - + + + - - + + - - - + + + - - + + - + - - - - - - + + - - - - + + + + - + + + + + - - + + diff --git a/adapter/etc/adapter_1.png b/adapter/etc/adapter_1.png deleted file mode 100644 index 64eb34b84f79..000000000000 Binary files a/adapter/etc/adapter_1.png and /dev/null differ diff --git a/adapter/index.md b/adapter/index.md index 36a2a0ad3f58..4263eb322c60 100644 --- a/adapter/index.md +++ b/adapter/index.md @@ -7,26 +7,30 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Beginner --- -**Also known as:** Wrapper +## Also known as +Wrapper -**Intent:** Convert the interface of a class into another interface the clients +## Intent +Convert the interface of a class into another interface the clients expect. Adapter lets classes work together that couldn't otherwise because of 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 * you want to use an existing class, and its interface does not match the one you need * you want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don't necessarily have compatible interfaces * you need to use several existing subclasses, but it's impractical to adapt their interface by subclassing every one. An object adapter can adapt the interface of its parent class. -**Real world examples:** +## Real world examples * [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/adapter/pom.xml b/adapter/pom.xml index ddb81441fc4c..736ce16ec7fe 100644 --- a/adapter/pom.xml +++ b/adapter/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT adapter diff --git a/adapter/src/main/java/com/iluwatar/adapter/App.java b/adapter/src/main/java/com/iluwatar/adapter/App.java index d2353eec4f06..d57cb91e4185 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/App.java +++ b/adapter/src/main/java/com/iluwatar/adapter/App.java @@ -5,14 +5,23 @@ * for an adapter. Interfaces may be incompatible but the inner functionality should suit the need. * The Adapter design pattern allows otherwise incompatible classes to work together by converting * the interface of one class into an interface expected by the clients. - * - *

There are two variations of the Adapter pattern: The class adapter implements the adaptee's + * + *

+ * 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. - * - *

The Adapter ({@link GnomeEngineer}) converts the interface of the target class ( - * {@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} ). + * + *

+ * The story of this implementation is this.
+ * 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 { @@ -22,7 +31,8 @@ public class App { * @param args command line args */ public static void main(String[] args) { - Engineer manager = new GnomeEngineeringManager(new GnomeEngineer()); - manager.operateDevice(); + Captain captain = new Captain(new BattleFishingBoat()); + captain.move(); + captain.fire(); } } diff --git a/adapter/src/main/java/com/iluwatar/adapter/BattleFishingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/BattleFishingBoat.java new file mode 100644 index 000000000000..3f573337f6f5 --- /dev/null +++ b/adapter/src/main/java/com/iluwatar/adapter/BattleFishingBoat.java @@ -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}).
+ * 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(); + } +} diff --git a/adapter/src/main/java/com/iluwatar/adapter/BattleShip.java b/adapter/src/main/java/com/iluwatar/adapter/BattleShip.java new file mode 100644 index 000000000000..d4f6036e6b37 --- /dev/null +++ b/adapter/src/main/java/com/iluwatar/adapter/BattleShip.java @@ -0,0 +1,14 @@ +package com.iluwatar.adapter; + +/** + * The interface expected by the client.
+ * A Battleship can fire and move. + * + */ +public interface BattleShip { + + void fire(); + + void move(); + +} diff --git a/adapter/src/main/java/com/iluwatar/adapter/Captain.java b/adapter/src/main/java/com/iluwatar/adapter/Captain.java new file mode 100644 index 000000000000..8c48daf699d9 --- /dev/null +++ b/adapter/src/main/java/com/iluwatar/adapter/Captain.java @@ -0,0 +1,33 @@ +package com.iluwatar.adapter; + +/** + * The Captain uses {@link BattleShip} to fight.
+ * 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(); + } + +} diff --git a/adapter/src/main/java/com/iluwatar/adapter/Engineer.java b/adapter/src/main/java/com/iluwatar/adapter/Engineer.java deleted file mode 100644 index a973cb530d04..000000000000 --- a/adapter/src/main/java/com/iluwatar/adapter/Engineer.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.iluwatar.adapter; - -/** - * - * Engineers can operate devices. - * - */ -public interface Engineer { - - void operateDevice(); -} diff --git a/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java new file mode 100644 index 000000000000..509bb8cdbbf5 --- /dev/null +++ b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java @@ -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 ..."); + } + +} diff --git a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineer.java b/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineer.java deleted file mode 100644 index 70e166ac3c11..000000000000 --- a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineer.java +++ /dev/null @@ -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(); - } -} diff --git a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineeringManager.java b/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineeringManager.java deleted file mode 100644 index ff4ddb617e45..000000000000 --- a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineeringManager.java +++ /dev/null @@ -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; - } -} diff --git a/adapter/src/main/java/com/iluwatar/adapter/GoblinGlider.java b/adapter/src/main/java/com/iluwatar/adapter/GoblinGlider.java deleted file mode 100644 index 79a9acef66a3..000000000000 --- a/adapter/src/main/java/com/iluwatar/adapter/GoblinGlider.java +++ /dev/null @@ -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!"); - } -} diff --git a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java index 866bfb968d9b..9fce02a3c8cb 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java @@ -1,37 +1,25 @@ package com.iluwatar.adapter; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; +import org.junit.Before; +import org.junit.Test; import java.util.HashMap; import java.util.Map; -import org.junit.Before; -import org.junit.Test; +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 - * for an adapter. Interfaces may be incompatible but the inner functionality should suit the need. - * The Adapter design pattern allows otherwise incompatible classes to work together by converting - * the interface of one class into an interface expected by the clients. - * - *

There are two variations of the Adapter pattern: - * The class adapter implements the adaptee's - * interface whereas the object adapter uses composition to contain the adaptee in the adapter - * object. This example uses the object adapter approach. + * Test class * - *

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 { private Map 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. @@ -40,29 +28,34 @@ public class AdapterPatternTest { public void setup() { beans = new HashMap<>(); - GnomeEngineer gnomeEngineer = spy(new GnomeEngineer()); - beans.put(ENGINEER_BEAN, gnomeEngineer); + BattleFishingBoat battleFishingBoat = spy(new BattleFishingBoat()); + beans.put(BATTLESHIP_BEAN, battleFishingBoat); - GnomeEngineeringManager manager = new GnomeEngineeringManager(); - manager.setEngineer((GnomeEngineer) beans.get(ENGINEER_BEAN)); - beans.put(MANAGER_BEAN, manager); + Captain captain = new Captain(); + captain.setBattleship((BattleFishingBoat) beans.get(BATTLESHIP_BEAN)); + beans.put(CAPTAIN_BEAN, captain); } /** - * This test asserts that when we call operateDevice() method on a manager bean, it is internally - * calling operateDevice method on the engineer object. The Adapter ({@link GnomeEngineer}) - * converts the interface of the target class ( {@link GoblinGlider}) into a suitable one expected - * by the client ({@link GnomeEngineeringManager} ). + * This test asserts that when we use the move() method on a captain bean(client), it is + * internally calling move method on the battleship object. The Adapter ({@link BattleFishingBoat} + * ) converts the interface of the target class ( {@link FishingBoat}) into a suitable one + * expected by the client ({@link Captain} ). */ @Test public void testAdapter() { - Engineer manager = (Engineer) beans.get(MANAGER_BEAN); + BattleShip captain = (BattleShip) beans.get(CAPTAIN_BEAN); + + // when captain moves + captain.move(); + + // the captain internally calls the battleship object to move + BattleShip battleship = (BattleShip) beans.get(BATTLESHIP_BEAN); + verify(battleship).move(); - // when manager is asked to operate device - manager.operateDevice(); + // 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(); } } diff --git a/async-method-invocation/index.md b/async-method-invocation/index.md index dfcee0208cce..93c0249d9639 100644 --- a/async-method-invocation/index.md +++ b/async-method-invocation/index.md @@ -4,24 +4,29 @@ title: Async Method Invocation folder: async-method-invocation permalink: /patterns/async-method-invocation/ 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 is not blocked while waiting results of tasks. The pattern provides parallel processing of multiple independent tasks and retrieving the results via callbacks or waiting until everything is done. ![alt text](./etc/async-method-invocation.png "Async Method Invocation") -**Applicability:** Use async method invocation pattern when +## Applicability +Use async method invocation pattern when * you have multiple independent tasks that can run in parallel * you need to improve the performance of a group of sequential tasks * you have limited amount of processing capacity or long running tasks and the caller should not wait the tasks to be ready -**Real world examples:** +## Real world examples * [FutureTask](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html), [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) and [ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) (Java) * [Task-based Asynchronous Pattern](https://msdn.microsoft.com/en-us/library/hh873175.aspx) (.NET) diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml index 87dd343acb13..3f2a62aee851 100644 --- a/async-method-invocation/pom.xml +++ b/async-method-invocation/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT async-method-invocation @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java index 0b8ee364952a..6f2d4a8fc30b 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java @@ -4,24 +4,23 @@ /** * This application demonstrates the async method invocation pattern. Key parts of the pattern are - * AsyncResult which is an intermediate container for an asynchronously evaluated - * value, AsyncCallback which can be provided to be executed on task completion and - * AsyncExecutor that manages the execution of the async tasks. + * AsyncResult which is an intermediate container for an asynchronously evaluated value, + * AsyncCallback which can be provided to be executed on task completion and AsyncExecutor + * that manages the execution of the async tasks. *

- * The main method shows example flow of async invocations. The main thread starts multiple tasks - * with variable durations and then continues its own work. When the main thread has done it's job - * it collects the results of the async tasks. Two of the tasks are handled with callbacks, meaning - * the callbacks are executed immediately when the tasks complete. + * The main method shows example flow of async invocations. The main thread starts multiple tasks with variable + * durations and then continues its own work. When the main thread has done it's job it collects the results of the + * async tasks. Two of the tasks are handled with callbacks, meaning the callbacks are executed immediately when the + * tasks complete. *

- * Noteworthy difference of thread usage between the async results and callbacks is that the async - * results are collected in the main thread but the callbacks are executed within the worker - * threads. This should be noted when working with thread pools. + * Noteworthy difference of thread usage between the async results and callbacks is that the async results are collected + * in the main thread but the callbacks are executed within the worker threads. This should be noted when working with + * thread pools. *

- * Java provides its own implementations of async method invocation pattern. FutureTask, - * CompletableFuture and ExecutorService are the real world implementations of this pattern. But due - * to the nature of parallel programming, the implementations are not trivial. This example does not - * take all possible scenarios into account but rather provides a simple version that helps to - * understand the pattern. + * Java provides its own implementations of async method invocation pattern. FutureTask, CompletableFuture and + * ExecutorService are the real world implementations of this pattern. But due to the nature of parallel programming, + * the implementations are not trivial. This example does not take all possible scenarios into account but rather + * provides a simple version that helps to understand the pattern. * * @see AsyncResult * @see AsyncCallback @@ -33,6 +32,9 @@ */ public class App { + /** + * Program entry point + */ public static void main(String[] args) throws Exception { // construct a new executor that will run async tasks AsyncExecutor executor = new ThreadAsyncExecutor(); @@ -41,10 +43,8 @@ public static void main(String[] args) throws Exception { AsyncResult asyncResult1 = executor.startProcess(lazyval(10, 500)); AsyncResult asyncResult2 = executor.startProcess(lazyval("test", 300)); AsyncResult asyncResult3 = executor.startProcess(lazyval(50L, 700)); - AsyncResult asyncResult4 = - executor.startProcess(lazyval(20, 400), callback("Callback result 4")); - AsyncResult asyncResult5 = - executor.startProcess(lazyval("callback", 600), callback("Callback result 5")); + AsyncResult asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4")); + AsyncResult 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 Thread.sleep(350); // Oh boy I'm working hard here @@ -66,8 +66,10 @@ public static void main(String[] args) throws Exception { /** * Creates a callable that lazily evaluates to given value with artificial delay. * - * @param value value to evaluate - * @param delayMillis artificial delay in milliseconds + * @param value + * value to evaluate + * @param delayMillis + * artificial delay in milliseconds * @return new callable for lazy evaluation */ private static Callable lazyval(T value, long delayMillis) { @@ -81,7 +83,8 @@ private static Callable lazyval(T value, long delayMillis) { /** * 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 */ private static AsyncCallback callback(String name) { diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java index 6d77df8ecd56..d64180dadebd 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java @@ -5,8 +5,6 @@ /** * * AsyncResult interface - * - * @param */ public interface AsyncResult { diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java index 3009345626b6..6e86b26e46d1 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -29,13 +29,12 @@ public AsyncResult startProcess(Callable task, AsyncCallback callba } catch (Exception ex) { result.setException(ex); } - }, "executor-" + idx.incrementAndGet()).start(); + } , "executor-" + idx.incrementAndGet()).start(); return result; } @Override - public T endProcess(AsyncResult asyncResult) throws ExecutionException, - InterruptedException { + public T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException { if (asyncResult.isCompleted()) { return asyncResult.getValue(); } else { @@ -45,9 +44,8 @@ public T endProcess(AsyncResult asyncResult) throws ExecutionException, } /** - * Simple implementation of async result that allows completing it successfully with a value or - * exceptionally with an exception. A really simplified version from its real life cousins - * FutureTask and CompletableFuture. + * Simple implementation of async result that allows completing it successfully with a value or exceptionally with an + * exception. A really simplified version from its real life cousins FutureTask and CompletableFuture. * * @see java.util.concurrent.FutureTask * @see java.util.concurrent.CompletableFuture @@ -71,10 +69,11 @@ private static class CompletableResult implements AsyncResult { } /** - * Sets the value from successful execution and executes callback if available. Notifies any - * thread waiting for completion. + * Sets the value from successful execution and executes callback if available. Notifies any thread waiting for + * completion. * - * @param value value of the evaluated task + * @param value + * value of the evaluated task */ void setValue(T value) { this.value = value; @@ -86,10 +85,11 @@ void setValue(T value) { } /** - * Sets the exception from failed execution and executes callback if available. Notifies any - * thread waiting for completion. + * Sets the exception from failed execution and executes callback if available. Notifies any thread waiting for + * completion. * - * @param exception exception of the failed task + * @param exception + * exception of the failed task */ void setException(Exception exception) { this.exception = exception; @@ -102,7 +102,7 @@ void setException(Exception exception) { @Override public boolean isCompleted() { - return (state > RUNNING); + return state > RUNNING; } @Override diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java new file mode 100644 index 000000000000..c9d222e55a38 --- /dev/null +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java @@ -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 task = mock(Callable.class); + when(task.call()).thenReturn(result); + + final AsyncResult 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 task = mock(Callable.class); + when(task.call()).thenReturn(result); + + final AsyncCallback callback = mock(AsyncCallback.class); + final AsyncResult 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> optionalCaptor = ArgumentCaptor.forClass((Class) Optional.class); + verify(callback, times(1)).onComplete(eq(result), optionalCaptor.capture()); + + final Optional 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 task = mock(Callable.class); + when(task.call()).thenAnswer(i -> { + Thread.sleep(1500); + return result; + }); + + final AsyncResult 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 task = mock(Callable.class); + when(task.call()).thenAnswer(i -> { + Thread.sleep(1500); + return result; + }); + + final AsyncCallback callback = mock(AsyncCallback.class); + final AsyncResult 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> optionalCaptor = ArgumentCaptor.forClass((Class) Optional.class); + verify(callback, timeout(3000).times(1)).onComplete(eq(result), optionalCaptor.capture()); + + final Optional 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 task = mock(Callable.class); + when(task.call()).thenAnswer(i -> { + Thread.sleep(1500); + return result; + }); + + final AsyncResult 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 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 callback = mock(AsyncCallback.class); + final AsyncResult 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> optionalCaptor = ArgumentCaptor.forClass((Class) Optional.class); + verify(callback, times(1)).onComplete(Matchers.isNull(), optionalCaptor.capture()); + + final Optional 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 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()); + } + + } + +} \ No newline at end of file diff --git a/bridge/index.md b/bridge/index.md index 4a1d0bcbb3f0..49dad14e46fa 100644 --- a/bridge/index.md +++ b/bridge/index.md @@ -7,17 +7,20 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Intermediate --- -**Also known as:** Handle/Body +## Also known as +Handle/Body -**Intent:** Decouple an abstraction from its implementation so that the two can +## Intent +Decouple an abstraction from its implementation so that the two can vary independently. - ![alt text](./etc/bridge.png "Bridge") -**Applicability:** Use the Bridge pattern when +## Applicability +Use the Bridge pattern when * you want to avoid a permanent binding between an abstraction and its implementation. This might be the case, for example, when the implementation must be selected or switched at run-time. * both the abstractions and their implementations should be extensible by subclassing. In this case, the Bridge pattern lets you combine the different abstractions and implementations and extend them independently @@ -25,6 +28,6 @@ vary independently. * you have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies * you want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien's String class, in which multiple objects can share the same string representation. -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/bridge/pom.xml b/bridge/pom.xml index c17b482a59df..8f0c00260843 100644 --- a/bridge/pom.xml +++ b/bridge/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT bridge @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java index 99faad43efa9..08f956b6ad70 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.bridge.App; - /** * * Application test diff --git a/bridge/src/test/java/com/iluwatar/bridge/BlindingMagicWeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/BlindingMagicWeaponTest.java new file mode 100644 index 000000000000..a7a2d1536b2e --- /dev/null +++ b/bridge/src/test/java/com/iluwatar/bridge/BlindingMagicWeaponTest.java @@ -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); + } + +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/FlyingMagicWeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/FlyingMagicWeaponTest.java new file mode 100644 index 000000000000..55b89bb363cf --- /dev/null +++ b/bridge/src/test/java/com/iluwatar/bridge/FlyingMagicWeaponTest.java @@ -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); + } + +} \ No newline at end of file diff --git a/bridge/src/test/java/com/iluwatar/bridge/MagicWeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/MagicWeaponTest.java new file mode 100644 index 000000000000..eb7bfb34ead0 --- /dev/null +++ b/bridge/src/test/java/com/iluwatar/bridge/MagicWeaponTest.java @@ -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); + + } + +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/SoulEatingMagicWeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/SoulEatingMagicWeaponTest.java new file mode 100644 index 000000000000..2d9c24083704 --- /dev/null +++ b/bridge/src/test/java/com/iluwatar/bridge/SoulEatingMagicWeaponTest.java @@ -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); + } + +} \ No newline at end of file diff --git a/builder/index.md b/builder/index.md index f350638d24d6..05056e7c9601 100644 --- a/builder/index.md +++ b/builder/index.md @@ -7,24 +7,27 @@ categories: Creational tags: - Java - Gang Of Four + - Difficulty-Intermediate --- -**Intent:** Separate the construction of a complex object from its +## Intent +Separate the construction of a complex object from its representation so that the same construction process can create different representations. ![alt text](./etc/builder_1.png "Builder") -**Applicability:** Use the Builder pattern when +## Applicability +Use the Builder pattern when * the algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled * the construction process must allow different representations for the object that's constructed -**Real world examples:** +## Real world examples * [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) * [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/builder/pom.xml b/builder/pom.xml index f26494b2e717..d718dbef7fb6 100644 --- a/builder/pom.xml +++ b/builder/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT builder diff --git a/builder/src/main/java/com/iluwatar/builder/Hero.java b/builder/src/main/java/com/iluwatar/builder/Hero.java index 54be11ea3aaa..cf7289d51ef3 100644 --- a/builder/src/main/java/com/iluwatar/builder/Hero.java +++ b/builder/src/main/java/com/iluwatar/builder/Hero.java @@ -93,6 +93,9 @@ public static class HeroBuilder { private Armor armor; private Weapon weapon; + /** + * Constructor + */ public HeroBuilder(Profession profession, String name) { if (profession == null || name == null) { throw new IllegalArgumentException("profession and name can not be null"); diff --git a/builder/src/test/java/com/iluwatar/builder/AppTest.java b/builder/src/test/java/com/iluwatar/builder/AppTest.java index 857f49dc9a4d..270537e8c214 100644 --- a/builder/src/test/java/com/iluwatar/builder/AppTest.java +++ b/builder/src/test/java/com/iluwatar/builder/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.builder.App; - /** * * Application test diff --git a/builder/src/test/java/com/iluwatar/builder/HeroTest.java b/builder/src/test/java/com/iluwatar/builder/HeroTest.java new file mode 100644 index 000000000000..2bedf3ef1fe9 --- /dev/null +++ b/builder/src/test/java/com/iluwatar/builder/HeroTest.java @@ -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()); + + } + +} \ No newline at end of file diff --git a/business-delegate/index.md b/business-delegate/index.md index a55febaf96ac..7d548da1133e 100644 --- a/business-delegate/index.md +++ b/business-delegate/index.md @@ -4,17 +4,21 @@ title: Business Delegate folder: business-delegate permalink: /patterns/business-delegate/ 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 presentation and business tiers. By using the pattern we gain loose coupling between the tiers and encapsulate knowledge about how to locate, connect to, and interact with the business objects that make up the application. ![alt text](./etc/business-delegate.png "Business Delegate") -**Applicability:** Use the Business Delegate pattern when +## Applicability +Use the Business Delegate pattern when * you want loose coupling between presentation and business tiers * you want to orchestrate calls to multiple business services diff --git a/business-delegate/pom.xml b/business-delegate/pom.xml index d07f2c0aef2a..204c3b85c8be 100644 --- a/business-delegate/pom.xml +++ b/business-delegate/pom.xml @@ -6,7 +6,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT business-delegate diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java index a3c9a00e76ab..7fb709bcfd11 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java @@ -5,20 +5,20 @@ */ public class BusinessDelegate { - private BusinessLookup lookupService; - private BusinessService businessService; - private ServiceType serviceType; + private BusinessLookup lookupService; + private BusinessService businessService; + private ServiceType serviceType; - public void setLookupService(BusinessLookup businessLookup) { - this.lookupService = businessLookup; - } + public void setLookupService(BusinessLookup businessLookup) { + this.lookupService = businessLookup; + } - public void setServiceType(ServiceType serviceType) { - this.serviceType = serviceType; - } + public void setServiceType(ServiceType serviceType) { + this.serviceType = serviceType; + } - public void doTask() { - businessService = lookupService.getBusinessService(serviceType); - businessService.doProcessing(); - } + public void doTask() { + businessService = lookupService.getBusinessService(serviceType); + businessService.doProcessing(); + } } diff --git a/caching/index.md b/caching/index.md index f79f13e42a6f..2b89d0559e96 100644 --- a/caching/index.md +++ b/caching/index.md @@ -6,19 +6,23 @@ permalink: /patterns/caching/ categories: Other tags: - 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 the resources immediately after their use. The resources retain their identity, are kept in some fast-access storage, and are re-used to avoid having to acquire them again. ![alt text](./etc/caching.png "Caching") -**Applicability:** Use the Caching pattern(s) when +## Applicability +Use the Caching pattern(s) when * Repetitious acquisition, initialization, and release of the same resource causes unnecessary performance overhead. -**Credits** +## Credits * [Write-through, write-around, write-back: Cache explained](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained) * [Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching](https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177) diff --git a/caching/pom.xml b/caching/pom.xml index e61b8ab8cbcc..f007f14d4f32 100644 --- a/caching/pom.xml +++ b/caching/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT caching diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java index c7f55db70f45..62eca47c3a43 100644 --- a/caching/src/main/java/com/iluwatar/caching/App.java +++ b/caching/src/main/java/com/iluwatar/caching/App.java @@ -21,7 +21,7 @@ * application data. The cache itself is implemented as an internal (Java) data structure. It adopts * a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The three * strategies are individually tested. The testing of the cache is restricted towards saving and - * querying of user accounts from the underlying data store ( {@link DBManager}). The main class ( + * querying of user accounts from the underlying data store ( {@link DbManager}). The main class ( * {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and * whether the data is coming from the cache or the DB (i.e. separation of concern). The AppManager * ({@link AppManager}) handles the transaction of data to-and-from the underlying data store @@ -43,7 +43,7 @@ public class App { * @param args command line args */ public static void main(String[] args) { - AppManager.initDB(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests + AppManager.initDb(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests // and the App class to avoid Maven compilation errors. Set flag to // true to run the tests with MongoDB (provided that MongoDB is // installed and socket connection is open). @@ -65,8 +65,8 @@ public void useReadAndWriteThroughStrategy() { AppManager.save(userAccount1); System.out.println(AppManager.printCacheContent()); - userAccount1 = AppManager.find("001"); - userAccount1 = AppManager.find("001"); + AppManager.find("001"); + AppManager.find("001"); } /** @@ -80,15 +80,15 @@ public void useReadThroughAndWriteAroundStrategy() { AppManager.save(userAccount2); System.out.println(AppManager.printCacheContent()); - userAccount2 = AppManager.find("002"); + AppManager.find("002"); System.out.println(AppManager.printCacheContent()); userAccount2 = AppManager.find("002"); userAccount2.setUserName("Jane G."); AppManager.save(userAccount2); System.out.println(AppManager.printCacheContent()); - userAccount2 = AppManager.find("002"); + AppManager.find("002"); System.out.println(AppManager.printCacheContent()); - userAccount2 = AppManager.find("002"); + AppManager.find("002"); } /** @@ -106,12 +106,12 @@ public void useReadThroughAndWriteBehindStrategy() { AppManager.save(userAccount4); AppManager.save(userAccount5); System.out.println(AppManager.printCacheContent()); - userAccount3 = AppManager.find("003"); + AppManager.find("003"); System.out.println(AppManager.printCacheContent()); UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child."); AppManager.save(userAccount6); System.out.println(AppManager.printCacheContent()); - userAccount4 = AppManager.find("004"); + AppManager.find("004"); System.out.println(AppManager.printCacheContent()); } } diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java index 08132e327880..519226640edf 100644 --- a/caching/src/main/java/com/iluwatar/caching/AppManager.java +++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java @@ -15,24 +15,30 @@ public class AppManager { private static CachingPolicy cachingPolicy; + private AppManager() { + } + /** * * Developer/Tester is able to choose whether the application should use MongoDB as its underlying * data storage or a simple Java data structure to (temporarily) store the data/objects during * runtime. */ - public static void initDB(boolean useMongoDB) { - if (useMongoDB) { + public static void initDb(boolean useMongoDb) { + if (useMongoDb) { try { - DBManager.connect(); + DbManager.connect(); } catch (ParseException e) { e.printStackTrace(); } } else { - DBManager.createVirtualDB(); + DbManager.createVirtualDb(); } } + /** + * Initialize caching policy + */ public static void initCachingPolicy(CachingPolicy policy) { cachingPolicy = policy; if (cachingPolicy == CachingPolicy.BEHIND) { @@ -50,15 +56,21 @@ public static void initCacheCapacity(int 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) { - return CacheStore.readThrough(userID); + return CacheStore.readThrough(userId); } else if (cachingPolicy == CachingPolicy.BEHIND) { - return CacheStore.readThroughWithWriteBackPolicy(userID); + return CacheStore.readThroughWithWriteBackPolicy(userId); } return null; } + /** + * Save user account + */ public static void save(UserAccount userAccount) { if (cachingPolicy == CachingPolicy.THROUGH) { CacheStore.writeThrough(userAccount); diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java index 2041ac14ffa8..1f4748307852 100644 --- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java +++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java @@ -9,73 +9,99 @@ */ public class CacheStore { - static LRUCache cache = null; + static LruCache cache = null; + private CacheStore() { + } + + /** + * Init cache capacity + */ public static void initCapacity(int capacity) { - if (null == cache) - cache = new LRUCache(capacity); - else + if (null == cache) { + cache = new LruCache(capacity); + } else { 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!"); - return cache.get(userID); + return cache.get(userId); } System.out.println("# Cache Miss!"); - UserAccount userAccount = DBManager.readFromDB(userID); - cache.set(userID, userAccount); + UserAccount userAccount = DbManager.readFromDb(userId); + cache.set(userId, userAccount); return userAccount; } + /** + * Get user account using write-through cache + */ public static void writeThrough(UserAccount userAccount) { - if (cache.contains(userAccount.getUserID())) { - DBManager.updateDB(userAccount); + if (cache.contains(userAccount.getUserId())) { + DbManager.updateDb(userAccount); } 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) { - if (cache.contains(userAccount.getUserID())) { - DBManager.updateDB(userAccount); - cache.invalidate(userAccount.getUserID()); // Cache data has been updated -- remove older + if (cache.contains(userAccount.getUserId())) { + DbManager.updateDb(userAccount); + cache.invalidate(userAccount.getUserId()); // Cache data has been updated -- remove older // version from cache. } else { - DBManager.writeToDB(userAccount); + 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!"); - return cache.get(userID); + return cache.get(userId); } System.out.println("# Cache Miss!"); - UserAccount userAccount = DBManager.readFromDB(userID); + UserAccount userAccount = DbManager.readFromDb(userId); if (cache.isFull()) { System.out.println("# Cache is FULL! Writing LRU data to DB..."); - UserAccount toBeWrittenToDB = cache.getLRUData(); - DBManager.upsertDB(toBeWrittenToDB); + UserAccount toBeWrittenToDb = cache.getLruData(); + DbManager.upsertDb(toBeWrittenToDb); } - cache.set(userID, userAccount); + cache.set(userId, userAccount); return userAccount; } + /** + * Set user account + */ 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..."); - UserAccount toBeWrittenToDB = cache.getLRUData(); - DBManager.upsertDB(toBeWrittenToDB); + UserAccount toBeWrittenToDb = cache.getLruData(); + DbManager.upsertDb(toBeWrittenToDb); } - cache.set(userAccount.getUserID(), userAccount); + cache.set(userAccount.getUserId(), userAccount); } + /** + * Clears cache + */ public static void clearCache() { - if (null != cache) + if (null != cache) { cache.clear(); + } } /** @@ -83,14 +109,18 @@ public static void clearCache() { */ public static void flushCache() { System.out.println("# flushCache..."); - if (null == cache) + if (null == cache) { return; + } ArrayList listOfUserAccounts = cache.getCacheDataInListForm(); for (UserAccount userAccount : listOfUserAccounts) { - DBManager.upsertDB(userAccount); + DbManager.upsertDb(userAccount); } } + /** + * Print user accounts + */ public static String print() { ArrayList listOfUserAccounts = cache.getCacheDataInListForm(); StringBuilder sb = new StringBuilder(); diff --git a/caching/src/main/java/com/iluwatar/caching/DBManager.java b/caching/src/main/java/com/iluwatar/caching/DbManager.java similarity index 72% rename from caching/src/main/java/com/iluwatar/caching/DBManager.java rename to caching/src/main/java/com/iluwatar/caching/DbManager.java index 07a5daeac76c..bfde07103ae6 100644 --- a/caching/src/main/java/com/iluwatar/caching/DBManager.java +++ b/caching/src/main/java/com/iluwatar/caching/DbManager.java @@ -21,7 +21,7 @@ * during runtime (createVirtualDB()).

* */ -public class DBManager { +public class DbManager { private static MongoClient mongoClient; private static MongoDatabase db; @@ -29,21 +29,34 @@ public class DBManager { private static HashMap virtualDB; - public static void createVirtualDB() { + private DbManager() { + } + + /** + * Create DB + */ + public static void createVirtualDb() { useMongoDB = false; virtualDB = new HashMap(); } + /** + * Connect to DB + */ public static void connect() throws ParseException { useMongoDB = true; mongoClient = new MongoClient(); db = mongoClient.getDatabase("test"); } - public static UserAccount readFromDB(String userID) { + /** + * Read user account from DB + */ + public static UserAccount readFromDb(String userId) { if (!useMongoDB) { - if (virtualDB.containsKey(userID)) - return virtualDB.get(userID); + if (virtualDB.containsKey(userId)) { + return virtualDB.get(userId); + } return null; } if (null == db) { @@ -54,18 +67,22 @@ public static UserAccount readFromDB(String userID) { } } FindIterable iterable = - db.getCollection("user_accounts").find(new Document("userID", userID)); - if (iterable == null) + db.getCollection("user_accounts").find(new Document("userID", userId)); + if (iterable == null) { return null; + } Document doc = iterable.first(); UserAccount userAccount = - new UserAccount(userID, doc.getString("userName"), doc.getString("additionalInfo")); + new UserAccount(userId, doc.getString("userName"), doc.getString("additionalInfo")); return userAccount; } - public static void writeToDB(UserAccount userAccount) { + /** + * Write user account to DB + */ + public static void writeToDb(UserAccount userAccount) { if (!useMongoDB) { - virtualDB.put(userAccount.getUserID(), userAccount); + virtualDB.put(userAccount.getUserId(), userAccount); return; } if (null == db) { @@ -76,13 +93,16 @@ public static void writeToDB(UserAccount userAccount) { } } 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())); } - public static void updateDB(UserAccount userAccount) { + /** + * Update DB + */ + public static void updateDb(UserAccount userAccount) { if (!useMongoDB) { - virtualDB.put(userAccount.getUserID(), userAccount); + virtualDB.put(userAccount.getUserId(), userAccount); return; } if (null == db) { @@ -93,7 +113,7 @@ public static void updateDB(UserAccount userAccount) { } } db.getCollection("user_accounts").updateOne( - new Document("userID", userAccount.getUserID()), + new Document("userID", userAccount.getUserId()), new Document("$set", new Document("userName", userAccount.getUserName()).append( "additionalInfo", userAccount.getAdditionalInfo()))); } @@ -102,9 +122,9 @@ public static void updateDB(UserAccount userAccount) { * * 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) { - virtualDB.put(userAccount.getUserID(), userAccount); + virtualDB.put(userAccount.getUserId(), userAccount); return; } if (null == db) { @@ -115,8 +135,8 @@ public static void upsertDB(UserAccount userAccount) { } } db.getCollection("user_accounts").updateOne( - new Document("userID", userAccount.getUserID()), - new Document("$set", new Document("userID", userAccount.getUserID()).append("userName", + new Document("userID", userAccount.getUserId()), + new Document("$set", new Document("userID", userAccount.getUserId()).append("userName", userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())), new UpdateOptions().upsert(true)); } diff --git a/caching/src/main/java/com/iluwatar/caching/LRUCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java similarity index 70% rename from caching/src/main/java/com/iluwatar/caching/LRUCache.java rename to caching/src/main/java/com/iluwatar/caching/LruCache.java index 872f9725655c..e20275a4000f 100644 --- a/caching/src/main/java/com/iluwatar/caching/LRUCache.java +++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java @@ -12,16 +12,16 @@ * LRU data is always at the end of the list. * */ -public class LRUCache { +public class LruCache { class Node { - String userID; + String userId; UserAccount userAccount; Node previous; Node next; - public Node(String userID, UserAccount userAccount) { - this.userID = userID; + public Node(String userId, UserAccount userAccount) { + this.userId = userId; this.userAccount = userAccount; } } @@ -31,13 +31,16 @@ public Node(String userID, UserAccount userAccount) { Node head = null; Node end = null; - public LRUCache(int capacity) { + public LruCache(int capacity) { this.capacity = capacity; } - public UserAccount get(String userID) { - if (cache.containsKey(userID)) { - Node node = cache.get(userID); + /** + * Get user account + */ + public UserAccount get(String userId) { + if (cache.containsKey(userId)) { + Node node = cache.get(userId); remove(node); setHead(node); return node.userAccount; @@ -69,52 +72,63 @@ public void remove(Node node) { public void setHead(Node node) { node.next = head; node.previous = null; - if (head != null) + if (head != null) { head.previous = node; + } head = node; - if (end == null) + if (end == null) { end = head; + } } - public void set(String userID, UserAccount userAccount) { - if (cache.containsKey(userID)) { - Node old = cache.get(userID); + /** + * Set user account + */ + public void set(String userId, UserAccount userAccount) { + if (cache.containsKey(userId)) { + Node old = cache.get(userId); old.userAccount = userAccount; remove(old); setHead(old); } else { - Node newNode = new Node(userID, userAccount); + Node newNode = new Node(userId, userAccount); if (cache.size() >= capacity) { - System.out.println("# Cache is FULL! Removing " + end.userID + " from cache..."); - cache.remove(end.userID); // remove LRU data from cache. + System.out.println("# Cache is FULL! Removing " + end.userId + " from cache..."); + cache.remove(end.userId); // remove LRU data from cache. remove(end); setHead(newNode); } else { setHead(newNode); } - cache.put(userID, newNode); + cache.put(userId, newNode); } } - public boolean contains(String userID) { - return cache.containsKey(userID); + public boolean contains(String userId) { + return cache.containsKey(userId); } - public void invalidate(String userID) { - System.out.println("# " + userID + " has been updated! Removing older version from cache..."); - Node toBeRemoved = cache.get(userID); + /** + * Invalidate cache for user + */ + public void invalidate(String userId) { + System.out.println("# " + userId + " has been updated! Removing older version from cache..."); + Node toBeRemoved = cache.get(userId); remove(toBeRemoved); - cache.remove(userID); + cache.remove(userId); } public boolean isFull() { return cache.size() >= capacity; } - public UserAccount getLRUData() { + public UserAccount getLruData() { return end.userAccount; } + /** + * Clear cache + */ public void clear() { head = null; end = null; @@ -135,6 +149,9 @@ public ArrayList getCacheDataInListForm() { return listOfCacheData; } + /** + * Set cache capacity + */ public void setCapacity(int newCapacity) { if (capacity > newCapacity) { clear(); // Behavior can be modified to accommodate for decrease in cache size. For now, we'll diff --git a/caching/src/main/java/com/iluwatar/caching/UserAccount.java b/caching/src/main/java/com/iluwatar/caching/UserAccount.java index eff0878adbea..0e281c429706 100644 --- a/caching/src/main/java/com/iluwatar/caching/UserAccount.java +++ b/caching/src/main/java/com/iluwatar/caching/UserAccount.java @@ -6,22 +6,25 @@ * */ public class UserAccount { - private String userID; + private String userId; private String userName; 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.additionalInfo = additionalInfo; } - public String getUserID() { - return userID; + public String getUserId() { + return userId; } - public void setUserID(String userID) { - this.userID = userID; + public void setUserId(String userId) { + this.userId = userId; } public String getUserName() { @@ -42,6 +45,6 @@ public void setAdditionalInfo(String additionalInfo) { @Override public String toString() { - return userID + ", " + userName + ", " + additionalInfo; + return userId + ", " + userName + ", " + additionalInfo; } } diff --git a/caching/src/test/java/com/iluwatar/caching/AppTest.java b/caching/src/test/java/com/iluwatar/caching/AppTest.java index ce5cddf08f16..35917da1c17c 100644 --- a/caching/src/test/java/com/iluwatar/caching/AppTest.java +++ b/caching/src/test/java/com/iluwatar/caching/AppTest.java @@ -16,7 +16,7 @@ public class AppTest { */ @Before 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 // tests with MongoDB (provided that MongoDB is installed and socket // connection is open). diff --git a/callback/index.md b/callback/index.md index b724f1edcac2..be73dc78f4bf 100644 --- a/callback/index.md +++ b/callback/index.md @@ -4,19 +4,25 @@ title: Callback folder: callback permalink: /patterns/callback/ 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 argument to other code, which is expected to call back (execute) the argument at some convenient time. ![alt text](./etc/callback.png "Callback") -**Applicability:** Use the Callback pattern when +## Applicability +Use the Callback pattern when * when some arbitrary synchronous or asynchronous action must be performed after execution of some defined activity. -**Real world examples:** +## Real world examples * [CyclicBarrier] (http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html#CyclicBarrier%28int,%20java.lang.Runnable%29) constructor can accept callback that will be triggered every time when barrier is tripped. diff --git a/callback/pom.xml b/callback/pom.xml index dc12efb66cc8..b4b2f6ed0ba2 100644 --- a/callback/pom.xml +++ b/callback/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT callback diff --git a/callback/src/main/java/com/iluwatar/callback/App.java b/callback/src/main/java/com/iluwatar/callback/App.java index 81cb16f738fb..bc8b08cf0968 100644 --- a/callback/src/main/java/com/iluwatar/callback/App.java +++ b/callback/src/main/java/com/iluwatar/callback/App.java @@ -9,6 +9,9 @@ */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { Task task = new SimpleTask(); Callback callback = new Callback() { diff --git a/callback/src/main/java/com/iluwatar/callback/Callback.java b/callback/src/main/java/com/iluwatar/callback/Callback.java index 08939298b7aa..7128938734e7 100644 --- a/callback/src/main/java/com/iluwatar/callback/Callback.java +++ b/callback/src/main/java/com/iluwatar/callback/Callback.java @@ -7,5 +7,5 @@ */ public interface Callback { - public void call(); + void call(); } diff --git a/callback/src/main/java/com/iluwatar/callback/LambdasApp.java b/callback/src/main/java/com/iluwatar/callback/LambdasApp.java new file mode 100644 index 000000000000..19dd17eae441 --- /dev/null +++ b/callback/src/main/java/com/iluwatar/callback/LambdasApp.java @@ -0,0 +1,19 @@ +package com.iluwatar.callback; + +/** + * + * This example generates the exact same output as {@link App} however the callback has been + * defined as a Lambdas expression. + * + */ +public class LambdasApp { + + /** + * Program entry point + */ + public static void main(String[] args) { + Task task = new SimpleTask(); + Callback c = () -> System.out.println("I'm done now."); + task.executeWith(c); + } +} diff --git a/callback/src/main/java/com/iluwatar/callback/Task.java b/callback/src/main/java/com/iluwatar/callback/Task.java index d3be6c7a0811..83e2cd4df31a 100644 --- a/callback/src/main/java/com/iluwatar/callback/Task.java +++ b/callback/src/main/java/com/iluwatar/callback/Task.java @@ -7,6 +7,9 @@ */ public abstract class Task { + /** + * Execute with callback + */ public final void executeWith(Callback callback) { execute(); if (callback != null) { diff --git a/callback/src/test/java/com/iluwatar/callback/AppTest.java b/callback/src/test/java/com/iluwatar/callback/AppTest.java index 67046a175590..28d6eaa1cea1 100644 --- a/callback/src/test/java/com/iluwatar/callback/AppTest.java +++ b/callback/src/test/java/com/iluwatar/callback/AppTest.java @@ -36,4 +36,22 @@ public void call() { assertEquals("Callback called twice", new Integer(2), callingCount); } + + @Test + public void testWithLambdasExample() { + Callback callback = () -> callingCount++; + + Task task = new SimpleTask(); + + assertEquals("Initial calling count of 0", new Integer(0), callingCount); + + task.executeWith(callback); + + assertEquals("Callback called once", new Integer(1), callingCount); + + task.executeWith(callback); + + assertEquals("Callback called twice", new Integer(2), callingCount); + + } } diff --git a/chain/index.md b/chain/index.md index 9be3763246fd..ef18f6f64ca5 100644 --- a/chain/index.md +++ b/chain/index.md @@ -7,25 +7,28 @@ categories: Behavioral tags: - Java - Gang Of Four + - Difficulty-Intermediate --- -**Intent:** Avoid coupling the sender of a request to its receiver by giving +## Intent +Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. ![alt text](./etc/chain_1.png "Chain of Responsibility") -**Applicability:** Use Chain of Responsibility when +## Applicability +Use Chain of Responsibility when * more than one object may handle a request, and the handler isn't known a priori. The handler should be ascertained automatically * you want to issue a request to one of several objects without specifying the receiver explicitly * the set of objects that can handle a request should be specified dynamically -**Real world examples:** +## Real world examples * [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29) * [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/chain/pom.xml b/chain/pom.xml index 1c044f7e18b8..ec3ecf1bb0cf 100644 --- a/chain/pom.xml +++ b/chain/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT chain diff --git a/chain/src/main/java/com/iluwatar/chain/OrcCommander.java b/chain/src/main/java/com/iluwatar/chain/OrcCommander.java index 73de1b8b2f9c..6cc495b996d0 100644 --- a/chain/src/main/java/com/iluwatar/chain/OrcCommander.java +++ b/chain/src/main/java/com/iluwatar/chain/OrcCommander.java @@ -15,6 +15,7 @@ public OrcCommander(RequestHandler handler) { public void handleRequest(Request req) { if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) { printHandling(req); + req.markHandled(); } else { super.handleRequest(req); } diff --git a/chain/src/main/java/com/iluwatar/chain/OrcOfficer.java b/chain/src/main/java/com/iluwatar/chain/OrcOfficer.java index 68df3ec6f0e2..e6d68c19c67f 100644 --- a/chain/src/main/java/com/iluwatar/chain/OrcOfficer.java +++ b/chain/src/main/java/com/iluwatar/chain/OrcOfficer.java @@ -15,6 +15,7 @@ public OrcOfficer(RequestHandler handler) { public void handleRequest(Request req) { if (req.getRequestType().equals(RequestType.TORTURE_PRISONER)) { printHandling(req); + req.markHandled(); } else { super.handleRequest(req); } diff --git a/chain/src/main/java/com/iluwatar/chain/OrcSoldier.java b/chain/src/main/java/com/iluwatar/chain/OrcSoldier.java index d96f51702573..bd2a8d11d1a6 100644 --- a/chain/src/main/java/com/iluwatar/chain/OrcSoldier.java +++ b/chain/src/main/java/com/iluwatar/chain/OrcSoldier.java @@ -15,6 +15,7 @@ public OrcSoldier(RequestHandler handler) { public void handleRequest(Request req) { if (req.getRequestType().equals(RequestType.COLLECT_TAX)) { printHandling(req); + req.markHandled(); } else { super.handleRequest(req); } diff --git a/chain/src/main/java/com/iluwatar/chain/Request.java b/chain/src/main/java/com/iluwatar/chain/Request.java index 0c62cfd432c0..5c3256a5524e 100644 --- a/chain/src/main/java/com/iluwatar/chain/Request.java +++ b/chain/src/main/java/com/iluwatar/chain/Request.java @@ -1,38 +1,78 @@ package com.iluwatar.chain; +import java.util.Objects; + /** - * * 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; + + /** + * A description of the request + */ + private final String requestDescription; - public Request(RequestType requestType, String requestDescription) { - this.setRequestType(requestType); - this.setRequestDescription(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() { 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() { 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 true when the request is handled, false if not + */ + public boolean isHandled() { + return this.handled; } @Override public String toString() { return getRequestDescription(); } + } diff --git a/chain/src/main/java/com/iluwatar/chain/RequestHandler.java b/chain/src/main/java/com/iluwatar/chain/RequestHandler.java index fd58b9ea8122..12db1f51caaa 100644 --- a/chain/src/main/java/com/iluwatar/chain/RequestHandler.java +++ b/chain/src/main/java/com/iluwatar/chain/RequestHandler.java @@ -13,6 +13,9 @@ public RequestHandler(RequestHandler next) { this.next = next; } + /** + * Request handler + */ public void handleRequest(Request req) { if (next != null) { next.handleRequest(req); diff --git a/chain/src/test/java/com/iluwatar/chain/AppTest.java b/chain/src/test/java/com/iluwatar/chain/AppTest.java index bd28b007a7a2..f1dc787594b1 100644 --- a/chain/src/test/java/com/iluwatar/chain/AppTest.java +++ b/chain/src/test/java/com/iluwatar/chain/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.chain.App; - /** * * Application test diff --git a/chain/src/test/java/com/iluwatar/chain/OrcKingTest.java b/chain/src/test/java/com/iluwatar/chain/OrcKingTest.java new file mode 100644 index 000000000000..fd3d573b602a --- /dev/null +++ b/chain/src/test/java/com/iluwatar/chain/OrcKingTest.java @@ -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() + ); + } + + } + +} \ No newline at end of file diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml new file mode 100644 index 000000000000..a3a2c23a8d22 --- /dev/null +++ b/checkstyle-suppressions.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/checkstyle.xml b/checkstyle.xml index 0ff943d951d7..706c188e0da8 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -25,8 +25,10 @@ + + - + @@ -48,7 +50,7 @@ - + @@ -61,7 +63,7 @@ - + @@ -86,9 +88,6 @@ - - - @@ -97,42 +96,19 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - @@ -180,11 +148,6 @@ - - - - @@ -195,7 +158,7 @@ - + @@ -205,4 +168,5 @@ + diff --git a/command/index.md b/command/index.md index 3fa774d8f481..2b9311537b45 100644 --- a/command/index.md +++ b/command/index.md @@ -7,17 +7,21 @@ categories: Behavioral tags: - Java - Gang Of Four + - Difficulty-Intermediate --- -**Also known as:** Action, Transaction +## Also known as +Action, Transaction -**Intent:** Encapsulate a request as an object, thereby letting you +## Intent +Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. ![alt text](./etc/command.png "Command") -**Applicability:** Use the Command pattern when you want to +## Applicability +Use the Command pattern when you want to * parameterize objects by an action to perform. You can express such parameterization in a procedural language with a callback function, that is, a function that's registered somewhere to be called at a later point. Commands are an object-oriented replacement for callbacks. * specify, queue, and execute requests at different times. A Command object can have a lifetime independent of the original request. If the receiver of a request can be represented in an address space-independent way, then you can transfer a command object for the request to a different process and fulfill the request there @@ -25,16 +29,16 @@ support undoable operations. * support logging changes so that they can be reapplied in case of a system crash. By augmenting the Command interface with load and store operations, you can keep a persistent log of changes. Recovering from a crash involves reloading logged commands from disk and re-executing them with the execute operation * structure a system around high-level operations build on primitive operations. Such a structure is common in information systems that support transactions. A transaction encapsulates a set of changes to data. The Command pattern offers a way to model transactions. Commands have a common interface, letting you invoke all transactions the same way. The pattern also makes it easy to extend the system with new transactions -**Typical Use Case:** +## Typical Use Case * to keep a history of requests * implement callback functionality * implement the undo functionality -**Real world examples:** +## Real world examples * [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/command/pom.xml b/command/pom.xml index 6001ebc339e3..837b149f6d23 100644 --- a/command/pom.xml +++ b/command/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT command diff --git a/command/src/main/java/com/iluwatar/command/Target.java b/command/src/main/java/com/iluwatar/command/Target.java index e12f758ff262..731fe4d1f795 100644 --- a/command/src/main/java/com/iluwatar/command/Target.java +++ b/command/src/main/java/com/iluwatar/command/Target.java @@ -30,6 +30,9 @@ public void setVisibility(Visibility visibility) { @Override public abstract String toString(); + /** + * Print status + */ public void printStatus() { System.out.println(String.format("%s, [size=%s] [visibility=%s]", this, getSize(), getVisibility())); diff --git a/command/src/main/java/com/iluwatar/command/Wizard.java b/command/src/main/java/com/iluwatar/command/Wizard.java index edef8d3a9164..fb6407c74913 100644 --- a/command/src/main/java/com/iluwatar/command/Wizard.java +++ b/command/src/main/java/com/iluwatar/command/Wizard.java @@ -15,12 +15,18 @@ public class Wizard { public Wizard() {} + /** + * Cast spell + */ public void castSpell(Command command, Target target) { System.out.println(this + " casts " + command + " at " + target); command.execute(target); undoStack.offerLast(command); } + /** + * Undo last spell + */ public void undoLastSpell() { if (!undoStack.isEmpty()) { Command previousSpell = undoStack.pollLast(); @@ -30,6 +36,9 @@ public void undoLastSpell() { } } + /** + * Redo last spell + */ public void redoLastSpell() { if (!redoStack.isEmpty()) { Command previousSpell = redoStack.pollLast(); diff --git a/composite/index.md b/composite/index.md index 4a31a1b33984..8b980292d9ce 100644 --- a/composite/index.md +++ b/composite/index.md @@ -7,24 +7,27 @@ categories: Structural tags: - Java - 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 hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly. ![alt text](./etc/composite_1.png "Composite") -**Applicability:** Use the Composite pattern when +## Applicability +Use the Composite pattern when * you want to represent part-whole hierarchies of objects * you want clients to be able to ignore the difference between compositions of objects and individual objects. Clients will treat all objects in the composite structure uniformly -**Real world examples:** +## Real world examples * [java.awt.Container](http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html) and [java.awt.Component](http://docs.oracle.com/javase/8/docs/api/java/awt/Component.html) * [Apache Wicket](https://github.com/apache/wicket) component tree, see [Component](https://github.com/apache/wicket/blob/91e154702ab1ff3481ef6cbb04c6044814b7e130/wicket-core/src/main/java/org/apache/wicket/Component.java) and [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/composite/pom.xml b/composite/pom.xml index 1551080640cf..584ba5476ea8 100644 --- a/composite/pom.xml +++ b/composite/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT composite diff --git a/composite/src/main/java/com/iluwatar/composite/LetterComposite.java b/composite/src/main/java/com/iluwatar/composite/LetterComposite.java index 1fdf4fdb6cff..39655fa370a5 100644 --- a/composite/src/main/java/com/iluwatar/composite/LetterComposite.java +++ b/composite/src/main/java/com/iluwatar/composite/LetterComposite.java @@ -24,6 +24,9 @@ public int count() { protected abstract void printThisAfter(); + /** + * Print + */ public void print() { printThisBefore(); for (LetterComposite letter : children) { diff --git a/composite/src/main/java/com/iluwatar/composite/Sentence.java b/composite/src/main/java/com/iluwatar/composite/Sentence.java index e6c626ea23d0..03f0c69490cd 100644 --- a/composite/src/main/java/com/iluwatar/composite/Sentence.java +++ b/composite/src/main/java/com/iluwatar/composite/Sentence.java @@ -9,6 +9,9 @@ */ public class Sentence extends LetterComposite { + /** + * Constructor + */ public Sentence(List words) { for (Word w : words) { this.add(w); diff --git a/composite/src/main/java/com/iluwatar/composite/Word.java b/composite/src/main/java/com/iluwatar/composite/Word.java index 3060b0a1b2a7..98c5f0b0d6ed 100644 --- a/composite/src/main/java/com/iluwatar/composite/Word.java +++ b/composite/src/main/java/com/iluwatar/composite/Word.java @@ -9,6 +9,9 @@ */ public class Word extends LetterComposite { + /** + * Constructor + */ public Word(List letters) { for (Letter l : letters) { this.add(l); diff --git a/composite/src/test/java/com/iluwatar/composite/AppTest.java b/composite/src/test/java/com/iluwatar/composite/AppTest.java index 574e8def4093..a5bc613c04b0 100644 --- a/composite/src/test/java/com/iluwatar/composite/AppTest.java +++ b/composite/src/test/java/com/iluwatar/composite/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.composite.App; - /** * * Application test diff --git a/composite/src/test/java/com/iluwatar/composite/MessengerTest.java b/composite/src/test/java/com/iluwatar/composite/MessengerTest.java new file mode 100644 index 000000000000..9d0b158cc3d4 --- /dev/null +++ b/composite/src/test/java/com/iluwatar/composite/MessengerTest.java @@ -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()); + } + +} diff --git a/dao/index.md b/dao/index.md index cf9f43a68899..785a1c362a58 100644 --- a/dao/index.md +++ b/dao/index.md @@ -3,22 +3,24 @@ layout: pattern title: Data Access Object folder: dao permalink: /patterns/dao/ -categories: Architectural +categories: Persistence Tier tags: - Java - Difficulty-Beginner --- -**Intent:** Object provides an abstract interface to some type of database or +## Intent +Object provides an abstract interface to some type of database or other persistence mechanism. ![alt text](./etc/dao.png "Data Access Object") -**Applicability:** Use the Data Access Object in any of the following situations +## Applicability +Use the Data Access Object in any of the following situations * when you want to consolidate how the data layer is accessed * when you want to avoid writing multiple data retrieval/persistence layers -**Credits:** +## Credits * [J2EE Design Patterns](http://www.amazon.com/J2EE-Design-Patterns-William-Crawford/dp/0596004273/ref=sr_1_2) diff --git a/dao/pom.xml b/dao/pom.xml index 3134dad964b2..8b0c260e50e7 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -6,7 +6,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT dao diff --git a/dao/src/main/java/com/iluwatar/dao/App.java b/dao/src/main/java/com/iluwatar/dao/App.java index 2e115d8ce2d1..a9351689d02e 100644 --- a/dao/src/main/java/com/iluwatar/dao/App.java +++ b/dao/src/main/java/com/iluwatar/dao/App.java @@ -21,7 +21,7 @@ */ public class App { - private static Logger LOGGER = Logger.getLogger(App.class); + private static Logger log = Logger.getLogger(App.class); /** * Program entry point. @@ -30,17 +30,17 @@ public class App { */ public static void main(final String[] args) { final CustomerDaoImpl customerDao = new CustomerDaoImpl(generateSampleCustomers()); - LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); - LOGGER.info("customerDao.getCusterById(2): " + customerDao.getCustomerById(2)); + log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); + log.info("customerDao.getCusterById(2): " + customerDao.getCustomerById(2)); final Customer customer = new Customer(4, "Dan", "Danson"); customerDao.addCustomer(customer); - LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); + log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); customer.setFirstName("Daniel"); customer.setLastName("Danielson"); customerDao.updateCustomer(customer); - LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); + log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); customerDao.deleteCustomer(customer); - LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); + log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); } /** diff --git a/dao/src/main/java/com/iluwatar/dao/Customer.java b/dao/src/main/java/com/iluwatar/dao/Customer.java index e6d7f776323f..ea13daf1185c 100644 --- a/dao/src/main/java/com/iluwatar/dao/Customer.java +++ b/dao/src/main/java/com/iluwatar/dao/Customer.java @@ -11,6 +11,9 @@ public class Customer { private String firstName; private String lastName; + /** + * Constructor + */ public Customer(final int id, final String firstName, final String lastName) { this.id = id; this.firstName = firstName; @@ -52,10 +55,11 @@ public boolean equals(final Object o) { boolean isEqual = false; if (this == o) { isEqual = true; - } else if (o != null && (getClass() == o.getClass())) { + } else if (o != null && getClass() == o.getClass()) { final Customer customer = (Customer) o; - if (getId() == customer.getId()) + if (getId() == customer.getId()) { isEqual = true; + } } return isEqual; } diff --git a/dao/src/main/resources/log4j.xml b/dao/src/main/resources/log4j.xml index 136817f50ebe..906e37170cf9 100644 --- a/dao/src/main/resources/log4j.xml +++ b/dao/src/main/resources/log4j.xml @@ -1,17 +1,17 @@ + xmlns:log4j='http://jakarta.apache.org/log4j/'> - - - - - + + + + + - - - - + + + + \ No newline at end of file diff --git a/decorator/etc/decorator.png b/decorator/etc/decorator.png index 1e4bfdac2b20..47a87b20b33d 100644 Binary files a/decorator/etc/decorator.png and b/decorator/etc/decorator.png differ diff --git a/decorator/etc/decorator.ucls b/decorator/etc/decorator.ucls index 7adb8c3a685a..a5353d4ecf4a 100644 --- a/decorator/etc/decorator.ucls +++ b/decorator/etc/decorator.ucls @@ -1,18 +1,18 @@ - + - + - - + + @@ -21,29 +21,46 @@ - + - + + + + + + + + + + + + - - - - + + + + - - + + + + + + diff --git a/decorator/etc/decorator_1.png b/decorator/etc/decorator_1.png deleted file mode 100644 index 5a7afe2d1a2c..000000000000 Binary files a/decorator/etc/decorator_1.png and /dev/null differ diff --git a/decorator/index.md b/decorator/index.md index 61eeeac6089f..108e0cc73bfc 100644 --- a/decorator/index.md +++ b/decorator/index.md @@ -7,22 +7,26 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Beginner --- -**Also known as:** Wrapper +## Also known as +Wrapper -**Intent:** Attach additional responsibilities to an object dynamically. +## Intent +Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. ![alt text](./etc/decorator_1.png "Decorator") -**Applicability:** Use Decorator +## Applicability +Use Decorator * to add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects * for responsibilities that can be withdrawn * when extension by subclassing is impractical. Sometimes a large number of independent extensions are possible and would produce an explosion of subclasses to support every combination. Or a class definition may be hidden or otherwise unavailable for subclassing -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/decorator/pom.xml b/decorator/pom.xml index 04403768867e..ea30f5b387f3 100644 --- a/decorator/pom.xml +++ b/decorator/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT decorator @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/decorator/src/main/java/com/iluwatar/decorator/App.java b/decorator/src/main/java/com/iluwatar/decorator/App.java index d58d3b61a930..242e72d11bbf 100644 --- a/decorator/src/main/java/com/iluwatar/decorator/App.java +++ b/decorator/src/main/java/com/iluwatar/decorator/App.java @@ -8,7 +8,7 @@ * runtime. *

* In this example we show how the simple {@link Troll} first attacks and then flees the battle. - * Then we decorate the {@link Troll} with a {@link SmartTroll} and perform the attack again. You + * Then we decorate the {@link Troll} with a {@link SmartHostile} and perform the attack again. You * can see how the behavior changes after the decoration. * */ @@ -30,7 +30,7 @@ public static void main(String[] args) { // change the behavior of the simple troll by adding a decorator System.out.println("\nA smart looking troll surprises you."); - Hostile smart = new SmartTroll(troll); + Hostile smart = new SmartHostile(troll); smart.attack(); smart.fleeBattle(); System.out.printf("Smart troll power %d.\n", smart.getAttackPower()); diff --git a/decorator/src/main/java/com/iluwatar/decorator/SmartTroll.java b/decorator/src/main/java/com/iluwatar/decorator/SmartHostile.java similarity index 56% rename from decorator/src/main/java/com/iluwatar/decorator/SmartTroll.java rename to decorator/src/main/java/com/iluwatar/decorator/SmartHostile.java index 93927237dfd8..93f494688e1a 100644 --- a/decorator/src/main/java/com/iluwatar/decorator/SmartTroll.java +++ b/decorator/src/main/java/com/iluwatar/decorator/SmartHostile.java @@ -1,34 +1,34 @@ package com.iluwatar.decorator; /** - * SmartTroll is a decorator for {@link Hostile} objects. The calls to the {@link Hostile} interface + * SmartHostile is a decorator for {@link Hostile} objects. The calls to the {@link Hostile} interface * are intercepted and decorated. Finally the calls are delegated to the decorated {@link Hostile} * object. * */ -public class SmartTroll implements Hostile { +public class SmartHostile implements Hostile { private Hostile decorated; - public SmartTroll(Hostile decorated) { + public SmartHostile(Hostile decorated) { this.decorated = decorated; } @Override public void attack() { - System.out.println("The troll throws a rock at you!"); + System.out.println("It throws a rock at you!"); decorated.attack(); } @Override public int getAttackPower() { - // decorated troll power + 20 because it is smart + // decorated hostile's power + 20 because it is smart return decorated.getAttackPower() + 20; } @Override public void fleeBattle() { - System.out.println("The troll calls for help!"); + System.out.println("It calls for help!"); decorated.fleeBattle(); } } diff --git a/decorator/src/test/java/com/iluwatar/decorator/AppTest.java b/decorator/src/test/java/com/iluwatar/decorator/AppTest.java index f6fa960922f6..4b2ced9624c1 100644 --- a/decorator/src/test/java/com/iluwatar/decorator/AppTest.java +++ b/decorator/src/test/java/com/iluwatar/decorator/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.decorator.App; - /** * * Application test diff --git a/decorator/src/test/java/com/iluwatar/decorator/SmartHostileTest.java b/decorator/src/test/java/com/iluwatar/decorator/SmartHostileTest.java new file mode 100644 index 000000000000..e5be32eae15a --- /dev/null +++ b/decorator/src/test/java/com/iluwatar/decorator/SmartHostileTest.java @@ -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 SmartHostileTest { + + @Test + public void testSmartHostile() 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 SmartHostile(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); + + } + +} diff --git a/decorator/src/test/java/com/iluwatar/decorator/TrollTest.java b/decorator/src/test/java/com/iluwatar/decorator/TrollTest.java new file mode 100644 index 000000000000..56f541cfccf2 --- /dev/null +++ b/decorator/src/test/java/com/iluwatar/decorator/TrollTest.java @@ -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); + } + +} \ No newline at end of file diff --git a/delegation/etc/delegation.png b/delegation/etc/delegation.png new file mode 100644 index 000000000000..375ef4d6b00f Binary files /dev/null and b/delegation/etc/delegation.png differ diff --git a/delegation/etc/delegation.ucls b/delegation/etc/delegation.ucls new file mode 100644 index 000000000000..e3ce088737b2 --- /dev/null +++ b/delegation/etc/delegation.ucls @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/delegation/index.md b/delegation/index.md new file mode 100644 index 000000000000..e5c0c63760c4 --- /dev/null +++ b/delegation/index.md @@ -0,0 +1,30 @@ +--- +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) diff --git a/delegation/pom.xml b/delegation/pom.xml new file mode 100644 index 000000000000..3d9ca390d173 --- /dev/null +++ b/delegation/pom.xml @@ -0,0 +1,27 @@ + + + + + java-design-patterns + com.iluwatar + 1.10.0-SNAPSHOT + + 4.0.0 + + delegation + + + + junit + junit + test + + + com.github.stefanbirkner + system-rules + test + + + \ No newline at end of file diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/App.java b/delegation/src/main/java/com/iluwatar/delegation/simple/App.java new file mode 100644 index 000000000000..050380c180df --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/App.java @@ -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); + } + +} diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/Printer.java b/delegation/src/main/java/com/iluwatar/delegation/simple/Printer.java new file mode 100644 index 000000000000..91531dfceb30 --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/Printer.java @@ -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); +} diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java b/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java new file mode 100644 index 000000000000..d237f0871b20 --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java @@ -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); + } +} diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java new file mode 100644 index 000000000000..ef63864293bc --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java @@ -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); + } + +} diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java new file mode 100644 index 000000000000..780d12bcb604 --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java @@ -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); + } + +} diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java new file mode 100644 index 000000000000..be8845ecedde --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java @@ -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); + } + +} diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java new file mode 100644 index 000000000000..189baa856b48 --- /dev/null +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java @@ -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); + } + +} diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java new file mode 100644 index 000000000000..9442b3033c2b --- /dev/null +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java @@ -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()); + } + +} diff --git a/dependency-injection/index.md b/dependency-injection/index.md index f6ead97a7f13..735f589b1555 100644 --- a/dependency-injection/index.md +++ b/dependency-injection/index.md @@ -4,10 +4,13 @@ title: Dependency Injection folder: dependency-injection permalink: /patterns/dependency-injection/ 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 more dependencies (or services) are injected, or passed by reference, into a dependent object (or client) and are made part of the client's state. The pattern separates the creation of a client's dependencies from its own @@ -16,7 +19,8 @@ inversion of control and single responsibility principles. ![alt text](./etc/dependency-injection.png "Dependency Injection") -**Applicability:** Use the Dependency Injection pattern when +## Applicability +Use the Dependency Injection pattern when * when you need to remove knowledge of concrete implementation from object * to enable unit testing of classes in isolation using mock objects or stubs diff --git a/dependency-injection/pom.xml b/dependency-injection/pom.xml index b05451afa046..e0aee6a6a1c6 100644 --- a/dependency-injection/pom.xml +++ b/dependency-injection/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT dependency-injection @@ -13,6 +13,11 @@ junit junit test + + + org.mockito + mockito-core + test com.google.inject diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java new file mode 100644 index 000000000000..5f7733a99653 --- /dev/null +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java @@ -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()); + } + + } + +} \ No newline at end of file diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java index 8d6411028473..36f016e47263 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.dependency.injection.App; - /** * * Application test diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java new file mode 100644 index 000000000000..d84ffad849aa --- /dev/null +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java @@ -0,0 +1,78 @@ +package com.iluwatar.dependency.injection; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; + +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:57 PM + * + * @author Jeroen Meulemeester + */ +public class GuiceWizardTest extends StdOutTest { + + /** + * Test if the {@link GuiceWizard} smokes whatever instance of {@link Tobacco} is passed to him + * through the constructor parameter + */ + @Test + public void testSmokeEveryThingThroughConstructor() throws Exception { + + final Tobacco[] tobaccos = { + new OldTobyTobacco(), new RivendellTobacco(), new SecondBreakfastTobacco() + }; + + for (final Tobacco tobacco : tobaccos) { + final GuiceWizard guiceWizard = new GuiceWizard(tobacco); + guiceWizard.smoke(); + + // Verify if the wizard is smoking the correct tobacco ... + verify(getStdOutMock(), times(1)).println("GuiceWizard smoking " + tobacco.getClass().getSimpleName()); + + // ... and nothing else is happening. + verifyNoMoreInteractions(getStdOutMock()); + } + + } + + /** + * Test if the {@link GuiceWizard} smokes whatever instance of {@link Tobacco} is passed to him + * through the Guice google inject framework + */ + @Test + public void testSmokeEveryThingThroughInjectionFramework() throws Exception { + + @SuppressWarnings("unchecked") + final Class[] tobaccos = new Class[]{ + OldTobyTobacco.class, RivendellTobacco.class, SecondBreakfastTobacco.class + }; + + for (final Class tobaccoClass : tobaccos) { + // Configure the tobacco in the injection framework ... + final Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Tobacco.class).to(tobaccoClass); + } + }); + + // ... and create a new wizard with it + final GuiceWizard guiceWizard = injector.getInstance(GuiceWizard.class); + guiceWizard.smoke(); + + // Verify if the wizard is smoking the correct tobacco ... + verify(getStdOutMock(), times(1)).println("GuiceWizard smoking " + tobaccoClass.getSimpleName()); + + // ... and nothing else is happening. + verifyNoMoreInteractions(getStdOutMock()); + } + + } + +} \ No newline at end of file diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java new file mode 100644 index 000000000000..9b3f4ea3a143 --- /dev/null +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java @@ -0,0 +1,26 @@ +package com.iluwatar.dependency.injection; + +import org.junit.Test; + +import static org.mockito.Mockito.*; + +/** + * Date: 12/10/15 - 8:26 PM + * + * @author Jeroen Meulemeester + */ +public class SimpleWizardTest extends StdOutTest { + + /** + * Test if the {@link SimpleWizard} does the only thing it can do: Smoke it's {@link + * OldTobyTobacco} + */ + @Test + public void testSmoke() { + final SimpleWizard simpleWizard = new SimpleWizard(); + simpleWizard.smoke(); + verify(getStdOutMock(), times(1)).println("SimpleWizard smoking OldTobyTobacco"); + verifyNoMoreInteractions(getStdOutMock()); + } + +} \ No newline at end of file diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/StdOutTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/StdOutTest.java new file mode 100644 index 000000000000..13c18fcd1ade --- /dev/null +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.dependency.injection; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since the actions of the wizard don't + * have any influence on any other accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } +} diff --git a/double-checked-locking/index.md b/double-checked-locking/index.md index b1b0108ec128..da1fdd1a2ee7 100644 --- a/double-checked-locking/index.md +++ b/double-checked-locking/index.md @@ -4,17 +4,22 @@ title: Double Checked Locking folder: double-checked-locking permalink: /patterns/double-checked-locking/ categories: Concurrency -tags: Java +tags: + - Java + - Difficulty-Beginner + - Idiom --- -**Intent:** Reduce the overhead of acquiring a lock by first testing the +## Intent +Reduce the overhead of acquiring a lock by first testing the locking criterion (the "lock hint") without actually acquiring the lock. Only if the locking criterion check indicates that locking is required does the actual locking logic proceed. ![alt text](./etc/double_checked_locking_1.png "Double Checked Locking") -**Applicability:** Use the Double Checked Locking pattern when +## Applicability +Use the Double Checked Locking pattern when * there is a concurrent access in object creation, e.g. singleton, where you want to create single instance of the same class and checking if it's null or not maybe not be enough when there are two or more threads that checks if instance is null or not. * there is a concurrent access on a method where method's behaviour changes according to the some constraints and these constraint change within this method. diff --git a/double-checked-locking/pom.xml b/double-checked-locking/pom.xml index a9c0f220bdee..465184e4ce85 100644 --- a/double-checked-locking/pom.xml +++ b/double-checked-locking/pom.xml @@ -3,7 +3,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT double-checked-locking @@ -12,5 +12,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java index 0cc62c995430..79bf6aefd37d 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java @@ -28,7 +28,7 @@ public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 3; i++) { executorService.execute(() -> { - while (inventory.addItem(new Item())); + while (inventory.addItem(new Item())) {}; }); } diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java index b51e000a7d9f..1011b78b4339 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java @@ -1,6 +1,7 @@ package com.iluwatar.doublechecked.locking; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -16,12 +17,18 @@ public class Inventory { private final List items; private final Lock lock; + /** + * Constructor + */ public Inventory(int inventorySize) { this.inventorySize = inventorySize; this.items = new ArrayList<>(inventorySize); this.lock = new ReentrantLock(); } + /** + * Add item + */ public boolean addItem(Item item) { if (items.size() < inventorySize) { lock.lock(); @@ -38,4 +45,14 @@ public boolean addItem(Item item) { } return false; } + + /** + * Get all the items in the inventory + * + * @return All the items of the inventory, as an unmodifiable list + */ + public final List getItems() { + return Collections.unmodifiableList(items); + } + } diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java index 5efe06215b8a..bba4970a3697 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java @@ -6,7 +6,4 @@ * */ public class Item { - - private String name; - private int level; } diff --git a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java index bd88f223c34c..012d00648b74 100644 --- a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java +++ b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.doublechecked.locking.App; - /** * * Application test diff --git a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java new file mode 100644 index 000000000000..a09f19e576f7 --- /dev/null +++ b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java @@ -0,0 +1,110 @@ +package com.iluwatar.doublechecked.locking; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.io.PrintStream; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/10/15 - 9:34 PM + * + * @author Jeroen Meulemeester + */ +public class InventoryTest { + + /** + * The mocked standard out {@link PrintStream}, used to verify a steady increasing size of the + * {@link Inventory} while adding items from multiple threads concurrently + */ + 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); + } + + /** + * The number of threads used to stress test the locking of the {@link Inventory#addItem(Item)} + * method + */ + private static final int THREAD_COUNT = 8; + + /** + * The maximum number of {@link Item}s allowed in the {@link Inventory} + */ + private static final int INVENTORY_SIZE = 1000; + + /** + * Concurrently add multiple items to the inventory, and check if the items were added in order by + * checking the stdOut for continuous growth of the inventory. When 'items.size()=xx' shows up out + * of order, it means that the locking is not ok, increasing the risk of going over the inventory + * item limit. + */ + @Test(timeout = 10000) + public void testAddItem() throws Exception { + // Create a new inventory with a limit of 1000 items and put some load on the add method + final Inventory inventory = new Inventory(INVENTORY_SIZE); + final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT); + for (int i = 0; i < THREAD_COUNT; i++) { + executorService.execute(() -> { + while (inventory.addItem(new Item())) {}; + }); + } + + // Wait until all threads have finished + executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.SECONDS); + + // Check the number of items in the inventory. It should not have exceeded the allowed maximum + final List items = inventory.getItems(); + assertNotNull(items); + assertEquals(INVENTORY_SIZE, items.size()); + + // Capture all stdOut messages ... + final ArgumentCaptor stdOutCaptor = ArgumentCaptor.forClass(String.class); + verify(this.stdOutMock, times(INVENTORY_SIZE)).println(stdOutCaptor.capture()); + + // ... verify if we got all 1000 + final List values = stdOutCaptor.getAllValues(); + assertEquals(INVENTORY_SIZE, values.size()); + + // ... and check if the inventory size is increasing continuously + for (int i = 0; i < values.size(); i++) { + assertNotNull(values.get(i)); + assertTrue(values.get(i).contains("items.size()=" + (i + 1))); + } + + verifyNoMoreInteractions(this.stdOutMock); + } + +} \ No newline at end of file diff --git a/double-dispatch/index.md b/double-dispatch/index.md index 6847b7a41e79..ae87208a2639 100644 --- a/double-dispatch/index.md +++ b/double-dispatch/index.md @@ -4,18 +4,23 @@ title: Double Dispatch folder: double-dispatch permalink: /patterns/double-dispatch/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Intermediate + - Idiom --- -**Intent:** Double Dispatch pattern is a way to create maintainable dynamic +## Intent +Double Dispatch pattern is a way to create maintainable dynamic behavior based on receiver and parameter types. ![alt text](./etc/double-dispatch.png "Double Dispatch") -**Applicability:** Use the Double Dispatch pattern when +## Applicability +Use the Double Dispatch pattern when * the dynamic behavior is not defined only based on receiving object's type but also on the receiving method's parameter type. -**Real world examples:** +## Real world examples * [ObjectOutputStream](https://docs.oracle.com/javase/8/docs/api/java/io/ObjectOutputStream.html) diff --git a/double-dispatch/pom.xml b/double-dispatch/pom.xml index 290ddb68b4d1..719827cf0b1c 100644 --- a/double-dispatch/pom.xml +++ b/double-dispatch/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT double-dispatch @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java index 6514feb7f51b..98e19b770187 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java @@ -5,22 +5,20 @@ /** * - * When a message with a parameter is sent to an object, the resultant behaviour is defined by the - * implementation of that method in the receiver. Sometimes the behaviour must also be determined by - * the type of the parameter. + * When a message with a parameter is sent to an object, the resultant behaviour is defined by the implementation of + * that method in the receiver. Sometimes the behaviour must also be determined by the type of the parameter. *

- * One way to implement this would be to create multiple instanceof-checks for the methods - * parameter. However, this creates a maintenance issue. When new types are added we would also need - * to change the method's implementation and add a new instanceof-check. This violates the single - * responsibility principle - a class should have only one reason to change. + * One way to implement this would be to create multiple instanceof-checks for the methods parameter. However, this + * creates a maintenance issue. When new types are added we would also need to change the method's implementation and + * add a new instanceof-check. This violates the single responsibility principle - a class should have only one reason + * to change. *

- * Instead of the instanceof-checks a better way is to make another virtual call on the parameter - * object. This way new functionality can be easily added without the need to modify existing - * implementation (open-closed principle). + * Instead of the instanceof-checks a better way is to make another virtual call on the parameter object. This way new + * functionality can be easily added without the need to modify existing implementation (open-closed principle). *

- * In this example we have hierarchy of objects ({@link GameObject}) that can collide to each other. - * Each object has its own coordinates which are checked against the other objects' coordinates. If - * there is an overlap, then the objects collide utilizing the Double Dispatch pattern. + * In this example we have hierarchy of objects ({@link GameObject}) that can collide to each other. Each object has its + * own coordinates which are checked against the other objects' coordinates. If there is an overlap, then the objects + * collide utilizing the Double Dispatch pattern. * */ public class App { @@ -28,7 +26,8 @@ public class App { /** * Program entry point * - * @param args command line args + * @param args + * command line args */ public static void main(String[] args) { // initialize game objects and print their status @@ -42,8 +41,9 @@ public static void main(String[] args) { // collision check objects.stream().forEach(o1 -> objects.stream().forEach(o2 -> { - if (o1 != o2 && o1.intersectsWith(o2)) + if (o1 != o2 && o1.intersectsWith(o2)) { o1.collision(o2); + } })); System.out.println(""); diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java index db26265cc01a..e1e3eab7b0b7 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java @@ -12,6 +12,9 @@ public class Rectangle { private int right; private int bottom; + /** + * Constructor + */ public Rectangle(int left, int top, int right, int bottom) { this.left = left; this.top = top; diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java index c5cd213b5d62..83caca6130b9 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.doubledispatch.App; - /** * * Application test diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java new file mode 100644 index 000000000000..6792a5d37a75 --- /dev/null +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java @@ -0,0 +1,136 @@ +package com.iluwatar.doubledispatch; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class CollisionTest { + + /** + * The mocked standard out {@link PrintStream}, required if some of the actions on the tested + * object don't have a direct influence on any other accessible objects, except for writing to + * 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + + /** + * Get the tested object + * + * @return The tested object, should never return 'null' + */ + abstract O getTestedObject(); + + /** + * Collide the tested item with the other given item and verify if the damage and fire state is as + * expected + * + * @param other The other object we have to collide with + * @param otherDamaged Indicates if the other object should be damaged after the collision + * @param otherOnFire Indicates if the other object should be burning after the collision + * @param thisDamaged Indicates if the test object should be damaged after the collision + * @param thisOnFire Indicates if the other object should be burning after the collision + * @param description The expected description of the collision + */ + void testCollision(final GameObject other, final boolean otherDamaged, final boolean otherOnFire, + final boolean thisDamaged, final boolean thisOnFire, final String description) { + + Objects.requireNonNull(other); + Objects.requireNonNull(getTestedObject()); + + final O tested = getTestedObject(); + + tested.collision(other); + + verify(getStdOutMock(), times(1)).println(description); + verifyNoMoreInteractions(getStdOutMock()); + + testOnFire(other, tested, otherOnFire); + testDamaged(other, tested, otherDamaged); + + testOnFire(tested, other, thisOnFire); + testDamaged(tested, other, thisDamaged); + + } + + /** + * Test if the fire state of the target matches the expected state after colliding with the given + * object + * + * @param target The target object + * @param other The other object + * @param expectTargetOnFire The expected state of fire on the target object + */ + private void testOnFire(final GameObject target, final GameObject other, final boolean expectTargetOnFire) { + final String targetName = target.getClass().getSimpleName(); + final String otherName = other.getClass().getSimpleName(); + + final String errorMessage = expectTargetOnFire + ? "Expected [" + targetName + "] to be on fire after colliding with [" + otherName + "] but it was not!" + : "Expected [" + targetName + "] not to be on fire after colliding with [" + otherName + "] but it was!"; + + assertEquals(errorMessage, expectTargetOnFire, target.isOnFire()); + } + + /** + * Test if the damage state of the target matches the expected state after colliding with the + * given object + * + * @param target The target object + * @param other The other object + * @param expectedDamage The expected state of damage on the target object + */ + private void testDamaged(final GameObject target, final GameObject other, final boolean expectedDamage) { + final String targetName = target.getClass().getSimpleName(); + final String otherName = other.getClass().getSimpleName(); + + final String errorMessage = expectedDamage + ? "Expected [" + targetName + "] to be damaged after colliding with [" + otherName + "] but it was not!" + : "Expected [" + targetName + "] not to be damaged after colliding with [" + otherName + "] but it was!"; + + assertEquals(errorMessage, expectedDamage, target.isDamaged()); + } + +} diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java new file mode 100644 index 000000000000..4cbc052c7a7b --- /dev/null +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java @@ -0,0 +1,88 @@ +package com.iluwatar.doubledispatch; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/10/15 - 11:31 PM + * + * @author Jeroen Meulemeester + */ +public class FlamingAsteroidTest extends CollisionTest { + + @Override + final FlamingAsteroid getTestedObject() { + return new FlamingAsteroid(1, 2, 3, 4); + } + + /** + * Test the constructor parameters + */ + @Test + public void testConstructor() { + final FlamingAsteroid asteroid = new FlamingAsteroid(1, 2, 3, 4); + assertEquals(1, asteroid.getLeft()); + assertEquals(2, asteroid.getTop()); + assertEquals(3, asteroid.getRight()); + assertEquals(4, asteroid.getBottom()); + assertTrue(asteroid.isOnFire()); + assertFalse(asteroid.isDamaged()); + assertEquals("FlamingAsteroid at [1,2,3,4] damaged=false onFire=true", asteroid.toString()); + } + + /** + * Test what happens we collide with an asteroid + */ + @Test + public void testCollideFlamingAsteroid() { + testCollision( + new FlamingAsteroid(1, 2, 3, 4), + false, true, + false, true, + "FlamingAsteroid hits FlamingAsteroid." + ); + } + + /** + * Test what happens we collide with an meteoroid + */ + @Test + public void testCollideMeteoroid() { + testCollision( + new Meteoroid(1, 1, 3, 4), + false, false, + false, true, + "FlamingAsteroid hits Meteoroid." + ); + } + + /** + * Test what happens we collide with ISS + */ + @Test + public void testCollideSpaceStationIss() { + testCollision( + new SpaceStationIss(1, 1, 3, 4), + true, true, + false, true, + "FlamingAsteroid hits SpaceStationIss. SpaceStationIss is damaged! SpaceStationIss is set on fire!" + ); + } + + /** + * Test what happens we collide with MIR + */ + @Test + public void testCollideSpaceStationMir() { + testCollision( + new SpaceStationMir(1, 1, 3, 4), + true, true, + false, true, + "FlamingAsteroid hits SpaceStationMir. SpaceStationMir is damaged! SpaceStationMir is set on fire!" + ); + } + +} \ No newline at end of file diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java new file mode 100644 index 000000000000..6f90fbaabc08 --- /dev/null +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java @@ -0,0 +1,87 @@ +package com.iluwatar.doubledispatch; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * Date: 12/10/15 - 11:31 PM + * + * @author Jeroen Meulemeester + */ +public class MeteoroidTest extends CollisionTest { + + @Override + final Meteoroid getTestedObject() { + return new Meteoroid(1, 2, 3, 4); + } + + /** + * Test the constructor parameters + */ + @Test + public void testConstructor() { + final Meteoroid meteoroid = new Meteoroid(1, 2, 3, 4); + assertEquals(1, meteoroid.getLeft()); + assertEquals(2, meteoroid.getTop()); + assertEquals(3, meteoroid.getRight()); + assertEquals(4, meteoroid.getBottom()); + assertFalse(meteoroid.isOnFire()); + assertFalse(meteoroid.isDamaged()); + assertEquals("Meteoroid at [1,2,3,4] damaged=false onFire=false", meteoroid.toString()); + } + + /** + * Test what happens we collide with an asteroid + */ + @Test + public void testCollideFlamingAsteroid() { + testCollision( + new FlamingAsteroid(1, 1, 3, 4), + false, true, + false, false, + "Meteoroid hits FlamingAsteroid." + ); + } + + /** + * Test what happens we collide with an meteoroid + */ + @Test + public void testCollideMeteoroid() { + testCollision( + new Meteoroid(1, 1, 3, 4), + false, false, + false, false, + "Meteoroid hits Meteoroid." + ); + } + + /** + * Test what happens we collide with ISS + */ + @Test + public void testCollideSpaceStationIss() { + testCollision( + new SpaceStationIss(1, 1, 3, 4), + true, false, + false, false, + "Meteoroid hits SpaceStationIss. SpaceStationIss is damaged!" + ); + } + + /** + * Test what happens we collide with MIR + */ + @Test + public void testCollideSpaceStationMir() { + testCollision( + new SpaceStationMir(1, 1, 3, 4), + true, false, + false, false, + "Meteoroid hits SpaceStationMir. SpaceStationMir is damaged!" + ); + } + +} \ No newline at end of file diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java index fad10c490a29..e2563f7db913 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java @@ -1,22 +1,47 @@ package com.iluwatar.doubledispatch; -import org.junit.Assert; import org.junit.Test; -import com.iluwatar.doubledispatch.Rectangle; +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** - * * Unit test for Rectangle - * */ public class RectangleTest { + /** + * Test if the values passed through the constructor matches the values fetched from the getters + */ @Test - public void test() { - Assert.assertTrue(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(0, 0, 1, 1))); - Assert.assertTrue(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(-1, -5, 7, 8))); - Assert.assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(2, 2, 3, 3))); - Assert.assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(-2, -2, -1, -1))); + public void testConstructor() { + final Rectangle rectangle = new Rectangle(1, 2, 3, 4); + assertEquals(1, rectangle.getLeft()); + assertEquals(2, rectangle.getTop()); + assertEquals(3, rectangle.getRight()); + assertEquals(4, rectangle.getBottom()); } + + /** + * Test if the values passed through the constructor matches the values in the {@link + * #toString()} + */ + @Test + public void testToString() throws Exception { + final Rectangle rectangle = new Rectangle(1, 2, 3, 4); + assertEquals("[1,2,3,4]", rectangle.toString()); + } + + /** + * Test if the {@link Rectangle} class can detect if it intersects with another rectangle. + */ + @Test + public void testIntersection() { + assertTrue(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(0, 0, 1, 1))); + assertTrue(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(-1, -5, 7, 8))); + assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(2, 2, 3, 3))); + assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(-2, -2, -1, -1))); + } + } diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java new file mode 100644 index 000000000000..f3ce24e9c424 --- /dev/null +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java @@ -0,0 +1,87 @@ +package com.iluwatar.doubledispatch; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * Date: 12/10/15 - 11:31 PM + * + * @author Jeroen Meulemeester + */ +public class SpaceStationIssTest extends CollisionTest { + + @Override + final SpaceStationIss getTestedObject() { + return new SpaceStationIss(1, 2, 3, 4); + } + + /** + * Test the constructor parameters + */ + @Test + public void testConstructor() { + final SpaceStationIss iss = new SpaceStationIss(1, 2, 3, 4); + assertEquals(1, iss.getLeft()); + assertEquals(2, iss.getTop()); + assertEquals(3, iss.getRight()); + assertEquals(4, iss.getBottom()); + assertFalse(iss.isOnFire()); + assertFalse(iss.isDamaged()); + assertEquals("SpaceStationIss at [1,2,3,4] damaged=false onFire=false", iss.toString()); + } + + /** + * Test what happens we collide with an asteroid + */ + @Test + public void testCollideFlamingAsteroid() { + testCollision( + new FlamingAsteroid(1, 1, 3, 4), + false, true, + false, false, + "SpaceStationIss hits FlamingAsteroid." + ); + } + + /** + * Test what happens we collide with an meteoroid + */ + @Test + public void testCollideMeteoroid() { + testCollision( + new Meteoroid(1, 1, 3, 4), + false, false, + false, false, + "SpaceStationIss hits Meteoroid." + ); + } + + /** + * Test what happens we collide with ISS + */ + @Test + public void testCollideSpaceStationIss() { + testCollision( + new SpaceStationIss(1, 1, 3, 4), + true, false, + false, false, + "SpaceStationIss hits SpaceStationIss. SpaceStationIss is damaged!" + ); + } + + /** + * Test what happens we collide with MIR + */ + @Test + public void testCollideSpaceStationMir() { + testCollision( + new SpaceStationMir(1, 1, 3, 4), + true, false, + false, false, + "SpaceStationIss hits SpaceStationMir. SpaceStationMir is damaged!" + ); + } + +} \ No newline at end of file diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java new file mode 100644 index 000000000000..7d557dd309de --- /dev/null +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java @@ -0,0 +1,87 @@ +package com.iluwatar.doubledispatch; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * Date: 12/10/15 - 11:31 PM + * + * @author Jeroen Meulemeester + */ +public class SpaceStationMirTest extends CollisionTest { + + @Override + final SpaceStationMir getTestedObject() { + return new SpaceStationMir(1, 2, 3, 4); + } + + /** + * Test the constructor parameters + */ + @Test + public void testConstructor() { + final SpaceStationMir mir = new SpaceStationMir(1, 2, 3, 4); + assertEquals(1, mir.getLeft()); + assertEquals(2, mir.getTop()); + assertEquals(3, mir.getRight()); + assertEquals(4, mir.getBottom()); + assertFalse(mir.isOnFire()); + assertFalse(mir.isDamaged()); + assertEquals("SpaceStationMir at [1,2,3,4] damaged=false onFire=false", mir.toString()); + } + + /** + * Test what happens we collide with an asteroid + */ + @Test + public void testCollideFlamingAsteroid() { + testCollision( + new FlamingAsteroid(1, 1, 3, 4), + false, true, + false, false, + "SpaceStationMir hits FlamingAsteroid." + ); + } + + /** + * Test what happens we collide with an meteoroid + */ + @Test + public void testCollideMeteoroid() { + testCollision( + new Meteoroid(1, 1, 3, 4), + false, false, + false, false, + "SpaceStationMir hits Meteoroid." + ); + } + + /** + * Test what happens we collide with ISS + */ + @Test + public void testCollideSpaceStationIss() { + testCollision( + new SpaceStationIss(1, 1, 3, 4), + true, false, + false, false, + "SpaceStationMir hits SpaceStationIss. SpaceStationIss is damaged!" + ); + } + + /** + * Test what happens we collide with MIR + */ + @Test + public void testCollideSpaceStationMir() { + testCollision( + new SpaceStationMir(1, 1, 3, 4), + true, false, + false, false, + "SpaceStationMir hits SpaceStationMir. SpaceStationMir is damaged!" + ); + } + +} \ No newline at end of file diff --git a/event-aggregator/index.md b/event-aggregator/index.md index 1c89c71885dc..5462a2a5d8bc 100644 --- a/event-aggregator/index.md +++ b/event-aggregator/index.md @@ -4,10 +4,13 @@ title: Event Aggregator folder: event-aggregator permalink: /patterns/event-aggregator/ categories: Structural -tags: Java +tags: + - Java + - Difficulty-Beginner --- -**Intent:** A system with lots of objects can lead to complexities when a +## Intent +A system with lots of objects can lead to complexities when a client wants to subscribe to events. The client has to find and register for each object individually, if each object has multiple events then each event requires a separate subscription. An Event Aggregator acts as a single source @@ -16,7 +19,8 @@ allowing clients to register with just the aggregator. ![alt text](./etc/classes.png "Event Aggregator") -**Applicability:** Use the Event Aggregator pattern when +## Applicability +Use the Event Aggregator pattern when * Event Aggregator is a good choice when you have lots of objects that are potential event sources. Rather than have the observer deal with registering @@ -24,6 +28,6 @@ allowing clients to register with just the aggregator. Aggregator. As well as simplifying registration, a Event Aggregator also simplifies the memory management issues in using observers. -**Credits:** +## Credits * [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html) diff --git a/event-aggregator/pom.xml b/event-aggregator/pom.xml index d5001f992aad..70d585cbb461 100644 --- a/event-aggregator/pom.xml +++ b/event-aggregator/pom.xml @@ -4,7 +4,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT event-aggregator @@ -13,5 +13,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java index 0cb76c2153f2..d0c37c3a88c0 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.event.aggregator.App; - /** * * Application test diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java new file mode 100644 index 000000000000..37bd36da4a21 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java @@ -0,0 +1,133 @@ +package com.iluwatar.event.aggregator; + +import org.junit.Test; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 10:58 PM + * + * @author Jeroen Meulemeester + */ +public abstract class EventEmitterTest { + + /** + * Factory used to create a new instance of the test object with a default observer + */ + private final Function factoryWithDefaultObserver; + + /** + * Factory used to create a new instance of the test object without passing a default observer + */ + private final Supplier factoryWithoutDefaultObserver; + + /** + * The day of the week an event is expected + */ + private final Weekday specialDay; + + /** + * The expected event, emitted on the special day + */ + private final Event event; + + /** + * Create a new event emitter test, using the given test object factories, special day and event + */ + EventEmitterTest(final Weekday specialDay, final Event event, + final Function factoryWithDefaultObserver, + final Supplier factoryWithoutDefaultObserver) { + + this.specialDay = specialDay; + this.event = event; + this.factoryWithDefaultObserver = Objects.requireNonNull(factoryWithDefaultObserver); + this.factoryWithoutDefaultObserver = Objects.requireNonNull(factoryWithoutDefaultObserver); + } + + /** + * Go over every day of the month, and check if the event is emitted on the given day. This test + * is executed twice, once without a default emitter and once with + */ + @Test + public void testAllDays() { + testAllDaysWithoutDefaultObserver(specialDay, event); + testAllDaysWithDefaultObserver(specialDay, event); + } + + /** + * Pass each week of the day, day by day to the event emitter and verify of the given observers + * received the correct event on the special day. + * + * @param specialDay The special day on which an event is emitted + * @param event The expected event emitted by the test object + * @param emitter The event emitter + * @param observers The registered observer mocks + */ + private void testAllDays(final Weekday specialDay, final Event event, final E emitter, + final EventObserver... observers) { + + for (final Weekday weekday : Weekday.values()) { + // Pass each week of the day, day by day to the event emitter + emitter.timePasses(weekday); + + if (weekday == specialDay) { + // On a special day, every observer should have received the event + for (final EventObserver observer : observers) { + verify(observer, times(1)).onEvent(eq(event)); + } + } else { + // On any other normal day, the observers should have received nothing at all + verifyZeroInteractions(observers); + } + } + + // The observers should not have received any additional events after the week + verifyNoMoreInteractions(observers); + } + + /** + * Go over every day of the month, and check if the event is emitted on the given day. Use an + * event emitter without a default observer + * + * @param specialDay The special day on which an event is emitted + * @param event The expected event emitted by the test object + */ + private void testAllDaysWithoutDefaultObserver(final Weekday specialDay, final Event event) { + final EventObserver observer1 = mock(EventObserver.class); + final EventObserver observer2 = mock(EventObserver.class); + + final E emitter = this.factoryWithoutDefaultObserver.get(); + emitter.registerObserver(observer1); + emitter.registerObserver(observer2); + + testAllDays(specialDay, event, emitter, observer1, observer2); + } + + /** + * Go over every day of the month, and check if the event is emitted on the given day. + * + * @param specialDay The special day on which an event is emitted + * @param event The expected event emitted by the test object + */ + private void testAllDaysWithDefaultObserver(final Weekday specialDay, final Event event) { + final EventObserver defaultObserver = mock(EventObserver.class); + final EventObserver observer1 = mock(EventObserver.class); + final EventObserver observer2 = mock(EventObserver.class); + + final E emitter = this.factoryWithDefaultObserver.apply(defaultObserver); + emitter.registerObserver(observer1); + emitter.registerObserver(observer2); + + testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2); + } + +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java new file mode 100644 index 000000000000..3f2cdb0feaa8 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java @@ -0,0 +1,27 @@ +package com.iluwatar.event.aggregator; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/12/15 - 2:52 PM + * + * @author Jeroen Meulemeester + */ +public class EventTest { + + /** + * Verify if every event has a non-null, non-empty description + */ + @Test + public void testToString() { + for (final Event event : Event.values()) { + final String toString = event.toString(); + assertNotNull(toString); + assertFalse(toString.trim().isEmpty()); + } + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java new file mode 100644 index 000000000000..c1d0549363fc --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java @@ -0,0 +1,67 @@ +package com.iluwatar.event.aggregator; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 3:04 PM + * + * @author Jeroen Meulemeester + */ +public class KingJoffreyTest { + + /** + * The mocked standard out {@link PrintStream}, required since {@link KingJoffrey} does nothing + * except for writing to 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 if {@link KingJoffrey} tells us what event he received + */ + @Test + public void testOnEvent() { + final KingJoffrey kingJoffrey = new KingJoffrey(); + + for (final Event event : Event.values()) { + verifyZeroInteractions(this.stdOutMock); + kingJoffrey.onEvent(event); + + final String expectedMessage = "Received event from the King's Hand: " + event.toString(); + verify(this.stdOutMock, times(1)).println(expectedMessage); + verifyNoMoreInteractions(this.stdOutMock); + } + + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java new file mode 100644 index 000000000000..e62bb3f52793 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java @@ -0,0 +1,48 @@ +package com.iluwatar.event.aggregator; + +import org.junit.Test; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 10:57 AM + * + * @author Jeroen Meulemeester + */ +public class KingsHandTest extends EventEmitterTest { + + /** + * Create a new test instance, using the correct object factory + */ + public KingsHandTest() { + super(null, null, KingsHand::new, KingsHand::new); + } + + /** + * The {@link KingsHand} is both an {@link EventEmitter} as an {@link EventObserver} so verify if every + * event received is passed up to it's superior, in most cases {@link KingJoffrey} but now just a + * mocked observer. + */ + @Test + public void testPassThrough() throws Exception { + final EventObserver observer = mock(EventObserver.class); + final KingsHand kingsHand = new KingsHand(observer); + + // The kings hand should not pass any events before he received one + verifyZeroInteractions(observer); + + // Verify if each event is passed on to the observer, nothing less, nothing more. + for (final Event event : Event.values()) { + kingsHand.onEvent(event); + verify(observer, times(1)).onEvent(eq(event)); + verifyNoMoreInteractions(observer); + } + + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java new file mode 100644 index 000000000000..dbc867859a92 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.event.aggregator; + +/** + * Date: 12/12/15 - 10:57 AM + * + * @author Jeroen Meulemeester + */ +public class LordBaelishTest extends EventEmitterTest { + + /** + * Create a new test instance, using the correct object factory + */ + public LordBaelishTest() { + super(Weekday.FRIDAY, Event.STARK_SIGHTED, LordBaelish::new, LordBaelish::new); + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java new file mode 100644 index 000000000000..050af55766cb --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.event.aggregator; + +/** + * Date: 12/12/15 - 10:57 AM + * + * @author Jeroen Meulemeester + */ +public class LordVarysTest extends EventEmitterTest { + + /** + * Create a new test instance, using the correct object factory + */ + public LordVarysTest() { + super(Weekday.SATURDAY, Event.TRAITOR_DETECTED, LordVarys::new, LordVarys::new); + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java new file mode 100644 index 000000000000..435e07821d90 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.event.aggregator; + +/** + * Date: 12/12/15 - 10:57 AM + * + * @author Jeroen Meulemeester + */ +public class ScoutTest extends EventEmitterTest { + + /** + * Create a new test instance, using the correct object factory + */ + public ScoutTest() { + super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new); + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java new file mode 100644 index 000000000000..37b3008511ae --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.event.aggregator; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/12/15 - 2:12 PM + * + * @author Jeroen Meulemeester + */ +public class WeekdayTest { + + @Test + public void testToString() throws Exception { + for (final Weekday weekday : Weekday.values()) { + final String toString = weekday.toString(); + assertNotNull(toString); + assertEquals(weekday.name(), toString.toUpperCase()); + } + } + +} \ No newline at end of file diff --git a/event-driven-architecture/etc/eda.png b/event-driven-architecture/etc/eda.png new file mode 100644 index 000000000000..38c433a409e0 Binary files /dev/null and b/event-driven-architecture/etc/eda.png differ diff --git a/event-driven-architecture/etc/eda.ucls b/event-driven-architecture/etc/eda.ucls new file mode 100644 index 000000000000..4ddb8b20ca65 --- /dev/null +++ b/event-driven-architecture/etc/eda.ucls @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/event-driven-architecture/index.md b/event-driven-architecture/index.md new file mode 100644 index 000000000000..7c786b76c75c --- /dev/null +++ b/event-driven-architecture/index.md @@ -0,0 +1,30 @@ +layout: pattern +title: Event Driven Architecture +folder: event-driven-architecture +permalink: /patterns/event-driven-architecture + +## Intent +Send and notify state changes of your objects to other applications using an Event-driven Architecture. + +![alt text](./etc/eda.png "Event Driven Architecture") + +## Applicability +Use an Event-driven architecture when + +* you want to create a loosely coupled system +* you want to build a more responsive system +* you want a system that is easier to extend + +## Real world examples + +* SendGrid, an email API, sends events whenever an email is processed, delivered, opened etc... (https://sendgrid.com/docs/API_Reference/Webhooks/event.html) +* Chargify, a billing API, exposes payment activity through various events (https://docs.chargify.com/api-events) +* Amazon's AWS Lambda, lets you execute code in response to events such as changes to Amazon S3 buckets, updates to an Amazon DynamoDB table, or custom events generated by your applications or devices. (https://aws.amazon.com/lambda) +* MySQL runs triggers based on events such as inserts and update events happening on database tables. + +## Credits + +* [Event-driven architecture - Wikipedia](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained) +* [Fundamental Components of an Event-Driven Architecture](http://giocc.com/fundamental-components-of-an-event-driven-architecture.html) +* [Real World Applications/Event Driven Applications](https://wiki.haskell.org/Real_World_Applications/Event_Driven_Applications) +* [Event-driven architecture definition](http://searchsoa.techtarget.com/definition/event-driven-architecture) diff --git a/event-driven-architecture/pom.xml b/event-driven-architecture/pom.xml new file mode 100644 index 000000000000..32b0bfb3e93d --- /dev/null +++ b/event-driven-architecture/pom.xml @@ -0,0 +1,28 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.10.0-SNAPSHOT + + + event-driven-architecture + + + + junit + junit + test + + + + org.mockito + mockito-core + test + + + \ No newline at end of file diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java new file mode 100644 index 000000000000..a1e4c6652191 --- /dev/null +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java @@ -0,0 +1,42 @@ +package com.iluwatar.eda; + +import com.iluwatar.eda.event.Event; +import com.iluwatar.eda.event.UserCreatedEvent; +import com.iluwatar.eda.event.UserUpdatedEvent; +import com.iluwatar.eda.framework.EventDispatcher; +import com.iluwatar.eda.handler.UserCreatedEventHandler; +import com.iluwatar.eda.handler.UserUpdatedEventHandler; +import com.iluwatar.eda.model.User; + +/** + * An event-driven architecture (EDA) is a framework that orchestrates behavior around the + * production, detection and consumption of events as well as the responses they evoke. An event is + * any identifiable occurrence that has significance for system hardware or software.

The + * example below uses an {@link EventDispatcher} to link/register {@link Event} objects to their + * respective handlers once an {@link Event} is dispatched, it's respective handler is invoked and + * the {@link Event} is handled accordingly. + * + */ +public class App { + + /** + * Once the {@link EventDispatcher} is initialised, handlers related to specific events have to be + * made known to the dispatcher by registering them. In this case the {@link UserCreatedEvent} is + * bound to the UserCreatedEventHandler, whilst the {@link UserUpdatedEvent} is bound to the + * {@link UserUpdatedEventHandler}. The dispatcher can now be called to dispatch specific events. + * When a user is saved, the {@link UserCreatedEvent} can be dispatched. + * On the other hand, when a user is updated, {@link UserUpdatedEvent} can be dispatched. + * + */ + public static void main(String[] args) { + + EventDispatcher dispatcher = new EventDispatcher(); + dispatcher.registerChannel(UserCreatedEvent.class, new UserCreatedEventHandler()); + dispatcher.registerChannel(UserUpdatedEvent.class, new UserUpdatedEventHandler()); + + User user = new User("iluwatar"); + dispatcher.onEvent(new UserCreatedEvent(user)); + dispatcher.onEvent(new UserUpdatedEvent(user)); + } + +} diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/Event.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/Event.java new file mode 100644 index 000000000000..bcf78f275554 --- /dev/null +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/Event.java @@ -0,0 +1,27 @@ +package com.iluwatar.eda.event; + +import com.iluwatar.eda.framework.EventDispatcher; +import com.iluwatar.eda.framework.Message; + +/** + * The {@link Event} class serves as a base class for defining custom events happening with your + * system. In this example we have two types of events defined. + *

    + *
  • {@link UserCreatedEvent} - used when a user is created
  • + *
  • {@link UserUpdatedEvent} - used when a user is updated
  • + *
+ * Events can be distinguished using the {@link #getType() getType} method. + */ +public class Event implements Message { + + /** + * Returns the event type as a {@link Class} object + * In this example, this method is used by the {@link EventDispatcher} to + * dispatch events depending on their type. + * + * @return the Event type as a {@link Class}. + */ + public Class getType() { + return getClass(); + } +} \ No newline at end of file diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java new file mode 100644 index 000000000000..f7beaf82c233 --- /dev/null +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java @@ -0,0 +1,21 @@ +package com.iluwatar.eda.event; + +import com.iluwatar.eda.model.User; + +/** + * The {@link UserCreatedEvent} should should be dispatched whenever a user has been created. + * This class can be extended to contain details about the user has been created. In this example, + * the entire {@link User} object is passed on as data with the event. + */ +public class UserCreatedEvent extends Event { + + private User user; + + public UserCreatedEvent(User user) { + this.user = user; + } + + public User getUser() { + return user; + } +} diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java new file mode 100644 index 000000000000..c07e83e7c1c8 --- /dev/null +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java @@ -0,0 +1,21 @@ +package com.iluwatar.eda.event; + +import com.iluwatar.eda.model.User; + +/** + * The {@link UserUpdatedEvent} should should be dispatched whenever a user has been updated. + * This class can be extended to contain details about the user has been updated. In this example, + * the entire {@link User} object is passed on as data with the event. + */ +public class UserUpdatedEvent extends Event { + + private User user; + + public UserUpdatedEvent(User user) { + this.user = user; + } + + public User getUser() { + return user; + } +} diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java new file mode 100644 index 000000000000..d5436acbf045 --- /dev/null +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java @@ -0,0 +1,41 @@ +package com.iluwatar.eda.framework; + +import com.iluwatar.eda.event.Event; + +import java.util.HashMap; +import java.util.Map; + +/** + * Handles the routing of {@link Event} messages to associated handlers. + * A {@link HashMap} is used to store the association between events and their respective handlers. + * + */ +public class EventDispatcher { + + private Map, Handler> handlers; + + public EventDispatcher() { + handlers = new HashMap<>(); + } + + /** + * Links an {@link Event} to a specific {@link Handler}. + * + * @param eventType The {@link Event} to be registered + * @param handler The {@link Handler} that will be handling the {@link Event} + */ + public void registerChannel(Class eventType, + Handler handler) { + handlers.put(eventType, handler); + } + + /** + * Dispatches an {@link Event} depending on it's type. + * + * @param event The {@link Event} to be dispatched + */ + public void onEvent(Event event) { + handlers.get(event.getClass()).onEvent(event); + } + +} \ No newline at end of file diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/Handler.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/Handler.java new file mode 100644 index 000000000000..cba2f08b2088 --- /dev/null +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/Handler.java @@ -0,0 +1,18 @@ +package com.iluwatar.eda.framework; + +import com.iluwatar.eda.event.Event; + +/** + * This interface can be implemented to handle different types of messages. + * Every handler is responsible for a single of type message + */ +public interface Handler { + + /** + * The onEvent method should implement and handle behavior related to the event. + * This can be as simple as calling another service to handle the event on publishing the event on + * a queue to be consumed by other sub systems. + * @param event the {@link Event} object to be handled. + */ + void onEvent(Event event); +} \ No newline at end of file diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/Message.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/Message.java new file mode 100644 index 000000000000..f8f8c7dfca3f --- /dev/null +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/Message.java @@ -0,0 +1,15 @@ +package com.iluwatar.eda.framework; + +/** + * A {@link Message} is an object with a specific type that is associated + * to a specific {@link Handler}. + */ +public interface Message { + + /** + * Returns the message type as a {@link Class} object. In this example the message type is + * used to handle events by their type. + * @return the message type as a {@link Class}. + */ + Class getType(); +} diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java new file mode 100644 index 000000000000..7db4a2d81c48 --- /dev/null +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java @@ -0,0 +1,18 @@ +package com.iluwatar.eda.handler; + +import com.iluwatar.eda.event.Event; +import com.iluwatar.eda.event.UserCreatedEvent; +import com.iluwatar.eda.framework.Handler; + +/** + * Handles the {@link UserCreatedEvent} message. + */ +public class UserCreatedEventHandler implements Handler { + + @Override + public void onEvent(Event message) { + + UserCreatedEvent userCreatedEvent = (UserCreatedEvent) message; + System.out.printf("User with %s has been Created!", userCreatedEvent.getUser().getUsername()); + } +} diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java new file mode 100644 index 000000000000..754a75c45a73 --- /dev/null +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java @@ -0,0 +1,18 @@ +package com.iluwatar.eda.handler; + +import com.iluwatar.eda.event.Event; +import com.iluwatar.eda.event.UserUpdatedEvent; +import com.iluwatar.eda.framework.Handler; + +/** + * Handles the {@link UserUpdatedEvent} message. + */ +public class UserUpdatedEventHandler implements Handler { + + @Override + public void onEvent(Event message) { + + UserUpdatedEvent userUpdatedEvent = (UserUpdatedEvent) message; + System.out.printf("User with %s has been Updated!", userUpdatedEvent.getUser().getUsername()); + } +} diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java new file mode 100644 index 000000000000..02a7a464177a --- /dev/null +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java @@ -0,0 +1,21 @@ +package com.iluwatar.eda.model; + +import com.iluwatar.eda.event.UserCreatedEvent; +import com.iluwatar.eda.event.UserUpdatedEvent; + +/** + * This {@link User} class is a basic pojo used to demonstrate user data sent along with + * the {@link UserCreatedEvent} and {@link UserUpdatedEvent} events. + */ +public class User { + + private String username; + + public User(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } +} diff --git a/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java b/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java new file mode 100644 index 000000000000..108280bf122a --- /dev/null +++ b/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.eda.event; + +import com.iluwatar.eda.model.User; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * {@link UserCreatedEventTest} tests and verifies {@link Event} behaviour. + */ +public class UserCreatedEventTest { + + /** + * This unit test should correctly return the {@link Event} class type when calling the + * {@link Event#getType() getType} method. + */ + @Test + public void testGetEventType() { + User user = new User("iluwatar"); + UserCreatedEvent userCreatedEvent = new UserCreatedEvent(user); + assertEquals(UserCreatedEvent.class, userCreatedEvent.getType()); + } +} diff --git a/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java b/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java new file mode 100644 index 000000000000..163ffed6e4d8 --- /dev/null +++ b/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java @@ -0,0 +1,50 @@ +package com.iluwatar.eda.framework; + +import com.iluwatar.eda.framework.EventDispatcher; +import com.iluwatar.eda.event.UserCreatedEvent; +import com.iluwatar.eda.event.UserUpdatedEvent; +import com.iluwatar.eda.handler.UserCreatedEventHandler; +import com.iluwatar.eda.handler.UserUpdatedEventHandler; +import com.iluwatar.eda.model.User; + +import org.junit.Test; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +/** + * Event Dispatcher unit tests to assert and verify correct event dispatcher behaviour + */ +public class EventDispatcherTest { + + /** + * This unit test should register events and event handlers correctly with the event dispatcher + * and events should be dispatched accordingly. + */ + @Test + public void testEventDriverPattern() { + + EventDispatcher dispatcher = spy(new EventDispatcher()); + UserCreatedEventHandler userCreatedEventHandler = spy(new UserCreatedEventHandler()); + UserUpdatedEventHandler userUpdatedEventHandler = spy(new UserUpdatedEventHandler()); + dispatcher.registerChannel(UserCreatedEvent.class, userCreatedEventHandler); + dispatcher.registerChannel(UserUpdatedEvent.class, userUpdatedEventHandler); + + User user = new User("iluwatar"); + + UserCreatedEvent userCreatedEvent = new UserCreatedEvent(user); + UserUpdatedEvent userUpdatedEvent = new UserUpdatedEvent(user); + + //fire a userCreatedEvent and verify that userCreatedEventHandler has been invoked. + dispatcher.onEvent(userCreatedEvent); + verify(userCreatedEventHandler).onEvent(userCreatedEvent); + verify(dispatcher).onEvent(userCreatedEvent); + + //fire a userCreatedEvent and verify that userUpdatedEventHandler has been invoked. + dispatcher.onEvent(userUpdatedEvent); + verify(userUpdatedEventHandler).onEvent(userUpdatedEvent); + verify(dispatcher).onEvent(userUpdatedEvent); + } + + +} diff --git a/exclude-pmd.properties b/exclude-pmd.properties new file mode 100644 index 000000000000..d97b3b827d3b --- /dev/null +++ b/exclude-pmd.properties @@ -0,0 +1,3 @@ +com.iluwatar.servicelayer.common.BaseEntity=UnusedPrivateField +com.iluwatar.doublechecked.locking.App=EmptyStatementNotInLoop,EmptyWhileStmt +com.iluwatar.doublechecked.locking.InventoryTest=EmptyStatementNotInLoop,EmptyWhileStmt diff --git a/execute-around/index.md b/execute-around/index.md index 56ece4ac42f4..ec543bdc7e95 100644 --- a/execute-around/index.md +++ b/execute-around/index.md @@ -4,16 +4,21 @@ title: Execute Around folder: execute-around permalink: /patterns/execute-around/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Beginner + - Idiom --- -**Intent:** Execute Around idiom frees the user from certain actions that +## Intent +Execute Around idiom frees the user from certain actions that should always be executed before and after the business method. A good example of this is resource allocation and deallocation leaving the user to specify only what to do with the resource. ![alt text](./etc/execute-around.png "Execute Around") -**Applicability:** Use the Execute Around idiom when +## Applicability +Use the Execute Around idiom when * you use an API that requires methods to be called in pairs such as open/close or allocate/deallocate. diff --git a/execute-around/pom.xml b/execute-around/pom.xml index dae6a4dadf7d..d644d6a0fd8d 100644 --- a/execute-around/pom.xml +++ b/execute-around/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT execute-around diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/App.java b/execute-around/src/main/java/com/iluwatar/execute/around/App.java index 4a0648dbeec0..4695b8df573d 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/App.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/App.java @@ -17,9 +17,6 @@ public class App { /** * Program entry point - * - * @param args command line args - * @throws IOException */ public static void main(String[] args) throws IOException { diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java b/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java index be89ff9ce1fa..e1a9073eff20 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java @@ -11,6 +11,9 @@ */ public class SimpleFileWriter { + /** + * Constructor + */ public SimpleFileWriter(String filename, FileWriterAction action) throws IOException { FileWriter writer = new FileWriter(filename); try { diff --git a/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java b/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java index 9eb3dbf5fdb9..80dff657b45a 100644 --- a/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java +++ b/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java @@ -1,13 +1,11 @@ package com.iluwatar.execute.around; -import java.io.File; -import java.io.IOException; - import org.junit.After; import org.junit.Before; import org.junit.Test; -import com.iluwatar.execute.around.App; +import java.io.File; +import java.io.IOException; /** * diff --git a/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java b/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java new file mode 100644 index 000000000000..1f4380fe478d --- /dev/null +++ b/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java @@ -0,0 +1,74 @@ +package com.iluwatar.execute.around; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/12/15 - 3:21 PM + * + * @author Jeroen Meulemeester + */ +public class SimpleFileWriterTest { + + /** + * Create a temporary folder, used to generate files in during this test + */ + @Rule + public final TemporaryFolder testFolder = new TemporaryFolder(); + + /** + * Verify if the given writer is not 'null' + */ + @Test + public void testWriterNotNull() throws Exception { + final File temporaryFile = this.testFolder.newFile(); + new SimpleFileWriter(temporaryFile.getPath(), Assert::assertNotNull); + } + + /** + * Test if the {@link SimpleFileWriter} creates a file if it doesn't exist + */ + @Test + public void testNonExistentFile() throws Exception { + final File nonExistingFile = new File(this.testFolder.getRoot(), "non-existing-file"); + assertFalse(nonExistingFile.exists()); + + new SimpleFileWriter(nonExistingFile.getPath(), Assert::assertNotNull); + assertTrue(nonExistingFile.exists()); + } + + /** + * Test if the data written to the file writer actually gets in the file + */ + @Test + public void testActualWrite() throws Exception { + final String testMessage = "Test message"; + + final File temporaryFile = this.testFolder.newFile(); + assertTrue(temporaryFile.exists()); + + new SimpleFileWriter(temporaryFile.getPath(), writer -> writer.write(testMessage)); + assertTrue(Files.lines(temporaryFile.toPath()).allMatch(testMessage::equals)); + } + + /** + * Verify if an {@link IOException} during the write ripples through + */ + @Test(expected = IOException.class) + public void testIoException() throws Exception { + final File temporaryFile = this.testFolder.newFile(); + new SimpleFileWriter(temporaryFile.getPath(), writer -> { + throw new IOException(""); + }); + } + +} diff --git a/facade/index.md b/facade/index.md index 59ff888b2fe8..c416552c7505 100644 --- a/facade/index.md +++ b/facade/index.md @@ -7,19 +7,22 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Beginner --- -**Intent:** Provide a unified interface to a set of interfaces in a subsystem. +## Intent +Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use. ![alt text](./etc/facade_1.png "Facade") -**Applicability:** Use the Facade pattern when +## Applicability +Use the Facade pattern when * you want to provide a simple interface to a complex subsystem. Subsystems often get more complex as they evolve. Most patterns, when applied, result in more and smaller classes. This makes the subsystem more reusable and easier to customize, but it also becomes harder to use for clients that don't need to customize it. A facade can provide a simple default view of the subsystem that is good enough for most clients. Only clients needing more customizability will need to look beyond the facade. * there are many dependencies between clients and the implementation classes of an abstraction. Introduce a facade to decouple the subsystem from clients and other subsystems, thereby promoting subsystem independence and portability. * you want to layer your subsystems. Use a facade to define an entry point to each subsystem level. If subsystems are dependent, the you can simplify the dependencies between them by making them communicate with each other solely through their facades -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/facade/pom.xml b/facade/pom.xml index 4447cc03b713..56f308ae4456 100644 --- a/facade/pom.xml +++ b/facade/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT facade @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java index d6b653eaac24..fd37e40c5b24 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java @@ -16,6 +16,9 @@ public class DwarvenGoldmineFacade { private final List workers; + /** + * Constructor + */ public DwarvenGoldmineFacade() { workers = new ArrayList<>(); workers.add(new DwarvenGoldDigger()); diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java b/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java index d329fe84b614..3190c9365764 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java @@ -46,6 +46,9 @@ private void action(Action action) { } } + /** + * Perform actions + */ public void action(Action... actions) { for (Action action : actions) { action(action); diff --git a/facade/src/test/java/com/iluwatar/facade/AppTest.java b/facade/src/test/java/com/iluwatar/facade/AppTest.java index 49b7c01c4998..3de38f26ea18 100644 --- a/facade/src/test/java/com/iluwatar/facade/AppTest.java +++ b/facade/src/test/java/com/iluwatar/facade/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.facade.App; - /** * * Application test diff --git a/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java b/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java new file mode 100644 index 000000000000..9a9bc5d661f7 --- /dev/null +++ b/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java @@ -0,0 +1,103 @@ +package com.iluwatar.facade; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.PrintStream; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +/** + * Date: 12/9/15 - 9:40 PM + * + * @author Jeroen Meulemeester + */ +public class DwarvenGoldmineFacadeTest { + + /** + * The mocked standard out {@link PrintStream}, required since the actions on the gold mine facade + * don't have any influence on any other accessible objects, except for writing to 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 a complete day cycle in the gold mine by executing all three different steps: {@link + * DwarvenGoldmineFacade#startNewDay()}, {@link DwarvenGoldmineFacade#digOutGold()} and {@link + * DwarvenGoldmineFacade#endDay()}. + * + * See if the workers are doing what's expected from them on each step. + */ + @Test + public void testFullWorkDay() { + final DwarvenGoldmineFacade goldMine = new DwarvenGoldmineFacade(); + goldMine.startNewDay(); + + // On the start of a day, all workers should wake up ... + verify(this.stdOutMock, times(1)).println(eq("Dwarf gold digger wakes up.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarf cart operator wakes up.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarven tunnel digger wakes up.")); + + // ... and go to the mine + verify(this.stdOutMock, times(1)).println(eq("Dwarf gold digger goes to the mine.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarf cart operator goes to the mine.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarven tunnel digger goes to the mine.")); + + // No other actions were invoked, so the workers shouldn't have done (printed) anything else + verifyNoMoreInteractions(this.stdOutMock); + + // Now do some actual work, start digging gold! + goldMine.digOutGold(); + + // Since we gave the dig command, every worker should be doing it's job ... + verify(this.stdOutMock, times(1)).println(eq("Dwarf gold digger digs for gold.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarf cart operator moves gold chunks out of the mine.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarven tunnel digger creates another promising tunnel.")); + + // Again, they shouldn't be doing anything else. + verifyNoMoreInteractions(this.stdOutMock); + + // Enough gold, lets end the day. + goldMine.endDay(); + + // Check if the workers go home ... + verify(this.stdOutMock, times(1)).println(eq("Dwarf gold digger goes home.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarf cart operator goes home.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarven tunnel digger goes home.")); + + // ... and go to sleep. We need well rested workers the next day :) + verify(this.stdOutMock, times(1)).println(eq("Dwarf gold digger goes to sleep.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarf cart operator goes to sleep.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarven tunnel digger goes to sleep.")); + + // Every worker should be sleeping now, no other actions allowed + verifyNoMoreInteractions(this.stdOutMock); + } + +} diff --git a/factory-method/index.md b/factory-method/index.md index 3568a1109dcd..42237883d6b6 100644 --- a/factory-method/index.md +++ b/factory-method/index.md @@ -10,20 +10,23 @@ tags: - Gang Of Four --- -**Also known as:** Virtual Constructor +## Also known as +Virtual Constructor -**Intent:** Define an interface for creating an object, but let subclasses +## Intent +Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. ![alt text](./etc/factory-method_1.png "Factory Method") -**Applicability:** Use the Factory Method pattern when +## Applicability +Use the Factory Method pattern when * a class can't anticipate the class of objects it must create * a class wants its subclasses to specify the objects it creates * classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/factory-method/pom.xml b/factory-method/pom.xml index 97c1c56811e9..f3dc2646a6d0 100644 --- a/factory-method/pom.xml +++ b/factory-method/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT factory-method diff --git a/faq.md b/faq.md index b98bc7589ecb..69f7b795ee92 100644 --- a/faq.md +++ b/faq.md @@ -3,7 +3,7 @@ layout: page title: FAQ permalink: /faq/ icon: fa-question -page-index: 2 +page-index: 1 --- ### Q1: What is the difference between State and Strategy patterns? {#Q1} @@ -64,4 +64,4 @@ Flyweight. ### Q7: What are the differences between FluentInterface and Builder patterns? {#Q7} -Fluent interfaces are sometimes confused with the Builder pattern, because they share method chaining and a fluent usage. However, fluent interfaces are not primarily used to create shared (mutable) objects, but to configure complex objects without having to respecify the target object on every property change. \ No newline at end of file +Fluent interfaces are sometimes confused with the Builder pattern, because they share method chaining and a fluent usage. However, fluent interfaces are not primarily used to create shared (mutable) objects, but to configure complex objects without having to respecify the target object on every property change. diff --git a/fluentinterface/index.md b/fluentinterface/index.md index 27a4d1a26b6d..767792da79ab 100644 --- a/fluentinterface/index.md +++ b/fluentinterface/index.md @@ -7,11 +7,13 @@ categories: Other tags: - Java - Difficulty-Intermediate + - Functional --- -**Intent:** A fluent interface provides an easy-readable, flowing interface, that often mimics a domain specific language. Using this pattern results in code that can be read nearly as human language. +## Intent +A fluent interface provides an easy-readable, flowing interface, that often mimics a domain specific language. Using this pattern results in code that can be read nearly as human language. -**Implementation:** +## Implementation A fluent interface can be implemented using any of @@ -21,13 +23,13 @@ A fluent interface can be implemented using any of ![Fluent Interface](./etc/fluentinterface.png "Fluent Interface") - -**Applicability:** Use the Fluent Interface pattern when +## Applicability +Use the Fluent Interface pattern when * you provide an API that would benefit from a DSL-like usage * you have objects that are difficult to configure or use -**Real world examples:** +## Real world examples * [Java 8 Stream API](http://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html) * [Google Guava FluentInterable](https://github.com/google/guava/wiki/FunctionalExplained) @@ -35,7 +37,7 @@ A fluent interface can be implemented using any of * [Mockito](http://mockito.org/) * [Java Hamcrest](http://code.google.com/p/hamcrest/wiki/Tutorial) -**Credits** +## Credits * [Fluent Interface - Martin Fowler](http://www.martinfowler.com/bliki/FluentInterface.html) * [Evolutionary architecture and emergent design: Fluent interfaces - Neal Ford](http://www.ibm.com/developerworks/library/j-eaed14/) diff --git a/fluentinterface/pom.xml b/fluentinterface/pom.xml index 48ec0816181c..1260bad3d2fc 100644 --- a/fluentinterface/pom.xml +++ b/fluentinterface/pom.xml @@ -5,7 +5,7 @@ java-design-patterns com.iluwatar - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT 4.0.0 @@ -16,5 +16,10 @@ junit test + + org.mockito + mockito-core + test + \ No newline at end of file diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java index bdff83e17850..4e5ab3767659 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java @@ -1,14 +1,18 @@ package com.iluwatar.fluentinterface.app; -import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; -import com.iluwatar.fluentinterface.fluentiterable.lazy.LazyFluentIterable; -import com.iluwatar.fluentinterface.fluentiterable.simple.SimpleFluentIterable; +import static java.lang.String.valueOf; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.StringJoiner; import java.util.function.Function; import java.util.function.Predicate; -import static java.lang.String.valueOf; +import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; +import com.iluwatar.fluentinterface.fluentiterable.lazy.LazyFluentIterable; +import com.iluwatar.fluentinterface.fluentiterable.simple.SimpleFluentIterable; /** * The Fluent Interface pattern is useful when you want to provide an easy readable, flowing API. @@ -24,6 +28,9 @@ */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { List integerList = new ArrayList<>(); @@ -58,7 +65,7 @@ public static void main(String[] args) { List lastTwoOfFirstFourStringMapped = LazyFluentIterable.from(integerList).filter(positives()).first(4).last(2) - .map(number -> "String[" + String.valueOf(number) + "]").asList(); + .map(number -> "String[" + valueOf(number) + "]").asList(); prettyPrint( "The lazy list contains the last two of the first four positive numbers mapped to Strings: ", lastTwoOfFirstFourStringMapped); @@ -78,19 +85,19 @@ private static Function transformToString() { } private static Predicate negatives() { - return integer -> (integer < 0); + return integer -> integer < 0; } private static Predicate positives() { - return integer -> (integer > 0); + return integer -> integer > 0; } private static void prettyPrint(String prefix, Iterable iterable) { - prettyPrint(", ", prefix, ".", iterable); + prettyPrint(", ", prefix, iterable); } - private static void prettyPrint(String delimiter, String prefix, String suffix, - Iterable iterable) { + private static void prettyPrint(String delimiter, String prefix, + Iterable iterable) { StringJoiner joiner = new StringJoiner(delimiter, prefix, "."); Iterator iterator = iterable.iterator(); while (iterator.hasNext()) { diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java index e80356d8e634..dae300c4e030 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java @@ -5,8 +5,6 @@ /** * This class is used to realize LazyFluentIterables. It decorates a given iterator. Does not * support consecutive hasNext() calls. - * - * @param */ public abstract class DecoratingIterator implements Iterator { @@ -16,8 +14,6 @@ public abstract class DecoratingIterator implements Iterator { /** * Creates an iterator that decorates the given iterator. - * - * @param fromIterator */ public DecoratingIterator(Iterator fromIterator) { this.fromIterator = fromIterator; diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java index 560b10189d97..e887ad556fc3 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java @@ -53,10 +53,9 @@ public Iterator iterator() { public TYPE computeNext() { while (fromIterator.hasNext()) { TYPE candidate = fromIterator.next(); - if (!predicate.test(candidate)) { - continue; + if (predicate.test(candidate)) { + return candidate; } - return candidate; } return null; @@ -94,12 +93,10 @@ public Iterator iterator() { @Override public TYPE computeNext() { - if (currentIndex < count) { - if (fromIterator.hasNext()) { - TYPE candidate = fromIterator.next(); - currentIndex++; - return candidate; - } + if (currentIndex < count && fromIterator.hasNext()) { + TYPE candidate = fromIterator.next(); + currentIndex++; + return candidate; } return null; } @@ -188,11 +185,12 @@ public Iterator iterator() { @Override public NEW_TYPE computeNext() { - while (oldTypeIterator.hasNext()) { + if (oldTypeIterator.hasNext()) { TYPE candidate = oldTypeIterator.next(); return function.apply(candidate); + } else { + return null; } - return null; } }; } @@ -215,7 +213,7 @@ public Iterator iterator() { return new DecoratingIterator(iterable.iterator()) { @Override public TYPE computeNext() { - return fromIterator.next(); + return fromIterator.hasNext() ? fromIterator.next() : null; } }; } diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java index 19283152e5b9..ef1859529f58 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java @@ -1,12 +1,16 @@ package com.iluwatar.fluentinterface.fluentiterable.simple; -import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; - -import java.util.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Spliterator; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; + /** * This is a simple implementation of the FluentIterable interface. It evaluates all chained * operations eagerly. This implementation would be costly to be utilized in real applications. diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java index 29ad885c00fb..2268b0428d6c 100644 --- a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java +++ b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.fluentinterface.app.App; - public class AppTest { @Test diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java new file mode 100644 index 000000000000..baabbe09672a --- /dev/null +++ b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java @@ -0,0 +1,170 @@ +package com.iluwatar.fluentinterface.fluentiterable; + +import org.junit.Test; + +import java.util.*; +import java.util.function.Consumer; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Date: 12/12/15 - 7:00 PM + * + * @author Jeroen Meulemeester + */ +public abstract class FluentIterableTest { + + /** + * Create a new {@link FluentIterable} from the given integers + * + * @param integers The integers + * @return The new iterable, use for testing + */ + protected abstract FluentIterable createFluentIterable(final Iterable integers); + + @Test + public void testFirst() throws Exception { + final List integers = Arrays.asList(1, 2, 3, 10, 9, 8); + final Optional first = createFluentIterable(integers).first(); + assertNotNull(first); + assertTrue(first.isPresent()); + assertEquals(integers.get(0), first.get()); + } + + @Test + public void testFirstEmptyCollection() throws Exception { + final List integers = Collections.emptyList(); + final Optional first = createFluentIterable(integers).first(); + assertNotNull(first); + assertFalse(first.isPresent()); + } + + @Test + public void testFirstCount() throws Exception { + final List integers = Arrays.asList(1, 2, 3, 10, 9, 8); + final List first4 = createFluentIterable(integers) + .first(4) + .asList(); + + assertNotNull(first4); + assertEquals(4, first4.size()); + + assertEquals(integers.get(0), first4.get(0)); + assertEquals(integers.get(1), first4.get(1)); + assertEquals(integers.get(2), first4.get(2)); + assertEquals(integers.get(3), first4.get(3)); + } + + @Test + public void testFirstCountLessItems() throws Exception { + final List integers = Arrays.asList(1, 2, 3); + final List first4 = createFluentIterable(integers) + .first(4) + .asList(); + + assertNotNull(first4); + assertEquals(3, first4.size()); + + assertEquals(integers.get(0), first4.get(0)); + assertEquals(integers.get(1), first4.get(1)); + assertEquals(integers.get(2), first4.get(2)); + } + + @Test + public void testLast() throws Exception { + final List integers = Arrays.asList(1, 2, 3, 10, 9, 8); + final Optional last = createFluentIterable(integers).last(); + assertNotNull(last); + assertTrue(last.isPresent()); + assertEquals(integers.get(integers.size() - 1), last.get()); + } + + @Test + public void testLastEmptyCollection() throws Exception { + final List integers = Collections.emptyList(); + final Optional last = createFluentIterable(integers).last(); + assertNotNull(last); + assertFalse(last.isPresent()); + } + + @Test + public void testLastCount() throws Exception { + final List integers = Arrays.asList(1, 2, 3, 10, 9, 8); + final List last4 = createFluentIterable(integers) + .last(4) + .asList(); + + assertNotNull(last4); + assertEquals(4, last4.size()); + assertEquals(Integer.valueOf(3), last4.get(0)); + assertEquals(Integer.valueOf(10), last4.get(1)); + assertEquals(Integer.valueOf(9), last4.get(2)); + assertEquals(Integer.valueOf(8), last4.get(3)); + } + + @Test + public void testLastCountLessItems() throws Exception { + final List integers = Arrays.asList(1, 2, 3); + final List last4 = createFluentIterable(integers) + .last(4) + .asList(); + + assertNotNull(last4); + assertEquals(3, last4.size()); + + assertEquals(Integer.valueOf(1), last4.get(0)); + assertEquals(Integer.valueOf(2), last4.get(1)); + assertEquals(Integer.valueOf(3), last4.get(2)); + } + + @Test + public void testFilter() throws Exception { + final List integers = Arrays.asList(1, 2, 3, 10, 9, 8); + final List evenItems = createFluentIterable(integers) + .filter(i -> i % 2 == 0) + .asList(); + + assertNotNull(evenItems); + assertEquals(3, evenItems.size()); + assertEquals(Integer.valueOf(2), evenItems.get(0)); + assertEquals(Integer.valueOf(10), evenItems.get(1)); + assertEquals(Integer.valueOf(8), evenItems.get(2)); + } + + @Test + public void testMap() throws Exception { + final List integers = Arrays.asList(1, 2, 3); + final List longs = createFluentIterable(integers) + .map(Integer::longValue) + .asList(); + + assertNotNull(longs); + assertEquals(integers.size(), longs.size()); + assertEquals(Long.valueOf(1), longs.get(0)); + assertEquals(Long.valueOf(2), longs.get(1)); + assertEquals(Long.valueOf(3), longs.get(2)); + } + + @Test + public void testForEach() throws Exception { + final List integers = Arrays.asList(1, 2, 3); + + final Consumer consumer = mock(Consumer.class); + createFluentIterable(integers).forEach(consumer); + + verify(consumer, times(1)).accept(Integer.valueOf(1)); + verify(consumer, times(1)).accept(Integer.valueOf(2)); + verify(consumer, times(1)).accept(Integer.valueOf(3)); + verifyNoMoreInteractions(consumer); + + } + + @Test + public void testSpliterator() throws Exception { + final List integers = Arrays.asList(1, 2, 3); + final Spliterator split = createFluentIterable(integers).spliterator(); + assertNotNull(split); + } + +} \ No newline at end of file diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java new file mode 100644 index 000000000000..aa51327e3929 --- /dev/null +++ b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java @@ -0,0 +1,18 @@ +package com.iluwatar.fluentinterface.fluentiterable.lazy; + +import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; +import com.iluwatar.fluentinterface.fluentiterable.FluentIterableTest; + +/** + * Date: 12/12/15 - 7:56 PM + * + * @author Jeroen Meulemeester + */ +public class LazyFluentIterableTest extends FluentIterableTest { + + @Override + protected FluentIterable createFluentIterable(Iterable integers) { + return LazyFluentIterable.from(integers); + } + +} diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java new file mode 100644 index 000000000000..360d6e22211c --- /dev/null +++ b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java @@ -0,0 +1,18 @@ +package com.iluwatar.fluentinterface.fluentiterable.simple; + +import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; +import com.iluwatar.fluentinterface.fluentiterable.FluentIterableTest; + +/** + * Date: 12/12/15 - 7:56 PM + * + * @author Jeroen Meulemeester + */ +public class SimpleFluentIterableTest extends FluentIterableTest { + + @Override + protected FluentIterable createFluentIterable(Iterable integers) { + return SimpleFluentIterable.fromCopyOf(integers); + } + +} diff --git a/flux/index.md b/flux/index.md index 227237168698..7ac312c443d2 100644 --- a/flux/index.md +++ b/flux/index.md @@ -4,20 +4,24 @@ title: Flux folder: flux permalink: /patterns/flux/ categories: Presentation Tier -tags: Java +tags: + - Java + - Difficulty-Intermediate --- -**Intent:** Flux eschews MVC in favor of a unidirectional data flow. When a +## Intent +Flux eschews MVC in favor of a unidirectional data flow. When a user interacts with a view, the view propagates an action through a central dispatcher, to the various stores that hold the application's data and business logic, which updates all of the views that are affected. ![alt text](./etc/flux.png "Flux") -**Applicability:** Use the Flux pattern when +## Applicability +Use the Flux pattern when * you want to focus on creating explicit and understandable update paths for your application's data, which makes tracing changes during development simpler and makes bugs easier to track down and fix. -**Credits:** +## Credits * [Flux - Application architecture for building user interfaces](http://facebook.github.io/flux/) diff --git a/flux/pom.xml b/flux/pom.xml index 28a634cebcdb..aff38380062a 100644 --- a/flux/pom.xml +++ b/flux/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT flux @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java b/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java index 26c836b0e048..1ff624e11fcf 100644 --- a/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java +++ b/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java @@ -31,6 +31,9 @@ public void registerStore(Store store) { stores.add(store); } + /** + * Menu item selected handler + */ public void menuItemSelected(MenuItem menuItem) { dispatchAction(new MenuAction(menuItem)); switch (menuItem) { diff --git a/flux/src/main/java/com/iluwatar/flux/view/View.java b/flux/src/main/java/com/iluwatar/flux/view/View.java index a642b5b2c07a..892527fdc0ff 100644 --- a/flux/src/main/java/com/iluwatar/flux/view/View.java +++ b/flux/src/main/java/com/iluwatar/flux/view/View.java @@ -9,7 +9,7 @@ */ public interface View { - public void storeChanged(Store store); + void storeChanged(Store store); - public void render(); + void render(); } diff --git a/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java b/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java new file mode 100644 index 000000000000..7781c1d90adc --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.flux.action; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/12/15 - 10:11 PM + * + * @author Jeroen Meulemeester + */ +public class ContentTest { + + @Test + public void testToString() throws Exception { + for (final Content content : Content.values()) { + final String toString = content.toString(); + assertNotNull(toString); + assertFalse(toString.trim().isEmpty()); + } + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java b/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java new file mode 100644 index 000000000000..02fa781e6770 --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.flux.action; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/12/15 - 10:15 PM + * + * @author Jeroen Meulemeester + */ +public class MenuItemTest { + + @Test + public void testToString() throws Exception { + for (final MenuItem menuItem : MenuItem.values()) { + final String toString = menuItem.toString(); + assertNotNull(toString); + assertFalse(toString.trim().isEmpty()); + } + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/app/AppTest.java b/flux/src/test/java/com/iluwatar/flux/app/AppTest.java index 918c76acd24a..d833c321cd65 100644 --- a/flux/src/test/java/com/iluwatar/flux/app/AppTest.java +++ b/flux/src/test/java/com/iluwatar/flux/app/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.flux.app.App; - /** * * Application test diff --git a/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java b/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java new file mode 100644 index 000000000000..7c66e7a81e7a --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java @@ -0,0 +1,91 @@ +package com.iluwatar.flux.dispatcher; + +import com.iluwatar.flux.action.Action; +import com.iluwatar.flux.action.ActionType; +import com.iluwatar.flux.action.Content; +import com.iluwatar.flux.action.ContentAction; +import com.iluwatar.flux.action.MenuAction; +import com.iluwatar.flux.action.MenuItem; +import com.iluwatar.flux.store.Store; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 8:22 PM + * + * @author Jeroen Meulemeester + */ +public class DispatcherTest { + + /** + * Dispatcher is a singleton with no way to reset it's internal state back to the beginning. + * Replace the instance with a fresh one before each test to make sure test cases have no + * influence on each other. + */ + @Before + public void setUp() throws Exception { + final Constructor constructor; + constructor = Dispatcher.class.getDeclaredConstructor(); + constructor.setAccessible(true); + + final Field field = Dispatcher.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(Dispatcher.getInstance(), constructor.newInstance()); + } + + @Test + public void testGetInstance() throws Exception { + assertNotNull(Dispatcher.getInstance()); + assertSame(Dispatcher.getInstance(), Dispatcher.getInstance()); + } + + @Test + public void testMenuItemSelected() throws Exception { + final Dispatcher dispatcher = Dispatcher.getInstance(); + + final Store store = mock(Store.class); + dispatcher.registerStore(store); + dispatcher.menuItemSelected(MenuItem.HOME); + dispatcher.menuItemSelected(MenuItem.COMPANY); + + // We expect 4 events, 2 menu selections and 2 content change actions + final ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(Action.class); + verify(store, times(4)).onAction(actionCaptor.capture()); + verifyNoMoreInteractions(store); + + final List actions = actionCaptor.getAllValues(); + final List menuActions = actions.stream() + .filter(a -> a.getType().equals(ActionType.MENU_ITEM_SELECTED)) + .map(a -> (MenuAction) a) + .collect(Collectors.toList()); + + final List contentActions = actions.stream() + .filter(a -> a.getType().equals(ActionType.CONTENT_CHANGED)) + .map(a -> (ContentAction) a) + .collect(Collectors.toList()); + + assertEquals(2, menuActions.size()); + assertEquals(1, menuActions.stream().map(MenuAction::getMenuItem).filter(MenuItem.HOME::equals).count()); + assertEquals(1, menuActions.stream().map(MenuAction::getMenuItem).filter(MenuItem.COMPANY::equals).count()); + + assertEquals(2, contentActions.size()); + assertEquals(1, contentActions.stream().map(ContentAction::getContent).filter(Content.PRODUCTS::equals).count()); + assertEquals(1, contentActions.stream().map(ContentAction::getContent).filter(Content.COMPANY::equals).count()); + + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java b/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java new file mode 100644 index 000000000000..00a7a924dc64 --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java @@ -0,0 +1,46 @@ +package com.iluwatar.flux.store; + +import com.iluwatar.flux.action.Content; +import com.iluwatar.flux.action.ContentAction; +import com.iluwatar.flux.action.MenuAction; +import com.iluwatar.flux.action.MenuItem; +import com.iluwatar.flux.view.View; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 10:18 PM + * + * @author Jeroen Meulemeester + */ +public class ContentStoreTest { + + @Test + public void testOnAction() throws Exception { + final ContentStore contentStore = new ContentStore(); + + final View view = mock(View.class); + contentStore.registerView(view); + + verifyZeroInteractions(view); + + // Content should not react on menu action ... + contentStore.onAction(new MenuAction(MenuItem.PRODUCTS)); + verifyZeroInteractions(view); + + // ... but it should react on a content action + contentStore.onAction(new ContentAction(Content.COMPANY)); + verify(view, times(1)).storeChanged(eq(contentStore)); + verifyNoMoreInteractions(view); + assertEquals(Content.COMPANY, contentStore.getContent()); + + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java b/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java new file mode 100644 index 000000000000..6fdc4e5d36ab --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java @@ -0,0 +1,46 @@ +package com.iluwatar.flux.store; + +import com.iluwatar.flux.action.Content; +import com.iluwatar.flux.action.ContentAction; +import com.iluwatar.flux.action.MenuAction; +import com.iluwatar.flux.action.MenuItem; +import com.iluwatar.flux.view.View; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 10:18 PM + * + * @author Jeroen Meulemeester + */ +public class MenuStoreTest { + + @Test + public void testOnAction() throws Exception { + final MenuStore menuStore = new MenuStore(); + + final View view = mock(View.class); + menuStore.registerView(view); + + verifyZeroInteractions(view); + + // Menu should not react on content action ... + menuStore.onAction(new ContentAction(Content.COMPANY)); + verifyZeroInteractions(view); + + // ... but it should react on a menu action + menuStore.onAction(new MenuAction(MenuItem.PRODUCTS)); + verify(view, times(1)).storeChanged(eq(menuStore)); + verifyNoMoreInteractions(view); + assertEquals(MenuItem.PRODUCTS, menuStore.getSelected()); + + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java b/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java new file mode 100644 index 000000000000..cf452233b5d9 --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java @@ -0,0 +1,32 @@ +package com.iluwatar.flux.view; + +import com.iluwatar.flux.action.Content; +import com.iluwatar.flux.store.ContentStore; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 10:31 PM + * + * @author Jeroen Meulemeester + */ +public class ContentViewTest { + + @Test + public void testStoreChanged() throws Exception { + final ContentStore store = mock(ContentStore.class); + when(store.getContent()).thenReturn(Content.PRODUCTS); + + final ContentView view = new ContentView(); + view.storeChanged(store); + + verify(store, times(1)).getContent(); + verifyNoMoreInteractions(store); + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java b/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java new file mode 100644 index 000000000000..08a601c7108e --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java @@ -0,0 +1,49 @@ +package com.iluwatar.flux.view; + +import com.iluwatar.flux.action.Action; +import com.iluwatar.flux.action.MenuItem; +import com.iluwatar.flux.dispatcher.Dispatcher; +import com.iluwatar.flux.store.MenuStore; +import com.iluwatar.flux.store.Store; +import org.junit.Test; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Date: 12/12/15 - 10:31 PM + * + * @author Jeroen Meulemeester + */ +public class MenuViewTest { + + @Test + public void testStoreChanged() throws Exception { + final MenuStore store = mock(MenuStore.class); + when(store.getSelected()).thenReturn(MenuItem.HOME); + + final MenuView view = new MenuView(); + view.storeChanged(store); + + verify(store, times(1)).getSelected(); + verifyNoMoreInteractions(store); + } + + @Test + public void testItemClicked() throws Exception { + final Store store = mock(Store.class); + Dispatcher.getInstance().registerStore(store); + + final MenuView view = new MenuView(); + view.itemClicked(MenuItem.PRODUCTS); + + // We should receive a menu click action and a content changed action + verify(store, times(2)).onAction(any(Action.class)); + + } + +} diff --git a/flyweight/index.md b/flyweight/index.md index e2273c197b97..a98dced8e4e3 100644 --- a/flyweight/index.md +++ b/flyweight/index.md @@ -7,14 +7,18 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Intermediate + - Performance --- -**Intent:** Use sharing to support large numbers of fine-grained objects +## Intent +Use sharing to support large numbers of fine-grained objects efficiently. ![alt text](./etc/flyweight_1.png "Flyweight") -**Applicability:** The Flyweight pattern's effectiveness depends heavily on how +## Applicability +The Flyweight pattern's effectiveness depends heavily on how and where it's used. Apply the Flyweight pattern when all of the following are true @@ -24,10 +28,10 @@ true * many groups of objects may be replaced by relatively few shared objects once extrinsic state is removed * the application doesn't depend on object identity. Since flyweight objects may be shared, identity tests will return true for conceptually distinct objects. -**Real world examples:** +## Real world examples * [java.lang.Integer#valueOf(int)](http://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#valueOf%28int%29) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/flyweight/pom.xml b/flyweight/pom.xml index 3aa89d15c78b..fe282ea2ebf3 100644 --- a/flyweight/pom.xml +++ b/flyweight/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT flyweight diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java b/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java index 15206a84a803..8418e01e6595 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java @@ -1,6 +1,7 @@ package com.iluwatar.flyweight; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -13,6 +14,9 @@ public class AlchemistShop { private List topShelf; private List bottomShelf; + /** + * Constructor + */ public AlchemistShop() { topShelf = new ArrayList<>(); bottomShelf = new ArrayList<>(); @@ -39,6 +43,27 @@ private void fillShelves() { bottomShelf.add(factory.createPotion(PotionType.HOLY_WATER)); } + /** + * Get a read-only list of all the items on the top shelf + * + * @return The top shelf potions + */ + public final List getTopShelf() { + return Collections.unmodifiableList(this.topShelf); + } + + /** + * Get a read-only list of all the items on the bottom shelf + * + * @return The bottom shelf potions + */ + public final List getBottomShelf() { + return Collections.unmodifiableList(this.bottomShelf); + } + + /** + * Enumerate potions + */ public void enumerate() { System.out.println("Enumerating top shelf potions\n"); diff --git a/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java b/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java new file mode 100644 index 000000000000..d99a98cf9c1d --- /dev/null +++ b/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java @@ -0,0 +1,40 @@ +package com.iluwatar.flyweight; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/12/15 - 10:54 PM + * + * @author Jeroen Meulemeester + */ +public class AlchemistShopTest { + + @Test + public void testShop() throws Exception { + final AlchemistShop shop = new AlchemistShop(); + + final List bottomShelf = shop.getBottomShelf(); + assertNotNull(bottomShelf); + assertEquals(5, bottomShelf.size()); + + final List topShelf = shop.getTopShelf(); + assertNotNull(topShelf); + assertEquals(8, topShelf.size()); + + final List allPotions = new ArrayList<>(); + allPotions.addAll(topShelf); + allPotions.addAll(bottomShelf); + + // There are 13 potion instances, but only 5 unique instance types + assertEquals(13, allPotions.size()); + assertEquals(5, allPotions.stream().map(System::identityHashCode).distinct().count()); + + } + +} diff --git a/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java b/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java index 16fdb005e31b..5e0bd98b8e9f 100644 --- a/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java +++ b/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.flyweight.App; - /** * * Application test diff --git a/front-controller/index.md b/front-controller/index.md index ba593a157fd4..a462a08e0ab6 100644 --- a/front-controller/index.md +++ b/front-controller/index.md @@ -4,26 +4,30 @@ title: Front Controller folder: front-controller permalink: /patterns/front-controller/ categories: Presentation Tier -tags: Java +tags: + - Java + - Difficulty-Intermediate --- -**Intent:** Introduce a common handler for all requests for a web site. This +## Intent +Introduce a common handler for all requests for a web site. This way we can encapsulate common functionality such as security, internationalization, routing and logging in a single place. ![alt text](./etc/front-controller.png "Front Controller") -**Applicability:** Use the Front Controller pattern when +## Applicability +Use the Front Controller pattern when * you want to encapsulate common request handling functionality in single place * you want to implements dynamic request handling i.e. change routing without modifying code * make web server configuration portable, you only need to register the handler web server specific way -**Real world examples:** +## Real world examples * [Apache Struts](https://struts.apache.org/) -**Credits:** +## Credits * [J2EE Design Patterns](http://www.amazon.com/J2EE-Design-Patterns-William-Crawford/dp/0596004273/ref=sr_1_2) * [Presentation Tier Patterns](http://www.javagyan.com/tutorials/corej2eepatterns/presentation-tier-patterns) diff --git a/front-controller/pom.xml b/front-controller/pom.xml index 5b0ef21553c6..3f56aaa8ca8d 100644 --- a/front-controller/pom.xml +++ b/front-controller/pom.xml @@ -6,7 +6,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT front-controller @@ -15,5 +15,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/App.java b/front-controller/src/main/java/com/iluwatar/front/controller/App.java index 18a92d37d00b..1beac119caf8 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/App.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/App.java @@ -2,32 +2,34 @@ /** * - * The Front Controller is a presentation tier pattern. Essentially it defines a - * controller that handles all requests for a web site. + * The Front Controller is a presentation tier pattern. Essentially it defines a controller that + * handles all requests for a web site. *

- * The Front Controller pattern consolidates request handling through a single handler - * object ({@link FrontController}). This object can carry out the common the behavior such as + * The Front Controller pattern consolidates request handling through a single handler object ( + * {@link FrontController}). This object can carry out the common the behavior such as * authorization, request logging and routing requests to corresponding views. *

- * Typically the requests are mapped to command objects ({@link Command}) which then display - * the correct view ({@link View}). + * Typically the requests are mapped to command objects ({@link Command}) which then display the + * correct view ({@link View}). *

* In this example we have implemented two views: {@link ArcherView} and {@link CatapultView}. These - * are displayed by sending correct request to the {@link FrontController} object. For example, - * the {@link ArcherView} gets displayed when {@link FrontController} receives request "Archer". When + * are displayed by sending correct request to the {@link FrontController} object. For example, the + * {@link ArcherView} gets displayed when {@link FrontController} receives request "Archer". When * the request is unknown, we display the error view ({@link ErrorView}). * */ public class App { - - /** - * Program entry point - * @param args command line args - */ - public static void main(String[] args) { - FrontController controller = new FrontController(); - controller.handleRequest("Archer"); - controller.handleRequest("Catapult"); - controller.handleRequest("foobar"); - } + + /** + * Program entry point + * + * @param args + * command line args + */ + public static void main(String[] args) { + FrontController controller = new FrontController(); + controller.handleRequest("Archer"); + controller.handleRequest("Catapult"); + controller.handleRequest("foobar"); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java b/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java index b3963d8e9807..bb44d34f094e 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java @@ -7,9 +7,9 @@ */ public class ApplicationException extends RuntimeException { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public ApplicationException(Throwable cause) { - super(cause); - } + public ApplicationException(Throwable cause) { + super(cause); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java index 117aa0c8c264..8396d5cfcc45 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java @@ -7,8 +7,8 @@ */ public class ArcherCommand implements Command { - @Override - public void process() { - new ArcherView().display(); - } + @Override + public void process() { + new ArcherView().display(); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java index d8cae33c1936..d16fe8b71e4c 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java @@ -7,8 +7,8 @@ */ public class ArcherView implements View { - @Override - public void display() { - System.out.println("Displaying archers"); - } + @Override + public void display() { + System.out.println("Displaying archers"); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java index fae5d1753830..b5ad9e37c2bf 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java @@ -7,8 +7,8 @@ */ public class CatapultCommand implements Command { - @Override - public void process() { - new CatapultView().display(); - } + @Override + public void process() { + new CatapultView().display(); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java index 9ad94d522485..161b4ed4ebdb 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java @@ -7,8 +7,8 @@ */ public class CatapultView implements View { - @Override - public void display() { - System.out.println("Displaying catapults"); - } + @Override + public void display() { + System.out.println("Displaying catapults"); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/Command.java b/front-controller/src/main/java/com/iluwatar/front/controller/Command.java index 95bd00129343..2ad41a6297f5 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/Command.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/Command.java @@ -6,6 +6,6 @@ * */ public interface Command { - - void process(); + + void process(); } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java b/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java index 04c31dd34d9e..c1045c82155d 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java @@ -7,8 +7,8 @@ */ public class ErrorView implements View { - @Override - public void display() { - System.out.println("Error 500"); - } + @Override + public void display() { + System.out.println("Error 500"); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java b/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java index 5a6dc2a78903..6b84d7f7814a 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java @@ -2,33 +2,33 @@ /** * - * FrontController is the handler class that takes in all the requests and - * renders the correct response. + * FrontController is the handler class that takes in all the requests and renders the correct + * response. * */ public class FrontController { - - public void handleRequest(String request) { - Command command = getCommand(request); - command.process(); - } - - private Command getCommand(String request) { - Class commandClass = getCommandClass(request); - try { - return (Command) commandClass.newInstance(); - } catch (Exception e) { - throw new ApplicationException(e); - } - } - - private Class getCommandClass(String request) { - Class result; - try { - result = Class.forName("com.iluwatar." + request + "Command"); - } catch (ClassNotFoundException e) { - result = UnknownCommand.class; - } - return result; - } + + public void handleRequest(String request) { + Command command = getCommand(request); + command.process(); + } + + private Command getCommand(String request) { + Class commandClass = getCommandClass(request); + try { + return (Command) commandClass.newInstance(); + } catch (Exception e) { + throw new ApplicationException(e); + } + } + + private Class getCommandClass(String request) { + Class result; + try { + result = Class.forName("com.iluwatar.front.controller." + request + "Command"); + } catch (ClassNotFoundException e) { + result = UnknownCommand.class; + } + return result; + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java index f8f93e7e0c0c..d800d4db0d7d 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java @@ -7,8 +7,8 @@ */ public class UnknownCommand implements Command { - @Override - public void process() { - new ErrorView().display(); - } + @Override + public void process() { + new ErrorView().display(); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/View.java b/front-controller/src/main/java/com/iluwatar/front/controller/View.java index 29c5f0fcba2b..55bb187ce70c 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/View.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/View.java @@ -7,5 +7,5 @@ */ public interface View { - void display(); + void display(); } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java index 2c28aa8ce454..cc09de662c7f 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java @@ -2,18 +2,16 @@ import org.junit.Test; -import com.iluwatar.front.controller.App; - /** * * Application test * */ public class AppTest { - - @Test - public void test() { - String[] args = {}; - App.main(args); - } + + @Test + public void test() { + String[] args = {}; + App.main(args); + } } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java new file mode 100644 index 000000000000..4b038cfda029 --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java @@ -0,0 +1,20 @@ +package com.iluwatar.front.controller; + +import static org.junit.Assert.assertSame; + +import org.junit.Test; + +/** + * Date: 12/13/15 - 1:35 PM + * + * @author Jeroen Meulemeester + */ +public class ApplicationExceptionTest { + + @Test + public void testCause() throws Exception { + final Exception cause = new Exception(); + assertSame(cause, new ApplicationException(cause).getCause()); + } + +} \ No newline at end of file diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java new file mode 100644 index 000000000000..fa85caa397dc --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java @@ -0,0 +1,62 @@ +package com.iluwatar.front.controller; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/13/15 - 1:39 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class CommandTest extends StdOutTest { + + @Parameters + public static List data() { + final List parameters = new ArrayList<>(); + parameters.add(new Object[]{"Archer", "Displaying archers"}); + parameters.add(new Object[]{"Catapult", "Displaying catapults"}); + parameters.add(new Object[]{"NonExistentCommand", "Error 500"}); + return parameters; + } + + /** + * The view that's been tested + */ + private final String request; + + /** + * The expected display message + */ + private final String displayMessage; + + /** + * Create a new instance of the {@link CommandTest} with the given view and expected message + * + * @param request The request that's been tested + * @param displayMessage The expected display message + */ + public CommandTest(final String request, final String displayMessage) { + this.displayMessage = displayMessage; + this.request = request; + } + + @Test + public void testDisplay() { + final FrontController frontController = new FrontController(); + verifyZeroInteractions(getStdOutMock()); + frontController.handleRequest(request); + verify(getStdOutMock()).println(displayMessage); + verifyNoMoreInteractions(getStdOutMock()); + } + +} diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java new file mode 100644 index 000000000000..9bc4253c088d --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java @@ -0,0 +1,61 @@ +package com.iluwatar.front.controller; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/13/15 - 1:39 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class FrontControllerTest extends StdOutTest { + + @Parameters + public static List data() { + final List parameters = new ArrayList<>(); + parameters.add(new Object[]{new ArcherCommand(), "Displaying archers"}); + parameters.add(new Object[]{new CatapultCommand(), "Displaying catapults"}); + parameters.add(new Object[]{new UnknownCommand(), "Error 500"}); + return parameters; + } + + /** + * The view that's been tested + */ + private final Command command; + + /** + * The expected display message + */ + private final String displayMessage; + + /** + * Create a new instance of the {@link FrontControllerTest} with the given view and expected message + * + * @param command The command that's been tested + * @param displayMessage The expected display message + */ + public FrontControllerTest(final Command command, final String displayMessage) { + this.displayMessage = displayMessage; + this.command = command; + } + + @Test + public void testDisplay() { + verifyZeroInteractions(getStdOutMock()); + this.command.process(); + verify(getStdOutMock()).println(displayMessage); + verifyNoMoreInteractions(getStdOutMock()); + } + +} diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/StdOutTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/StdOutTest.java new file mode 100644 index 000000000000..31d061b08166 --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/StdOutTest.java @@ -0,0 +1,54 @@ +package com.iluwatar.front.controller; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since the actions of the views don't have + * any influence on any other accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java new file mode 100644 index 000000000000..fb2df1c60a43 --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java @@ -0,0 +1,61 @@ +package com.iluwatar.front.controller; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/13/15 - 1:39 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class ViewTest extends StdOutTest { + + @Parameters + public static List data() { + final List parameters = new ArrayList<>(); + parameters.add(new Object[]{new ArcherView(), "Displaying archers"}); + parameters.add(new Object[]{new CatapultView(), "Displaying catapults"}); + parameters.add(new Object[]{new ErrorView(), "Error 500"}); + return parameters; + } + + /** + * The view that's been tested + */ + private final View view; + + /** + * The expected display message + */ + private final String displayMessage; + + /** + * Create a new instance of the {@link ViewTest} with the given view and expected message + * + * @param view The view that's been tested + * @param displayMessage The expected display message + */ + public ViewTest(final View view, final String displayMessage) { + this.displayMessage = displayMessage; + this.view = view; + } + + @Test + public void testDisplay() { + verifyZeroInteractions(getStdOutMock()); + this.view.display(); + verify(getStdOutMock()).println(displayMessage); + verifyNoMoreInteractions(getStdOutMock()); + } + +} \ No newline at end of file diff --git a/half-sync-half-async/index.md b/half-sync-half-async/index.md index dc1930e3b0b9..8a091f8133a3 100644 --- a/half-sync-half-async/index.md +++ b/half-sync-half-async/index.md @@ -4,16 +4,20 @@ title: Half-Sync/Half-Async folder: half-sync-half-async permalink: /patterns/half-sync-half-async/ categories: Concurrency -tags: Java +tags: + - Java + - Difficulty-Intermediate --- -**Intent:** The Half-Sync/Half-Async pattern decouples synchronous I/O from +## Intent +The Half-Sync/Half-Async pattern decouples synchronous I/O from asynchronous I/O in a system to simplify concurrent programming effort without degrading execution efficiency. ![Half-Sync/Half-Async class diagram](./etc/half-sync-half-async.png) -**Applicability:** Use Half-Sync/Half-Async pattern when +## Applicability +Use Half-Sync/Half-Async pattern when * a system possesses following characteristics: * the system must perform tasks in response to external events that occur asynchronously, like hardware interrupts in OS @@ -21,13 +25,13 @@ degrading execution efficiency. * the higher level tasks in the system can be simplified significantly if I/O is performed synchronously. * one or more tasks in a system must run in a single thread of control, while other tasks may benefit from multi-threading. -**Real world examples:** +## Real world examples * [BSD Unix networking subsystem](http://www.cs.wustl.edu/~schmidt/PDF/PLoP-95.pdf) * [Real Time CORBA](http://www.omg.org/news/meetings/workshops/presentations/realtime2001/4-3_Pyarali_thread-pool.pdf) * [Android AsyncTask framework](http://developer.android.com/reference/android/os/AsyncTask.html) -**Credits:** +## Credits * [Douglas C. Schmidt and Charles D. Cranor - Half Sync/Half Async](http://www.cs.wustl.edu/~schmidt/PDF/PLoP-95.pdf) * [Pattern Oriented Software Architecture Vol I-V](http://www.amazon.com/Pattern-Oriented-Software-Architecture-Volume-Patterns/dp/0471958697) diff --git a/half-sync-half-async/pom.xml b/half-sync-half-async/pom.xml index e7a27c32175a..7ea1a203b0b5 100644 --- a/half-sync-half-async/pom.xml +++ b/half-sync-half-async/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT half-sync-half-async @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java index 80f2eefb2d44..b67b602ca132 100644 --- a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java @@ -21,17 +21,15 @@ * *

* APPLICABILITY
- *

    - *
  • UNIX network subsystems - In operating systems network operations are carried out - * asynchronously with help of hardware level interrupts.
  • - *
  • CORBA - At the asynchronous layer one thread is associated with each socket that is connected + * UNIX network subsystems - In operating systems network operations are carried out + * asynchronously with help of hardware level interrupts.
    + * CORBA - At the asynchronous layer one thread is associated with each socket that is connected * to the client. Thread blocks waiting for CORBA requests from the client. On receiving request it * is inserted in the queuing layer which is then picked up by synchronous layer which processes the - * request and sends response back to the client.
  • - *
  • Android AsyncTask framework - Framework provides a way to execute long running blocking + * request and sends response back to the client.
    + * Android AsyncTask framework - Framework provides a way to execute long running blocking * calls, such as downloading a file, in background threads so that the UI thread remains free to - * respond to user inputs. - *
+ * respond to user inputs.
* *

* IMPLEMENTATION
@@ -121,6 +119,7 @@ private static long ap(long i) { try { Thread.sleep(i); } catch (InterruptedException e) { + System.out.println(e); } return (i) * (i + 1) / 2; } diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java index 457dffa208e6..3be340c02982 100644 --- a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java @@ -50,6 +50,7 @@ public void execute(final AsyncTask task) { task.onPreCall(); } catch (Exception e) { task.onError(e); + return; } service.submit(new FutureTask(task) { diff --git a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java new file mode 100644 index 000000000000..16b51d0b515b --- /dev/null +++ b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java @@ -0,0 +1,72 @@ +package com.iluwatar.halfsynchalfasync; + +import org.junit.Test; +import org.mockito.InOrder; + +import java.io.IOException; +import java.util.concurrent.LinkedBlockingQueue; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +/** + * Date: 12/12/15 - 11:15 PM + * + * @author Jeroen Meulemeester + */ +public class AsynchronousServiceTest { + + @Test + public void testPerfectExecution() throws Exception { + final AsynchronousService service = new AsynchronousService(new LinkedBlockingQueue<>()); + final AsyncTask task = mock(AsyncTask.class); + final Object result = new Object(); + when(task.call()).thenReturn(result); + service.execute(task); + + verify(task, timeout(2000)).onPostCall(eq(result)); + + final InOrder inOrder = inOrder(task); + inOrder.verify(task, times(1)).onPreCall(); + inOrder.verify(task, times(1)).call(); + inOrder.verify(task, times(1)).onPostCall(eq(result)); + + verifyNoMoreInteractions(task); + } + + @Test + public void testCallException() throws Exception { + final AsynchronousService service = new AsynchronousService(new LinkedBlockingQueue<>()); + final AsyncTask task = mock(AsyncTask.class); + final IOException exception = new IOException(); + when(task.call()).thenThrow(exception); + service.execute(task); + + verify(task, timeout(2000)).onError(eq(exception)); + + final InOrder inOrder = inOrder(task); + inOrder.verify(task, times(1)).onPreCall(); + inOrder.verify(task, times(1)).call(); + inOrder.verify(task, times(1)).onError(exception); + + verifyNoMoreInteractions(task); + } + + @Test + public void testPreCallException() throws Exception { + final AsynchronousService service = new AsynchronousService(new LinkedBlockingQueue<>()); + final AsyncTask task = mock(AsyncTask.class); + final IllegalStateException exception = new IllegalStateException(); + doThrow(exception).when(task).onPreCall(); + service.execute(task); + + verify(task, timeout(2000)).onError(eq(exception)); + + final InOrder inOrder = inOrder(task); + inOrder.verify(task, times(1)).onPreCall(); + inOrder.verify(task, times(1)).onError(exception); + + verifyNoMoreInteractions(task); + } + +} \ No newline at end of file diff --git a/intercepting-filter/index.md b/intercepting-filter/index.md index 41825745b730..327f091b1e93 100644 --- a/intercepting-filter/index.md +++ b/intercepting-filter/index.md @@ -4,25 +4,29 @@ title: Intercepting Filter folder: intercepting-filter permalink: /patterns/intercepting-filter/ categories: Behavioral -tags: Java +tags: + - Java + - Difficulty-Intermediate --- -**Intent:** Provide pluggable filters to conduct necessary pre-processing and +## Intent +Provide pluggable filters to conduct necessary pre-processing and post-processing to requests from a client to a target ![alt text](./etc/intercepting-filter.png "Intercepting Filter") -**Applicability:** Use the Intercepting Filter pattern when +## Applicability +Use the Intercepting Filter pattern when * a system uses pre-processing or post-processing requests * a system should do the authentication/ authorization/ logging or tracking of request and then pass the requests to corresponding handlers * you want a modular approach to configuring pre-processing and post-processing schemes -**Real world examples:** +## Real world examples * [Struts 2 - Interceptors](https://struts.apache.org/docs/interceptors.html) -**Credits:** +## Credits * [TutorialsPoint - Intercepting Filter](http://www.tutorialspoint.com/design_pattern/intercepting_filter_pattern.htm) * [Presentation Tier Patterns](http://www.javagyan.com/tutorials/corej2eepatterns/presentation-tier-patterns) diff --git a/intercepting-filter/pom.xml b/intercepting-filter/pom.xml index 1f1d89e96c53..d18126ba97ca 100644 --- a/intercepting-filter/pom.xml +++ b/intercepting-filter/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT intercepting-filter @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java index c1aa0780b324..38a762483120 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java @@ -14,7 +14,8 @@ public String execute(Order order) { String result = super.execute(order); if (order.getAddress() == null || order.getAddress().isEmpty()) { return result + "Invalid address! "; - } else + } else { return result; + } } } diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java index 817ae7587a6f..f0a2267d287d 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java @@ -34,7 +34,7 @@ public class App { * @param args command line args */ public static void main(String[] args) { - FilterManager filterManager = new FilterManager(new Target()); + FilterManager filterManager = new FilterManager(); filterManager.addFilter(new NameFilter()); filterManager.addFilter(new ContactFilter()); filterManager.addFilter(new AddressFilter()); diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java index 02499ed0ab2b..5934da75cc2a 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java @@ -32,8 +32,12 @@ public class Client extends JFrame { private JLabel jl; private JTextField[] jtFields; private JTextArea[] jtAreas; - private JButton clearButton, processButton; + private JButton clearButton; + private JButton processButton; + /** + * Constructor + */ public Client() { super("Client System"); setDefaultCloseOperation(EXIT_ON_CLOSE); diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java index 9496bde36ba2..a1ea5b4eec48 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java @@ -11,30 +11,21 @@ public interface Filter { /** * Execute order processing filter. - * - * @param order - * @return empty string on success, otherwise error message. */ String execute(Order order); /** * Set next filter in chain after this. - * - * @param filter */ void setNext(Filter filter); /** * Get next filter in chain after this. - * - * @return */ Filter getNext(); /** * Get last filter in the chain. - * - * @return */ Filter getLast(); } diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java index 987678cc77dd..e8f3ca70f1cf 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java @@ -10,12 +10,15 @@ public class FilterChain { private Filter chain; - private final Target target; - - public FilterChain(Target target) { - this.target = target; + /** + * Constructor + */ + public FilterChain() { } + /** + * Adds filter + */ public void addFilter(Filter filter) { if (chain == null) { chain = filter; @@ -24,6 +27,9 @@ public void addFilter(Filter filter) { } } + /** + * Execute filter chain + */ public String execute(Order order) { if (chain != null) { return chain.execute(order); diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java index 7cdaab103bf3..d6e01598e4cb 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java @@ -10,8 +10,8 @@ public class FilterManager { private FilterChain filterChain; - public FilterManager(Target target) { - filterChain = new FilterChain(target); + public FilterManager() { + filterChain = new FilterChain(); } public void addFilter(Filter filter) { diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java index a458e475b714..2f431caad774 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java @@ -14,7 +14,7 @@ public String execute(Order order) { String result = super.execute(order); if (order.getName() == null || order.getName().isEmpty() || order.getName().matches(".*[^\\w|\\s]+.*")) { - return result + "Invalid order! "; + return result + "Invalid name! "; } else { return result; } diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java index 5b30fee3586c..53d1a3dd9f2a 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java @@ -14,6 +14,9 @@ public class Order { public Order() {} + /** + * Constructor + */ public Order(String name, String contactNumber, String address, String depositNumber, String order) { this.name = name; this.contactNumber = contactNumber; diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java index cb96cd6e01d4..ffb13c160374 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java @@ -29,6 +29,9 @@ public class Target extends JFrame { private DefaultTableModel dtm; private JButton del; + /** + * Constructor + */ public Target() { super("Order System"); setDefaultCloseOperation(EXIT_ON_CLOSE); @@ -67,8 +70,9 @@ class DListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { int temp = jt.getSelectedRow(); - if (temp == -1) + if (temp == -1) { return; + } int temp2 = jt.getSelectedRowCount(); for (int i = 0; i < temp2; i++) { dtm.removeRow(temp); diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java index bcdf7c09b32a..9abdcc18179e 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.intercepting.filter.App; - /** * * Application test. diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java new file mode 100644 index 000000000000..022bd75866b5 --- /dev/null +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java @@ -0,0 +1,46 @@ +package com.iluwatar.intercepting.filter; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +/** + * Date: 12/13/15 - 3:01 PM + * + * @author Jeroen Meulemeester + */ +public class FilterManagerTest { + + @Test + public void testFilterRequest() throws Exception { + final Target target = mock(Target.class); + final FilterManager filterManager = new FilterManager(); + assertEquals("RUNNING...", filterManager.filterRequest(mock(Order.class))); + verifyZeroInteractions(target); + } + + @Test + public void testAddFilter() throws Exception { + final Target target = mock(Target.class); + final FilterManager filterManager = new FilterManager(); + + verifyZeroInteractions(target); + + final Filter filter = mock(Filter.class); + when(filter.execute(any(Order.class))).thenReturn("filter"); + + filterManager.addFilter(filter); + + final Order order = mock(Order.class); + assertEquals("filter", filterManager.filterRequest(order)); + + verify(filter, times(1)).execute(any(Order.class)); + verifyZeroInteractions(target, filter, order); + } +} \ No newline at end of file diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java new file mode 100644 index 000000000000..71d9bf250c02 --- /dev/null +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java @@ -0,0 +1,98 @@ +package com.iluwatar.intercepting.filter; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.TestCase.assertSame; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * Date: 12/13/15 - 2:17 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class FilterTest { + + private static final Order PERFECT_ORDER = new Order("name", "12345678901", "addr", "dep", "order"); + private static final Order WRONG_ORDER = new Order("name", "12345678901", "addr", "dep", ""); + private static final Order WRONG_DEPOSIT = new Order("name", "12345678901", "addr", "", "order"); + private static final Order WRONG_ADDRESS = new Order("name", "12345678901", "", "dep", "order"); + private static final Order WRONG_CONTACT = new Order("name", "", "addr", "dep", "order"); + private static final Order WRONG_NAME = new Order("", "12345678901", "addr", "dep", "order"); + + @Parameters + public static List getTestData() { + final List testData = new ArrayList<>(); + testData.add(new Object[]{new NameFilter(), PERFECT_ORDER, ""}); + testData.add(new Object[]{new NameFilter(), WRONG_NAME, "Invalid name!"}); + testData.add(new Object[]{new NameFilter(), WRONG_CONTACT, ""}); + testData.add(new Object[]{new NameFilter(), WRONG_ADDRESS, ""}); + testData.add(new Object[]{new NameFilter(), WRONG_DEPOSIT, ""}); + testData.add(new Object[]{new NameFilter(), WRONG_ORDER, ""}); + + testData.add(new Object[]{new ContactFilter(), PERFECT_ORDER, ""}); + testData.add(new Object[]{new ContactFilter(), WRONG_NAME, ""}); + testData.add(new Object[]{new ContactFilter(), WRONG_CONTACT, "Invalid contact number!"}); + testData.add(new Object[]{new ContactFilter(), WRONG_ADDRESS, ""}); + testData.add(new Object[]{new ContactFilter(), WRONG_DEPOSIT, ""}); + testData.add(new Object[]{new ContactFilter(), WRONG_ORDER, ""}); + + testData.add(new Object[]{new AddressFilter(), PERFECT_ORDER, ""}); + testData.add(new Object[]{new AddressFilter(), WRONG_NAME, ""}); + testData.add(new Object[]{new AddressFilter(), WRONG_CONTACT, ""}); + testData.add(new Object[]{new AddressFilter(), WRONG_ADDRESS, "Invalid address!"}); + testData.add(new Object[]{new AddressFilter(), WRONG_DEPOSIT, ""}); + testData.add(new Object[]{new AddressFilter(), WRONG_ORDER, ""}); + + testData.add(new Object[]{new DepositFilter(), PERFECT_ORDER, ""}); + testData.add(new Object[]{new DepositFilter(), WRONG_NAME, ""}); + testData.add(new Object[]{new DepositFilter(), WRONG_CONTACT, ""}); + testData.add(new Object[]{new DepositFilter(), WRONG_ADDRESS, ""}); + testData.add(new Object[]{new DepositFilter(), WRONG_DEPOSIT, "Invalid deposit number!"}); + testData.add(new Object[]{new DepositFilter(), WRONG_ORDER, ""}); + + testData.add(new Object[]{new OrderFilter(), PERFECT_ORDER, ""}); + testData.add(new Object[]{new OrderFilter(), WRONG_NAME, ""}); + testData.add(new Object[]{new OrderFilter(), WRONG_CONTACT, ""}); + testData.add(new Object[]{new OrderFilter(), WRONG_ADDRESS, ""}); + testData.add(new Object[]{new OrderFilter(), WRONG_DEPOSIT, ""}); + testData.add(new Object[]{new OrderFilter(), WRONG_ORDER, "Invalid order!"}); + + return testData; + } + + private final Filter filter; + private final Order order; + private final String result; + + /** + * Constructor + */ + public FilterTest(Filter filter, Order order, String result) { + this.filter = filter; + this.order = order; + this.result = result; + } + + @Test + public void testExecute() throws Exception { + final String result = this.filter.execute(this.order); + assertNotNull(result); + assertEquals(this.result, result.trim()); + } + + @Test + public void testNext() throws Exception { + assertNull(this.filter.getNext()); + assertSame(this.filter, this.filter.getLast()); + } + +} diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java new file mode 100644 index 000000000000..70862ed0fdaf --- /dev/null +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java @@ -0,0 +1,51 @@ +package com.iluwatar.intercepting.filter; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/13/15 - 2:57 PM + * + * @author Jeroen Meulemeester + */ +public class OrderTest { + + private static final String EXPECTED_VALUE = "test"; + + @Test + public void testSetName() throws Exception { + final Order order = new Order(); + order.setName(EXPECTED_VALUE); + assertEquals(EXPECTED_VALUE, order.getName()); + } + + @Test + public void testSetContactNumber() throws Exception { + final Order order = new Order(); + order.setContactNumber(EXPECTED_VALUE); + assertEquals(EXPECTED_VALUE, order.getContactNumber()); + } + + @Test + public void testSetAddress() throws Exception { + final Order order = new Order(); + order.setAddress(EXPECTED_VALUE); + assertEquals(EXPECTED_VALUE, order.getAddress()); + } + + @Test + public void testSetDepositNumber() throws Exception { + final Order order = new Order(); + order.setDepositNumber(EXPECTED_VALUE); + assertEquals(EXPECTED_VALUE, order.getDepositNumber()); + } + + @Test + public void testSetOrder() throws Exception { + final Order order = new Order(); + order.setOrder(EXPECTED_VALUE); + assertEquals(EXPECTED_VALUE, order.getOrder()); + } + +} diff --git a/interpreter/index.md b/interpreter/index.md index 57b117e06f6e..87c1c47f7892 100644 --- a/interpreter/index.md +++ b/interpreter/index.md @@ -7,21 +7,24 @@ categories: Behavioral tags: - Java - Gang Of Four + - Difficulty-Intermediate --- -**Intent:** Given a language, define a representation for its grammar along +## Intent +Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language. ![alt text](./etc/interpreter_1.png "Interpreter") -**Applicability:** Use the Interpreter pattern when there is a language to +## Applicability +Use the Interpreter pattern when there is a language to interpret, and you can represent statements in the language as abstract syntax trees. The Interpreter pattern works best when * the grammar is simple. For complex grammars, the class hierarchy for the grammar becomes large and unmanageable. Tools such as parser generators are a better alternative in such cases. They can interpret expressions without building abstract syntax trees, which can save space and possibly time * efficiency is not a critical concern. The most efficient interpreters are usually not implemented by interpreting parse trees directly but by first translating them into another form. For example, regular expressions are often transformed into state machines. But even then, the translator can be implemented by the Interpreter pattern, so the pattern is still applicable -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/interpreter/pom.xml b/interpreter/pom.xml index 863a00199796..72fd0d51b5d2 100644 --- a/interpreter/pom.xml +++ b/interpreter/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT interpreter diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/App.java b/interpreter/src/main/java/com/iluwatar/interpreter/App.java index 2f88951f13e5..e4e238c15510 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/App.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/App.java @@ -55,6 +55,9 @@ public static boolean isOperator(String s) { return s.equals("+") || s.equals("-") || s.equals("*"); } + /** + * Get expression for string + */ public static Expression getOperatorInstance(String s, Expression left, Expression right) { switch (s) { case "+": @@ -63,7 +66,8 @@ public static Expression getOperatorInstance(String s, Expression left, Expressi return new MinusExpression(left, right); case "*": return new MultiplyExpression(left, right); + default: + return new MultiplyExpression(left, right); } - return null; } } diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java index cb7e957c9304..be696f0725b2 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.interpreter.App; - /** * * Application test diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java new file mode 100644 index 000000000000..150596cd89de --- /dev/null +++ b/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java @@ -0,0 +1,111 @@ +package com.iluwatar.interpreter; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/14/15 - 11:48 AM + * + * @author Jeroen Meulemeester + */ +public abstract class ExpressionTest { + + /** + * Generate inputs ranging from -10 to 10 for both input params and calculate the expected result + * + * @param resultCalc The function used to calculate the expected result + * @return A data set with test entries + */ + static List prepareParameters(final BiFunction resultCalc) { + final List testData = new ArrayList<>(); + for (int i = -10; i < 10; i++) { + for (int j = -10; j < 10; j++) { + testData.add(new Object[]{ + new NumberExpression(i), + new NumberExpression(j), + resultCalc.apply(i, j) + }); + } + } + return testData; + } + + /** + * The input used as first parameter during the test + */ + private final NumberExpression first; + + /** + * The input used as second parameter during the test + */ + private final NumberExpression second; + + /** + * The expected result of the calculation, taking the first and second parameter in account + */ + private final int result; + + /** + * The expected {@link E#toString()} response + */ + private final String expectedToString; + + /** + * Factory, used to create a new test object instance with the correct first and second parameter + */ + private final BiFunction factory; + + /** + * Create a new test instance with the given parameters and expected results + * + * @param first The input used as first parameter during the test + * @param second The input used as second parameter during the test + * @param result The expected result of the tested expression + * @param expectedToString The expected {@link E#toString()} response + * @param factory Factory, used to create a new test object instance + */ + ExpressionTest(final NumberExpression first, final NumberExpression second, final int result, + final String expectedToString, final BiFunction factory) { + + this.first = first; + this.second = second; + this.result = result; + this.expectedToString = expectedToString; + this.factory = factory; + } + + /** + * Get the first parameter + * + * @return The first parameter + */ + final NumberExpression getFirst() { + return this.first; + } + + /** + * Verify if the expression calculates the correct result when calling {@link E#interpret()} + */ + @Test + public void testInterpret() { + final E expression = this.factory.apply(this.first, this.second); + assertNotNull(expression); + assertEquals(this.result, expression.interpret()); + } + + /** + * Verify if the expression has the expected {@link E#toString()} value + */ + @Test + public void testToString() { + final E expression = this.factory.apply(this.first, this.second); + assertNotNull(expression); + assertEquals(expectedToString, expression.toString()); + } +} diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java new file mode 100644 index 000000000000..3b6d322fe427 --- /dev/null +++ b/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.interpreter; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.List; + +/** + * Date: 12/14/15 - 12:08 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class MinusExpressionTest extends ExpressionTest { + + /** + * Create a new set of test entries with the expected result + * + * @return The list of parameters used during this test + */ + @Parameters + public static List data() { + return prepareParameters((f, s) -> f - s); + } + + /** + * Create a new test instance using the given test parameters and expected result + * + * @param first The first expression parameter + * @param second The second expression parameter + * @param result The expected result + */ + public MinusExpressionTest(final NumberExpression first, final NumberExpression second, final int result) { + super(first, second, result, "-", MinusExpression::new); + } + +} \ No newline at end of file diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java new file mode 100644 index 000000000000..91ecdb00845e --- /dev/null +++ b/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.interpreter; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.List; + +/** + * Date: 12/14/15 - 12:08 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class MultiplyExpressionTest extends ExpressionTest { + + /** + * Create a new set of test entries with the expected result + * + * @return The list of parameters used during this test + */ + @Parameters + public static List data() { + return prepareParameters((f, s) -> f * s); + } + + /** + * Create a new test instance using the given test parameters and expected result + * + * @param first The first expression parameter + * @param second The second expression parameter + * @param result The expected result + */ + public MultiplyExpressionTest(final NumberExpression first, final NumberExpression second, final int result) { + super(first, second, result, "*", MultiplyExpression::new); + } + +} \ No newline at end of file diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java new file mode 100644 index 000000000000..2c4a35be7336 --- /dev/null +++ b/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java @@ -0,0 +1,52 @@ +package com.iluwatar.interpreter; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/14/15 - 12:08 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class NumberExpressionTest extends ExpressionTest { + + /** + * Create a new set of test entries with the expected result + * + * @return The list of parameters used during this test + */ + @Parameters + public static List data() { + return prepareParameters((f, s) -> f); + } + + /** + * Create a new test instance using the given test parameters and expected result + * + * @param first The first expression parameter + * @param second The second expression parameter + * @param result The expected result + */ + public NumberExpressionTest(final NumberExpression first, final NumberExpression second, final int result) { + super(first, second, result, "number", (f, s) -> f); + } + + /** + * Verify if the {@link NumberExpression#NumberExpression(String)} constructor works as expected + */ + @Test + public void testFromString() throws Exception { + final int expectedValue = getFirst().interpret(); + final String testStingValue = String.valueOf(expectedValue); + final NumberExpression numberExpression = new NumberExpression(testStingValue); + assertEquals(expectedValue, numberExpression.interpret()); + } + +} \ No newline at end of file diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java new file mode 100644 index 000000000000..06521363157e --- /dev/null +++ b/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.interpreter; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.List; + +/** + * Date: 12/14/15 - 12:08 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class PlusExpressionTest extends ExpressionTest { + + /** + * Create a new set of test entries with the expected result + * + * @return The list of parameters used during this test + */ + @Parameters + public static List data() { + return prepareParameters((f, s) -> f + s); + } + + /** + * Create a new test instance using the given test parameters and expected result + * + * @param first The first expression parameter + * @param second The second expression parameter + * @param result The expected result + */ + public PlusExpressionTest(final NumberExpression first, final NumberExpression second, final int result) { + super(first, second, result, "+", PlusExpression::new); + } + +} \ No newline at end of file diff --git a/iterator/index.md b/iterator/index.md index fe6c1fe359f8..d6be7758dda1 100644 --- a/iterator/index.md +++ b/iterator/index.md @@ -10,23 +10,26 @@ tags: - Gang Of Four --- -**Also known as:** Cursor +## Also known as +Cursor -**Intent:** Provide a way to access the elements of an aggregate object +## Intent +Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. ![alt text](./etc/iterator_1.png "Iterator") -**Applicability:** Use the Iterator pattern +## Applicability +Use the Iterator pattern * to access an aggregate object's contents without exposing its internal representation * to support multiple traversals of aggregate objects * to provide a uniform interface for traversing different aggregate structures -**Real world examples:** +## Real world examples * [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/iterator/pom.xml b/iterator/pom.xml index 258c1e3ee4a2..9f57dedb680e 100644 --- a/iterator/pom.xml +++ b/iterator/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT iterator diff --git a/iterator/src/main/java/com/iluwatar/iterator/App.java b/iterator/src/main/java/com/iluwatar/iterator/App.java index c9c5fa521320..467040ca679a 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/App.java +++ b/iterator/src/main/java/com/iluwatar/iterator/App.java @@ -20,28 +20,28 @@ public class App { public static void main(String[] args) { TreasureChest chest = new TreasureChest(); - ItemIterator ringIterator = chest.Iterator(ItemType.RING); + ItemIterator ringIterator = chest.iterator(ItemType.RING); while (ringIterator.hasNext()) { System.out.println(ringIterator.next()); } System.out.println("----------"); - ItemIterator potionIterator = chest.Iterator(ItemType.POTION); + ItemIterator potionIterator = chest.iterator(ItemType.POTION); while (potionIterator.hasNext()) { System.out.println(potionIterator.next()); } System.out.println("----------"); - ItemIterator weaponIterator = chest.Iterator(ItemType.WEAPON); + ItemIterator weaponIterator = chest.iterator(ItemType.WEAPON); while (weaponIterator.hasNext()) { System.out.println(weaponIterator.next()); } System.out.println("----------"); - ItemIterator it = chest.Iterator(ItemType.ANY); + ItemIterator it = chest.iterator(ItemType.ANY); while (it.hasNext()) { System.out.println(it.next()); } diff --git a/iterator/src/main/java/com/iluwatar/iterator/TreasureChest.java b/iterator/src/main/java/com/iluwatar/iterator/TreasureChest.java index 02496e33c4be..6b5c54a5a6d6 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/TreasureChest.java +++ b/iterator/src/main/java/com/iluwatar/iterator/TreasureChest.java @@ -12,6 +12,9 @@ public class TreasureChest { private List items; + /** + * Constructor + */ public TreasureChest() { items = new ArrayList<>(); items.add(new Item(ItemType.POTION, "Potion of courage")); @@ -26,10 +29,13 @@ public TreasureChest() { items.add(new Item(ItemType.WEAPON, "Dagger of poison")); } - ItemIterator Iterator(ItemType type) { - return new TreasureChestItemIterator(this, type); + ItemIterator iterator(ItemType itemType) { + return new TreasureChestItemIterator(this, itemType); } + /** + * Get all items + */ public List getItems() { ArrayList list = new ArrayList<>(); list.addAll(items); diff --git a/iterator/src/main/java/com/iluwatar/iterator/TreasureChestItemIterator.java b/iterator/src/main/java/com/iluwatar/iterator/TreasureChestItemIterator.java index 39c12ab44ac0..a8303f308f20 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/TreasureChestItemIterator.java +++ b/iterator/src/main/java/com/iluwatar/iterator/TreasureChestItemIterator.java @@ -13,6 +13,9 @@ public class TreasureChestItemIterator implements ItemIterator { private int idx; private ItemType type; + /** + * Constructor + */ public TreasureChestItemIterator(TreasureChest chest, ItemType type) { this.chest = chest; this.type = type; diff --git a/iterator/src/test/java/com/iluwatar/iterator/AppTest.java b/iterator/src/test/java/com/iluwatar/iterator/AppTest.java index b6198f5c5147..5ec59ec74f43 100644 --- a/iterator/src/test/java/com/iluwatar/iterator/AppTest.java +++ b/iterator/src/test/java/com/iluwatar/iterator/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.iterator.App; - /** * * Application test diff --git a/iterator/src/test/java/com/iluwatar/iterator/TreasureChestTest.java b/iterator/src/test/java/com/iluwatar/iterator/TreasureChestTest.java new file mode 100644 index 000000000000..a2102a2e2b3a --- /dev/null +++ b/iterator/src/test/java/com/iluwatar/iterator/TreasureChestTest.java @@ -0,0 +1,108 @@ +package com.iluwatar.iterator; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +/** + * Date: 12/14/15 - 2:58 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class TreasureChestTest { + + /** + * Create a list of all expected items in the chest. + * + * @return The set of all expected items in the chest + */ + @Parameterized.Parameters + public static List data() { + final List parameters = new ArrayList<>(); + parameters.add(new Object[]{new Item(ItemType.POTION, "Potion of courage")}); + parameters.add(new Object[]{new Item(ItemType.RING, "Ring of shadows")}); + parameters.add(new Object[]{new Item(ItemType.POTION, "Potion of wisdom")}); + parameters.add(new Object[]{new Item(ItemType.POTION, "Potion of blood")}); + parameters.add(new Object[]{new Item(ItemType.WEAPON, "Sword of silver +1")}); + parameters.add(new Object[]{new Item(ItemType.POTION, "Potion of rust")}); + parameters.add(new Object[]{new Item(ItemType.POTION, "Potion of healing")}); + parameters.add(new Object[]{new Item(ItemType.RING, "Ring of armor")}); + parameters.add(new Object[]{new Item(ItemType.WEAPON, "Steel halberd")}); + parameters.add(new Object[]{new Item(ItemType.WEAPON, "Dagger of poison")}); + return parameters; + } + + /** + * One of the expected items in the chest + */ + private final Item expectedItem; + + /** + * Create a new test instance, test if the given expected item can be retrieved from the chest + * + * @param expectedItem One of the items that should be in the chest + */ + public TreasureChestTest(final Item expectedItem) { + this.expectedItem = expectedItem; + } + + /** + * Test if the expected item can be retrieved from the chest using the {@link ItemIterator} + */ + @Test + public void testIterator() { + final TreasureChest chest = new TreasureChest(); + final ItemIterator iterator = chest.iterator(expectedItem.getType()); + assertNotNull(iterator); + + while (iterator.hasNext()) { + final Item item = iterator.next(); + assertNotNull(item); + assertEquals(this.expectedItem.getType(), item.getType()); + + final String name = item.toString(); + assertNotNull(name); + if (this.expectedItem.toString().equals(name)) { + return; + } + } + + fail("Expected to find item [" + this.expectedItem + "] using iterator, but we didn't."); + + } + + /** + * Test if the expected item can be retrieved from the chest using the {@link + * TreasureChest#getItems()} method + */ + @Test + public void testGetItems() throws Exception { + final TreasureChest chest = new TreasureChest(); + final List items = chest.getItems(); + assertNotNull(items); + + for (final Item item : items) { + assertNotNull(item); + assertNotNull(item.getType()); + assertNotNull(item.toString()); + + final boolean sameType = this.expectedItem.getType() == item.getType(); + final boolean sameName = this.expectedItem.toString().equals(item.toString()); + if (sameType && sameName) { + return; + } + } + + fail("Expected to find item [" + this.expectedItem + "] in the item list, but we didn't."); + + } + +} \ No newline at end of file diff --git a/layers/index.md b/layers/index.md index 37089a19c986..8e8eda36666e 100644 --- a/layers/index.md +++ b/layers/index.md @@ -4,21 +4,25 @@ title: Layers folder: layers permalink: /patterns/layers/ categories: Architectural -tags: Java +tags: + - Java + - Difficulty-Intermediate + - Spring --- -**Intent:** Layers is an architectural style where software responsibilities are +## Intent +Layers is an architectural style where software responsibilities are divided among the different layers of the application. ![alt text](./etc/layers.png "Layers") -**Applicability:** Use the Layers architecture when +## Applicability +Use the Layers architecture when * you want clearly divide software responsibilities into differents parts of the program * you want to prevent a change from propagating throughout the application * you want to make your application more maintainable and testable -**Credits:** +## Credits * [Pattern Oriented Software Architecture Vol I-V](http://www.amazon.com/Pattern-Oriented-Software-Architecture-Volume-Patterns/dp/0471958697) - diff --git a/layers/pom.xml b/layers/pom.xml index e338a557f675..685f8b65c97b 100644 --- a/layers/pom.xml +++ b/layers/pom.xml @@ -6,7 +6,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT com.iluwatar.layers layers @@ -32,5 +32,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/layers/src/main/java/com/iluwatar/layers/App.java b/layers/src/main/java/com/iluwatar/layers/App.java index d175553f7beb..ecb532510b59 100644 --- a/layers/src/main/java/com/iluwatar/layers/App.java +++ b/layers/src/main/java/com/iluwatar/layers/App.java @@ -4,31 +4,31 @@ /** * - * Layers is an architectural style where software responsibilities are divided among the different - * layers of the application. + * Layers is an architectural style where software responsibilities are divided among the different layers of the + * application. *

- * This example demonstrates a traditional 3-layer architecture consisting of data access layer, - * business layer and presentation layer. + * This example demonstrates a traditional 3-layer architecture consisting of data access layer, business layer and + * presentation layer. *

- * The data access layer is formed of Spring Data repositories CakeDao, - * CakeToppingDao and CakeLayerDao. The repositories can be used for CRUD - * operations on cakes, cake toppings and cake layers respectively. + * The data access layer is formed of Spring Data repositories CakeDao, CakeToppingDao and + * CakeLayerDao. The repositories can be used for CRUD operations on cakes, cake toppings and cake layers + * respectively. *

- * The business layer is built on top of the data access layer. CakeBakingService - * offers methods to retrieve available cake toppings and cake layers and baked cakes. Also the - * service is used to create new cakes out of cake toppings and cake layers. + * The business layer is built on top of the data access layer. CakeBakingService offers methods to + * retrieve available cake toppings and cake layers and baked cakes. Also the service is used to create new cakes out of + * cake toppings and cake layers. *

- * The presentation layer is built on the business layer and in this example it simply lists the - * cakes that have been baked. + * The presentation layer is built on the business layer and in this example it simply lists the cakes that have been + * baked. *

- * We have applied so called strict layering which means that the layers can only access the classes - * directly beneath them. This leads the solution to create an additional set of DTOs ( - * CakeInfo, CakeToppingInfo, CakeLayerInfo) to translate - * data between layers. In other words, CakeBakingService cannot return entities ( - * Cake, CakeTopping, CakeLayer) directly since these reside - * on data access layer but instead translates these into business layer DTOs (CakeInfo, CakeToppingInfo, CakeLayerInfo) and returns them instead. This way - * the presentation layer does not have any knowledge of other layers than the business layer and - * thus is not affected by changes to them. + * We have applied so called strict layering which means that the layers can only access the classes directly beneath + * them. This leads the solution to create an additional set of DTOs ( CakeInfo, + * CakeToppingInfo, CakeLayerInfo) to translate data between layers. In other words, + * CakeBakingService cannot return entities ( Cake, CakeTopping, + * CakeLayer) directly since these reside on data access layer but instead translates these into business + * layer DTOs (CakeInfo, CakeToppingInfo, CakeLayerInfo) and returns them + * instead. This way the presentation layer does not have any knowledge of other layers than the business layer and thus + * is not affected by changes to them. * * @see Cake * @see CakeTopping @@ -63,8 +63,6 @@ public static void main(String[] args) { /** * Initializes the example data - * - * @param cakeBakingService */ private static void initializeData(CakeBakingService cakeBakingService) { cakeBakingService.saveNewLayer(new CakeLayerInfo("chocolate", 1200)); diff --git a/layers/src/main/java/com/iluwatar/layers/CakeBakingService.java b/layers/src/main/java/com/iluwatar/layers/CakeBakingService.java index 80bd3438bfe6..adfa585d6175 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeBakingService.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeBakingService.java @@ -11,44 +11,31 @@ public interface CakeBakingService { /** * Bakes new cake according to parameters - * - * @param cakeInfo - * @throws CakeBakingException */ void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException; /** * Get all cakes - * - * @return */ List getAllCakes(); /** * Store new cake topping - * - * @param toppingInfo */ void saveNewTopping(CakeToppingInfo toppingInfo); /** * Get available cake toppings - * - * @return */ List getAvailableToppings(); /** * Add new cake layer - * - * @param layerInfo */ void saveNewLayer(CakeLayerInfo layerInfo); /** * Get available cake layers - * - * @return */ List getAvailableLayers(); } diff --git a/layers/src/main/java/com/iluwatar/layers/CakeBakingServiceImpl.java b/layers/src/main/java/com/iluwatar/layers/CakeBakingServiceImpl.java index a519ec2ce487..79917842d3f2 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeBakingServiceImpl.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeBakingServiceImpl.java @@ -30,9 +30,9 @@ public CakeBakingServiceImpl() { @Override public void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException { - List allToppings = getAvailableToppings(); - List matchingToppings = - allToppings.stream().filter((t) -> t.name.equals(cakeInfo.cakeToppingInfo.name)) + List allToppings = getAvailableToppingEntities(); + List matchingToppings = + allToppings.stream().filter((t) -> t.getName().equals(cakeInfo.cakeToppingInfo.name)) .collect(Collectors.toList()); if (matchingToppings.isEmpty()) { throw new CakeBakingException(String.format("Topping %s is not available", @@ -50,7 +50,7 @@ public void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException { } } CakeToppingDao toppingBean = context.getBean(CakeToppingDao.class); - CakeTopping topping = toppingBean.findOne(matchingToppings.iterator().next().id.get()); + CakeTopping topping = toppingBean.findOne(matchingToppings.iterator().next().getId()); CakeDao cakeBean = context.getBean(CakeDao.class); Cake cake = new Cake(); cake.setTopping(topping); diff --git a/layers/src/main/java/com/iluwatar/layers/CakeInfo.java b/layers/src/main/java/com/iluwatar/layers/CakeInfo.java index f60ee9a144ec..dc374bf60dc6 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeInfo.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeInfo.java @@ -14,18 +14,27 @@ public class CakeInfo { public final CakeToppingInfo cakeToppingInfo; public final List cakeLayerInfos; + /** + * Constructor + */ public CakeInfo(Long id, CakeToppingInfo cakeToppingInfo, List cakeLayerInfos) { this.id = Optional.of(id); this.cakeToppingInfo = cakeToppingInfo; this.cakeLayerInfos = cakeLayerInfos; } + /** + * Constructor + */ public CakeInfo(CakeToppingInfo cakeToppingInfo, List cakeLayerInfos) { this.id = Optional.empty(); this.cakeToppingInfo = cakeToppingInfo; this.cakeLayerInfos = cakeLayerInfos; } + /** + * Calculate calories + */ public int calculateTotalCalories() { int total = cakeToppingInfo != null ? cakeToppingInfo.calories : 0; total += cakeLayerInfos.stream().mapToInt(c -> c.calories).sum(); @@ -34,7 +43,7 @@ public int calculateTotalCalories() { @Override public String toString() { - return String.format("CakeInfo id=%d topping=%s layers=%s totalCalories=%d", id.get(), + return String.format("CakeInfo id=%d topping=%s layers=%s totalCalories=%d", id.orElse(-1L), cakeToppingInfo, cakeLayerInfos, calculateTotalCalories()); } } diff --git a/layers/src/main/java/com/iluwatar/layers/CakeLayerInfo.java b/layers/src/main/java/com/iluwatar/layers/CakeLayerInfo.java index 3dff379daf52..5bc38b1092de 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeLayerInfo.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeLayerInfo.java @@ -13,12 +13,18 @@ public class CakeLayerInfo { public final String name; public final int calories; + /** + * Constructor + */ public CakeLayerInfo(Long id, String name, int calories) { this.id = Optional.of(id); this.name = name; this.calories = calories; } + /** + * Constructor + */ public CakeLayerInfo(String name, int calories) { this.id = Optional.empty(); this.name = name; @@ -27,6 +33,6 @@ public CakeLayerInfo(String name, int calories) { @Override public String toString() { - return String.format("CakeLayerInfo id=%d name=%s calories=%d", id.get(), name, calories); + return String.format("CakeLayerInfo id=%d name=%s calories=%d", id.orElse(-1L), name, calories); } } diff --git a/layers/src/main/java/com/iluwatar/layers/CakeTopping.java b/layers/src/main/java/com/iluwatar/layers/CakeTopping.java index 6dc9c45fc644..9f2107f1e475 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeTopping.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeTopping.java @@ -58,7 +58,7 @@ public void setCalories(int calories) { @Override public String toString() { - return String.format("id=%s name=%s calories=%d", name, calories); + return String.format("id=%s name=%s calories=%d", id, name, calories); } public Cake getCake() { diff --git a/layers/src/main/java/com/iluwatar/layers/CakeToppingInfo.java b/layers/src/main/java/com/iluwatar/layers/CakeToppingInfo.java index 4e432ec44292..4c9be6a3ec95 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeToppingInfo.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeToppingInfo.java @@ -13,12 +13,18 @@ public class CakeToppingInfo { public final String name; public final int calories; + /** + * Constructor + */ public CakeToppingInfo(Long id, String name, int calories) { this.id = Optional.of(id); this.name = name; this.calories = calories; } + /** + * Constructor + */ public CakeToppingInfo(String name, int calories) { this.id = Optional.empty(); this.name = name; @@ -27,6 +33,6 @@ public CakeToppingInfo(String name, int calories) { @Override public String toString() { - return String.format("CakeToppingInfo id=%d name=%s calories=%d", id.get(), name, calories); + return String.format("CakeToppingInfo id=%d name=%s calories=%d", id.orElse(-1L), name, calories); } } diff --git a/layers/src/main/resources/META-INF/persistence.xml b/layers/src/main/resources/META-INF/persistence.xml index d94d8582b686..96856e1b98f7 100644 --- a/layers/src/main/resources/META-INF/persistence.xml +++ b/layers/src/main/resources/META-INF/persistence.xml @@ -1,8 +1,8 @@ + xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> - + \ No newline at end of file diff --git a/layers/src/main/resources/applicationContext.xml b/layers/src/main/resources/applicationContext.xml index 0c908ad2ee8a..6b3bc466d1ed 100644 --- a/layers/src/main/resources/applicationContext.xml +++ b/layers/src/main/resources/applicationContext.xml @@ -1,42 +1,39 @@ - + - + - + - - - + + + - - - - - - + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/layers/src/test/java/com/iluwatar/layers/AppTest.java b/layers/src/test/java/com/iluwatar/layers/AppTest.java index 7db3f6ecd43e..cd03ae8151d5 100644 --- a/layers/src/test/java/com/iluwatar/layers/AppTest.java +++ b/layers/src/test/java/com/iluwatar/layers/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.layers.App; - /** * * Application test diff --git a/layers/src/test/java/com/iluwatar/layers/CakeBakingExceptionTest.java b/layers/src/test/java/com/iluwatar/layers/CakeBakingExceptionTest.java new file mode 100644 index 000000000000..87381a309870 --- /dev/null +++ b/layers/src/test/java/com/iluwatar/layers/CakeBakingExceptionTest.java @@ -0,0 +1,30 @@ +package com.iluwatar.layers; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Date: 12/15/15 - 7:57 PM + * + * @author Jeroen Meulemeester + */ +public class CakeBakingExceptionTest { + + @Test + public void testConstructor() throws Exception { + final CakeBakingException exception = new CakeBakingException(); + assertNull(exception.getMessage()); + assertNull(exception.getCause()); + } + + @Test + public void testConstructorWithMessage() throws Exception { + final String expectedMessage = "message"; + final CakeBakingException exception = new CakeBakingException(expectedMessage); + assertEquals(expectedMessage, exception.getMessage()); + assertNull(exception.getCause()); + } + +} diff --git a/layers/src/test/java/com/iluwatar/layers/CakeBakingServiceImplTest.java b/layers/src/test/java/com/iluwatar/layers/CakeBakingServiceImplTest.java new file mode 100644 index 000000000000..11caab9ea714 --- /dev/null +++ b/layers/src/test/java/com/iluwatar/layers/CakeBakingServiceImplTest.java @@ -0,0 +1,159 @@ +package com.iluwatar.layers; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/15/15 - 9:55 PM + * + * @author Jeroen Meulemeester + */ +public class CakeBakingServiceImplTest { + + @Test + public void testLayers() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final List initialLayers = service.getAvailableLayers(); + assertNotNull(initialLayers); + assertTrue(initialLayers.isEmpty()); + + service.saveNewLayer(new CakeLayerInfo("Layer1", 1000)); + service.saveNewLayer(new CakeLayerInfo("Layer2", 2000)); + + final List availableLayers = service.getAvailableLayers(); + assertNotNull(availableLayers); + assertEquals(2, availableLayers.size()); + for (final CakeLayerInfo layer : availableLayers) { + assertNotNull(layer.id); + assertNotNull(layer.name); + assertNotNull(layer.toString()); + assertTrue(layer.calories > 0); + } + + } + + @Test + public void testToppings() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final List initialToppings = service.getAvailableToppings(); + assertNotNull(initialToppings); + assertTrue(initialToppings.isEmpty()); + + service.saveNewTopping(new CakeToppingInfo("Topping1", 1000)); + service.saveNewTopping(new CakeToppingInfo("Topping2", 2000)); + + final List availableToppings = service.getAvailableToppings(); + assertNotNull(availableToppings); + assertEquals(2, availableToppings.size()); + for (final CakeToppingInfo topping : availableToppings) { + assertNotNull(topping.id); + assertNotNull(topping.name); + assertNotNull(topping.toString()); + assertTrue(topping.calories > 0); + } + + } + + @Test + public void testBakeCakes() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final List initialCakes = service.getAllCakes(); + assertNotNull(initialCakes); + assertTrue(initialCakes.isEmpty()); + + final CakeToppingInfo topping1 = new CakeToppingInfo("Topping1", 1000); + final CakeToppingInfo topping2 = new CakeToppingInfo("Topping2", 2000); + service.saveNewTopping(topping1); + service.saveNewTopping(topping2); + + final CakeLayerInfo layer1 = new CakeLayerInfo("Layer1", 1000); + final CakeLayerInfo layer2 = new CakeLayerInfo("Layer2", 2000); + final CakeLayerInfo layer3 = new CakeLayerInfo("Layer3", 2000); + service.saveNewLayer(layer1); + service.saveNewLayer(layer2); + service.saveNewLayer(layer3); + + service.bakeNewCake(new CakeInfo(topping1, Arrays.asList(layer1, layer2))); + service.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer3))); + + final List allCakes = service.getAllCakes(); + assertNotNull(allCakes); + assertEquals(2, allCakes.size()); + for (final CakeInfo cakeInfo : allCakes) { + assertNotNull(cakeInfo.id); + assertNotNull(cakeInfo.cakeToppingInfo); + assertNotNull(cakeInfo.cakeLayerInfos); + assertNotNull(cakeInfo.toString()); + assertFalse(cakeInfo.cakeLayerInfos.isEmpty()); + assertTrue(cakeInfo.calculateTotalCalories() > 0); + } + + } + + @Test(expected = CakeBakingException.class) + public void testBakeCakeMissingTopping() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final CakeLayerInfo layer1 = new CakeLayerInfo("Layer1", 1000); + final CakeLayerInfo layer2 = new CakeLayerInfo("Layer2", 2000); + service.saveNewLayer(layer1); + service.saveNewLayer(layer2); + + final CakeToppingInfo missingTopping = new CakeToppingInfo("Topping1", 1000); + service.bakeNewCake(new CakeInfo(missingTopping, Arrays.asList(layer1, layer2))); + } + + @Test(expected = CakeBakingException.class) + public void testBakeCakeMissingLayer() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final List initialCakes = service.getAllCakes(); + assertNotNull(initialCakes); + assertTrue(initialCakes.isEmpty()); + + final CakeToppingInfo topping1 = new CakeToppingInfo("Topping1", 1000); + service.saveNewTopping(topping1); + + final CakeLayerInfo layer1 = new CakeLayerInfo("Layer1", 1000); + service.saveNewLayer(layer1); + + final CakeLayerInfo missingLayer = new CakeLayerInfo("Layer2", 2000); + service.bakeNewCake(new CakeInfo(topping1, Arrays.asList(layer1, missingLayer))); + + } + + @Test(expected = CakeBakingException.class) + public void testBakeCakesUsedLayer() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final List initialCakes = service.getAllCakes(); + assertNotNull(initialCakes); + assertTrue(initialCakes.isEmpty()); + + final CakeToppingInfo topping1 = new CakeToppingInfo("Topping1", 1000); + final CakeToppingInfo topping2 = new CakeToppingInfo("Topping2", 2000); + service.saveNewTopping(topping1); + service.saveNewTopping(topping2); + + final CakeLayerInfo layer1 = new CakeLayerInfo("Layer1", 1000); + final CakeLayerInfo layer2 = new CakeLayerInfo("Layer2", 2000); + service.saveNewLayer(layer1); + service.saveNewLayer(layer2); + + service.bakeNewCake(new CakeInfo(topping1, Arrays.asList(layer1, layer2))); + service.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer2))); + + } + +} diff --git a/layers/src/test/java/com/iluwatar/layers/CakeTest.java b/layers/src/test/java/com/iluwatar/layers/CakeTest.java new file mode 100644 index 000000000000..8c2bd4c15efb --- /dev/null +++ b/layers/src/test/java/com/iluwatar/layers/CakeTest.java @@ -0,0 +1,97 @@ +package com.iluwatar.layers; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/15/15 - 8:02 PM + * + * @author Jeroen Meulemeester + */ +public class CakeTest { + + @Test + public void testSetId() { + final Cake cake = new Cake(); + assertNull(cake.getId()); + + final Long expectedId = Long.valueOf(1234L); + cake.setId(expectedId); + assertEquals(expectedId, cake.getId()); + } + + @Test + public void testSetTopping() { + final Cake cake = new Cake(); + assertNull(cake.getTopping()); + + final CakeTopping expectedTopping = new CakeTopping("DummyTopping", 1000); + cake.setTopping(expectedTopping); + assertEquals(expectedTopping, cake.getTopping()); + } + + @Test + public void testSetLayers() { + final Cake cake = new Cake(); + assertNotNull(cake.getLayers()); + assertTrue(cake.getLayers().isEmpty()); + + final Set expectedLayers = new HashSet<>(); + expectedLayers.add(new CakeLayer("layer1", 1000)); + expectedLayers.add(new CakeLayer("layer2", 2000)); + expectedLayers.add(new CakeLayer("layer3", 3000)); + + cake.setLayers(expectedLayers); + assertEquals(expectedLayers, cake.getLayers()); + } + + @Test + public void testAddLayer() { + final Cake cake = new Cake(); + assertNotNull(cake.getLayers()); + assertTrue(cake.getLayers().isEmpty()); + + final Set initialLayers = new HashSet<>(); + initialLayers.add(new CakeLayer("layer1", 1000)); + initialLayers.add(new CakeLayer("layer2", 2000)); + + cake.setLayers(initialLayers); + assertEquals(initialLayers, cake.getLayers()); + + final CakeLayer newLayer = new CakeLayer("layer3", 3000); + cake.addLayer(newLayer); + + final Set expectedLayers = new HashSet<>(); + expectedLayers.addAll(initialLayers); + expectedLayers.addAll(initialLayers); + expectedLayers.add(newLayer); + assertEquals(expectedLayers, cake.getLayers()); + } + + @Test + public void testToString() { + final CakeTopping topping = new CakeTopping("topping", 20); + topping.setId(2345L); + + final CakeLayer layer = new CakeLayer("layer", 100); + layer.setId(3456L); + + final Cake cake = new Cake(); + cake.setId(1234L); + cake.setTopping(topping); + cake.addLayer(layer); + + final String expected = "id=1234 topping=id=2345 name=topping calories=20 " + + "layers=[id=3456 name=layer calories=100]"; + assertEquals(expected, cake.toString()); + + } + +} diff --git a/layers/src/test/java/com/iluwatar/layers/CakeViewImplTest.java b/layers/src/test/java/com/iluwatar/layers/CakeViewImplTest.java new file mode 100644 index 000000000000..ce8299170fe6 --- /dev/null +++ b/layers/src/test/java/com/iluwatar/layers/CakeViewImplTest.java @@ -0,0 +1,44 @@ +package com.iluwatar.layers; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.*; + +/** + * Date: 12/15/15 - 10:04 PM + * + * @author Jeroen Meulemeester + */ +public class CakeViewImplTest extends StdOutTest { + + /** + * Verify if the cake view renders the expected result + */ + @Test + public void testRender() { + + final List layers = new ArrayList<>(); + layers.add(new CakeLayerInfo("layer1", 1000)); + layers.add(new CakeLayerInfo("layer2", 2000)); + layers.add(new CakeLayerInfo("layer3", 3000)); + + final List cakes = new ArrayList<>(); + final CakeInfo cake = new CakeInfo(new CakeToppingInfo("topping", 1000), layers); + cakes.add(cake); + + final CakeBakingService bakingService = mock(CakeBakingService.class); + when(bakingService.getAllCakes()).thenReturn(cakes); + + final CakeViewImpl cakeView = new CakeViewImpl(bakingService); + + verifyZeroInteractions(getStdOutMock()); + + cakeView.render(); + verify(getStdOutMock(), times(1)).println(cake); + + } + +} diff --git a/layers/src/test/java/com/iluwatar/layers/StdOutTest.java b/layers/src/test/java/com/iluwatar/layers/StdOutTest.java new file mode 100644 index 000000000000..fe72bbb8a61c --- /dev/null +++ b/layers/src/test/java/com/iluwatar/layers/StdOutTest.java @@ -0,0 +1,54 @@ +package com.iluwatar.layers; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since the actions of the views don't have + * any influence on any other accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/lazy-loading/index.md b/lazy-loading/index.md index 700892af04b2..8a06700d35ba 100644 --- a/lazy-loading/index.md +++ b/lazy-loading/index.md @@ -4,20 +4,26 @@ title: Lazy Loading folder: lazy-loading permalink: /patterns/lazy-loading/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Beginner + - Idiom + - Performance --- -**Intent:** Lazy loading is a design pattern commonly used to defer +## Intent +Lazy loading is a design pattern commonly used to defer initialization of an object until the point at which it is needed. It can contribute to efficiency in the program's operation if properly and appropriately used. ![alt text](./etc/lazy-loading.png "Lazy Loading") -**Applicability:** Use the Lazy Loading idiom when +## Applicability +Use the Lazy Loading idiom when * eager loading is expensive or the object to be loaded might not be needed at all -**Real world examples:** +## Real world examples * JPA annotations @OneToOne, @OneToMany, @ManyToOne, @ManyToMany and fetch = FetchType.LAZY diff --git a/lazy-loading/pom.xml b/lazy-loading/pom.xml index eafc0f559199..44c950f4a86b 100644 --- a/lazy-loading/pom.xml +++ b/lazy-loading/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT lazy-loading diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java index 25e46d8b938f..dabd8c313295 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java @@ -7,6 +7,9 @@ */ public class Heavy { + /** + * Constructor + */ public Heavy() { System.out.println("Creating Heavy ..."); try { diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java index 132ebaa5fa24..f78005c73b31 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java @@ -9,10 +9,16 @@ public class HolderNaive { private Heavy heavy; + /** + * Constructor + */ public HolderNaive() { System.out.println("HolderNaive created"); } + /** + * Get heavy object + */ public Heavy getHeavy() { if (heavy == null) { heavy = new Heavy(); diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java index d2b15a3afa2d..56074846e1f4 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java @@ -10,10 +10,16 @@ public class HolderThreadSafe { private Heavy heavy; + /** + * Constructor + */ public HolderThreadSafe() { System.out.println("HolderThreadSafe created"); } + /** + * Get heavy object + */ public synchronized Heavy getHeavy() { if (heavy == null) { heavy = new Heavy(); diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Java8Holder.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Java8Holder.java index da021e014f99..aa86e3b34768 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Java8Holder.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Java8Holder.java @@ -5,7 +5,7 @@ /** * * This lazy loader is thread safe and more efficient than {@link HolderThreadSafe}. It utilizes - * Java 8 functional interface {@link Supplier} as {@link Heavy} factory. + * Java 8 functional interface {@link Supplier} as {@link Heavy} factory. * */ public class Java8Holder { diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java new file mode 100644 index 000000000000..99523cd0a32f --- /dev/null +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java @@ -0,0 +1,41 @@ +package com.iluwatar.lazy.loading; + +import org.junit.Test; + +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertSame; +import static junit.framework.TestCase.assertNull; + +/** + * Date: 12/19/15 - 11:58 AM + * + * @author Jeroen Meulemeester + */ +public abstract class AbstractHolderTest { + + /** + * Get the internal state of the holder value + * + * @return The internal value + */ + abstract Heavy getInternalHeavyValue() throws Exception; + + /** + * Request a lazy loaded {@link Heavy} object from the holder. + * + * @return The lazy loaded {@link Heavy} object + */ + abstract Heavy getHeavy() throws Exception; + + /** + * This test shows that the heavy field is not instantiated until the method getHeavy is called + */ + @Test(timeout = 3000) + public void testGetHeavy() throws Exception { + assertNull(getInternalHeavyValue()); + assertNotNull(getHeavy()); + assertNotNull(getInternalHeavyValue()); + assertSame(getHeavy(), getInternalHeavyValue()); + } + +} diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java index 591b1282dc06..29176a6b5610 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.lazy.loading.App; - /** * * Application test diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java new file mode 100644 index 000000000000..2c539e8ca86c --- /dev/null +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java @@ -0,0 +1,26 @@ +package com.iluwatar.lazy.loading; + +import java.lang.reflect.Field; + +/** + * Date: 12/19/15 - 12:05 PM + * + * @author Jeroen Meulemeester + */ +public class HolderNaiveTest extends AbstractHolderTest { + + private final HolderNaive holder = new HolderNaive(); + + @Override + Heavy getInternalHeavyValue() throws Exception { + final Field holderField = HolderNaive.class.getDeclaredField("heavy"); + holderField.setAccessible(true); + return (Heavy) holderField.get(this.holder); + } + + @Override + Heavy getHeavy() { + return holder.getHeavy(); + } + +} \ No newline at end of file diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java index d827f186bc5d..f6aed73b77f4 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java @@ -1,45 +1,26 @@ package com.iluwatar.lazy.loading; -import org.junit.Test; - import java.lang.reflect.Field; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - /** - * Using reflection this test shows that the heavy field is not instantiated until the method - * getHeavy is called + * Date: 12/19/15 - 12:19 PM * - * Created by jones on 11/10/2015. + * @author Jeroen Meulemeester */ -public class HolderThreadSafeTest { - - @Test - public void test() throws IllegalAccessException { - HolderThreadSafe hts = new HolderThreadSafe(); - - { - // first call is null - Field[] ff = HolderThreadSafe.class.getDeclaredFields(); - for (Field f : ff) { - f.setAccessible(true); - } +public class HolderThreadSafeTest extends AbstractHolderTest { - assertNull(ff[0].get(hts)); - } + private final HolderThreadSafe holder = new HolderThreadSafe(); - // now it is lazily loaded - hts.getHeavy(); - - { - // now it is not null - call via reflection so that the test is the same before and after - Field[] ff = HolderThreadSafe.class.getDeclaredFields(); - for (Field f : ff) { - f.setAccessible(true); - } + @Override + Heavy getInternalHeavyValue() throws Exception { + final Field holderField = HolderThreadSafe.class.getDeclaredField("heavy"); + holderField.setAccessible(true); + return (Heavy) holderField.get(this.holder); + } - assertNotNull(ff[0].get(hts)); - } + @Override + Heavy getHeavy() throws Exception { + return this.holder.getHeavy(); } -} + +} \ No newline at end of file diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java new file mode 100644 index 000000000000..aed9a054efcc --- /dev/null +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java @@ -0,0 +1,40 @@ +package com.iluwatar.lazy.loading; + +import java.lang.reflect.Field; +import java.util.function.Supplier; + +/** + * Date: 12/19/15 - 12:27 PM + * + * @author Jeroen Meulemeester + */ +public class Java8HolderTest extends AbstractHolderTest { + + private final Java8Holder holder = new Java8Holder(); + + + @Override + Heavy getInternalHeavyValue() throws Exception { + final Field holderField = Java8Holder.class.getDeclaredField("heavy"); + holderField.setAccessible(true); + + final Supplier supplier = (Supplier) holderField.get(this.holder); + final Class supplierClass = supplier.getClass(); + + // This is a little fishy, but I don't know another way to test this: + // The lazy holder is at first a lambda, but gets replaced with a new supplier after loading ... + if (supplierClass.isLocalClass()) { + final Field instanceField = supplierClass.getDeclaredField("heavyInstance"); + instanceField.setAccessible(true); + return (Heavy) instanceField.get(supplier); + } else { + return null; + } + } + + @Override + Heavy getHeavy() throws Exception { + return holder.getHeavy(); + } + +} \ No newline at end of file diff --git a/mediator/index.md b/mediator/index.md index cb4ce7fb17f1..c7a0478c8c66 100644 --- a/mediator/index.md +++ b/mediator/index.md @@ -10,18 +10,20 @@ tags: - Difficulty-Intermediate --- -**Intent:** Define an object that encapsulates how a set of objects interact. +## Intent +Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently. ![alt text](./etc/mediator_1.png "Mediator") -**Applicability:** Use the Mediator pattern when +## Applicability +Use the Mediator pattern when * a set of objects communicate in well-defined but complex ways. The resulting interdependencies are unstructured and difficult to understand * reusing an object is difficult because it refers to and communicates with many other objects * a behavior that's distributed between several classes should be customizable without a lot of subclassing -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/mediator/pom.xml b/mediator/pom.xml index 60999c5aa39c..449ce6e35c98 100644 --- a/mediator/pom.xml +++ b/mediator/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT mediator @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java b/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java index acc70a0e631f..ab15c26dae2d 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java +++ b/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java @@ -19,7 +19,7 @@ public PartyImpl() { @Override public void act(PartyMember actor, Action action) { for (PartyMember member : members) { - if (member != actor) { + if (!member.equals(actor)) { member.partyAction(action); } } diff --git a/mediator/src/test/java/com/iluwatar/mediator/AppTest.java b/mediator/src/test/java/com/iluwatar/mediator/AppTest.java index 4b3269244851..3e10dd84687b 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/AppTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.mediator.App; - /** * * Application test diff --git a/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java b/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java new file mode 100644 index 000000000000..992662fb2d6e --- /dev/null +++ b/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java @@ -0,0 +1,41 @@ +package com.iluwatar.mediator; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/19/15 - 10:00 PM + * + * @author Jeroen Meulemeester + */ +public class PartyImplTest { + + /** + * Verify if a member is notified when it's joining a party. Generate an action and see if the + * other member gets it. Also check members don't get their own actions. + */ + @Test + public void testPartyAction() { + final PartyMember partyMember1 = mock(PartyMember.class); + final PartyMember partyMember2 = mock(PartyMember.class); + + final PartyImpl party = new PartyImpl(); + party.addMember(partyMember1); + party.addMember(partyMember2); + + verify(partyMember1).joinedParty(party); + verify(partyMember2).joinedParty(party); + + party.act(partyMember1, Action.GOLD); + verifyZeroInteractions(partyMember1); + verify(partyMember2).partyAction(Action.GOLD); + + verifyNoMoreInteractions(partyMember1, partyMember2); + + } + +} diff --git a/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java b/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java new file mode 100644 index 000000000000..31b7222e9d65 --- /dev/null +++ b/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java @@ -0,0 +1,128 @@ +package com.iluwatar.mediator; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Supplier; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/19/15 - 10:13 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class PartyMemberTest { + + @Parameterized.Parameters + public static Collection[]> data() { + return Arrays.asList( + new Supplier[]{Hobbit::new}, + new Supplier[]{Hunter::new}, + new Supplier[]{Rogue::new}, + new Supplier[]{Wizard::new} + ); + } + + /** + * The mocked standard out {@link PrintStream}, required since some actions on a {@link + * PartyMember} have any influence on any other accessible objects, except for writing to 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); + } + + /** + * The factory, used to create a new instance of the tested party member + */ + private final Supplier memberSupplier; + + /** + * Create a new test instance, using the given {@link PartyMember} factory + * + * @param memberSupplier The party member factory + */ + public PartyMemberTest(final Supplier memberSupplier) { + this.memberSupplier = memberSupplier; + } + + /** + * Verify if a party action triggers the correct output to the std-Out + */ + @Test + public void testPartyAction() { + final PartyMember member = this.memberSupplier.get(); + + for (final Action action : Action.values()) { + member.partyAction(action); + verify(this.stdOutMock).println(member.toString() + " " + action.getDescription()); + } + + verifyNoMoreInteractions(this.stdOutMock); + } + + /** + * Verify if a member action triggers the expected interactions with the party class + */ + @Test + public void testAct() { + final PartyMember member = this.memberSupplier.get(); + + member.act(Action.GOLD); + verifyZeroInteractions(this.stdOutMock); + + final Party party = mock(Party.class); + member.joinedParty(party); + verify(this.stdOutMock).println(member.toString() + " joins the party"); + + for (final Action action : Action.values()) { + member.act(action); + verify(this.stdOutMock).println(member.toString() + " " + action.toString()); + verify(party).act(member, action); + } + + verifyNoMoreInteractions(party, this.stdOutMock); + } + + /** + * Verify if {@link PartyMember#toString()} generate the expected output + */ + @Test + public void testToString() throws Exception { + final PartyMember member = this.memberSupplier.get(); + final Class memberClass = member.getClass(); + assertEquals(memberClass.getSimpleName(), member.toString()); + } + +} diff --git a/memento/index.md b/memento/index.md index f299506e086a..463b5fec0836 100644 --- a/memento/index.md +++ b/memento/index.md @@ -7,24 +7,28 @@ categories: Behavioral tags: - Java - Gang Of Four + - Difficulty-Intermediate --- -**Also known as:** Token +## Also known as +Token -**Intent:** Without violating encapsulation, capture and externalize an +## Intent +Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later. ![alt text](./etc/memento.png "Memento") -**Applicability:** Use the Memento pattern when +## Applicability +Use the Memento pattern when * a snapshot of an object's state must be saved so that it can be restored to that state later, and * a direct interface to obtaining the state would expose implementation details and break the object's encapsulation -**Real world examples:** +## Real world examples * [java.util.Date](http://docs.oracle.com/javase/8/docs/api/java/util/Date.html) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/memento/pom.xml b/memento/pom.xml index 08d5c3a022be..258cd270a76b 100644 --- a/memento/pom.xml +++ b/memento/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT memento diff --git a/memento/src/main/java/com/iluwatar/memento/App.java b/memento/src/main/java/com/iluwatar/memento/App.java index c9989468014b..e08e9a106743 100644 --- a/memento/src/main/java/com/iluwatar/memento/App.java +++ b/memento/src/main/java/com/iluwatar/memento/App.java @@ -23,6 +23,9 @@ */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { Stack states = new Stack<>(); diff --git a/memento/src/main/java/com/iluwatar/memento/Star.java b/memento/src/main/java/com/iluwatar/memento/Star.java index b4ec1c6693de..f67edfd1585f 100644 --- a/memento/src/main/java/com/iluwatar/memento/Star.java +++ b/memento/src/main/java/com/iluwatar/memento/Star.java @@ -11,12 +11,18 @@ public class Star { private int ageYears; private int massTons; + /** + * Constructor + */ public Star(StarType startType, int startAge, int startMass) { this.type = startType; this.ageYears = startAge; this.massTons = startMass; } + /** + * Makes time pass for the star + */ public void timePasses() { ageYears *= 2; massTons *= 8; diff --git a/memento/src/test/java/com/iluwatar/memento/AppTest.java b/memento/src/test/java/com/iluwatar/memento/AppTest.java index 4eda4a6f9921..79ffea00f1d2 100644 --- a/memento/src/test/java/com/iluwatar/memento/AppTest.java +++ b/memento/src/test/java/com/iluwatar/memento/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.memento.App; - /** * * Application test diff --git a/memento/src/test/java/com/iluwatar/memento/StarTest.java b/memento/src/test/java/com/iluwatar/memento/StarTest.java new file mode 100644 index 000000000000..b5c7d9be0e22 --- /dev/null +++ b/memento/src/test/java/com/iluwatar/memento/StarTest.java @@ -0,0 +1,75 @@ +package com.iluwatar.memento; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/20/15 - 10:08 AM + * + * @author Jeroen Meulemeester + */ +public class StarTest { + + /** + * Verify the stages of a dying sun, without going back in time + */ + @Test + public void testTimePasses() { + final Star star = new Star(StarType.SUN, 1, 2); + assertEquals("sun age: 1 years mass: 2 tons", star.toString()); + + star.timePasses(); + assertEquals("red giant age: 2 years mass: 16 tons", star.toString()); + + star.timePasses(); + assertEquals("white dwarf age: 4 years mass: 128 tons", star.toString()); + + star.timePasses(); + assertEquals("supernova age: 8 years mass: 1024 tons", star.toString()); + + star.timePasses(); + assertEquals("dead star age: 16 years mass: 8192 tons", star.toString()); + + star.timePasses(); + assertEquals("dead star age: 64 years mass: 0 tons", star.toString()); + + star.timePasses(); + assertEquals("dead star age: 256 years mass: 0 tons", star.toString()); + } + + /** + * Verify some stage of a dying sun, but go back in time to test the memento + */ + @Test + public void testSetMemento() { + final Star star = new Star(StarType.SUN, 1, 2); + final StarMemento firstMemento = star.getMemento(); + assertEquals("sun age: 1 years mass: 2 tons", star.toString()); + + star.timePasses(); + final StarMemento secondMemento = star.getMemento(); + assertEquals("red giant age: 2 years mass: 16 tons", star.toString()); + + star.timePasses(); + final StarMemento thirdMemento = star.getMemento(); + assertEquals("white dwarf age: 4 years mass: 128 tons", star.toString()); + + star.timePasses(); + assertEquals("supernova age: 8 years mass: 1024 tons", star.toString()); + + star.setMemento(thirdMemento); + assertEquals("white dwarf age: 4 years mass: 128 tons", star.toString()); + + star.timePasses(); + assertEquals("supernova age: 8 years mass: 1024 tons", star.toString()); + + star.setMemento(secondMemento); + assertEquals("red giant age: 2 years mass: 16 tons", star.toString()); + + star.setMemento(firstMemento); + assertEquals("sun age: 1 years mass: 2 tons", star.toString()); + + } + +} diff --git a/message-channel/index.md b/message-channel/index.md index 06cf93488964..7015c726c2bf 100644 --- a/message-channel/index.md +++ b/message-channel/index.md @@ -7,17 +7,20 @@ categories: Integration tags: - Java - EIP + - Camel --- -**Intent:** When two applications communicate using a messaging system they do it by using logical addresses +## Intent +When two applications communicate using a messaging system they do it by using logical addresses of the system, so called Message Channels. ![alt text](./etc/message-channel.png "Message Channel") -**Applicability:** Use the Message Channel pattern when +## Applicability +Use the Message Channel pattern when * two or more applications need to communicate using a messaging system -**Real world examples:** +## Real world examples * [akka-camel](http://doc.akka.io/docs/akka/snapshot/scala/camel.html) diff --git a/message-channel/pom.xml b/message-channel/pom.xml index 0c34678a6e07..4f5f90339bd6 100644 --- a/message-channel/pom.xml +++ b/message-channel/pom.xml @@ -6,7 +6,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT message-channel diff --git a/message-channel/src/main/java/com/iluwatar/message/channel/App.java b/message-channel/src/main/java/com/iluwatar/message/channel/App.java index a41dd74dcf6a..b0aeb690fb4a 100644 --- a/message-channel/src/main/java/com/iluwatar/message/channel/App.java +++ b/message-channel/src/main/java/com/iluwatar/message/channel/App.java @@ -30,9 +30,6 @@ public class App { /** * Program entry point - * - * @param args command line args - * @throws Exception */ public static void main(String[] args) throws Exception { CamelContext context = new DefaultCamelContext(); diff --git a/model-view-controller/index.md b/model-view-controller/index.md index 1ba1089c05c2..bc96f7ab1e01 100644 --- a/model-view-controller/index.md +++ b/model-view-controller/index.md @@ -4,21 +4,25 @@ title: Model-View-Controller folder: model-view-controller permalink: /patterns/model-view-controller/ categories: Presentation Tier -tags: Java +tags: + - Java + - Difficulty-Intermediate --- -**Intent:** Separate the user interface into three interconnected components: +## Intent +Separate the user interface into three interconnected components: the model, the view and the controller. Let the model manage the data, the view display the data and the controller mediate updating the data and redrawing the display. ![alt text](./etc/model-view-controller.png "Model-View-Controller") -**Applicability:** Use the Model-View-Controller pattern when +## Applicability +Use the Model-View-Controller pattern when * you want to clearly separate the domain data from its user interface representation -**Credits:** +## Credits * [Trygve Reenskaug - Model-view-controller](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) * [J2EE Design Patterns](http://www.amazon.com/J2EE-Design-Patterns-William-Crawford/dp/0596004273/ref=sr_1_2) diff --git a/model-view-controller/pom.xml b/model-view-controller/pom.xml index 0f4539747751..6db4d556f1cd 100644 --- a/model-view-controller/pom.xml +++ b/model-view-controller/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT model-view-controller @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java index 7142c2979f3b..286ab9119bac 100644 --- a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.model.view.controller.App; - /** * * Application test diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java new file mode 100644 index 000000000000..0090f2d1d793 --- /dev/null +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java @@ -0,0 +1,100 @@ +package com.iluwatar.model.view.controller; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/20/15 - 2:19 PM + * + * @author Jeroen Meulemeester + */ +public class GiantControllerTest { + + /** + * Verify if the controller passes the health level through to the model and vice versa + */ + @Test + public void testSetHealth() { + final GiantModel model = mock(GiantModel.class); + final GiantView view = mock(GiantView.class); + final GiantController controller = new GiantController(model, view); + + verifyZeroInteractions(model, view); + + for (final Health health : Health.values()) { + controller.setHealth(health); + verify(model).setHealth(health); + verifyZeroInteractions(view); + } + + controller.getHealth(); + verify(model).getHealth(); + + verifyNoMoreInteractions(model, view); + } + + /** + * Verify if the controller passes the fatigue level through to the model and vice versa + */ + @Test + public void testSetFatigue() { + final GiantModel model = mock(GiantModel.class); + final GiantView view = mock(GiantView.class); + final GiantController controller = new GiantController(model, view); + + verifyZeroInteractions(model, view); + + for (final Fatigue fatigue : Fatigue.values()) { + controller.setFatigue(fatigue); + verify(model).setFatigue(fatigue); + verifyZeroInteractions(view); + } + + controller.getFatigue(); + verify(model).getFatigue(); + + verifyNoMoreInteractions(model, view); + } + + /** + * Verify if the controller passes the nourishment level through to the model and vice versa + */ + @Test + public void testSetNourishment() { + final GiantModel model = mock(GiantModel.class); + final GiantView view = mock(GiantView.class); + final GiantController controller = new GiantController(model, view); + + verifyZeroInteractions(model, view); + + for (final Nourishment nourishment : Nourishment.values()) { + controller.setNourishment(nourishment); + verify(model).setNourishment(nourishment); + verifyZeroInteractions(view); + } + + controller.getNourishment(); + verify(model).getNourishment(); + + verifyNoMoreInteractions(model, view); + } + + @Test + public void testUpdateView() { + final GiantModel model = mock(GiantModel.class); + final GiantView view = mock(GiantView.class); + final GiantController controller = new GiantController(model, view); + + verifyZeroInteractions(model, view); + + controller.updateView(); + verify(view).displayGiant(model); + + verifyNoMoreInteractions(model, view); + } + +} \ No newline at end of file diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java new file mode 100644 index 000000000000..9513a62ec8f8 --- /dev/null +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java @@ -0,0 +1,56 @@ +package com.iluwatar.model.view.controller; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/20/15 - 2:10 PM + * + * @author Jeroen Meulemeester + */ +public class GiantModelTest { + + /** + * Verify if the health value is set properly though the constructor and setter + */ + @Test + public void testSetHealth() { + final GiantModel model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + assertEquals(Health.HEALTHY, model.getHealth()); + for (final Health health : Health.values()) { + model.setHealth(health); + assertEquals(health, model.getHealth()); + assertEquals("The giant looks " + health.toString() + ", alert and saturated.", model.toString()); + } + } + + /** + * Verify if the fatigue level is set properly though the constructor and setter + */ + @Test + public void testSetFatigue() { + final GiantModel model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + assertEquals(Fatigue.ALERT, model.getFatigue()); + for (final Fatigue fatigue : Fatigue.values()) { + model.setFatigue(fatigue); + assertEquals(fatigue, model.getFatigue()); + assertEquals("The giant looks healthy, " + fatigue.toString() + " and saturated.", model.toString()); + } + } + + /** + * Verify if the nourishment level is set properly though the constructor and setter + */ + @Test + public void testSetNourishment() { + final GiantModel model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + assertEquals(Nourishment.SATURATED, model.getNourishment()); + for (final Nourishment nourishment : Nourishment.values()) { + model.setNourishment(nourishment); + assertEquals(nourishment, model.getNourishment()); + assertEquals("The giant looks healthy, alert and " + nourishment.toString() + ".", model.toString()); + } + } + +} diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java new file mode 100644 index 000000000000..8d7a7dfbf0d5 --- /dev/null +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java @@ -0,0 +1,64 @@ +package com.iluwatar.model.view.controller; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/20/15 - 2:04 PM + * + * @author Jeroen Meulemeester + */ +public class GiantViewTest { + + /** + * The mocked standard out {@link PrintStream}, required since the actions of the views don't have + * any influence on any other accessible objects, except for writing to 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); + } + + /** + * Verify if the {@link GiantView} does what it has to do: Print the {@link GiantModel} to the + * standard out stream, nothing more, nothing less. + */ + @Test + public void testDisplayGiant() { + final GiantView view = new GiantView(); + + final GiantModel model = mock(GiantModel.class); + view.displayGiant(model); + + verify(this.stdOutMock).println(model); + verifyNoMoreInteractions(model, this.stdOutMock); + + } + +} \ No newline at end of file diff --git a/model-view-presenter/index.md b/model-view-presenter/index.md index b51268013205..a3b921ce4f04 100644 --- a/model-view-presenter/index.md +++ b/model-view-presenter/index.md @@ -4,20 +4,24 @@ title: Model-View-Presenter folder: model-view-presenter permalink: /patterns/model-view-presenter/ categories: Presentation Tier -tags: Java +tags: + - Java + - Difficulty-Intermediate --- -**Intent:** Apply a "Separation of Concerns" principle in a way that allows +## Intent +Apply a "Separation of Concerns" principle in a way that allows developers to build and test user interfaces. ![alt text](./etc/model-view-presenter_1.png "Model-View-Presenter") -**Applicability:** Use the Model-View-Presenter in any of the following +## Applicability +Use the Model-View-Presenter in any of the following situations * when you want to improve the "Separation of Concerns" principle in presentation logic * when a user interface development and testing is necessary. -**Real world examples:** +## Real world examples * [MVP4J](https://github.com/amineoualialami/mvp4j) diff --git a/model-view-presenter/pom.xml b/model-view-presenter/pom.xml index 09410afc6eba..cea55b47f413 100644 --- a/model-view-presenter/pom.xml +++ b/model-view-presenter/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT model-view-presenter model-view-presenter diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java index d04f284ac906..0cf4f8c34e42 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java @@ -39,9 +39,7 @@ public String loadData() { br.close(); return sb.toString(); - } - - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJFrame.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJFrame.java index 02cb2703a3c3..db08d525bd5a 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJFrame.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJFrame.java @@ -26,7 +26,7 @@ public class FileSelectorJFrame extends JFrame implements FileSelectorView, Acti /** * The "OK" button for loading the file. */ - private JButton OK; + private JButton ok; /** * The cancel button. @@ -121,10 +121,10 @@ public FileSelectorJFrame() { /* * Add the OK button. */ - this.OK = new JButton("OK"); - this.panel.add(OK); - this.OK.setBounds(250, 50, 100, 25); - this.OK.addActionListener(this); + this.ok = new JButton("OK"); + this.panel.add(ok); + this.ok.setBounds(250, 50, 100, 25); + this.ok.addActionListener(this); /* * Add the cancel button. @@ -140,13 +140,11 @@ public FileSelectorJFrame() { @Override public void actionPerformed(ActionEvent e) { - if (e.getSource() == this.OK) { + if (this.ok.equals(e.getSource())) { this.fileName = this.input.getText(); presenter.fileNameChanged(); presenter.confirmed(); - } - - else if (e.getSource() == this.cancel) { + } else if (this.cancel.equals(e.getSource())) { presenter.cancelled(); } } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java index 133d8555df39..f38dc26558ca 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java @@ -51,6 +51,9 @@ public void fileNameChanged() { loader.setFileName(view.getFileName()); } + /** + * Ok button handler + */ public void confirmed() { if (loader.getFileName() == null || loader.getFileName().equals("")) { view.showMessage("Please give the name of the file first!"); @@ -60,9 +63,7 @@ public void confirmed() { if (loader.fileExists()) { String data = loader.loadData(); view.displayData(data); - } - - else { + } else { view.showMessage("The file specified does not exist."); } } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java index 80cfadd28007..f124c0054cce 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java @@ -9,53 +9,53 @@ public interface FileSelectorView { /** * Opens the view. */ - public void open(); + void open(); /** * Closes the view. */ - public void close(); + void close(); /** * @return True, if the view is opened, false otherwise. */ - public boolean isOpened(); + boolean isOpened(); /** * Sets the presenter component, to the one given as parameter. * * @param presenter The new presenter component. */ - public void setPresenter(FileSelectorPresenter presenter); + void setPresenter(FileSelectorPresenter presenter); /** * @return The presenter Component. */ - public FileSelectorPresenter getPresenter(); + FileSelectorPresenter getPresenter(); /** * Sets the file's name, to the value given as parameter. * * @param name The new name of the file. */ - public void setFileName(String name); + void setFileName(String name); /** * @return The name of the file. */ - public String getFileName(); + String getFileName(); /** * Displays a message to the users. * * @param message The message to be displayed. */ - public void showMessage(String message); + void showMessage(String message); /** * Displays the data to the view. * * @param data The data to be written. */ - public void displayData(String data); + void displayData(String data); } diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java new file mode 100644 index 000000000000..9b4aabc4dd2b --- /dev/null +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java @@ -0,0 +1,18 @@ +package com.iluwatar.model.view.presenter; + +import org.junit.Test; + +/** + * + * Application test + * + */ +public class AppTest { + + @Test + public void test() { + String[] args = {}; + App.main(args); + } + +} diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java new file mode 100644 index 000000000000..ed1fc0e9e6cc --- /dev/null +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java @@ -0,0 +1,21 @@ +package com.iluwatar.model.view.presenter; + +import org.junit.Test; + +import static org.junit.Assert.assertNull; + +/** + * Date: 12/21/15 - 12:12 PM + * + * @author Jeroen Meulemeester + */ +public class FileLoaderTest { + + @Test + public void testLoadData() throws Exception { + final FileLoader fileLoader = new FileLoader(); + fileLoader.setFileName("non-existing-file"); + assertNull(fileLoader.loadData()); + } + +} \ No newline at end of file diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java index dfdcba31b9b6..ba371525a91e 100644 --- a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java @@ -1,14 +1,13 @@ package com.iluwatar.model.view.presenter; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; -import com.iluwatar.model.view.presenter.FileLoader; -import com.iluwatar.model.view.presenter.FileSelectorPresenter; -import com.iluwatar.model.view.presenter.FileSelectorStub; - /** * This test case is responsible for testing our application by taking advantage of the * Model-View-Controller architectural pattern. @@ -57,13 +56,13 @@ public void wiring() { */ @Test public void updateFileNameToLoader() { - String EXPECTED_FILE = "Stamatis"; - stub.setFileName(EXPECTED_FILE); + String expectedFile = "Stamatis"; + stub.setFileName(expectedFile); presenter.start(); presenter.fileNameChanged(); - assertEquals(EXPECTED_FILE, loader.getFileName()); + assertEquals(expectedFile, loader.getFileName()); } /** diff --git a/monostate/index.md b/monostate/index.md index 2b88f131e837..415d13f0e7f9 100644 --- a/monostate/index.md +++ b/monostate/index.md @@ -4,25 +4,29 @@ title: MonoState folder: monostate permalink: /patterns/monostate/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Beginner --- -**Intent:** Enforces a behaviour like sharing the same state amongst all instances. +## Intent +Enforces a behaviour like sharing the same state amongst all instances. ![alt text](./etc/monostate.png "MonoState") -**Applicability:** Use the Monostate pattern when +## Applicability +Use the Monostate pattern when * The same state must be shared across all instances of a class. * Typically this pattern might be used everywhere a Singleton might be used. Singleton usage however is not transparent, Monostate usage is. * Monostate has one major advantage over singleton. The subclasses might decorate the shared state as they wish and hence can provide dynamically different behaviour than the base class. -**Typical Use Case:** +## Typical Use Case * the logging class * managing a connection to a database * file manager -**Real world examples:** +## Real world examples Yet to see this. diff --git a/monostate/pom.xml b/monostate/pom.xml index 7531263f0318..f18c03e5c9de 100644 --- a/monostate/pom.xml +++ b/monostate/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT monostate @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/monostate/src/main/java/com/iluwatar/monostate/App.java b/monostate/src/main/java/com/iluwatar/monostate/App.java index 0daad5b67361..5c61371fa25a 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/App.java +++ b/monostate/src/main/java/com/iluwatar/monostate/App.java @@ -28,8 +28,8 @@ public class App { public static void main(String[] args) { LoadBalancer loadBalancer1 = new LoadBalancer(); LoadBalancer loadBalancer2 = new LoadBalancer(); - loadBalancer1.serverequest(new Request("Hello")); - loadBalancer2.serverequest(new Request("Hello World")); + loadBalancer1.serverRequest(new Request("Hello")); + loadBalancer2.serverRequest(new Request("Hello World")); } } diff --git a/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java b/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java index b81e4425128f..697c48bb486f 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java +++ b/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java @@ -24,6 +24,9 @@ public class LoadBalancer { servers.add(new Server("localhost", 8084, ++id)); } + /** + * Add new server + */ public final void addServer(Server server) { synchronized (servers) { servers.add(server); @@ -39,7 +42,10 @@ public static int getLastServedId() { return lastServedId; } - public void serverequest(Request request) { + /** + * Handle request + */ + public void serverRequest(Request request) { if (lastServedId >= servers.size()) { lastServedId = 0; } diff --git a/monostate/src/main/java/com/iluwatar/monostate/Server.java b/monostate/src/main/java/com/iluwatar/monostate/Server.java index f48f4ad0f9dc..0cf9ac41f995 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/Server.java +++ b/monostate/src/main/java/com/iluwatar/monostate/Server.java @@ -11,6 +11,9 @@ public class Server { public final int port; public final int id; + /** + * Constructor + */ public Server(String host, int port, int id) { this.host = host; this.port = port; @@ -25,7 +28,7 @@ public int getPort() { return port; } - public final void serve(Request request) { + public void serve(Request request) { System.out.println("Server ID " + id + " associated to host : " + getHost() + " and Port " + getPort() + " Processed request with value " + request.value); } diff --git a/monostate/src/test/java/com/iluwatar/monostate/AppTest.java b/monostate/src/test/java/com/iluwatar/monostate/AppTest.java index c502dd14a2a6..053cd6649566 100644 --- a/monostate/src/test/java/com/iluwatar/monostate/AppTest.java +++ b/monostate/src/test/java/com/iluwatar/monostate/AppTest.java @@ -1,26 +1,13 @@ package com.iluwatar.monostate; -import org.junit.Assert; import org.junit.Test; public class AppTest { - @Test - public void testSameStateAmonstAllInstances() { - LoadBalancer balancer = new LoadBalancer(); - LoadBalancer balancer2 = new LoadBalancer(); - balancer.addServer(new Server("localhost", 8085, 6)); - // Both should have the same number of servers. - Assert.assertTrue(balancer.getNoOfServers() == balancer2.getNoOfServers()); - // Both Should have the same LastServedId - Assert.assertTrue(balancer.getLastServedId() == balancer2.getLastServedId()); - } - @Test public void testMain() { String[] args = {}; App.main(args); - Assert.assertTrue(LoadBalancer.getLastServedId() == 2); } } diff --git a/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java b/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java new file mode 100644 index 000000000000..5488f12f38ab --- /dev/null +++ b/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java @@ -0,0 +1,49 @@ +package com.iluwatar.monostate; + +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +/** + * Date: 12/21/15 - 12:26 PM + * + * @author Jeroen Meulemeester + */ +public class LoadBalancerTest { + + @Test + public void testSameStateAmongstAllInstances() { + final LoadBalancer firstBalancer = new LoadBalancer(); + final LoadBalancer secondBalancer = new LoadBalancer(); + firstBalancer.addServer(new Server("localhost", 8085, 6)); + // Both should have the same number of servers. + Assert.assertTrue(firstBalancer.getNoOfServers() == secondBalancer.getNoOfServers()); + // Both Should have the same LastServedId + Assert.assertTrue(firstBalancer.getLastServedId() == secondBalancer.getLastServedId()); + } + + @Test + public void testServe() { + final Server server = mock(Server.class); + when(server.getHost()).thenReturn("testhost"); + when(server.getPort()).thenReturn(1234); + doNothing().when(server).serve(any(Request.class)); + + final LoadBalancer loadBalancer = new LoadBalancer(); + loadBalancer.addServer(server); + + verifyZeroInteractions(server); + + final Request request = new Request("test"); + for (int i = 0; i < loadBalancer.getNoOfServers() * 2; i++) { + loadBalancer.serverRequest(request); + } + + verify(server, times(2)).serve(request); + verifyNoMoreInteractions(server); + + } + +} diff --git a/multiton/index.md b/multiton/index.md index 617bedb6a16f..68fb6bbc6c3e 100644 --- a/multiton/index.md +++ b/multiton/index.md @@ -4,14 +4,18 @@ title: Multiton folder: multiton permalink: /patterns/multiton/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Beginner --- -**Intent:** Ensure a class only has limited number of instances, and provide a +## Intent +Ensure a class only has limited number of instances, and provide a global point of access to them. ![alt text](./etc/multiton.png "Multiton") -**Applicability:** Use the Multiton pattern when +## Applicability +Use the Multiton pattern when * there must be specific number of instances of a class, and they must be accessible to clients from a well-known access point diff --git a/multiton/pom.xml b/multiton/pom.xml index 4cbb326e8d79..5240ba6be61a 100644 --- a/multiton/pom.xml +++ b/multiton/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT multiton diff --git a/multiton/src/test/java/com/iluwatar/multiton/AppTest.java b/multiton/src/test/java/com/iluwatar/multiton/AppTest.java index 41b1387a6a2b..6901e6086891 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/AppTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.multiton.App; - /** * * Application test diff --git a/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java b/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java new file mode 100644 index 000000000000..923f76b1ee92 --- /dev/null +++ b/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java @@ -0,0 +1,29 @@ +package com.iluwatar.multiton; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +/** + * Date: 12/22/15 - 22:28 AM + * + * @author Jeroen Meulemeester + */ +public class NazgulTest { + + /** + * Verify if {@link Nazgul#getInstance(NazgulName)} returns the correct Nazgul multiton instance + */ + @Test + public void testGetInstance() { + for (final NazgulName name : NazgulName.values()) { + final Nazgul nazgul = Nazgul.getInstance(name); + assertNotNull(nazgul); + assertSame(nazgul, Nazgul.getInstance(name)); + assertEquals(name, nazgul.getName()); + } + } + +} diff --git a/naked-objects/dom/pom.xml b/naked-objects/dom/pom.xml index bd71db272d09..24a054c67b98 100644 --- a/naked-objects/dom/pom.xml +++ b/naked-objects/dom/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT naked-objects-dom diff --git a/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java b/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java index 6769f95ddfc2..fa1e740489f7 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java +++ b/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java @@ -21,9 +21,7 @@ import org.apache.isis.applib.annotation.NatureOfService; import org.apache.isis.applib.annotation.SemanticsOf; -@DomainService(nature = NatureOfService.VIEW_CONTRIBUTIONS_ONLY // trick to suppress the actions - // from the top-level menu -) +@DomainService(nature = NatureOfService.VIEW_CONTRIBUTIONS_ONLY) public class HomePageService { // region > homePage (action) diff --git a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java index 5e4642455c9f..849f01c5d617 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java +++ b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java @@ -69,9 +69,12 @@ public CreateDomainEvent(final SimpleObjects source, final Identifier identifier } } + /** + * Create simple object + */ @Action(domainEvent = CreateDomainEvent.class) @MemberOrder(sequence = "3") - public SimpleObject create(final @ParameterLayout(named = "Name") String name) { + public SimpleObject create(@ParameterLayout(named = "Name") final String name) { final SimpleObject obj = container.newTransientInstance(SimpleObject.class); obj.setName(name); container.persistIfNotAlready(obj); diff --git a/naked-objects/fixture/pom.xml b/naked-objects/fixture/pom.xml index 057bbb9ff47a..2cc097e92aca 100644 --- a/naked-objects/fixture/pom.xml +++ b/naked-objects/fixture/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT naked-objects-fixture diff --git a/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java b/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java index 2918fe7f6f3f..9a922a7bef2c 100644 --- a/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java +++ b/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java @@ -45,8 +45,6 @@ public SimpleObjectCreate setName(final String name) { /** * The created simple object (output). - * - * @return */ public SimpleObject getSimpleObject() { return simpleObject; @@ -57,9 +55,9 @@ public SimpleObject getSimpleObject() { @Override protected void execute(final ExecutionContext ec) { - String name = checkParam("name", ec, String.class); + String paramName = checkParam("name", ec, String.class); - this.simpleObject = wrap(simpleObjects).create(name); + this.simpleObject = wrap(simpleObjects).create(paramName); // also make available to UI ec.addResult(this, simpleObject); diff --git a/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java b/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java index c978e0b82795..6d17d9b63622 100644 --- a/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java +++ b/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java @@ -29,7 +29,7 @@ public class RecreateSimpleObjects extends FixtureScript { - public final List NAMES = Collections.unmodifiableList(Arrays.asList("Foo", "Bar", "Baz", + public final List names = Collections.unmodifiableList(Arrays.asList("Foo", "Bar", "Baz", "Frodo", "Froyo", "Fizz", "Bip", "Bop", "Bang", "Boo")); public RecreateSimpleObjects() { @@ -69,12 +69,12 @@ public List getSimpleObjects() { protected void execute(final ExecutionContext ec) { // defaults - final int number = defaultParam("number", ec, 3); + final int paramNumber = defaultParam("number", ec, 3); // validate - if (number < 0 || number > NAMES.size()) { + if (paramNumber < 0 || paramNumber > names.size()) { throw new IllegalArgumentException(String.format("number must be in range [0,%d)", - NAMES.size())); + names.size())); } // @@ -82,8 +82,8 @@ protected void execute(final ExecutionContext ec) { // ec.executeChild(this, new SimpleObjectsTearDown()); - for (int i = 0; i < number; i++) { - final SimpleObjectCreate fs = new SimpleObjectCreate().setName(NAMES.get(i)); + for (int i = 0; i < paramNumber; i++) { + final SimpleObjectCreate fs = new SimpleObjectCreate().setName(names.get(i)); ec.executeChild(this, fs.getName(), fs); simpleObjects.add(fs.getSimpleObject()); } diff --git a/naked-objects/index.md b/naked-objects/index.md index 805cea810454..eb1c083b14e2 100644 --- a/naked-objects/index.md +++ b/naked-objects/index.md @@ -4,25 +4,29 @@ title: Naked Objects folder: naked-objects permalink: /patterns/naked-objects/ categories: Architectural -tags: Java +tags: + - Java + - Difficulty-Expert --- -**Intent:** The Naked Objects architectural pattern is well suited for rapid +## Intent +The Naked Objects architectural pattern is well suited for rapid prototyping. Using the pattern, you only need to write the domain objects, everything else is autogenerated by the framework. ![alt text](./etc/naked-objects.png "Naked Objects") -**Applicability:** Use the Naked Objects pattern when +## Applicability +Use the Naked Objects pattern when * you are prototyping and need fast development cycle * an autogenerated user interface is good enough * you want to automatically publish the domain as REST services -**Real world examples:** +## Real world examples * [Apache Isis](https://isis.apache.org/) -**Credits:** +## Credits * [Richard Pawson - Naked Objects](http://downloads.nakedobjects.net/resources/Pawson%20thesis.pdf) diff --git a/naked-objects/integtests/pom.xml b/naked-objects/integtests/pom.xml index 78819358a1dd..d5fb3c581607 100644 --- a/naked-objects/integtests/pom.xml +++ b/naked-objects/integtests/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT naked-objects-integtests diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java b/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java index c617915f13c9..90ae45d95a01 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java @@ -21,6 +21,12 @@ public class SimpleAppSystemInitializer { + private SimpleAppSystemInitializer() { + } + + /** + * Init test system + */ public static void initIsft() { IsisSystemForTest isft = IsisSystemForTest.getElseNull(); if (isft == null) { diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java index ef601291921e..2ea375b4a749 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java @@ -29,7 +29,7 @@ public class SimpleObjectGlue extends CukeGlueAbstract { @Given("^there are.* (\\d+) simple objects$") - public void there_are_N_simple_objects(int n) throws Throwable { + public void thereAreNumSimpleObjects(int n) throws Throwable { try { final List findAll = service(SimpleObjects.class).listAll(); assertThat(findAll.size(), is(n)); @@ -41,7 +41,7 @@ public void there_are_N_simple_objects(int n) throws Throwable { } @When("^I create a new simple object$") - public void I_create_a_new_simple_object() throws Throwable { + public void createNewSimpleObject() throws Throwable { service(SimpleObjects.class).create(UUID.randomUUID().toString()); } diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java index 3ceef4e63fc5..7a7ad91b262b 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java @@ -27,13 +27,13 @@ public abstract class SimpleAppIntegTest extends IntegrationTestAbstract { - @BeforeClass - public static void initClass() { - org.apache.log4j.PropertyConfigurator.configure("logging.properties"); - SimpleAppSystemInitializer.initIsft(); + @BeforeClass + public static void initClass() { + org.apache.log4j.PropertyConfigurator.configure("logging.properties"); + SimpleAppSystemInitializer.initIsft(); - // instantiating will install onto ThreadLocal - new ScenarioExecutionForIntegration(); - } + // instantiating will install onto ThreadLocal + new ScenarioExecutionForIntegration(); + } } diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java index 610136bb8029..872aff7a3d0f 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java @@ -35,87 +35,86 @@ public class SimpleObjectIntegTest extends SimpleAppIntegTest { - @Inject - FixtureScripts fixtureScripts; + @Inject + FixtureScripts fixtureScripts; - RecreateSimpleObjects fs; - SimpleObject simpleObjectPojo; - SimpleObject simpleObjectWrapped; + RecreateSimpleObjects fs; + SimpleObject simpleObjectPojo; + SimpleObject simpleObjectWrapped; - @Before - public void setUp() throws Exception { - // given - fs = new RecreateSimpleObjects().setNumber(1); - fixtureScripts.runFixtureScript(fs, null); + @Before + public void setUp() throws Exception { + // given + fs = new RecreateSimpleObjects().setNumber(1); + fixtureScripts.runFixtureScript(fs, null); - simpleObjectPojo = fs.getSimpleObjects().get(0); + simpleObjectPojo = fs.getSimpleObjects().get(0); - assertThat(simpleObjectPojo).isNotNull(); - simpleObjectWrapped = wrap(simpleObjectPojo); - } + assertThat(simpleObjectPojo).isNotNull(); + simpleObjectWrapped = wrap(simpleObjectPojo); + } - public static class Name extends SimpleObjectIntegTest { + public static class Name extends SimpleObjectIntegTest { - @Test - public void accessible() throws Exception { - // when - final String name = simpleObjectWrapped.getName(); - // then - assertThat(name).isEqualTo(fs.NAMES.get(0)); - } + @Test + public void accessible() throws Exception { + // when + final String name = simpleObjectWrapped.getName(); + // then + assertThat(name).isEqualTo(fs.names.get(0)); + } - @Test - public void cannotBeUpdatedDirectly() throws Exception { + @Test + public void cannotBeUpdatedDirectly() throws Exception { - // expect - expectedExceptions.expect(DisabledException.class); + // expect + expectedExceptions.expect(DisabledException.class); - // when - simpleObjectWrapped.setName("new name"); - } + // when + simpleObjectWrapped.setName("new name"); } + } - public static class UpdateName extends SimpleObjectIntegTest { + public static class UpdateName extends SimpleObjectIntegTest { - @Test - public void happyCase() throws Exception { + @Test + public void happyCase() throws Exception { - // when - simpleObjectWrapped.updateName("new name"); + // when + simpleObjectWrapped.updateName("new name"); - // then - assertThat(simpleObjectWrapped.getName()).isEqualTo("new name"); - } + // then + assertThat(simpleObjectWrapped.getName()).isEqualTo("new name"); + } - @Test - public void failsValidation() throws Exception { + @Test + public void failsValidation() throws Exception { - // expect - expectedExceptions.expect(InvalidException.class); - expectedExceptions.expectMessage("Exclamation mark is not allowed"); + // expect + expectedExceptions.expect(InvalidException.class); + expectedExceptions.expectMessage("Exclamation mark is not allowed"); - // when - simpleObjectWrapped.updateName("new name!"); - } + // when + simpleObjectWrapped.updateName("new name!"); } + } + public static class Title extends SimpleObjectIntegTest { - public static class Title extends SimpleObjectIntegTest { - - @Inject - DomainObjectContainer container; + @Inject + DomainObjectContainer container; - @Test - public void interpolatesName() throws Exception { + @Test + public void interpolatesName() throws Exception { - // given - final String name = simpleObjectWrapped.getName(); + // given + final String name = simpleObjectWrapped.getName(); - // when - final String title = container.titleOf(simpleObjectWrapped); + // when + final String title = container.titleOf(simpleObjectWrapped); - // then - assertThat(title).isEqualTo("Object: " + name); - } + // then + assertThat(title).isEqualTo("Object: " + name); } + } } \ No newline at end of file diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java index fd3b0ff46cc3..332213542fd1 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java @@ -42,102 +42,102 @@ public class SimpleObjectsIntegTest extends SimpleAppIntegTest { - @Inject - FixtureScripts fixtureScripts; - @Inject - SimpleObjects simpleObjects; + @Inject + FixtureScripts fixtureScripts; + @Inject + SimpleObjects simpleObjects; - public static class ListAll extends SimpleObjectsIntegTest { + public static class ListAll extends SimpleObjectsIntegTest { - @Test - public void happyCase() throws Exception { + @Test + public void happyCase() throws Exception { - // given - RecreateSimpleObjects fs = new RecreateSimpleObjects(); - fixtureScripts.runFixtureScript(fs, null); - nextTransaction(); + // given + RecreateSimpleObjects fs = new RecreateSimpleObjects(); + fixtureScripts.runFixtureScript(fs, null); + nextTransaction(); - // when - final List all = wrap(simpleObjects).listAll(); + // when + final List all = wrap(simpleObjects).listAll(); - // then - assertThat(all).hasSize(fs.getSimpleObjects().size()); + // then + assertThat(all).hasSize(fs.getSimpleObjects().size()); - SimpleObject simpleObject = wrap(all.get(0)); - assertThat(simpleObject.getName()).isEqualTo(fs.getSimpleObjects().get(0).getName()); - } + SimpleObject simpleObject = wrap(all.get(0)); + assertThat(simpleObject.getName()).isEqualTo(fs.getSimpleObjects().get(0).getName()); + } - @Test - public void whenNone() throws Exception { + @Test + public void whenNone() throws Exception { - // given - FixtureScript fs = new SimpleObjectsTearDown(); - fixtureScripts.runFixtureScript(fs, null); - nextTransaction(); + // given + FixtureScript fs = new SimpleObjectsTearDown(); + fixtureScripts.runFixtureScript(fs, null); + nextTransaction(); - // when - final List all = wrap(simpleObjects).listAll(); + // when + final List all = wrap(simpleObjects).listAll(); - // then - assertThat(all).hasSize(0); - } + // then + assertThat(all).hasSize(0); } + } - public static class Create extends SimpleObjectsIntegTest { + public static class Create extends SimpleObjectsIntegTest { - @Test - public void happyCase() throws Exception { + @Test + public void happyCase() throws Exception { - // given - FixtureScript fs = new SimpleObjectsTearDown(); - fixtureScripts.runFixtureScript(fs, null); - nextTransaction(); + // given + FixtureScript fs = new SimpleObjectsTearDown(); + fixtureScripts.runFixtureScript(fs, null); + nextTransaction(); - // when - wrap(simpleObjects).create("Faz"); + // when + wrap(simpleObjects).create("Faz"); - // then - final List all = wrap(simpleObjects).listAll(); - assertThat(all).hasSize(1); - } + // then + final List all = wrap(simpleObjects).listAll(); + assertThat(all).hasSize(1); + } + + @Test + public void whenAlreadyExists() throws Exception { - @Test - public void whenAlreadyExists() throws Exception { + // given + FixtureScript fs = new SimpleObjectsTearDown(); + fixtureScripts.runFixtureScript(fs, null); + nextTransaction(); + wrap(simpleObjects).create("Faz"); + nextTransaction(); - // given - FixtureScript fs = new SimpleObjectsTearDown(); - fixtureScripts.runFixtureScript(fs, null); - nextTransaction(); - wrap(simpleObjects).create("Faz"); - nextTransaction(); + // then + expectedExceptions.expectCause(causalChainContains(SQLIntegrityConstraintViolationException.class)); - // then - expectedExceptions.expectCause(causalChainContains(SQLIntegrityConstraintViolationException.class)); + // when + wrap(simpleObjects).create("Faz"); + nextTransaction(); + } - // when - wrap(simpleObjects).create("Faz"); - nextTransaction(); + private static Matcher causalChainContains(final Class cls) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(Throwable item) { + final List causalChain = Throwables.getCausalChain(item); + for (Throwable throwable : causalChain) { + if (cls.isAssignableFrom(throwable.getClass())) { + return true; + } + } + return false; } - private static Matcher causalChainContains(final Class cls) { - return new TypeSafeMatcher() { - @Override - protected boolean matchesSafely(Throwable item) { - final List causalChain = Throwables.getCausalChain(item); - for (Throwable throwable : causalChain) { - if(cls.isAssignableFrom(throwable.getClass())){ - return true; - } - } - return false; - } - - @Override - public void describeTo(Description description) { - description.appendText("exception with causal chain containing " + cls.getSimpleName()); - } - }; + @Override + public void describeTo(Description description) { + description.appendText("exception with causal chain containing " + cls.getSimpleName()); } + }; } + } } \ No newline at end of file diff --git a/naked-objects/pom.xml b/naked-objects/pom.xml index aad8a360dfb8..c5b1880984f4 100644 --- a/naked-objects/pom.xml +++ b/naked-objects/pom.xml @@ -15,7 +15,7 @@ java-design-patterns com.iluwatar - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT naked-objects @@ -350,17 +350,17 @@ ${project.groupId} naked-objects-dom - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT ${project.groupId} naked-objects-fixture - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT ${project.groupId} naked-objects-webapp - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT diff --git a/naked-objects/webapp/pom.xml b/naked-objects/webapp/pom.xml index 4935bd3ef5aa..ad43bf91f1ba 100644 --- a/naked-objects/webapp/pom.xml +++ b/naked-objects/webapp/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT naked-objects-webapp diff --git a/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java b/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java index c7bbd8c8018b..a292a7779575 100644 --- a/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java +++ b/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java @@ -74,7 +74,7 @@ public class SimpleApplication extends IsisWicketApplication { *

* for demos only, obvious. */ - private final static boolean DEMO_MODE_USING_CREDENTIALS_AS_QUERYARGS = false; + private static final boolean DEMO_MODE_USING_CREDENTIALS_AS_QUERYARGS = false; @Override @@ -116,6 +116,7 @@ public WebRequest newWebRequest(HttpServletRequest servletRequest, String filter servletRequest.getSession().invalidate(); } } catch (Exception e) { + System.out.println(e); } WebRequest request = super.newWebRequest(servletRequest, filterPath); return request; diff --git a/null-object/index.md b/null-object/index.md index 5127e85659dc..b5fb279db6a4 100644 --- a/null-object/index.md +++ b/null-object/index.md @@ -4,10 +4,13 @@ title: Null Object folder: null-object permalink: /patterns/null-object/ categories: Behavioral -tags: Java +tags: + - Java + - Difficulty-Beginner --- -**Intent:** In most object-oriented languages, such as Java or C#, references +## Intent +In most object-oriented languages, such as Java or C#, references may be null. These references need to be checked to ensure they are not null before invoking any methods, because methods typically cannot be invoked on null references. Instead of using a null reference to convey absence of an @@ -18,6 +21,7 @@ Object is very predictable and has no side effects: it does nothing. ![alt text](./etc/null-object.png "Null Object") -**Applicability:** Use the Null Object pattern when +## Applicability +Use the Null Object pattern when * you want to avoid explicit null checks and keep the algorithm elegant and easy to read. diff --git a/null-object/pom.xml b/null-object/pom.xml index 597dad233497..c7312f22702a 100644 --- a/null-object/pom.xml +++ b/null-object/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT null-object @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java b/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java index 5de258890022..4478b9bfad8a 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java @@ -11,6 +11,9 @@ public class NodeImpl implements Node { private final Node left; private final Node right; + /** + * Constructor + */ public NodeImpl(String name, Node left, Node right) { this.name = name; this.left = left; diff --git a/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java b/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java index 58f03da28d35..0231c7b1a1dd 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.nullobject.App; - /** * * Application test diff --git a/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java b/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java new file mode 100644 index 000000000000..2bb9a1b4aad2 --- /dev/null +++ b/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java @@ -0,0 +1,43 @@ +package com.iluwatar.nullobject; + +import org.junit.Test; +import org.mockito.Mockito; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * Date: 12/26/15 - 11:47 PM + * + * @author Jeroen Meulemeester + */ +public class NullNodeTest extends StdOutTest { + + /** + * Verify if {@link NullNode#getInstance()} actually returns the same object instance + */ + @Test + public void testGetInstance() { + final NullNode instance = NullNode.getInstance(); + assertNotNull(instance); + assertSame(instance, NullNode.getInstance()); + } + + @Test + public void testFields() { + final NullNode node = NullNode.getInstance(); + assertEquals(0, node.getTreeSize()); + assertNull(node.getName()); + assertNull(node.getLeft()); + assertNull(node.getRight()); + } + + @Test + public void testWalk() throws Exception { + NullNode.getInstance().walk(); + Mockito.verifyZeroInteractions(getStdOutMock()); + } + +} \ No newline at end of file diff --git a/null-object/src/test/java/com/iluwatar/nullobject/StdOutTest.java b/null-object/src/test/java/com/iluwatar/nullobject/StdOutTest.java new file mode 100644 index 000000000000..5a9bae163a0f --- /dev/null +++ b/null-object/src/test/java/com/iluwatar/nullobject/StdOutTest.java @@ -0,0 +1,54 @@ +package com.iluwatar.nullobject; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since walking through the tree has no + * influence on any other accessible object, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java b/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java new file mode 100644 index 000000000000..5d796858415e --- /dev/null +++ b/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java @@ -0,0 +1,101 @@ +package com.iluwatar.nullobject; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +/** + * Date: 12/26/15 - 11:44 PM + * + * @author Jeroen Meulemeester + */ +public class TreeTest extends StdOutTest { + + /** + * During the tests, the same tree structure will be used, shown below. End points will be + * terminated with the {@link NullNode} instance. + * + *

+   * root
+   * ├── level1_a
+   * │   ├── level2_a
+   * │   │   ├── level3_a
+   * │   │   └── level3_b
+   * │   └── level2_b
+   * └── level1_b
+   * 
+ */ + private static final Node TREE_ROOT; + + static { + final NodeImpl level1B = new NodeImpl("level1_b", NullNode.getInstance(), NullNode.getInstance()); + final NodeImpl level2B = new NodeImpl("level2_b", NullNode.getInstance(), NullNode.getInstance()); + final NodeImpl level3A = new NodeImpl("level3_a", NullNode.getInstance(), NullNode.getInstance()); + final NodeImpl level3B = new NodeImpl("level3_b", NullNode.getInstance(), NullNode.getInstance()); + final NodeImpl level2A = new NodeImpl("level2_a", level3A, level3B); + final NodeImpl level1A = new NodeImpl("level1_a", level2A, level2B); + TREE_ROOT = new NodeImpl("root", level1A, level1B); + } + + /** + * Verify the number of items in the tree. The root has 6 children so we expect a {@link + * Node#getTreeSize()} of 7 {@link Node}s in total. + */ + @Test + public void testTreeSize() { + assertEquals(7, TREE_ROOT.getTreeSize()); + } + + /** + * Walk through the tree and verify if every item is handled + */ + @Test + public void testWalk() { + TREE_ROOT.walk(); + + final InOrder inOrder = Mockito.inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("root"); + inOrder.verify(getStdOutMock()).println("level1_a"); + inOrder.verify(getStdOutMock()).println("level2_a"); + inOrder.verify(getStdOutMock()).println("level3_a"); + inOrder.verify(getStdOutMock()).println("level3_b"); + inOrder.verify(getStdOutMock()).println("level2_b"); + inOrder.verify(getStdOutMock()).println("level1_b"); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testGetLeft() throws Exception { + final Node level1 = TREE_ROOT.getLeft(); + assertNotNull(level1); + assertEquals("level1_a", level1.getName()); + assertEquals(5, level1.getTreeSize()); + + final Node level2 = level1.getLeft(); + assertNotNull(level2); + assertEquals("level2_a", level2.getName()); + assertEquals(3, level2.getTreeSize()); + + final Node level3 = level2.getLeft(); + assertNotNull(level3); + assertEquals("level3_a", level3.getName()); + assertEquals(1, level3.getTreeSize()); + assertSame(NullNode.getInstance(), level3.getRight()); + assertSame(NullNode.getInstance(), level3.getLeft()); + } + + @Test + public void testGetRight() throws Exception { + final Node level1 = TREE_ROOT.getRight(); + assertNotNull(level1); + assertEquals("level1_b", level1.getName()); + assertEquals(1, level1.getTreeSize()); + assertSame(NullNode.getInstance(), level1.getRight()); + assertSame(NullNode.getInstance(), level1.getLeft()); + } + +} diff --git a/object-pool/index.md b/object-pool/index.md index 276d9a1f6aba..cf36d98809e7 100644 --- a/object-pool/index.md +++ b/object-pool/index.md @@ -4,17 +4,22 @@ title: Object Pool folder: object-pool permalink: /patterns/object-pool/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Beginner + - Performance --- -**Intent:** When objects are expensive to create and they are needed only for +## Intent +When objects are expensive to create and they are needed only for short periods of time it is advantageous to utilize the Object Pool pattern. The Object Pool provides a cache for instantiated objects tracking which ones are in use and which are available. ![alt text](./etc/object-pool.png "Object Pool") -**Applicability:** Use the Object Pool pattern when +## Applicability +Use the Object Pool pattern when * the objects are expensive to create (allocation cost) * you need a large number of short-lived objects (memory fragmentation) diff --git a/object-pool/pom.xml b/object-pool/pom.xml index 75bc894e877b..c03d025687d2 100644 --- a/object-pool/pom.xml +++ b/object-pool/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT object-pool diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java b/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java index 79d7d6345cc5..d85955f0af29 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java @@ -5,8 +5,6 @@ /** * * Generic object pool - * - * @param */ public abstract class ObjectPool { @@ -15,6 +13,9 @@ public abstract class ObjectPool { protected abstract T create(); + /** + * Checkout object from pool + */ public synchronized T checkOut() { if (available.size() <= 0) { available.add(create()); diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java b/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java index aeefd6b3a537..f3923fff588a 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java @@ -11,6 +11,9 @@ public class Oliphaunt { private final int id; + /** + * Constructor + */ public Oliphaunt() { id = counter++; try { diff --git a/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java b/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java index fd2920d88519..b36a7e4a167f 100644 --- a/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java +++ b/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.object.pool.App; - /** * * Application test diff --git a/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java b/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java new file mode 100644 index 000000000000..347e0b4c9090 --- /dev/null +++ b/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java @@ -0,0 +1,99 @@ +package com.iluwatar.object.pool; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/27/15 - 1:05 AM + * + * @author Jeroen Meulemeester + */ +public class OliphauntPoolTest { + + /** + * Use the same object 100 times subsequently. This should not take much time since the heavy + * object instantiation is done only once. Verify if we get the same object each time. + */ + @Test(timeout = 5000) + public void testSubsequentCheckinCheckout() { + final OliphauntPool pool = new OliphauntPool(); + assertEquals(pool.toString(), "Pool available=0 inUse=0"); + + final Oliphaunt expectedOliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=1"); + + pool.checkIn(expectedOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=0"); + + for (int i = 0; i < 100; i++) { + final Oliphaunt oliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=1"); + assertSame(expectedOliphaunt, oliphaunt); + assertEquals(expectedOliphaunt.getId(), oliphaunt.getId()); + assertEquals(expectedOliphaunt.toString(), oliphaunt.toString()); + + pool.checkIn(oliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=0"); + } + + } + + /** + * Use the same object 100 times subsequently. This should not take much time since the heavy + * object instantiation is done only once. Verify if we get the same object each time. + */ + @Test(timeout = 5000) + public void testConcurrentCheckinCheckout() { + final OliphauntPool pool = new OliphauntPool(); + assertEquals(pool.toString(), "Pool available=0 inUse=0"); + + final Oliphaunt firstOliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=1"); + + final Oliphaunt secondOliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + + assertNotSame(firstOliphaunt, secondOliphaunt); + assertEquals(firstOliphaunt.getId() + 1, secondOliphaunt.getId()); + + // After checking in the second, we should get the same when checking out a new oliphaunt ... + pool.checkIn(secondOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + final Oliphaunt oliphaunt3 = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertSame(secondOliphaunt, oliphaunt3); + + // ... and the same applies for the first one + pool.checkIn(firstOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + final Oliphaunt oliphaunt4 = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertSame(firstOliphaunt, oliphaunt4); + + // When both oliphaunt return to the pool, we should still get the same instances + pool.checkIn(firstOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + pool.checkIn(secondOliphaunt); + assertEquals(pool.toString(), "Pool available=2 inUse=0"); + + // The order of the returned instances is not determined, so just put them in a list + // and verify if both expected instances are in there. + final List oliphaunts = Arrays.asList(pool.checkOut(), pool.checkOut()); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertTrue(oliphaunts.contains(firstOliphaunt)); + assertTrue(oliphaunts.contains(secondOliphaunt)); + + } + + +} \ No newline at end of file diff --git a/observer/index.md b/observer/index.md index ea1667ef5609..7e83e899d1e6 100644 --- a/observer/index.md +++ b/observer/index.md @@ -10,28 +10,31 @@ tags: - Gang Of Four --- -**Also known as:** Dependents, Publish-Subscribe +## Also known as +Dependents, Publish-Subscribe -**Intent:** Define a one-to-many dependency between objects so that when one +## Intent +Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. ![alt text](./etc/observer_1.png "Observer") -**Applicability:** Use the Observer pattern in any of the following situations +## Applicability +Use the Observer pattern in any of the following situations * when an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently * when a change to one object requires changing others, and you don't know how many objects need to be changed * when an object should be able to notify other objects without making assumptions about who these objects are. In other words, you don't want these objects tightly coupled -**Typical Use Case:** +## Typical Use Case * changing in one object leads to a change in other objects -**Real world examples:** +## Real world examples * [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/observer/pom.xml b/observer/pom.xml index a3dd25d85393..a824a551e354 100644 --- a/observer/pom.xml +++ b/observer/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT observer @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/observer/src/main/java/com/iluwatar/observer/Weather.java b/observer/src/main/java/com/iluwatar/observer/Weather.java index 6349539453b0..4e04143a2a6b 100644 --- a/observer/src/main/java/com/iluwatar/observer/Weather.java +++ b/observer/src/main/java/com/iluwatar/observer/Weather.java @@ -27,6 +27,9 @@ public void removeObserver(WeatherObserver obs) { observers.remove(obs); } + /** + * Makes time pass for weather + */ public void timePasses() { WeatherType[] enumValues = WeatherType.values(); currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; diff --git a/observer/src/main/java/com/iluwatar/observer/generic/GWeather.java b/observer/src/main/java/com/iluwatar/observer/generic/GWeather.java index 9d1c6ed077f2..d503c84217f3 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/GWeather.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/GWeather.java @@ -15,6 +15,9 @@ public GWeather() { currentWeather = WeatherType.SUNNY; } + /** + * Makes time pass for weather + */ public void timePasses() { WeatherType[] enumValues = WeatherType.values(); currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; diff --git a/observer/src/main/java/com/iluwatar/observer/generic/Observable.java b/observer/src/main/java/com/iluwatar/observer/generic/Observable.java index f1ad2dca6777..e764245b7ae3 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/Observable.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/Observable.java @@ -22,6 +22,13 @@ public void addObserver(O observer) { this.observers.add(observer); } + public void removeObserver(O observer) { + this.observers.remove(observer); + } + + /** + * Notify observers + */ @SuppressWarnings("unchecked") public void notifyObservers(A argument) { for (O observer : observers) { diff --git a/observer/src/main/java/com/iluwatar/observer/generic/Observer.java b/observer/src/main/java/com/iluwatar/observer/generic/Observer.java index b0195541917b..34b9ac359898 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/Observer.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/Observer.java @@ -3,10 +3,6 @@ /** * * Observer - * - * @param - * @param - * @param */ public interface Observer, O extends Observer, A> { diff --git a/observer/src/test/java/com/iluwatar/observer/AppTest.java b/observer/src/test/java/com/iluwatar/observer/AppTest.java index 65976626d799..d41acad33871 100644 --- a/observer/src/test/java/com/iluwatar/observer/AppTest.java +++ b/observer/src/test/java/com/iluwatar/observer/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.observer.App; - /** * * Application test diff --git a/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java b/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java new file mode 100644 index 000000000000..3571ced63fa4 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java @@ -0,0 +1,37 @@ +package com.iluwatar.observer; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Date: 12/27/15 - 12:07 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class HobbitsTest extends WeatherObserverTest { + + @Parameterized.Parameters + public static Collection data() { + final ArrayList testData = new ArrayList<>(); + testData.add(new Object[]{WeatherType.SUNNY, "The happy hobbits bade in the warm sun."}); + testData.add(new Object[]{WeatherType.RAINY, "The hobbits look for cover from the rain."}); + testData.add(new Object[]{WeatherType.WINDY, "The hobbits hold their hats tightly in the windy weather."}); + testData.add(new Object[]{WeatherType.COLD, "The hobbits are shivering in the cold weather."}); + return testData; + } + + /** + * Create a new test with the given weather and expected response + * + * @param weather The weather that should be unleashed on the observer + * @param response The expected response from the observer + */ + public HobbitsTest(final WeatherType weather, final String response) { + super(weather, response, Hobbits::new); + } + +} diff --git a/observer/src/test/java/com/iluwatar/observer/OrcsTest.java b/observer/src/test/java/com/iluwatar/observer/OrcsTest.java new file mode 100644 index 000000000000..a59288ab2fdf --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/OrcsTest.java @@ -0,0 +1,37 @@ +package com.iluwatar.observer; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Date: 12/27/15 - 12:07 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class OrcsTest extends WeatherObserverTest { + + @Parameterized.Parameters + public static Collection data() { + final ArrayList testData = new ArrayList<>(); + testData.add(new Object[]{WeatherType.SUNNY, "The sun hurts the orcs' eyes."}); + testData.add(new Object[]{WeatherType.RAINY, "The orcs are dripping wet."}); + testData.add(new Object[]{WeatherType.WINDY, "The orc smell almost vanishes in the wind."}); + testData.add(new Object[]{WeatherType.COLD, "The orcs are freezing cold."}); + return testData; + } + + /** + * Create a new test with the given weather and expected response + * + * @param weather The weather that should be unleashed on the observer + * @param response The expected response from the observer + */ + public OrcsTest(final WeatherType weather, final String response) { + super(weather, response, Orcs::new); + } + +} diff --git a/observer/src/test/java/com/iluwatar/observer/StdOutTest.java b/observer/src/test/java/com/iluwatar/observer/StdOutTest.java new file mode 100644 index 000000000000..3ea0bb119e75 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/StdOutTest.java @@ -0,0 +1,54 @@ +package com.iluwatar.observer; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/27/15 - 12:16 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since changes in the weather doesn't has + * any influence on any other accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + protected final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java b/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java new file mode 100644 index 000000000000..e4d6a44300ad --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java @@ -0,0 +1,59 @@ +package com.iluwatar.observer; + +import org.junit.Test; + +import java.util.function.Supplier; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/27/15 - 11:44 AM + * + * @author Jeroen Meulemeester + */ +public abstract class WeatherObserverTest extends StdOutTest { + + /** + * The observer instance factory + */ + private final Supplier factory; + + /** + * The weather type currently tested + */ + private final WeatherType weather; + + /** + * The expected response from the observer + */ + private final String response; + + /** + * Create a new test instance using the given parameters + * + * @param weather The weather currently being tested + * @param response The expected response from the observer + * @param factory The factory, used to create an instance of the tested observer + */ + WeatherObserverTest(final WeatherType weather, final String response, final Supplier factory) { + this.weather = weather; + this.response = response; + this.factory = factory; + } + + /** + * Verify if the weather has the expected influence on the observer + */ + @Test + public void testObserver() { + final O observer = this.factory.get(); + verifyZeroInteractions(getStdOutMock()); + + observer.update(this.weather); + verify(getStdOutMock()).println(this.response); + verifyNoMoreInteractions(getStdOutMock()); + } + +} \ No newline at end of file diff --git a/observer/src/test/java/com/iluwatar/observer/WeatherTest.java b/observer/src/test/java/com/iluwatar/observer/WeatherTest.java new file mode 100644 index 000000000000..a195be526254 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/WeatherTest.java @@ -0,0 +1,61 @@ +package com.iluwatar.observer; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/27/15 - 11:08 AM + * + * @author Jeroen Meulemeester + */ +public class WeatherTest extends StdOutTest { + + /** + * Add a {@link WeatherObserver}, verify if it gets notified of a weather change, remove the + * observer again and verify that there are no more notifications. + */ + @Test + public void testAddRemoveObserver() { + final WeatherObserver observer = mock(WeatherObserver.class); + + final Weather weather = new Weather(); + weather.addObserver(observer); + verifyZeroInteractions(observer); + + weather.timePasses(); + verify(getStdOutMock()).println("The weather changed to rainy."); + verify(observer).update(WeatherType.RAINY); + + weather.removeObserver(observer); + weather.timePasses(); + verify(getStdOutMock()).println("The weather changed to windy."); + + verifyNoMoreInteractions(observer, getStdOutMock()); + } + + /** + * Verify if the weather passes in the order of the {@link WeatherType}s + */ + @Test + public void testTimePasses() { + final WeatherObserver observer = mock(WeatherObserver.class); + final Weather weather = new Weather(); + weather.addObserver(observer); + + final InOrder inOrder = inOrder(observer, getStdOutMock()); + final WeatherType[] weatherTypes = WeatherType.values(); + for (int i = 1; i < 20; i++) { + weather.timePasses(); + inOrder.verify(observer).update(weatherTypes[i % weatherTypes.length]); + } + + verifyNoMoreInteractions(observer); + } + +} \ No newline at end of file diff --git a/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java b/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java new file mode 100644 index 000000000000..6e955cf541b0 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java @@ -0,0 +1,41 @@ +package com.iluwatar.observer.generic; + +import com.iluwatar.observer.Hobbits; +import com.iluwatar.observer.WeatherObserverTest; +import com.iluwatar.observer.WeatherType; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Date: 12/27/15 - 12:07 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class GHobbitsTest extends ObserverTest { + + @Parameterized.Parameters + public static Collection data() { + final ArrayList testData = new ArrayList<>(); + testData.add(new Object[]{WeatherType.SUNNY, "The happy hobbits bade in the warm sun."}); + testData.add(new Object[]{WeatherType.RAINY, "The hobbits look for cover from the rain."}); + testData.add(new Object[]{WeatherType.WINDY, "The hobbits hold their hats tightly in the windy weather."}); + testData.add(new Object[]{WeatherType.COLD, "The hobbits are shivering in the cold weather."}); + return testData; + } + + /** + * Create a new test with the given weather and expected response + * + * @param weather The weather that should be unleashed on the observer + * @param response The expected response from the observer + */ + public GHobbitsTest(final WeatherType weather, final String response) { + super(weather, response, GHobbits::new); + } + +} diff --git a/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java b/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java new file mode 100644 index 000000000000..b7a5381671a7 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java @@ -0,0 +1,65 @@ +package com.iluwatar.observer.generic; + +import com.iluwatar.observer.StdOutTest; +import com.iluwatar.observer.WeatherObserver; +import com.iluwatar.observer.WeatherType; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/27/15 - 11:08 AM + * + * @author Jeroen Meulemeester + */ +public class GWeatherTest extends StdOutTest { + + /** + * Add a {@link WeatherObserver}, verify if it gets notified of a weather change, remove the + * observer again and verify that there are no more notifications. + */ + @Test + public void testAddRemoveObserver() { + final Race observer = mock(Race.class); + + final GWeather weather = new GWeather(); + weather.addObserver(observer); + verifyZeroInteractions(observer); + + weather.timePasses(); + verify(getStdOutMock()).println("The weather changed to rainy."); + verify(observer).update(weather, WeatherType.RAINY); + + weather.removeObserver(observer); + weather.timePasses(); + verify(getStdOutMock()).println("The weather changed to windy."); + + verifyNoMoreInteractions(observer, getStdOutMock()); + } + + /** + * Verify if the weather passes in the order of the {@link WeatherType}s + */ + @Test + public void testTimePasses() { + final Race observer = mock(Race.class); + final GWeather weather = new GWeather(); + weather.addObserver(observer); + + final InOrder inOrder = inOrder(observer, getStdOutMock()); + final WeatherType[] weatherTypes = WeatherType.values(); + for (int i = 1; i < 20; i++) { + weather.timePasses(); + inOrder.verify(observer).update(weather, weatherTypes[i % weatherTypes.length]); + } + + verifyNoMoreInteractions(observer); + } + +} \ No newline at end of file diff --git a/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java b/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java new file mode 100644 index 000000000000..2e664bd580da --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java @@ -0,0 +1,62 @@ +package com.iluwatar.observer.generic; + +import com.iluwatar.observer.StdOutTest; +import com.iluwatar.observer.WeatherType; + +import org.junit.Test; + +import java.util.function.Supplier; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/27/15 - 11:44 AM + * + * @author Jeroen Meulemeester + */ +public abstract class ObserverTest extends StdOutTest { + + /** + * The observer instance factory + */ + private final Supplier factory; + + /** + * The weather type currently tested + */ + private final WeatherType weather; + + /** + * The expected response from the observer + */ + private final String response; + + /** + * Create a new test instance using the given parameters + * + * @param weather The weather currently being tested + * @param response The expected response from the observer + * @param factory The factory, used to create an instance of the tested observer + */ + ObserverTest(final WeatherType weather, final String response, final Supplier factory) { + this.weather = weather; + this.response = response; + this.factory = factory; + } + + /** + * Verify if the weather has the expected influence on the observer + */ + @Test + public void testObserver() { + final O observer = this.factory.get(); + verifyZeroInteractions(getStdOutMock()); + + observer.update(null, this.weather); + verify(getStdOutMock()).println(this.response); + verifyNoMoreInteractions(getStdOutMock()); + } + +} \ No newline at end of file diff --git a/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java b/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java new file mode 100644 index 000000000000..508380970c59 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java @@ -0,0 +1,39 @@ +package com.iluwatar.observer.generic; + +import com.iluwatar.observer.WeatherType; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Date: 12/27/15 - 12:07 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class OrcsTest extends ObserverTest { + + @Parameterized.Parameters + public static Collection data() { + final ArrayList testData = new ArrayList<>(); + testData.add(new Object[]{WeatherType.SUNNY, "The sun hurts the orcs' eyes."}); + testData.add(new Object[]{WeatherType.RAINY, "The orcs are dripping wet."}); + testData.add(new Object[]{WeatherType.WINDY, "The orc smell almost vanishes in the wind."}); + testData.add(new Object[]{WeatherType.COLD, "The orcs are freezing cold."}); + return testData; + } + + /** + * Create a new test with the given weather and expected response + * + * @param weather The weather that should be unleashed on the observer + * @param response The expected response from the observer + */ + public OrcsTest(final WeatherType weather, final String response) { + super(weather, response, GOrcs::new); + } + +} diff --git a/poison-pill/index.md b/poison-pill/index.md index cd60e1a68b88..eb37c5ada9f1 100644 --- a/poison-pill/index.md +++ b/poison-pill/index.md @@ -4,18 +4,22 @@ title: Poison Pill folder: poison-pill permalink: /patterns/poison-pill/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Intermediate --- -**Intent:** Poison Pill is known predefined data item that allows to provide +## Intent +Poison Pill is known predefined data item that allows to provide graceful shutdown for separate distributed consumption process. ![alt text](./etc/poison-pill.png "Poison Pill") -**Applicability:** Use the Poison Pill idiom when +## Applicability +Use the Poison Pill idiom when * need to send signal from one thread/process to another to terminate -**Real world examples:** +## Real world examples * [akka.actor.PoisonPill](http://doc.akka.io/docs/akka/2.1.4/java/untyped-actors.html) diff --git a/poison-pill/pom.xml b/poison-pill/pom.xml index 340445414d6c..c416fe0be0c2 100644 --- a/poison-pill/pom.xml +++ b/poison-pill/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT poison-pill @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java index ff06d7d5ba3c..4530ef953c10 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java @@ -7,20 +7,23 @@ */ public class Consumer { - private final MQSubscribePoint queue; + private final MqSubscribePoint queue; private final String name; - public Consumer(String name, MQSubscribePoint queue) { + public Consumer(String name, MqSubscribePoint queue) { this.name = name; this.queue = queue; } + /** + * Consume message + */ public void consume() { while (true) { Message msg; try { msg = queue.take(); - if (msg == Message.POISON_PILL) { + if (Message.POISON_PILL.equals(msg)) { System.out.println(String.format("Consumer %s receive request to terminate.", name)); break; } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java index b0fc6d6dcb0a..4f253376e5b5 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java @@ -8,7 +8,7 @@ */ public interface Message { - public static final Message POISON_PILL = new Message() { + Message POISON_PILL = new Message() { @Override public void addHeader(Headers header, String value) { @@ -45,13 +45,13 @@ public enum Headers { DATE, SENDER } - public void addHeader(Headers header, String value); + void addHeader(Headers header, String value); - public String getHeader(Headers header); + String getHeader(Headers header); - public Map getHeaders(); + Map getHeaders(); - public void setBody(String body); + void setBody(String body); - public String getBody(); + String getBody(); } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java index 99231ab51390..aa0d3699de54 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java @@ -3,6 +3,6 @@ /** * Represents abstraction of channel (or pipe) that bounds {@link Producer} and {@link Consumer} */ -public interface MessageQueue extends MQPublishPoint, MQSubscribePoint { +public interface MessageQueue extends MqPublishPoint, MqSubscribePoint { } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MQPublishPoint.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java similarity index 50% rename from poison-pill/src/main/java/com/iluwatar/poison/pill/MQPublishPoint.java rename to poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java index a266d9f4d7f3..9a9558e4c172 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MQPublishPoint.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java @@ -3,7 +3,7 @@ /** * Endpoint to publish {@link Message} to queue */ -public interface MQPublishPoint { +public interface MqPublishPoint { - public void put(Message msg) throws InterruptedException; + void put(Message msg) throws InterruptedException; } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MQSubscribePoint.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java similarity index 52% rename from poison-pill/src/main/java/com/iluwatar/poison/pill/MQSubscribePoint.java rename to poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java index c093b1412e1c..03623edbcfe0 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MQSubscribePoint.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java @@ -3,7 +3,7 @@ /** * Endpoint to retrieve {@link Message} from queue */ -public interface MQSubscribePoint { +public interface MqSubscribePoint { - public Message take() throws InterruptedException; + Message take() throws InterruptedException; } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java index ecde39e35bc9..5405de869361 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java @@ -10,16 +10,22 @@ */ public class Producer { - private final MQPublishPoint queue; + private final MqPublishPoint queue; private final String name; private boolean isStopped; - public Producer(String name, MQPublishPoint queue) { + /** + * Constructor + */ + public Producer(String name, MqPublishPoint queue) { this.name = name; this.queue = queue; this.isStopped = false; } + /** + * Send message to queue + */ public void send(String body) { if (isStopped) { throw new IllegalStateException(String.format( @@ -38,6 +44,9 @@ public void send(String body) { } } + /** + * Stop system by sending poison pill + */ public void stop() { isStopped = true; try { diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java index c9b619016648..20861ded18bf 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.poison.pill.App; - /** * * Application test diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java new file mode 100644 index 000000000000..c152fbbd25f1 --- /dev/null +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java @@ -0,0 +1,56 @@ +package com.iluwatar.poison.pill; + +import org.junit.Test; +import org.mockito.InOrder; + +import java.time.LocalDateTime; + +import static org.mockito.Mockito.inOrder; + +/** + * Date: 12/27/15 - 9:45 PM + * + * @author Jeroen Meulemeester + */ +public class ConsumerTest extends StdOutTest { + + @Test + public void testConsume() throws Exception { + final Message[] messages = new Message[]{ + createMessage("you", "Hello!"), + createMessage("me", "Hi!"), + Message.POISON_PILL, + createMessage("late_for_the_party", "Hello? Anyone here?"), + }; + + final MessageQueue queue = new SimpleMessageQueue(messages.length); + for (final Message message : messages) { + queue.put(message); + } + + new Consumer("NSA", queue).consume(); + + final InOrder inOrder = inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("Message [Hello!] from [you] received by [NSA]"); + inOrder.verify(getStdOutMock()).println("Message [Hi!] from [me] received by [NSA]"); + inOrder.verify(getStdOutMock()).println("Consumer NSA receive request to terminate."); + inOrder.verifyNoMoreInteractions(); + + } + + /** + * Create a new message from the given sender with the given message body + * + * @param sender The sender's name + * @param message The message body + * @return The message instance + */ + private static Message createMessage(final String sender, final String message) { + final SimpleMessage msg = new SimpleMessage(); + msg.addHeader(Message.Headers.SENDER, sender); + msg.addHeader(Message.Headers.DATE, LocalDateTime.now().toString()); + msg.setBody(message); + return msg; + } + +} \ No newline at end of file diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java new file mode 100644 index 000000000000..9fb733aad768 --- /dev/null +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java @@ -0,0 +1,40 @@ +package com.iluwatar.poison.pill; + +import org.junit.Test; + +import static com.iluwatar.poison.pill.Message.Headers; +import static com.iluwatar.poison.pill.Message.POISON_PILL; + +/** + * Date: 12/27/15 - 10:30 PM + * + * @author Jeroen Meulemeester + */ +public class PoisonMessageTest { + + @Test(expected = UnsupportedOperationException.class) + public void testAddHeader() throws Exception { + POISON_PILL.addHeader(Headers.SENDER, "sender"); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetHeader() throws Exception { + POISON_PILL.getHeader(Headers.SENDER); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetHeaders() throws Exception { + POISON_PILL.getHeaders(); + } + + @Test(expected = UnsupportedOperationException.class) + public void testSetBody() throws Exception { + POISON_PILL.setBody("Test message."); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetBody() throws Exception { + POISON_PILL.getBody(); + } + +} diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java new file mode 100644 index 000000000000..103020e4a2c9 --- /dev/null +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java @@ -0,0 +1,64 @@ +package com.iluwatar.poison.pill; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/27/15 - 10:32 PM + * + * @author Jeroen Meulemeester + */ +public class ProducerTest { + + @Test + public void testSend() throws Exception { + final MqPublishPoint publishPoint = mock(MqPublishPoint.class); + final Producer producer = new Producer("producer", publishPoint); + verifyZeroInteractions(publishPoint); + + producer.send("Hello!"); + + final ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + verify(publishPoint).put(messageCaptor.capture()); + + final Message message = messageCaptor.getValue(); + assertNotNull(message); + assertEquals("producer", message.getHeader(Message.Headers.SENDER)); + assertNotNull(message.getHeader(Message.Headers.DATE)); + assertEquals("Hello!", message.getBody()); + + verifyNoMoreInteractions(publishPoint); + } + + @Test + public void testStop() throws Exception { + final MqPublishPoint publishPoint = mock(MqPublishPoint.class); + final Producer producer = new Producer("producer", publishPoint); + verifyZeroInteractions(publishPoint); + + producer.stop(); + verify(publishPoint).put(eq(Message.POISON_PILL)); + + try { + producer.send("Hello!"); + fail("Expected 'IllegalStateException' at this point, since the producer has stopped!"); + } catch (IllegalStateException e) { + assertNotNull(e); + assertNotNull(e.getMessage()); + assertEquals("Producer Hello! was stopped and fail to deliver requested message [producer].", + e.getMessage()); + } + + verifyNoMoreInteractions(publishPoint); + } + +} diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java new file mode 100644 index 000000000000..f5c348e2bc6b --- /dev/null +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java @@ -0,0 +1,40 @@ +package com.iluwatar.poison.pill; + +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/27/15 - 10:25 PM + * + * @author Jeroen Meulemeester + */ +public class SimpleMessageTest { + + @Test + public void testGetHeaders() { + final SimpleMessage message = new SimpleMessage(); + assertNotNull(message.getHeaders()); + assertTrue(message.getHeaders().isEmpty()); + + final String senderName = "test"; + message.addHeader(Message.Headers.SENDER, senderName); + assertNotNull(message.getHeaders()); + assertFalse(message.getHeaders().isEmpty()); + assertEquals(senderName, message.getHeaders().get(Message.Headers.SENDER)); + } + + @Test(expected = UnsupportedOperationException.class) + public void testUnModifiableHeaders() { + final SimpleMessage message = new SimpleMessage(); + final Map headers = message.getHeaders(); + headers.put(Message.Headers.SENDER, "test"); + } + + +} \ No newline at end of file diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/StdOutTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/StdOutTest.java new file mode 100644 index 000000000000..9c533b5c2734 --- /dev/null +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.poison.pill; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/pom.xml b/pom.xml index e6603b06c58e..d2b02fdf79f7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,23 +5,25 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT pom UTF-8 5.0.1.Final - 4.1.7.RELEASE - 1.9.0.RELEASE - 1.4.188 + 4.2.4.RELEASE + 1.9.2.RELEASE + 1.4.190 4.12 3.0 4.0.0 0.7.2.201409121644 1.4 - 2.15.3 + 2.16.1 1.2.17 - 18.0 + 19.0 + 1.15.1 + 1.10.19 abstract-factory @@ -60,6 +62,7 @@ intercepting-filter producer-consumer poison-pill + reader-writer-lock lazy-loading service-layer specification @@ -78,16 +81,18 @@ front-controller repository async-method-invocation - monostate + monostate step-builder - business-delegate - half-sync-half-async + business-delegate + half-sync-half-async layers message-channel fluentinterface reactor caching publish-subscribe + delegation + event-driven-architecture @@ -141,7 +146,7 @@ org.mockito mockito-core - 1.10.19 + ${mockito.version} test @@ -154,6 +159,12 @@ guava ${guava.version} + + com.github.stefanbirkner + system-rules + ${systemrules.version} + test + @@ -244,7 +255,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.15 + 2.17 validate @@ -254,9 +265,11 @@ validate checkstyle.xml + checkstyle-suppressions.xml UTF-8 - false - false + true + true + true @@ -296,7 +309,37 @@ -Xmx1024M ${argLine} + + org.apache.maven.plugins + maven-pmd-plugin + 3.6 + + true + 5 + true + + + + + check + + + exclude-pmd.properties + + + + + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.6 + + + + diff --git a/private-class-data/index.md b/private-class-data/index.md index 83c95d3086e1..981208fa3f0e 100644 --- a/private-class-data/index.md +++ b/private-class-data/index.md @@ -4,15 +4,20 @@ title: Private Class Data folder: private-class-data permalink: /patterns/private-class-data/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Beginner + - Idiom --- -**Intent:** Private Class Data design pattern seeks to reduce exposure of +## Intent +Private Class Data design pattern seeks to reduce exposure of attributes by limiting their visibility. It reduces the number of class attributes by encapsulating them in single Data object. ![alt text](./etc/private-class-data.png "Private Class Data") -**Applicability:** Use the Private Class Data pattern when +## Applicability +Use the Private Class Data pattern when * you want to prevent write access to class data members diff --git a/private-class-data/pom.xml b/private-class-data/pom.xml index 362e061f1761..e783ac3158f2 100644 --- a/private-class-data/pom.xml +++ b/private-class-data/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT private-class-data @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java index 599a804079fc..849c2413c247 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java @@ -13,6 +13,9 @@ public ImmutableStew(int numPotatoes, int numCarrots, int numMeat, int numPepper data = new StewData(numPotatoes, numCarrots, numMeat, numPeppers); } + /** + * Mix the stew + */ public void mix() { System.out.println(String.format( "Mixing the immutable stew we find: %d potatoes, %d carrots, %d meat and %d peppers", diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java index 9deb32e16f3e..2efd0b4ee4e1 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java @@ -12,6 +12,9 @@ public class Stew { private int numMeat; private int numPeppers; + /** + * Constructor + */ public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { this.numPotatoes = numPotatoes; this.numCarrots = numCarrots; @@ -19,12 +22,18 @@ public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { this.numPeppers = numPeppers; } + /** + * Mix the stew + */ public void mix() { System.out.println(String.format( "Mixing the stew we find: %d potatoes, %d carrots, %d meat and %d peppers", numPotatoes, numCarrots, numMeat, numPeppers)); } + /** + * Taste the stew + */ public void taste() { System.out.println("Tasting the stew"); if (numPotatoes > 0) { diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java index 23d54ccf4746..0bd62ada3520 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java @@ -12,6 +12,9 @@ public class StewData { private int numMeat; private int numPeppers; + /** + * Constructor + */ public StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { this.numPotatoes = numPotatoes; this.numCarrots = numCarrots; diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java index 6623a43ade40..5fd53d99ff24 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.privateclassdata.App; - /** * * Application test diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java new file mode 100644 index 000000000000..da5335b0fa01 --- /dev/null +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java @@ -0,0 +1,52 @@ +package com.iluwatar.privateclassdata; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verify; + +/** + * Date: 12/27/15 - 10:46 PM + * + * @author Jeroen Meulemeester + */ +public class ImmutableStewTest extends StdOutTest { + + /** + * Verify if mixing the stew doesn't change the internal state + */ + @Test + public void testMix() { + final Stew stew = new Stew(1, 2, 3, 4); + final String message = "Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers"; + + final InOrder inOrder = inOrder(getStdOutMock()); + for (int i = 0; i < 20; i++) { + stew.mix(); + inOrder.verify(getStdOutMock()).println(message); + } + + inOrder.verifyNoMoreInteractions(); + } + + /** + * Verify if tasting the stew actually removes one of each ingredient + */ + @Test + public void testDrink() { + final Stew stew = new Stew(1, 2, 3, 4); + stew.mix(); + + verify(getStdOutMock()) + .println("Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers"); + + stew.taste(); + verify(getStdOutMock()).println("Tasting the stew"); + + stew.mix(); + verify(getStdOutMock()) + .println("Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers"); + + } +} \ No newline at end of file diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/StdOutTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StdOutTest.java new file mode 100644 index 000000000000..91904c31c752 --- /dev/null +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.privateclassdata; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java new file mode 100644 index 000000000000..8e0452fab493 --- /dev/null +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java @@ -0,0 +1,33 @@ +package com.iluwatar.privateclassdata; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; + +/** + * Date: 12/27/15 - 10:46 PM + * + * @author Jeroen Meulemeester + */ +public class StewTest extends StdOutTest { + + /** + * Verify if mixing the stew doesn't change the internal state + */ + @Test + public void testMix() { + final ImmutableStew stew = new ImmutableStew(1, 2, 3, 4); + final String expectedMessage = "Mixing the immutable stew we find: 1 potatoes, " + + "2 carrots, 3 meat and 4 peppers"; + + final InOrder inOrder = inOrder(getStdOutMock()); + for (int i = 0; i < 20; i++) { + stew.mix(); + inOrder.verify(getStdOutMock()).println(expectedMessage); + } + + inOrder.verifyNoMoreInteractions(); + } + +} \ No newline at end of file diff --git a/producer-consumer/index.md b/producer-consumer/index.md index 58dc45e0d241..c666ab12e2c4 100644 --- a/producer-consumer/index.md +++ b/producer-consumer/index.md @@ -3,20 +3,22 @@ layout: pattern title: Producer Consumer folder: producer-consumer permalink: /patterns/producer-consumer/ -categories: Other -tags: Java +categories: Concurrency +tags: + - Java + - Difficulty-Intermediate + - I/O --- -**Intent:** Producer Consumer Design pattern is a classic concurrency or threading pattern which reduces +## Intent +Producer Consumer Design pattern is a classic concurrency pattern which reduces coupling between Producer and Consumer by separating Identification of work with Execution of - Work.. - - + Work. ![alt text](./etc/producer-consumer.png "Producer Consumer") -**Applicability:** Use the Producer Consumer idiom when +## Applicability +Use the Producer Consumer idiom when * decouple system by separate work in two process produce and consume. * addresses the issue of different timing require to produce work or consuming work - diff --git a/producer-consumer/pom.xml b/producer-consumer/pom.xml index 57127210235a..fae5e36d9fe5 100644 --- a/producer-consumer/pom.xml +++ b/producer-consumer/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT producer-consumer @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java index 50d94d92f9f1..63cae9413491 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java @@ -5,14 +5,12 @@ import java.util.concurrent.TimeUnit; /** - * Producer Consumer Design pattern is a classic concurrency or threading pattern which reduces - * coupling between Producer and Consumer by separating Identification of work with Execution of - * Work. + * Producer Consumer Design pattern is a classic concurrency or threading pattern which reduces coupling between + * Producer and Consumer by separating Identification of work with Execution of Work. *

- * In producer consumer design pattern a shared queue is used to control the flow and this - * separation allows you to code producer and consumer separately. It also addresses the issue of - * different timing require to produce item or consuming item. by using producer consumer pattern - * both Producer and Consumer Thread can work with different speed. + * In producer consumer design pattern a shared queue is used to control the flow and this separation allows you to code + * producer and consumer separately. It also addresses the issue of different timing require to produce item or + * consuming item. by using producer consumer pattern both Producer and Consumer Thread can work with different speed. * */ public class App { @@ -20,7 +18,8 @@ public class App { /** * Program entry point * - * @param args command line args + * @param args + * command line args */ public static void main(String[] args) { @@ -35,7 +34,7 @@ public static void main(String[] args) { producer.produce(); } }); - }; + } for (int i = 0; i < 3; i++) { final Consumer consumer = new Consumer("Consumer_" + i, queue); diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java index 8bb3b75b6442..ff63ab41b41b 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java @@ -14,6 +14,9 @@ public Consumer(String name, ItemQueue queue) { this.queue = queue; } + /** + * Consume item from the queue + */ public void consume() throws InterruptedException { Item item = queue.take(); diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java index 40e71c60711c..8b122a5fcfd8 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java @@ -19,6 +19,9 @@ public Producer(String name, ItemQueue queue) { this.queue = queue; } + /** + * Put item in the queue + */ public void produce() throws InterruptedException { Item item = new Item(name, itemId++); diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java index cdd9ad0465af..e82e36da1f26 100644 --- a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.producer.consumer.App; - /** * * Application test diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java new file mode 100644 index 000000000000..4ff203d425dd --- /dev/null +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java @@ -0,0 +1,39 @@ +package com.iluwatar.producer.consumer; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; + +/** + * Date: 12/27/15 - 11:01 PM + * + * @author Jeroen Meulemeester + */ +public class ConsumerTest extends StdOutTest { + + private static final int ITEM_COUNT = 5; + + @Test + public void testConsume() throws Exception { + final ItemQueue queue = spy(new ItemQueue()); + for (int id = 0; id < ITEM_COUNT; id++) { + queue.put(new Item("producer", id)); + } + + reset(queue); // Don't count the preparation above as interactions with the queue + final Consumer consumer = new Consumer("consumer", queue); + + final InOrder inOrder = inOrder(getStdOutMock()); + for (int id = 0; id < ITEM_COUNT; id++) { + consumer.consume(); + inOrder.verify(getStdOutMock()) + .println("Consumer [consumer] consume item [" + id + "] produced by [producer]"); + } + + inOrder.verifyNoMoreInteractions(); + } + +} diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java new file mode 100644 index 000000000000..0605879dd577 --- /dev/null +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java @@ -0,0 +1,28 @@ +package com.iluwatar.producer.consumer; + +import org.junit.Test; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/28/15 - 12:12 AM + * + * @author Jeroen Meulemeester + */ +public class ProducerTest { + + @Test(timeout = 6000) + public void testProduce() throws Exception { + final ItemQueue queue = mock(ItemQueue.class); + final Producer producer = new Producer("producer", queue); + + producer.produce(); + verify(queue).put(any(Item.class)); + + verifyNoMoreInteractions(queue); + } + +} \ No newline at end of file diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/StdOutTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/StdOutTest.java new file mode 100644 index 000000000000..85d8fe6c0042 --- /dev/null +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.producer.consumer; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/property/index.md b/property/index.md index 1c5b28db6edd..0ac5c7a6c682 100644 --- a/property/index.md +++ b/property/index.md @@ -4,18 +4,22 @@ title: Property folder: property permalink: /patterns/property/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Beginner --- -**Intent:** Create hierarchy of objects and new objects using already existing +## Intent +Create hierarchy of objects and new objects using already existing objects as parents. ![alt text](./etc/property.png "Property") -**Applicability:** Use the Property pattern when +## Applicability +Use the Property pattern when * when you like to have objects with dynamic set of fields and prototype inheritance -**Real world examples:** +## Real world examples * [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) prototype inheritance diff --git a/property/pom.xml b/property/pom.xml index b6d10bdb0d70..0d1ff20166c9 100644 --- a/property/pom.xml +++ b/property/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT property diff --git a/property/src/main/java/com/iluwatar/property/Character.java b/property/src/main/java/com/iluwatar/property/Character.java index 10b8f495d992..50e564623af9 100644 --- a/property/src/main/java/com/iluwatar/property/Character.java +++ b/property/src/main/java/com/iluwatar/property/Character.java @@ -18,6 +18,9 @@ public enum Type { private String name; private Type type; + /** + * Constructor + */ public Character() { this.prototype = new Prototype() { // Null-value object @Override @@ -43,6 +46,9 @@ public Character(Type type, Prototype prototype) { this.prototype = prototype; } + /** + * Constructor + */ public Character(String name, Character prototype) { this.name = name; this.type = prototype.type; diff --git a/property/src/main/java/com/iluwatar/property/Prototype.java b/property/src/main/java/com/iluwatar/property/Prototype.java index 13b4c860882d..33e2d66d675e 100644 --- a/property/src/main/java/com/iluwatar/property/Prototype.java +++ b/property/src/main/java/com/iluwatar/property/Prototype.java @@ -5,11 +5,11 @@ */ public interface Prototype { - public Integer get(Stats stat); + Integer get(Stats stat); - public boolean has(Stats stat); + boolean has(Stats stat); - public void set(Stats stat, Integer val); + void set(Stats stat, Integer val); - public void remove(Stats stat); + void remove(Stats stat); } diff --git a/property/src/test/java/com/iluwatar/property/AppTest.java b/property/src/test/java/com/iluwatar/property/AppTest.java index 75be2f649c67..bfa48ffab841 100644 --- a/property/src/test/java/com/iluwatar/property/AppTest.java +++ b/property/src/test/java/com/iluwatar/property/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.property.App; - /** * * Application test diff --git a/property/src/test/java/com/iluwatar/property/CharacterTest.java b/property/src/test/java/com/iluwatar/property/CharacterTest.java new file mode 100644 index 000000000000..6d9a7a14bafe --- /dev/null +++ b/property/src/test/java/com/iluwatar/property/CharacterTest.java @@ -0,0 +1,103 @@ +package com.iluwatar.property; + +import org.junit.Test; + +import static com.iluwatar.property.Character.Type; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/28/15 - 7:46 PM + * + * @author Jeroen Meulemeester + */ +public class CharacterTest { + + @Test + public void testPrototypeStats() throws Exception { + final Character prototype = new Character(); + + for (final Stats stat : Stats.values()) { + assertFalse(prototype.has(stat)); + assertNull(prototype.get(stat)); + + final Integer expectedValue = stat.ordinal(); + prototype.set(stat, expectedValue); + assertTrue(prototype.has(stat)); + assertEquals(expectedValue, prototype.get(stat)); + + prototype.remove(stat); + assertFalse(prototype.has(stat)); + assertNull(prototype.get(stat)); + } + + } + + @Test + public void testCharacterStats() throws Exception { + final Character prototype = new Character(); + for (final Stats stat : Stats.values()) { + prototype.set(stat, stat.ordinal()); + } + + final Character mage = new Character(Type.MAGE, prototype); + for (final Stats stat : Stats.values()) { + final Integer expectedValue = stat.ordinal(); + assertTrue(mage.has(stat)); + assertEquals(expectedValue, mage.get(stat)); + } + } + + @Test + public void testToString() throws Exception { + final Character prototype = new Character(); + prototype.set(Stats.ARMOR, 1); + prototype.set(Stats.AGILITY, 2); + prototype.set(Stats.INTELLECT, 3); + assertEquals("Stats:\n - AGILITY:2\n - ARMOR:1\n - INTELLECT:3\n", prototype.toString()); + + final Character stupid = new Character(Type.ROGUE, prototype); + stupid.remove(Stats.INTELLECT); + assertEquals("Character type: ROGUE\nStats:\n - AGILITY:2\n - ARMOR:1\n", stupid.toString()); + + final Character weak = new Character("weak", prototype); + weak.remove(Stats.ARMOR); + assertEquals("Player: weak\nStats:\n - AGILITY:2\n - INTELLECT:3\n", weak.toString()); + + } + + @Test + public void testName() throws Exception { + final Character prototype = new Character(); + prototype.set(Stats.ARMOR, 1); + prototype.set(Stats.INTELLECT, 2); + assertNull(prototype.name()); + + final Character stupid = new Character(Type.ROGUE, prototype); + stupid.remove(Stats.INTELLECT); + assertNull(stupid.name()); + + final Character weak = new Character("weak", prototype); + weak.remove(Stats.ARMOR); + assertEquals("weak", weak.name()); + } + + @Test + public void testType() throws Exception { + final Character prototype = new Character(); + prototype.set(Stats.ARMOR, 1); + prototype.set(Stats.INTELLECT, 2); + assertNull(prototype.type()); + + final Character stupid = new Character(Type.ROGUE, prototype); + stupid.remove(Stats.INTELLECT); + assertEquals(Type.ROGUE, stupid.type()); + + final Character weak = new Character("weak", prototype); + weak.remove(Stats.ARMOR); + assertNull(weak.type()); + } + +} \ No newline at end of file diff --git a/prototype/index.md b/prototype/index.md index 9d108ff06425..632daca931aa 100644 --- a/prototype/index.md +++ b/prototype/index.md @@ -7,23 +7,26 @@ categories: Creational tags: - Java - Gang Of Four + - Difficulty-Beginner --- -**Intent:** Specify the kinds of objects to create using a prototypical +## Intent +Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype. ![alt text](./etc/prototype_1.png "Prototype") -**Applicability:** Use the Prototype pattern when a system should be independent of how its products are created, composed and represented; and +## Applicability +Use the Prototype pattern when a system should be independent of how its products are created, composed and represented; and * when the classes to instantiate are specified at run-time, for example, by dynamic loading; or * to avoid building a class hierarchy of factories that parallels the class hierarchy of products; or * when instances of a class can have one of only a few different combinations of state. It may be more convenient to install a corresponding number of prototypes and clone them rather than instantiating the class manually, each time with the appropriate state -**Real world examples:** +## Real world examples * [java.lang.Object#clone()](http://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#clone%28%29) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/prototype/pom.xml b/prototype/pom.xml index c08e795584c1..411517d2ea14 100644 --- a/prototype/pom.xml +++ b/prototype/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT prototype @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java b/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java index f5cb8bdaf8ce..679882097b4d 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java @@ -9,11 +9,9 @@ public class ElfBeast extends Beast { public ElfBeast() {} - public ElfBeast(ElfBeast beast) {} - @Override public Beast clone() throws CloneNotSupportedException { - return new ElfBeast(this); + return new ElfBeast(); } @Override diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java b/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java index c801e40079b9..42ce9d530fe2 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java @@ -9,11 +9,9 @@ public class ElfMage extends Mage { public ElfMage() {} - public ElfMage(ElfMage mage) {} - @Override public Mage clone() throws CloneNotSupportedException { - return new ElfMage(this); + return new ElfMage(); } @Override diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java b/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java index 8b5167b0e43c..1cba6943c505 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java @@ -9,11 +9,9 @@ public class ElfWarlord extends Warlord { public ElfWarlord() {} - public ElfWarlord(ElfWarlord warlord) {} - @Override public Warlord clone() throws CloneNotSupportedException { - return new ElfWarlord(this); + return new ElfWarlord(); } @Override diff --git a/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java b/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java index 4c5a60bcda93..85792104deec 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java +++ b/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java @@ -11,12 +11,18 @@ public class HeroFactoryImpl implements HeroFactory { private Warlord warlord; private Beast beast; + /** + * Constructor + */ public HeroFactoryImpl(Mage mage, Warlord warlord, Beast beast) { this.mage = mage; this.warlord = warlord; this.beast = beast; } + /** + * Create mage + */ public Mage createMage() { try { return mage.clone(); @@ -25,6 +31,9 @@ public Mage createMage() { } } + /** + * Create warlord + */ public Warlord createWarlord() { try { return warlord.clone(); @@ -33,6 +42,9 @@ public Warlord createWarlord() { } } + /** + * Create beast + */ public Beast createBeast() { try { return beast.clone(); diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java b/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java index 50a6b5ae2153..a45afb76791b 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java @@ -9,11 +9,9 @@ public class OrcBeast extends Beast { public OrcBeast() {} - public OrcBeast(OrcBeast beast) {} - @Override public Beast clone() throws CloneNotSupportedException { - return new OrcBeast(this); + return new OrcBeast(); } @Override diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java b/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java index f27d12519668..47a33379b9bb 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java @@ -9,11 +9,9 @@ public class OrcMage extends Mage { public OrcMage() {} - public OrcMage(OrcMage mage) {} - @Override public Mage clone() throws CloneNotSupportedException { - return new OrcMage(this); + return new OrcMage(); } @Override diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java b/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java index d21816d8eedf..40ab91113adc 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java @@ -9,11 +9,9 @@ public class OrcWarlord extends Warlord { public OrcWarlord() {} - public OrcWarlord(OrcWarlord warlord) {} - @Override public Warlord clone() throws CloneNotSupportedException { - return new OrcWarlord(this); + return new OrcWarlord(); } @Override diff --git a/prototype/src/test/java/com/iluwatar/prototype/AppTest.java b/prototype/src/test/java/com/iluwatar/prototype/AppTest.java index c2b8ea4ff9cd..772a88a036c3 100644 --- a/prototype/src/test/java/com/iluwatar/prototype/AppTest.java +++ b/prototype/src/test/java/com/iluwatar/prototype/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.prototype.App; - /** * * Application test diff --git a/prototype/src/test/java/com/iluwatar/prototype/HeroFactoryImplTest.java b/prototype/src/test/java/com/iluwatar/prototype/HeroFactoryImplTest.java new file mode 100644 index 000000000000..e237b43b716f --- /dev/null +++ b/prototype/src/test/java/com/iluwatar/prototype/HeroFactoryImplTest.java @@ -0,0 +1,39 @@ +package com.iluwatar.prototype; + +import org.junit.Test; + +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Date: 12/28/15 - 8:34 PM + * + * @author Jeroen Meulemeester + */ +public class HeroFactoryImplTest { + + @Test + public void testFactory() throws Exception { + final Mage mage = mock(Mage.class); + final Warlord warlord = mock(Warlord.class); + final Beast beast = mock(Beast.class); + + when(mage.clone()).thenThrow(CloneNotSupportedException.class); + when(warlord.clone()).thenThrow(CloneNotSupportedException.class); + when(beast.clone()).thenThrow(CloneNotSupportedException.class); + + final HeroFactoryImpl factory = new HeroFactoryImpl(mage, warlord, beast); + assertNull(factory.createMage()); + assertNull(factory.createWarlord()); + assertNull(factory.createBeast()); + + verify(mage).clone(); + verify(warlord).clone(); + verify(beast).clone(); + verifyNoMoreInteractions(mage, warlord, beast); + } + +} \ No newline at end of file diff --git a/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java b/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java new file mode 100644 index 000000000000..3e3d8f88bcee --- /dev/null +++ b/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java @@ -0,0 +1,66 @@ +package com.iluwatar.prototype; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + +/** + * Date: 12/28/15 - 8:45 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class PrototypeTest

{ + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList( + new Object[]{new OrcBeast(), "Orcish wolf"}, + new Object[]{new OrcMage(), "Orcish mage"}, + new Object[]{new OrcWarlord(), "Orcish warlord"}, + new Object[]{new ElfBeast(), "Elven eagle"}, + new Object[]{new ElfMage(), "Elven mage"}, + new Object[]{new ElfWarlord(), "Elven warlord"} + ); + } + + /** + * The tested prototype instance + */ + private final Prototype testedPrototype; + + /** + * The expected {@link Prototype#toString()} value + */ + private final String expectedToString; + + /** + * Create a new test instance, using the given test object and expected value + * + * @param testedPrototype The tested prototype instance + * @param expectedToString The expected {@link Prototype#toString()} value + */ + public PrototypeTest(final Prototype testedPrototype, final String expectedToString) { + this.expectedToString = expectedToString; + this.testedPrototype = testedPrototype; + } + + @Test + public void testPrototype() throws Exception { + assertEquals(this.expectedToString, this.testedPrototype.toString()); + + final Object clone = this.testedPrototype.clone(); + assertNotNull(clone); + assertNotSame(clone, this.testedPrototype); + assertSame(this.testedPrototype.getClass(), clone.getClass()); + } + +} diff --git a/proxy/index.md b/proxy/index.md index baa7596000d8..a3e03708e328 100644 --- a/proxy/index.md +++ b/proxy/index.md @@ -7,17 +7,20 @@ categories: Structural tags: - Java - Gang Of Four - - Difficulty-Intermediate + - Difficulty-Beginner --- -**Also known as:** Surrogate +## Also known as +Surrogate -**Intent:** Provide a surrogate or placeholder for another object to control +## Intent +Provide a surrogate or placeholder for another object to control access to it. ![alt text](./etc/proxy_1.png "Proxy") -**Applicability:** Proxy is applicable whenever there is a need for a more +## Applicability +Proxy is applicable whenever there is a need for a more versatile or sophisticated reference to an object than a simple pointer. Here are several common situations in which the Proxy pattern is applicable @@ -25,7 +28,7 @@ are several common situations in which the Proxy pattern is applicable * a virtual proxy creates expensive objects on demand. * a protection proxy controls access to the original object. Protection proxies are useful when objects should have different access rights. -**Typical Use Case:** +## Typical Use Case * control access to another object * lazy initialization @@ -33,11 +36,11 @@ are several common situations in which the Proxy pattern is applicable * facilitate network connection * to count references to an object -**Real world examples:** +## Real world examples * [java.lang.reflect.Proxy](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html) * [Apache Commons Proxy](https://commons.apache.org/proper/commons-proxy/) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/proxy/pom.xml b/proxy/pom.xml index 0bb88bb795e0..139934c13411 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT proxy @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/proxy/src/main/java/com/iluwatar/proxy/App.java b/proxy/src/main/java/com/iluwatar/proxy/App.java index 25a903e419ed..837424f28ba7 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/App.java +++ b/proxy/src/main/java/com/iluwatar/proxy/App.java @@ -18,6 +18,9 @@ */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { WizardTowerProxy tower = new WizardTowerProxy(); diff --git a/proxy/src/test/java/com/iluwatar/proxy/AppTest.java b/proxy/src/test/java/com/iluwatar/proxy/AppTest.java index a68629646cd6..0485dabb6a09 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/AppTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.proxy.App; - /** * * Application test diff --git a/proxy/src/test/java/com/iluwatar/proxy/StdOutTest.java b/proxy/src/test/java/com/iluwatar/proxy/StdOutTest.java new file mode 100644 index 000000000000..a145b7b8045f --- /dev/null +++ b/proxy/src/test/java/com/iluwatar/proxy/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.proxy; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java b/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java new file mode 100644 index 000000000000..c1b9e6fed9d0 --- /dev/null +++ b/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java @@ -0,0 +1,22 @@ +package com.iluwatar.proxy; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/28/15 - 9:02 PM + * + * @author Jeroen Meulemeester + */ +public class WizardTest { + + @Test + public void testToString() throws Exception { + final String[] wizardNames = {"Gandalf", "Dumbledore", "Oz", "Merlin"}; + for (final String name : wizardNames) { + assertEquals(name, new Wizard(name).toString()); + } + } + +} \ No newline at end of file diff --git a/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java new file mode 100644 index 000000000000..dcde88f8c4db --- /dev/null +++ b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.proxy; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; + +/** + * Date: 12/28/15 - 9:18 PM + * + * @author Jeroen Meulemeester + */ +public class WizardTowerProxyTest extends StdOutTest { + + @Test + public void testEnter() throws Exception { + final Wizard[] wizards = new Wizard[]{ + new Wizard("Gandalf"), + new Wizard("Dumbledore"), + new Wizard("Oz"), + new Wizard("Merlin") + }; + + final WizardTowerProxy tower = new WizardTowerProxy(); + for (final Wizard wizard : wizards) { + tower.enter(wizard); + } + + final InOrder inOrder = inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("Gandalf enters the tower."); + inOrder.verify(getStdOutMock()).println("Dumbledore enters the tower."); + inOrder.verify(getStdOutMock()).println("Oz enters the tower."); + inOrder.verify(getStdOutMock()).println("Merlin is not allowed to enter!"); + inOrder.verifyNoMoreInteractions(); + + } + +} \ No newline at end of file diff --git a/proxy/src/test/java/com/iluwatar/proxy/WizardTowerTest.java b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerTest.java new file mode 100644 index 000000000000..007b92a33fd9 --- /dev/null +++ b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.proxy; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; + +/** + * Date: 12/28/15 - 9:18 PM + * + * @author Jeroen Meulemeester + */ +public class WizardTowerTest extends StdOutTest { + + @Test + public void testEnter() throws Exception { + final Wizard[] wizards = new Wizard[]{ + new Wizard("Gandalf"), + new Wizard("Dumbledore"), + new Wizard("Oz"), + new Wizard("Merlin") + }; + + final WizardTower tower = new WizardTower(); + for (final Wizard wizard : wizards) { + tower.enter(wizard); + } + + final InOrder inOrder = inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("Gandalf enters the tower."); + inOrder.verify(getStdOutMock()).println("Dumbledore enters the tower."); + inOrder.verify(getStdOutMock()).println("Oz enters the tower."); + inOrder.verify(getStdOutMock()).println("Merlin enters the tower."); + inOrder.verifyNoMoreInteractions(); + + } + +} \ No newline at end of file diff --git a/publish-subscribe/index.md b/publish-subscribe/index.md index b91f22e3b177..a9fa7d4371a7 100644 --- a/publish-subscribe/index.md +++ b/publish-subscribe/index.md @@ -7,12 +7,15 @@ categories: Integration tags: - Java - EIP + - Camel --- -**Intent:** Broadcast messages from sender to all the interested receivers. +## Intent +Broadcast messages from sender to all the interested receivers. ![alt text](./etc/publish-subscribe.png "Publish Subscribe Channel") -**Applicability:** Use the Publish Subscribe Channel pattern when +## Applicability +Use the Publish Subscribe Channel pattern when * two or more applications need to communicate using a messaging system for broadcasts. diff --git a/publish-subscribe/pom.xml b/publish-subscribe/pom.xml index 07d704719a7d..fad968b19169 100644 --- a/publish-subscribe/pom.xml +++ b/publish-subscribe/pom.xml @@ -4,7 +4,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT publish-subscribe diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java index 30f982ed119a..f80dd1ad1499 100644 --- a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java @@ -28,10 +28,6 @@ public class App { /** * Program entry point - * - * @param args - * command line args - * @throws Exception */ public static void main(String[] args) throws Exception { CamelContext context = new DefaultCamelContext(); diff --git a/reactor/index.md b/reactor/index.md index 6e20598d2f07..b9ba98948064 100644 --- a/reactor/index.md +++ b/reactor/index.md @@ -3,27 +3,30 @@ layout: pattern title: Reactor folder: reactor permalink: /patterns/reactor/ -categories: Architectural +categories: Concurrency tags: - Java - Difficulty-Expert + - I/O --- -**Intent:** The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. The application can register specific handlers for processing which are called by reactor on specific events. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. +## Intent +The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. The application can register specific handlers for processing which are called by reactor on specific events. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. ![Reactor](./etc/reactor.png "Reactor") -**Applicability:** Use Reactor pattern when +## Applicability +Use Reactor pattern when * a server application needs to handle concurrent service requests from multiple clients. * a server application needs to be available for receiving requests from new clients even when handling older client requests. * a server must maximize throughput, minimize latency and use CPU efficiently without blocking. -**Real world examples:** +## Real world examples * [Spring Reactor](http://projectreactor.io/) -**Credits** +## Credits * [Douglas C. Schmidt - Reactor](https://www.dre.vanderbilt.edu/~schmidt/PDF/Reactor.pdf) * [Pattern Oriented Software Architecture Vol I-V](http://www.amazon.com/Pattern-Oriented-Software-Architecture-Volume-Patterns/dp/0471958697) diff --git a/reactor/pom.xml b/reactor/pom.xml index c60e8cf985dd..c980d3b10d3f 100644 --- a/reactor/pom.xml +++ b/reactor/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT reactor diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/App.java b/reactor/src/main/java/com/iluwatar/reactor/app/App.java index 2c49d9001f23..d074c9b197be 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/App.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/App.java @@ -80,8 +80,6 @@ public App(Dispatcher dispatcher) { /** * App entry. - * - * @throws IOException */ public static void main(String[] args) throws IOException { new App(new ThreadPoolDispatcher(2)).start(); diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java b/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java index ee25b0be039b..13cdd70e1a92 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java @@ -37,10 +37,10 @@ public static void main(String[] args) throws IOException { * @throws IOException if any I/O error occurs. */ public void start() throws IOException { - service.execute(new TCPLoggingClient("Client 1", 6666)); - service.execute(new TCPLoggingClient("Client 2", 6667)); - service.execute(new UDPLoggingClient("Client 3", 6668)); - service.execute(new UDPLoggingClient("Client 4", 6668)); + service.execute(new TcpLoggingClient("Client 1", 6666)); + service.execute(new TcpLoggingClient("Client 2", 6667)); + service.execute(new UdpLoggingClient("Client 3", 6668)); + service.execute(new UdpLoggingClient("Client 4", 6668)); } /** @@ -69,7 +69,7 @@ private static void artificialDelayOf(long millis) { /** * A logging client that sends requests to Reactor on TCP socket. */ - static class TCPLoggingClient implements Runnable { + static class TcpLoggingClient implements Runnable { private final int serverPort; private final String clientName; @@ -80,7 +80,7 @@ static class TCPLoggingClient implements Runnable { * @param clientName the name of the client to be sent in logging requests. * @param port the port on which client will send logging requests. */ - public TCPLoggingClient(String clientName, int serverPort) { + public TcpLoggingClient(String clientName, int serverPort) { this.clientName = clientName; this.serverPort = serverPort; } @@ -118,7 +118,7 @@ private void sendLogRequests(PrintWriter writer, InputStream inputStream) throws /** * A logging client that sends requests to Reactor on UDP socket. */ - static class UDPLoggingClient implements Runnable { + static class UdpLoggingClient implements Runnable { private final String clientName; private final InetSocketAddress remoteAddress; @@ -129,7 +129,7 @@ static class UDPLoggingClient implements Runnable { * @param port the port on which client will send logging requests. * @throws UnknownHostException if localhost is unknown */ - public UDPLoggingClient(String clientName, int port) throws UnknownHostException { + public UdpLoggingClient(String clientName, int port) throws UnknownHostException { this.clientName = clientName; this.remoteAddress = new InetSocketAddress(InetAddress.getLocalHost(), port); } diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java b/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java index df08426d0fee..cd1318c89412 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java @@ -131,6 +131,7 @@ void flush(SelectionKey key) throws IOException { * channel.write(buffer, key); * } * + * * * @param data the data to be written on underlying channel. * @param key the key which is writable. diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java index 16c13e5f91ff..271a6975da4b 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java @@ -14,40 +14,41 @@ import java.util.concurrent.TimeUnit; /** - * This class acts as Synchronous Event De-multiplexer and Initiation Dispatcher of Reactor pattern. - * Multiple handles i.e. {@link AbstractNioChannel}s can be registered to the reactor and it blocks - * for events from all these handles. Whenever an event occurs on any of the registered handles, it - * synchronously de-multiplexes the event which can be any of read, write or accept, and dispatches - * the event to the appropriate {@link ChannelHandler} using the {@link Dispatcher}. + * This class acts as Synchronous Event De-multiplexer and Initiation Dispatcher of Reactor pattern. Multiple handles + * i.e. {@link AbstractNioChannel}s can be registered to the reactor and it blocks for events from all these handles. + * Whenever an event occurs on any of the registered handles, it synchronously de-multiplexes the event which can be any + * of read, write or accept, and dispatches the event to the appropriate {@link ChannelHandler} using the + * {@link Dispatcher}. * *

- * Implementation: A NIO reactor runs in its own thread when it is started using {@link #start()} - * method. {@link NioReactor} uses {@link Selector} for realizing Synchronous Event De-multiplexing. + * Implementation: A NIO reactor runs in its own thread when it is started using {@link #start()} method. + * {@link NioReactor} uses {@link Selector} for realizing Synchronous Event De-multiplexing. * *

- * NOTE: This is one of the ways to implement NIO reactor and it does not take care of all possible - * edge cases which are required in a real application. This implementation is meant to demonstrate - * the fundamental concepts that lie behind Reactor pattern. + * NOTE: This is one of the ways to implement NIO reactor and it does not take care of all possible edge cases which are + * required in a real application. This implementation is meant to demonstrate the fundamental concepts that lie behind + * Reactor pattern. */ public class NioReactor { private final Selector selector; private final Dispatcher dispatcher; /** - * All the work of altering the SelectionKey operations and Selector operations are performed in - * the context of main event loop of reactor. So when any channel needs to change its readability - * or writability, a new command is added in the command queue and then the event loop picks up - * the command and executes it in next iteration. + * All the work of altering the SelectionKey operations and Selector operations are performed in the context of main + * event loop of reactor. So when any channel needs to change its readability or writability, a new command is added + * in the command queue and then the event loop picks up the command and executes it in next iteration. */ private final Queue pendingCommands = new ConcurrentLinkedQueue<>(); private final ExecutorService reactorMain = Executors.newSingleThreadExecutor(); /** - * Creates a reactor which will use provided {@code dispatcher} to dispatch events. The - * application can provide various implementations of dispatcher which suits its needs. + * Creates a reactor which will use provided {@code dispatcher} to dispatch events. The application can provide + * various implementations of dispatcher which suits its needs. * - * @param dispatcher a non-null dispatcher used to dispatch events on registered channels. - * @throws IOException if any I/O error occurs. + * @param dispatcher + * a non-null dispatcher used to dispatch events on registered channels. + * @throws IOException + * if any I/O error occurs. */ public NioReactor(Dispatcher dispatcher) throws IOException { this.dispatcher = dispatcher; @@ -57,7 +58,8 @@ public NioReactor(Dispatcher dispatcher) throws IOException { /** * Starts the reactor event loop in a new thread. * - * @throws IOException if any I/O error occurs. + * @throws IOException + * if any I/O error occurs. */ public void start() throws IOException { reactorMain.execute(() -> { @@ -73,8 +75,10 @@ public void start() throws IOException { /** * Stops the reactor and related resources such as dispatcher. * - * @throws InterruptedException if interrupted while stopping the reactor. - * @throws IOException if any I/O error occurs. + * @throws InterruptedException + * if interrupted while stopping the reactor. + * @throws IOException + * if any I/O error occurs. */ public void stop() throws InterruptedException, IOException { reactorMain.shutdownNow(); @@ -84,15 +88,15 @@ public void stop() throws InterruptedException, IOException { } /** - * Registers a new channel (handle) with this reactor. Reactor will start waiting for events on - * this channel and notify of any events. While registering the channel the reactor uses - * {@link AbstractNioChannel#getInterestedOps()} to know about the interested operation of this - * channel. + * Registers a new channel (handle) with this reactor. Reactor will start waiting for events on this channel and + * notify of any events. While registering the channel the reactor uses {@link AbstractNioChannel#getInterestedOps()} + * to know about the interested operation of this channel. * - * @param channel a new channel on which reactor will wait for events. The channel must be bound - * prior to being registered. + * @param channel + * a new channel on which reactor will wait for events. The channel must be bound prior to being registered. * @return this - * @throws IOException if any I/O error occurs. + * @throws IOException + * if any I/O error occurs. */ public NioReactor registerChannel(AbstractNioChannel channel) throws IOException { SelectionKey key = channel.getJavaChannel().register(selector, channel.getInterestedOps()); @@ -113,8 +117,8 @@ private void eventLoop() throws IOException { processPendingCommands(); /* - * Synchronous event de-multiplexing happens here, this is blocking call which returns when it - * is possible to initiate non-blocking operation on any of the registered channels. + * Synchronous event de-multiplexing happens here, this is blocking call which returns when it is possible to + * initiate non-blocking operation on any of the registered channels. */ selector.select(); @@ -147,8 +151,8 @@ private void processPendingCommands() { } /* - * Initiation dispatcher logic, it checks the type of event and notifier application specific - * event handler to handle the event. + * Initiation dispatcher logic, it checks the type of event and notifier application specific event handler to handle + * the event. */ private void processKey(SelectionKey key) throws IOException { if (key.isAcceptable()) { @@ -196,14 +200,15 @@ private void onChannelAcceptable(SelectionKey key) throws IOException { } /** - * Queues the change of operations request of a channel, which will change the interested - * operations of the channel sometime in future. + * Queues the change of operations request of a channel, which will change the interested operations of the channel + * sometime in future. *

- * This is a non-blocking method and does not guarantee that the operations have changed when this - * method returns. + * This is a non-blocking method and does not guarantee that the operations have changed when this method returns. * - * @param key the key for which operations have to be changed. - * @param interestedOps the new interest operations. + * @param key + * the key for which operations have to be changed. + * @param interestedOps + * the new interest operations. */ public void changeOps(SelectionKey key, int interestedOps) { pendingCommands.add(new ChangeKeyOpsCommand(key, interestedOps)); diff --git a/reader-writer-lock/etc/reader-writer-lock.png b/reader-writer-lock/etc/reader-writer-lock.png new file mode 100644 index 000000000000..f7b600530926 Binary files /dev/null and b/reader-writer-lock/etc/reader-writer-lock.png differ diff --git a/reader-writer-lock/etc/reader-writer-lock.ucls b/reader-writer-lock/etc/reader-writer-lock.ucls new file mode 100644 index 000000000000..920904e76843 --- /dev/null +++ b/reader-writer-lock/etc/reader-writer-lock.ucls @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reader-writer-lock/index.md b/reader-writer-lock/index.md new file mode 100644 index 000000000000..91d16892c4a5 --- /dev/null +++ b/reader-writer-lock/index.md @@ -0,0 +1,29 @@ +--- +layout: pattern +title: Reader Writer Lock +folder: reader-writer-lock +permalink: /patterns/reader-writer-lock/ +categories: Concurrent +tags: +- Java +--- + +**Intent:** + +Suppose we have a shared memory area with the basic constraints detailed above. It is possible to protect the shared data behind a mutual exclusion mutex, in which case no two threads can access the data at the same time. However, this solution is suboptimal, because it is possible that a reader R1 might have the lock, and then another reader R2 requests access. It would be foolish for R2 to wait until R1 was done before starting its own read operation; instead, R2 should start right away. This is the motivation for the Reader Writer Lock pattern. + +![alt text](./etc/reader-writer-lock.png "Reader writer lock") + +**Applicability:** + +Application need to increase the performance of resource synchronize for multiple thread, in particularly there are mixed read/write operations. + +**Real world examples:** + +* [Java Reader Writer Lock](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html) + +**Credits** + +* [Readers–writer lock](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock) + +* [Readers–writers_problem](https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem) \ No newline at end of file diff --git a/reader-writer-lock/pom.xml b/reader-writer-lock/pom.xml new file mode 100644 index 000000000000..14b17011deb4 --- /dev/null +++ b/reader-writer-lock/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.10.0-SNAPSHOT + + reader-writer-lock + + + junit + junit + test + + + org.mockito + mockito-core + test + + + + diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java new file mode 100644 index 000000000000..b11b11be76ca --- /dev/null +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java @@ -0,0 +1,56 @@ +package com.iluwatar.reader.writer.lock; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +/** + * + * In a multiple thread applications, the threads may try to synchronize the shared resources + * regardless of read or write operation. It leads to a low performance especially in a "read more + * write less" system as indeed the read operations are thread-safe to another read operation. + *

+ * Reader writer lock is a synchronization primitive that try to resolve this problem. This pattern + * allows concurrent access for read-only operations, while write operations require exclusive + * access. This means that multiple threads can read the data in parallel but an exclusive lock is + * needed for writing or modifying data. When a writer is writing the data, all other writers or + * readers will be blocked until the writer is finished writing. + * + *

+ * This example use two mutex to demonstrate the concurrent access of multiple readers and writers. + * + * + * @author hongshuwei@gmail.com + */ +public class App { + + /** + * Program entry point + * + * @param args command line args + */ + public static void main(String[] args) { + + ExecutorService executeService = Executors.newFixedThreadPool(10); + ReaderWriterLock lock = new ReaderWriterLock(); + + // Start 5 readers + IntStream.range(0, 5) + .forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock()))); + + // Start 5 writers + IntStream.range(0, 5) + .forEach(i -> executeService.submit(new Writer("Writer " + i, lock.writeLock()))); + // In the system console, it can see that the read operations are executed concurrently while + // write operations are exclusive. + executeService.shutdown(); + try { + executeService.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println("Error waiting for ExecutorService shutdown"); + } + + } + +} diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java new file mode 100644 index 000000000000..214528080810 --- /dev/null +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java @@ -0,0 +1,40 @@ +package com.iluwatar.reader.writer.lock; + +import java.util.concurrent.locks.Lock; + +/** + * Reader class, read when it acquired the read lock + */ +public class Reader implements Runnable { + + private Lock readLock; + + private String name; + + public Reader(String name, Lock readLock) { + this.name = name; + this.readLock = readLock; + } + + @Override + public void run() { + readLock.lock(); + try { + read(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + readLock.unlock(); + } + } + + /** + * Simulate the read operation + * + */ + public void read() throws InterruptedException { + System.out.println(name + " begin"); + Thread.sleep(250); + System.out.println(name + " finish"); + } +} diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java new file mode 100644 index 000000000000..b7edd149c04c --- /dev/null +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java @@ -0,0 +1,211 @@ +package com.iluwatar.reader.writer.lock; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; + +/** + * Class responsible for control the access for reader or writer + * + * Allows multiple readers to hold the lock at same time, but if any writer holds the lock then + * readers wait. If reader holds the lock then writer waits. This lock is not fair. + */ +public class ReaderWriterLock implements ReadWriteLock { + + + private Object readerMutex = new Object(); + + private int currentReaderCount = 0; + + /** + * Global mutex is used to indicate that whether reader or writer gets the lock in the moment. + *

+ * 1. When it contains the reference of {@link readerLock}, it means that the lock is acquired by + * the reader, another reader can also do the read operation concurrently.
+ * 2. When it contains the reference of reference of {@link writerLock}, it means that the lock is + * acquired by the writer exclusively, no more reader or writer can get the lock. + *

+ * This is the most important field in this class to control the access for reader/writer. + */ + private Set globalMutex = new HashSet<>(); + + private ReadLock readerLock = new ReadLock(); + private WriteLock writerLock = new WriteLock(); + + @Override + public Lock readLock() { + return readerLock; + } + + @Override + public Lock writeLock() { + return writerLock; + } + + /** + * return true when globalMutex hold the reference of writerLock + */ + private boolean doesWriterOwnThisLock() { + return globalMutex.contains(writerLock); + } + + /** + * return true when globalMutex hold the reference of readerLock + */ + private boolean doesReaderOwnThisLock() { + return globalMutex.contains(readerLock); + } + + /** + * Nobody get the lock when globalMutex contains nothing + * + */ + private boolean isLockFree() { + return globalMutex.isEmpty(); + } + + private void waitUninterruptibly(Object o) { + try { + o.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * Reader Lock, can be access for more than one reader concurrently if no writer get the lock + */ + private class ReadLock implements Lock { + + @Override + public void lock() { + + synchronized (readerMutex) { + + currentReaderCount++; + if (currentReaderCount == 1) { + // Try to get the globalMutex lock for the first reader + synchronized (globalMutex) { + while (true) { + // If the no one get the lock or the lock is locked by reader, just set the reference + // to the globalMutex to indicate that the lock is locked by Reader. + if (isLockFree() || doesReaderOwnThisLock()) { + globalMutex.add(this); + break; + } else { + // If lock is acquired by the write, let the thread wait until the writer release + // the lock + waitUninterruptibly(globalMutex); + } + } + } + + } + } + } + + @Override + public void unlock() { + + synchronized (readerMutex) { + currentReaderCount--; + // Release the lock only when it is the last reader, it is ensure that the lock is released + // when all reader is completely. + if (currentReaderCount == 0) { + synchronized (globalMutex) { + // Notify the waiter, mostly the writer + globalMutex.remove(this); + globalMutex.notifyAll(); + } + } + } + + } + + @Override + public void lockInterruptibly() throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + + } + + /** + * Writer Lock, can only be accessed by one writer concurrently + */ + private class WriteLock implements Lock { + + @Override + public void lock() { + + synchronized (globalMutex) { + + while (true) { + // When there is no one acquired the lock, just put the writeLock reference to the + // globalMutex to indicate that the lock is acquired by one writer. + // It is ensure that writer can only get the lock when no reader/writer acquired the lock. + if (isLockFree()) { + globalMutex.add(this); + break; + } else if (doesWriterOwnThisLock()) { + // Wait when other writer get the lock + waitUninterruptibly(globalMutex); + } else if (doesReaderOwnThisLock()) { + // Wait when other reader get the lock + waitUninterruptibly(globalMutex); + } else { + throw new AssertionError("it should never reach here"); + } + } + } + } + + @Override + public void unlock() { + + synchronized (globalMutex) { + globalMutex.remove(this); + // Notify the waiter, other writer or reader + globalMutex.notifyAll(); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java new file mode 100644 index 000000000000..ae7b17080ab2 --- /dev/null +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java @@ -0,0 +1,40 @@ +package com.iluwatar.reader.writer.lock; + +import java.util.concurrent.locks.Lock; + +/** + * Writer class, write when it acquired the write lock + */ +public class Writer implements Runnable { + + private Lock writeLock = null; + + private String name; + + public Writer(String name, Lock writeLock) { + this.name = name; + this.writeLock = writeLock; + } + + + @Override + public void run() { + writeLock.lock(); + try { + write(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + writeLock.unlock(); + } + } + + /** + * Simulate the write operation + */ + public void write() throws InterruptedException { + System.out.println(name + " begin"); + Thread.sleep(250); + System.out.println(name + " finish"); + } +} diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java new file mode 100644 index 000000000000..5dd6feaabfba --- /dev/null +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java @@ -0,0 +1,16 @@ +package com.iluwatar.reader.writer.lock; + +import org.junit.Test; + +/** + * Application test + */ +public class AppTest { + + @Test + public void test() throws Exception { + String[] args = {}; + App.main(args); + + } +} diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java new file mode 100644 index 000000000000..e29f578894b5 --- /dev/null +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java @@ -0,0 +1,81 @@ +package com.iluwatar.reader.writer.lock; + +import static org.mockito.Mockito.inOrder; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.mockito.InOrder; + +/** + * @author hongshuwei@gmail.com + */ +public class ReaderAndWriterTest extends StdOutTest { + + + + /** + * Verify reader and writer can only get the lock to read and write orderly + */ + @Test + public void testReadAndWrite() throws Exception { + + ReaderWriterLock lock = new ReaderWriterLock(); + + Reader reader1 = new Reader("Reader 1", lock.readLock()); + Writer writer1 = new Writer("Writer 1", lock.writeLock()); + + ExecutorService executeService = Executors.newFixedThreadPool(2); + executeService.submit(reader1); + // Let reader1 execute first + Thread.sleep(150); + executeService.submit(writer1); + + executeService.shutdown(); + try { + executeService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println("Error waiting for ExecutorService shutdown"); + } + + final InOrder inOrder = inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("Reader 1 begin"); + inOrder.verify(getStdOutMock()).println("Reader 1 finish"); + inOrder.verify(getStdOutMock()).println("Writer 1 begin"); + inOrder.verify(getStdOutMock()).println("Writer 1 finish"); + } + + /** + * Verify reader and writer can only get the lock to read and write orderly + */ + @Test + public void testWriteAndRead() throws Exception { + + ExecutorService executeService = Executors.newFixedThreadPool(2); + ReaderWriterLock lock = new ReaderWriterLock(); + + Reader reader1 = new Reader("Reader 1", lock.readLock()); + Writer writer1 = new Writer("Writer 1", lock.writeLock()); + + executeService.submit(writer1); + // Let writer1 execute first + Thread.sleep(150); + executeService.submit(reader1); + + executeService.shutdown(); + try { + executeService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println("Error waiting for ExecutorService shutdown"); + } + + final InOrder inOrder = inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("Writer 1 begin"); + inOrder.verify(getStdOutMock()).println("Writer 1 finish"); + inOrder.verify(getStdOutMock()).println("Reader 1 begin"); + inOrder.verify(getStdOutMock()).println("Reader 1 finish"); + } +} + diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java new file mode 100644 index 000000000000..e76fe29c249d --- /dev/null +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java @@ -0,0 +1,50 @@ +package com.iluwatar.reader.writer.lock; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.spy; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.mockito.InOrder; + +/** + * @author hongshuwei@gmail.com + */ +public class ReaderTest extends StdOutTest { + + /** + * Verify that multiple readers can get the read lock concurrently + */ + @Test + public void testRead() throws Exception { + + ExecutorService executeService = Executors.newFixedThreadPool(2); + ReaderWriterLock lock = new ReaderWriterLock(); + + Reader reader1 = spy(new Reader("Reader 1", lock.readLock())); + Reader reader2 = spy(new Reader("Reader 2", lock.readLock())); + + executeService.submit(reader1); + Thread.sleep(150); + executeService.submit(reader2); + + executeService.shutdown(); + try { + executeService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println("Error waiting for ExecutorService shutdown"); + } + + // Read operation will hold the read lock 250 milliseconds, so here we prove that multiple reads + // can be performed in the same time. + final InOrder inOrder = inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("Reader 1 begin"); + inOrder.verify(getStdOutMock()).println("Reader 2 begin"); + inOrder.verify(getStdOutMock()).println("Reader 1 finish"); + inOrder.verify(getStdOutMock()).println("Reader 2 finish"); + + } +} diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/StdOutTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/StdOutTest.java new file mode 100644 index 000000000000..762574b66bb0 --- /dev/null +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.reader.writer.lock; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java new file mode 100644 index 000000000000..ed37bf3e51aa --- /dev/null +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java @@ -0,0 +1,50 @@ +package com.iluwatar.reader.writer.lock; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.spy; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.mockito.InOrder; + +/** + * @author hongshuwei@gmail.com + */ +public class WriterTest extends StdOutTest { + + /** + * Verify that multiple writers will get the lock in order. + */ + @Test + public void testWrite() throws Exception { + + ExecutorService executeService = Executors.newFixedThreadPool(2); + ReaderWriterLock lock = new ReaderWriterLock(); + + Writer writer1 = spy(new Writer("Writer 1", lock.writeLock())); + Writer writer2 = spy(new Writer("Writer 2", lock.writeLock())); + + executeService.submit(writer1); + // Let write1 execute first + Thread.sleep(150); + executeService.submit(writer2); + + executeService.shutdown(); + try { + executeService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println("Error waiting for ExecutorService shutdown"); + } + // Write operation will hold the write lock 250 milliseconds, so here we verify that when two + // writer execute concurrently, the second writer can only writes only when the first one is + // finished. + final InOrder inOrder = inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("Writer 1 begin"); + inOrder.verify(getStdOutMock()).println("Writer 1 finish"); + inOrder.verify(getStdOutMock()).println("Writer 2 begin"); + inOrder.verify(getStdOutMock()).println("Writer 2 finish"); + } +} diff --git a/repository/index.md b/repository/index.md index 8ecd16528cea..67b3ea44e828 100644 --- a/repository/index.md +++ b/repository/index.md @@ -3,11 +3,15 @@ layout: pattern title: Repository folder: repository permalink: /patterns/repository/ -categories: Architectural -tags: Java +categories: Persistence Tier +tags: + - Java + - Difficulty-Intermediate + - Spring --- -**Intent:** Repository layer is added between the domain and data mapping +## Intent +Repository layer is added between the domain and data mapping layers to isolate domain objects from details of the database access code and to minimize scattering and duplication of query code. The Repository pattern is especially useful in systems where number of domain classes is large or heavy @@ -15,19 +19,19 @@ querying is utilized. ![alt text](./etc/repository.png "Repository") -**Applicability:** Use the Repository pattern when +## Applicability +Use the Repository pattern when * the number of domain objects is large * you want to avoid duplication of query code * you want to keep the database querying code in single place * you have multiple data sources -**Real world examples:** +## Real world examples * [Spring Data](http://projects.spring.io/spring-data/) -**Credits:** +## Credits * [Don’t use DAO, use Repository](http://thinkinginobjects.com/2012/08/26/dont-use-dao-use-repository/) * [Advanced Spring Data JPA - Specifications and Querydsl](https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/) - diff --git a/repository/pom.xml b/repository/pom.xml index c3adc7a922c2..05b468a04dee 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT repository diff --git a/repository/src/main/java/com/iluwatar/repository/App.java b/repository/src/main/java/com/iluwatar/repository/App.java index 2442c854cd48..9c940b36dc16 100644 --- a/repository/src/main/java/com/iluwatar/repository/App.java +++ b/repository/src/main/java/com/iluwatar/repository/App.java @@ -24,12 +24,13 @@ public class App { /** * Program entry point * - * @param args command line args + * @param args + * command line args */ public static void main(String[] args) { - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("applicationContext.xml"); + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( + "applicationContext.xml"); PersonRepository repository = context.getBean(PersonRepository.class); Person peter = new Person("Peter", "Sagan", 17); diff --git a/repository/src/main/java/com/iluwatar/repository/AppConfig.java b/repository/src/main/java/com/iluwatar/repository/AppConfig.java new file mode 100644 index 000000000000..62b9a4c0424b --- /dev/null +++ b/repository/src/main/java/com/iluwatar/repository/AppConfig.java @@ -0,0 +1,135 @@ +package com.iluwatar.repository; + +import java.sql.SQLException; +import java.util.List; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.apache.commons.dbcp.BasicDataSource; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; + +/** + * This is the same example as in {@link App} but with annotations based + * configuration for Spring. + * + */ +@EnableJpaRepositories +public class AppConfig { + + /** + * Creation of H2 db + * + * @return A new Instance of DataSource + */ + @Bean(destroyMethod = "close") + public DataSource dataSource() { + BasicDataSource basicDataSource = new BasicDataSource(); + basicDataSource.setDriverClassName("org.h2.Driver"); + basicDataSource.setUrl("jdbc:h2:~/databases/person"); + basicDataSource.setUsername("sa"); + basicDataSource.setPassword("sa"); + return (DataSource) basicDataSource; + } + + /** + * Factory to create a especific instance of Entity Manager + */ + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean(); + entityManager.setDataSource(dataSource()); + entityManager.setPackagesToScan("com.iluwatar"); + entityManager.setPersistenceProvider(new HibernatePersistenceProvider()); + entityManager.setJpaProperties(jpaProperties()); + + return entityManager; + } + + /** + * Properties for Jpa + */ + private Properties jpaProperties() { + Properties properties = new Properties(); + properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); + properties.setProperty("hibernate.hbm2ddl.auto", "create-drop"); + return properties; + } + + /** + * Get transaction manager + */ + @Bean + public JpaTransactionManager transactionManager() throws SQLException { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); + return transactionManager; + } + + /** + * Program entry point + * + * @param args + * command line args + */ + public static void main(String[] args) { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + AppConfig.class); + PersonRepository repository = context.getBean(PersonRepository.class); + + Person peter = new Person("Peter", "Sagan", 17); + Person nasta = new Person("Nasta", "Kuzminova", 25); + Person john = new Person("John", "lawrence", 35); + Person terry = new Person("Terry", "Law", 36); + + // Add new Person records + repository.save(peter); + repository.save(nasta); + repository.save(john); + repository.save(terry); + + // Count Person records + System.out.println("Count Person records: " + repository.count()); + + // Print all records + List persons = (List) repository.findAll(); + for (Person person : persons) { + System.out.println(person); + } + + // Update Person + nasta.setName("Barbora"); + nasta.setSurname("Spotakova"); + repository.save(nasta); + + System.out.println("Find by id 2: " + repository.findOne(2L)); + + // Remove record from Person + repository.delete(2L); + + // count records + System.out.println("Count Person records: " + repository.count()); + + // find by name + Person p = repository.findOne(new PersonSpecifications.NameEqualSpec("John")); + System.out.println("Find by John is " + p); + + // find by age + persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40)); + + System.out.println("Find Person with age between 20,40: "); + for (Person person : persons) { + System.out.println(person); + } + + context.close(); + + } + +} diff --git a/repository/src/main/java/com/iluwatar/repository/Person.java b/repository/src/main/java/com/iluwatar/repository/Person.java index 57439b8c0516..04d65a6d0c20 100644 --- a/repository/src/main/java/com/iluwatar/repository/Person.java +++ b/repository/src/main/java/com/iluwatar/repository/Person.java @@ -20,8 +20,12 @@ public class Person { private int age; - public Person() {} + public Person() { + } + /** + * Constructor + */ public Person(String name, String surname, int age) { this.name = name; this.surname = surname; @@ -52,7 +56,6 @@ public void setSurname(String surname) { this.surname = surname; } - public int getAge() { return age; } @@ -80,30 +83,40 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } Person other = (Person) obj; - if (age != other.age) + if (age != other.age) { return false; + } if (id == null) { - if (other.id != null) + if (other.id != null) { return false; - } else if (!id.equals(other.id)) + } + } else if (!id.equals(other.id)) { return false; + } if (name == null) { - if (other.name != null) + if (other.name != null) { return false; - } else if (!name.equals(other.name)) + } + } else if (!name.equals(other.name)) { return false; + } if (surname == null) { - if (other.surname != null) + if (other.surname != null) { return false; - } else if (!surname.equals(other.surname)) + } + } else if (!surname.equals(other.surname)) { return false; + } return true; } diff --git a/repository/src/main/java/com/iluwatar/repository/PersonRepository.java b/repository/src/main/java/com/iluwatar/repository/PersonRepository.java index 98bb7abce02a..8d687f32d9fa 100644 --- a/repository/src/main/java/com/iluwatar/repository/PersonRepository.java +++ b/repository/src/main/java/com/iluwatar/repository/PersonRepository.java @@ -13,5 +13,5 @@ public interface PersonRepository extends CrudRepository, JpaSpecificationExecutor { - public Person findByName(String name); + Person findByName(String name); } diff --git a/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java index dadaae36b577..fa96f3ca638a 100644 --- a/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java +++ b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java @@ -31,6 +31,11 @@ public Predicate toPredicate(Root root, CriteriaQuery query, Criteria } } + + /** + * Name specification + * + */ public static class NameEqualSpec implements Specification { public String name; @@ -39,6 +44,9 @@ public NameEqualSpec(String name) { this.name = name; } + /** + * Get predicate + */ public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return cb.equal(root.get("name"), this.name); @@ -47,4 +55,3 @@ public Predicate toPredicate(Root root, CriteriaQuery query, Criteria } } - diff --git a/repository/src/main/resources/META-INF/persistence.xml b/repository/src/main/resources/META-INF/persistence.xml index 0aded0dbd0da..00767fbc202a 100644 --- a/repository/src/main/resources/META-INF/persistence.xml +++ b/repository/src/main/resources/META-INF/persistence.xml @@ -1,8 +1,8 @@ + xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> - + diff --git a/repository/src/main/resources/applicationContext.xml b/repository/src/main/resources/applicationContext.xml index 9322c9f64c63..ed03aba0a67c 100644 --- a/repository/src/main/resources/applicationContext.xml +++ b/repository/src/main/resources/applicationContext.xml @@ -1,39 +1,37 @@ - + - - - - - + - - - - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java new file mode 100644 index 000000000000..6f8746aa6ef2 --- /dev/null +++ b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java @@ -0,0 +1,110 @@ +package com.iluwatar.repository; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Resource; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.google.common.collect.Lists; + +/** + * Test case to test the functions of {@link PersonRepository}, beside the CRUD functions, the query + * by {@link org.springframework.data.jpa.domain.Specification} are also test. + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { AppConfig.class }, loader = AnnotationConfigContextLoader.class) +public class AnnotationBasedRepositoryTest { + + @Resource + private PersonRepository repository; + + Person peter = new Person("Peter", "Sagan", 17); + Person nasta = new Person("Nasta", "Kuzminova", 25); + Person john = new Person("John", "lawrence", 35); + Person terry = new Person("Terry", "Law", 36); + + List persons = Arrays.asList(peter, nasta, john, terry); + + /** + * Prepare data for test + */ + @Before + public void setup() { + + repository.save(persons); + } + + @Test + public void testFindAll() { + + List actuals = Lists.newArrayList(repository.findAll()); + assertTrue(actuals.containsAll(persons) && persons.containsAll(actuals)); + } + + @Test + public void testSave() { + + Person terry = repository.findByName("Terry"); + terry.setSurname("Lee"); + terry.setAge(47); + repository.save(terry); + + terry = repository.findByName("Terry"); + assertEquals(terry.getSurname(), "Lee"); + assertEquals(47, terry.getAge()); + } + + @Test + public void testDelete() { + + Person terry = repository.findByName("Terry"); + repository.delete(terry); + + assertEquals(3, repository.count()); + assertNull(repository.findByName("Terry")); + } + + @Test + public void testCount() { + + assertEquals(4, repository.count()); + } + + @Test + public void testFindAllByAgeBetweenSpec() { + + List persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40)); + + assertEquals(3, persons.size()); + assertTrue(persons.stream().allMatch((item) -> { + return item.getAge() > 20 && item.getAge() < 40; + })); + } + + @Test + public void testFindOneByNameEqualSpec() { + + Person actual = repository.findOne(new PersonSpecifications.NameEqualSpec("Terry")); + assertEquals(terry, actual); + } + + @After + public void cleanup() { + + repository.deleteAll(); + } + +} diff --git a/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java new file mode 100644 index 000000000000..17393f2ff6d1 --- /dev/null +++ b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java @@ -0,0 +1,54 @@ +package com.iluwatar.repository; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.transaction.annotation.Transactional; + +/** + * This case is Just for test the Annotation Based configuration + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { AppConfig.class }, loader = AnnotationConfigContextLoader.class) +public class AppConfigTest { + + @Autowired + DataSource dataSource; + + /** + * Test for bean instance + */ + @Test + public void testDataSource() { + assertNotNull(dataSource); + } + + /** + * Test for correct query execution + */ + @Test + @Transactional + public void testQuery() throws SQLException { + ResultSet resultSet = dataSource.getConnection().createStatement().executeQuery("SELECT 1"); + String result = null; + String expected = "1"; + while (resultSet.next()) { + result = resultSet.getString(1); + + } + assertTrue(result.equals(expected)); + } + +} diff --git a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java index 26689321a395..3d670881515b 100644 --- a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java +++ b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java @@ -23,7 +23,7 @@ * by {@link org.springframework.data.jpa.domain.Specification} are also test. */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = {"classpath:applicationContext.xml"}) +@ContextConfiguration(locations = { "classpath:applicationContext.xml" }) public class RepositoryTest { @Resource @@ -106,4 +106,3 @@ public void cleanup() { } } - diff --git a/resource-acquisition-is-initialization/index.md b/resource-acquisition-is-initialization/index.md index c3aa6c045a26..821f220d74de 100644 --- a/resource-acquisition-is-initialization/index.md +++ b/resource-acquisition-is-initialization/index.md @@ -4,13 +4,18 @@ title: Resource Acquisition Is Initialization folder: resource-acquisition-is-initialization permalink: /patterns/resource-acquisition-is-initialization/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Beginner + - Idiom --- -**Intent:** Resource Acquisition Is Initialization pattern can be used to implement exception safe resource management. +## Intent +Resource Acquisition Is Initialization pattern can be used to implement exception safe resource management. ![alt text](./etc/resource-acquisition-is-initialization.png "Resource Acquisition Is Initialization") -**Applicability:** Use the Resource Acquisition Is Initialization pattern when +## Applicability +Use the Resource Acquisition Is Initialization pattern when * you have resources that must be closed in every condition diff --git a/resource-acquisition-is-initialization/pom.xml b/resource-acquisition-is-initialization/pom.xml index 4b0b2cadb4e4..3e3fc8729bf7 100644 --- a/resource-acquisition-is-initialization/pom.xml +++ b/resource-acquisition-is-initialization/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT resource-acquisition-is-initialization @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java index 32cd3792e84e..f734432af67f 100644 --- a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java +++ b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java @@ -24,9 +24,6 @@ public class App { /** * Program entry point - * - * @param args command line args - * @throws Exception */ public static void main(String[] args) throws Exception { diff --git a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java index 2859f74ba894..71b104b7c0de 100644 --- a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java +++ b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.resource.acquisition.is.initialization.App; - /** * * Application test diff --git a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java new file mode 100644 index 000000000000..423d0ab518d1 --- /dev/null +++ b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java @@ -0,0 +1,27 @@ +package com.iluwatar.resource.acquisition.is.initialization; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; + +/** + * Date: 12/28/15 - 9:31 PM + * + * @author Jeroen Meulemeester + */ +public class ClosableTest extends StdOutTest { + + @Test + public void testOpenClose() throws Exception { + final InOrder inOrder = inOrder(getStdOutMock()); + try (final SlidingDoor door = new SlidingDoor(); final TreasureChest chest = new TreasureChest()) { + inOrder.verify(getStdOutMock()).println("Sliding door opens."); + inOrder.verify(getStdOutMock()).println("Treasure chest opens."); + } + inOrder.verify(getStdOutMock()).println("Treasure chest closes."); + inOrder.verify(getStdOutMock()).println("Sliding door closes."); + inOrder.verifyNoMoreInteractions(); + } + +} \ No newline at end of file diff --git a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/StdOutTest.java b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/StdOutTest.java new file mode 100644 index 000000000000..2fdc09e27beb --- /dev/null +++ b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.resource.acquisition.is.initialization; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/servant/index.md b/servant/index.md index 38a8e2c60cab..895b87502a64 100644 --- a/servant/index.md +++ b/servant/index.md @@ -4,15 +4,19 @@ title: Servant folder: servant permalink: /patterns/servant/ categories: Structural -tags: Java +tags: + - Java + - Difficulty-Beginner --- -**Intent:** Servant is used for providing some behavior to a group of classes. +## Intent +Servant is used for providing some behavior to a group of classes. Instead of defining that behavior in each class - or when we cannot factor out this behavior in the common parent class - it is defined once in the Servant. ![alt text](./etc/servant-pattern.png "Servant") -**Applicability:** Use the Servant pattern when +## Applicability +Use the Servant pattern when * when we want some objects to perform a common action and don't want to define this action as a method in every class. diff --git a/servant/pom.xml b/servant/pom.xml index 348905d2493a..3da9cae694a8 100644 --- a/servant/pom.xml +++ b/servant/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT servant @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/servant/src/main/java/com/iluwatar/servant/App.java b/servant/src/main/java/com/iluwatar/servant/App.java index 42babbc1d424..cb5a63fa5e95 100644 --- a/servant/src/main/java/com/iluwatar/servant/App.java +++ b/servant/src/main/java/com/iluwatar/servant/App.java @@ -18,15 +18,13 @@ public class App { /** * Program entry point - * - * @param args */ public static void main(String[] args) { scenario(jenkins, 1); scenario(travis, 0); } - /* + /** * Can add a List with enum Actions for variable scenarios */ public static void scenario(Servant servant, int compliment) { @@ -44,16 +42,18 @@ public static void scenario(Servant servant, int compliment) { servant.giveWine(k); servant.giveWine(q); // compliment - servant.GiveCompliments(guests.get(compliment)); + servant.giveCompliments(guests.get(compliment)); // outcome of the night - for (Royalty r : guests) + for (Royalty r : guests) { r.changeMood(); + } // check your luck - if (servant.checkIfYouWillBeHanged(guests)) + if (servant.checkIfYouWillBeHanged(guests)) { System.out.println(servant.name + " will live another day"); - else + } else { System.out.println("Poor " + servant.name + ". His days are numbered"); + } } } diff --git a/servant/src/main/java/com/iluwatar/servant/King.java b/servant/src/main/java/com/iluwatar/servant/King.java index 5e931c149c56..ab99252adbd0 100644 --- a/servant/src/main/java/com/iluwatar/servant/King.java +++ b/servant/src/main/java/com/iluwatar/servant/King.java @@ -28,10 +28,12 @@ public void receiveCompliments() { @Override public void changeMood() { - if (!isHungry && isDrunk) + if (!isHungry && isDrunk) { isHappy = true; - if (complimentReceived) + } + if (complimentReceived) { isHappy = false; + } } @Override diff --git a/servant/src/main/java/com/iluwatar/servant/Queen.java b/servant/src/main/java/com/iluwatar/servant/Queen.java index db5446d347e1..b8568bdf175f 100644 --- a/servant/src/main/java/com/iluwatar/servant/Queen.java +++ b/servant/src/main/java/com/iluwatar/servant/Queen.java @@ -29,8 +29,9 @@ public void receiveCompliments() { @Override public void changeMood() { - if (complimentReceived && isFlirty && isDrunk) + if (complimentReceived && isFlirty && isDrunk && !isHungry) { isHappy = true; + } } @Override diff --git a/servant/src/main/java/com/iluwatar/servant/Servant.java b/servant/src/main/java/com/iluwatar/servant/Servant.java index 987bf8791dc2..dbb623331984 100644 --- a/servant/src/main/java/com/iluwatar/servant/Servant.java +++ b/servant/src/main/java/com/iluwatar/servant/Servant.java @@ -11,6 +11,9 @@ public class Servant { public String name; + /** + * Constructor + */ public Servant(String name) { this.name = name; } @@ -23,15 +26,20 @@ public void giveWine(Royalty r) { r.getDrink(); } - public void GiveCompliments(Royalty r) { + public void giveCompliments(Royalty r) { r.receiveCompliments(); } + /** + * Check if we will be hanged + */ public boolean checkIfYouWillBeHanged(ArrayList tableGuests) { boolean anotherDay = true; - for (Royalty r : tableGuests) - if (!r.getMood()) + for (Royalty r : tableGuests) { + if (!r.getMood()) { anotherDay = false; + } + } return anotherDay; } diff --git a/servant/src/test/java/com/iluwatar/servant/AppTest.java b/servant/src/test/java/com/iluwatar/servant/AppTest.java index d5a4042914b3..20d5e6c0f852 100644 --- a/servant/src/test/java/com/iluwatar/servant/AppTest.java +++ b/servant/src/test/java/com/iluwatar/servant/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.servant.App; - /** * * Application test diff --git a/servant/src/test/java/com/iluwatar/servant/KingTest.java b/servant/src/test/java/com/iluwatar/servant/KingTest.java new file mode 100644 index 000000000000..3c0811bc5775 --- /dev/null +++ b/servant/src/test/java/com/iluwatar/servant/KingTest.java @@ -0,0 +1,82 @@ +package com.iluwatar.servant; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Date: 12/28/15 - 9:40 PM + * + * @author Jeroen Meulemeester + */ +public class KingTest { + + @Test + public void testHungrySoberUncomplimentedKing() { + final King king = new King(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testFedSoberUncomplimentedKing() { + final King king = new King(); + king.getFed(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testHungryDrunkUncomplimentedKing() { + final King king = new King(); + king.getDrink(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testHungrySoberComplimentedKing() { + final King king = new King(); + king.receiveCompliments(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testFedDrunkUncomplimentedKing() { + final King king = new King(); + king.getFed(); + king.getDrink(); + king.changeMood(); + assertTrue(king.getMood()); + } + + @Test + public void testFedSoberComplimentedKing() { + final King king = new King(); + king.getFed(); + king.receiveCompliments(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testFedDrunkComplimentedKing() { + final King king = new King(); + king.getFed(); + king.getDrink(); + king.receiveCompliments(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testHungryDrunkComplimentedKing() { + final King king = new King(); + king.getDrink(); + king.receiveCompliments(); + king.changeMood(); + assertFalse(king.getMood()); + } + +} \ No newline at end of file diff --git a/servant/src/test/java/com/iluwatar/servant/QueenTest.java b/servant/src/test/java/com/iluwatar/servant/QueenTest.java new file mode 100644 index 000000000000..d6f02774c34a --- /dev/null +++ b/servant/src/test/java/com/iluwatar/servant/QueenTest.java @@ -0,0 +1,46 @@ +package com.iluwatar.servant; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Date: 12/28/15 - 9:52 PM + * + * @author Jeroen Meulemeester + */ +public class QueenTest { + + @Test + public void testNotFlirtyUncomplemented() throws Exception { + final Queen queen = new Queen(); + queen.setFlirtiness(false); + queen.changeMood(); + assertFalse(queen.getMood()); + } + + @Test + public void testNotFlirtyComplemented() throws Exception { + final Queen queen = new Queen(); + queen.setFlirtiness(false); + queen.receiveCompliments(); + queen.changeMood(); + assertFalse(queen.getMood()); + } + + @Test + public void testFlirtyUncomplemented() throws Exception { + final Queen queen = new Queen(); + queen.changeMood(); + assertFalse(queen.getMood()); + } + + @Test + public void testFlirtyComplemented() throws Exception { + final Queen queen = new Queen(); + queen.receiveCompliments(); + queen.changeMood(); + assertTrue(queen.getMood()); + } + +} \ No newline at end of file diff --git a/servant/src/test/java/com/iluwatar/servant/ServantTest.java b/servant/src/test/java/com/iluwatar/servant/ServantTest.java new file mode 100644 index 000000000000..9527bdbc9849 --- /dev/null +++ b/servant/src/test/java/com/iluwatar/servant/ServantTest.java @@ -0,0 +1,70 @@ +package com.iluwatar.servant; + +import org.junit.Test; + +import java.util.ArrayList; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Date: 12/28/15 - 10:02 PM + * + * @author Jeroen Meulemeester + */ +public class ServantTest { + + @Test + public void testFeed() throws Exception { + final Royalty royalty = mock(Royalty.class); + final Servant servant = new Servant("test"); + servant.feed(royalty); + verify(royalty).getFed(); + verifyNoMoreInteractions(royalty); + } + + @Test + public void testGiveWine() throws Exception { + final Royalty royalty = mock(Royalty.class); + final Servant servant = new Servant("test"); + servant.giveWine(royalty); + verify(royalty).getDrink(); + verifyNoMoreInteractions(royalty); + } + + @Test + public void testGiveCompliments() throws Exception { + final Royalty royalty = mock(Royalty.class); + final Servant servant = new Servant("test"); + servant.giveCompliments(royalty); + verify(royalty).receiveCompliments(); + verifyNoMoreInteractions(royalty); + } + + @Test + public void testCheckIfYouWillBeHanged() throws Exception { + final Royalty goodMoodRoyalty = mock(Royalty.class); + when(goodMoodRoyalty.getMood()).thenReturn(true); + + final Royalty badMoodRoyalty = mock(Royalty.class); + when(badMoodRoyalty.getMood()).thenReturn(true); + + final ArrayList goodCompany = new ArrayList<>(); + goodCompany.add(goodMoodRoyalty); + goodCompany.add(goodMoodRoyalty); + goodCompany.add(goodMoodRoyalty); + + final ArrayList badCompany = new ArrayList<>(); + goodCompany.add(goodMoodRoyalty); + goodCompany.add(goodMoodRoyalty); + goodCompany.add(badMoodRoyalty); + + assertTrue(new Servant("test").checkIfYouWillBeHanged(goodCompany)); + assertTrue(new Servant("test").checkIfYouWillBeHanged(badCompany)); + + } + +} \ No newline at end of file diff --git a/service-layer/index.md b/service-layer/index.md index ea3f3d0ba546..9b685d4e3f4b 100644 --- a/service-layer/index.md +++ b/service-layer/index.md @@ -4,10 +4,13 @@ title: Service Layer folder: service-layer permalink: /patterns/service-layer/ categories: Architectural -tags: Java +tags: + - Java + - Difficulty-Intermediate --- -**Intent:** Service Layer is an abstraction over domain logic. Typically +## Intent +Service Layer is an abstraction over domain logic. Typically applications require multiple kinds of interfaces to the data they store and logic they implement: data loaders, user interfaces, integration gateways, and others. Despite their different purposes, these interfaces often need common @@ -16,12 +19,13 @@ its business logic. The Service Layer fulfills this role. ![alt text](./etc/service-layer.png "Service Layer") -**Applicability:** Use the Service Layer pattern when +## Applicability +Use the Service Layer pattern when * you want to encapsulate domain logic under API * you need to implement multiple interfaces with common logic and data -**Credits:** +## Credits * [Martin Fowler - Service Layer](http://martinfowler.com/eaaCatalog/serviceLayer.html) * [Patterns of Enterprise Application Architecture](http://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420) diff --git a/service-layer/pom.xml b/service-layer/pom.xml index b188ba1d7bcd..b8b977829db9 100644 --- a/service-layer/pom.xml +++ b/service-layer/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT service-layer @@ -22,5 +22,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java b/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java index a7053165df26..ab0d3f9a07c5 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java @@ -47,6 +47,9 @@ public static void main(String[] args) { queryData(); } + /** + * Initialize data + */ public static void initData() { // spells Spell spell1 = new Spell("Ice dart"); @@ -149,6 +152,9 @@ public static void initData() { wizardDao.merge(wizard4); } + /** + * Query the data + */ public static void queryData() { MagicService service = new MagicServiceImpl(new WizardDaoImpl(), new SpellbookDaoImpl(), new SpellDaoImpl()); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java b/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java index d8c796427d27..ab00009227a6 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java @@ -12,8 +12,37 @@ */ @MappedSuperclass @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) -public class BaseEntity { +public abstract class BaseEntity { @Version private Long version; + + /** + * Indicates the unique id of this entity + * + * @return The id of the entity, or 'null' when not persisted + */ + public abstract Long getId(); + + /** + * Set the id of this entity + * + * @param id The new id + */ + public abstract void setId(Long id); + + /** + * Get the name of this entity + * + * @return The name of the entity + */ + public abstract String getName(); + + /** + * Set the name of this entity + * + * @param name The new name + */ + public abstract void setName(final String name); + } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java index eae9286fa465..2665ff8582c5 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java @@ -39,8 +39,9 @@ public E find(Long id) { result = (E) criteria.uniqueResult(); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); @@ -57,8 +58,9 @@ public void persist(E entity) { session.persist(entity); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); @@ -75,8 +77,9 @@ public E merge(E entity) { result = (E) session.merge(entity); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); @@ -93,8 +96,9 @@ public void delete(E entity) { session.delete(entity); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); @@ -111,8 +115,9 @@ public List findAll() { Criteria criteria = session.createCriteria(persistentClass); result = criteria.list(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java b/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java index 9d1aec48811f..9920a50df3e6 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java @@ -1,38 +1,56 @@ package com.iluwatar.servicelayer.hibernate; -import org.hibernate.SessionFactory; -import org.hibernate.cfg.Configuration; - import com.iluwatar.servicelayer.spell.Spell; import com.iluwatar.servicelayer.spellbook.Spellbook; import com.iluwatar.servicelayer.wizard.Wizard; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; + /** - * * Produces the Hibernate {@link SessionFactory}. - * */ public class HibernateUtil { - private static final SessionFactory sessionFactory; - - static { - try { - sessionFactory = - new Configuration().addAnnotatedClass(Wizard.class).addAnnotatedClass(Spellbook.class) - .addAnnotatedClass(Spell.class) - .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect") - .setProperty("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1") - .setProperty("hibernate.current_session_context_class", "thread") - .setProperty("hibernate.show_sql", "true") - .setProperty("hibernate.hbm2ddl.auto", "create-drop").buildSessionFactory(); - } catch (Throwable ex) { - System.err.println("Initial SessionFactory creation failed." + ex); - throw new ExceptionInInitializerError(ex); - } + /** + * The cached session factory + */ + private static volatile SessionFactory sessionFactory; + + private HibernateUtil() { } - public static SessionFactory getSessionFactory() { + /** + * Create the current session factory instance, create a new one when there is none yet. + * + * @return The session factory + */ + public static synchronized SessionFactory getSessionFactory() { + if (sessionFactory == null) { + try { + sessionFactory = + new Configuration().addAnnotatedClass(Wizard.class).addAnnotatedClass(Spellbook.class) + .addAnnotatedClass(Spell.class) + .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect") + .setProperty("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1") + .setProperty("hibernate.current_session_context_class", "thread") + .setProperty("hibernate.show_sql", "true") + .setProperty("hibernate.hbm2ddl.auto", "create-drop").buildSessionFactory(); + } catch (Throwable ex) { + System.err.println("Initial SessionFactory creation failed." + ex); + throw new ExceptionInInitializerError(ex); + } + } return sessionFactory; } + + /** + * Drop the current connection, resulting in a create-drop clean database next time. This is + * mainly used for JUnit testing since one test should not influence the other + */ + public static void dropSession() { + getSessionFactory().close(); + sessionFactory = null; + } + } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java index f46f55184f72..cda3fe58d6d6 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java @@ -21,6 +21,9 @@ public class MagicServiceImpl implements MagicService { private SpellbookDao spellbookDao; private SpellDao spellDao; + /** + * Constructor + */ public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { this.wizardDao = wizardDao; this.spellbookDao = spellbookDao; diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java index f5f017625751..708ba033e114 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java @@ -1,12 +1,12 @@ package com.iluwatar.servicelayer.spell; +import com.iluwatar.servicelayer.common.DaoBaseImpl; + import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.criterion.Restrictions; -import com.iluwatar.servicelayer.common.DaoBaseImpl; - /** * * SpellDao implementation. @@ -24,11 +24,11 @@ public Spell findByName(String name) { Criteria criteria = session.createCriteria(persistentClass); criteria.add(Restrictions.eq("name", name)); result = (Spell) criteria.uniqueResult(); - result.getSpellbook().getWizards().size(); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java index 49d81a955688..165dcdc22572 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java @@ -6,6 +6,7 @@ import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; @@ -50,7 +51,7 @@ public void setId(Long id) { private String name; - @ManyToMany(mappedBy = "spellbooks") + @ManyToMany(mappedBy = "spellbooks", fetch = FetchType.EAGER) private Set wizards; @OneToMany(mappedBy = "spellbook", orphanRemoval = true, cascade = CascadeType.ALL) diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java index 1de82d4a9825..842764056c14 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java @@ -28,8 +28,9 @@ public Spellbook findByName(String name) { result.getWizards().size(); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java index ad89dd28a3a1..9ff36edefd77 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java @@ -30,8 +30,9 @@ public Wizard findByName(String name) { } tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java index 2a6202104bf4..f92af7cfff09 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java @@ -1,8 +1,9 @@ package com.iluwatar.servicelayer.app; -import org.junit.Test; +import com.iluwatar.servicelayer.hibernate.HibernateUtil; -import com.iluwatar.servicelayer.app.App; +import org.junit.After; +import org.junit.Test; /** * @@ -16,4 +17,10 @@ public void test() { String[] args = {}; App.main(args); } + + @After + public void tearDown() throws Exception { + HibernateUtil.dropSession(); + } + } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java new file mode 100644 index 000000000000..1dabe117aab2 --- /dev/null +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java @@ -0,0 +1,123 @@ +package com.iluwatar.servicelayer.common; + +import com.iluwatar.servicelayer.hibernate.HibernateUtil; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * Date: 12/28/15 - 10:53 PM + * + * @author Jeroen Meulemeester + */ +public abstract class BaseDaoTest> { + + /** + * The number of entities stored before each test + */ + private static final int INITIAL_COUNT = 5; + + /** + * The unique id generator, shared between all entities + */ + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + + /** + * Factory, used to create new entity instances with the given name + */ + private final Function factory; + + /** + * The tested data access object + */ + private final D dao; + + /** + * Create a new test using the given factory and dao + * + * @param factory The factory, used to create new entity instances with the given name + * @param dao The tested data access object + */ + public BaseDaoTest(final Function factory, final D dao) { + this.factory = factory; + this.dao = dao; + } + + @Before + public void setUp() throws Exception { + for (int i = 0; i < INITIAL_COUNT; i++) { + final String className = dao.persistentClass.getSimpleName(); + final String entityName = String.format("%s%d", className, ID_GENERATOR.incrementAndGet()); + this.dao.persist(this.factory.apply(entityName)); + } + } + + @After + public void tearDown() throws Exception { + HibernateUtil.dropSession(); + } + + protected final D getDao() { + return this.dao; + } + + @Test + public void testFind() throws Exception { + final List all = this.dao.findAll(); + for (final E entity : all) { + final E byId = this.dao.find(entity.getId()); + assertNotNull(byId); + assertEquals(byId.getId(), byId.getId()); + } + } + + @Test + public void testDelete() throws Exception { + final List originalEntities = this.dao.findAll(); + this.dao.delete(originalEntities.get(1)); + this.dao.delete(originalEntities.get(2)); + + final List entitiesLeft = this.dao.findAll(); + assertNotNull(entitiesLeft); + assertEquals(INITIAL_COUNT - 2, entitiesLeft.size()); + } + + @Test + public void testFindAll() throws Exception { + final List all = this.dao.findAll(); + assertNotNull(all); + assertEquals(INITIAL_COUNT, all.size()); + } + + @Test + public void testSetId() throws Exception { + final E entity = this.factory.apply("name"); + assertNull(entity.getId()); + + final Long expectedId = Long.valueOf(1); + entity.setId(expectedId); + assertEquals(expectedId, entity.getId()); + } + + @Test + public void testSetName() throws Exception { + final E entity = this.factory.apply("name"); + assertEquals("name", entity.getName()); + assertEquals("name", entity.toString()); + + final String expectedName = "new name"; + entity.setName(expectedName); + assertEquals(expectedName, entity.getName()); + assertEquals(expectedName, entity.toString()); + } + +} \ No newline at end of file diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java new file mode 100644 index 000000000000..48f3ae9d34eb --- /dev/null +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java @@ -0,0 +1,138 @@ +package com.iluwatar.servicelayer.magic; + +import com.iluwatar.servicelayer.spell.Spell; +import com.iluwatar.servicelayer.spell.SpellDao; +import com.iluwatar.servicelayer.spellbook.Spellbook; +import com.iluwatar.servicelayer.spellbook.SpellbookDao; +import com.iluwatar.servicelayer.wizard.Wizard; +import com.iluwatar.servicelayer.wizard.WizardDao; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +/** + * Date: 12/29/15 - 12:06 AM + * + * @author Jeroen Meulemeester + */ +public class MagicServiceImplTest { + + @Test + public void testFindAllWizards() throws Exception { + final WizardDao wizardDao = mock(WizardDao.class); + final SpellbookDao spellbookDao = mock(SpellbookDao.class); + final SpellDao spellDao = mock(SpellDao.class); + + final MagicServiceImpl service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + verifyZeroInteractions(wizardDao, spellbookDao, spellDao); + + service.findAllWizards(); + verify(wizardDao).findAll(); + verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); + } + + @Test + public void testFindAllSpellbooks() throws Exception { + final WizardDao wizardDao = mock(WizardDao.class); + final SpellbookDao spellbookDao = mock(SpellbookDao.class); + final SpellDao spellDao = mock(SpellDao.class); + + final MagicServiceImpl service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + verifyZeroInteractions(wizardDao, spellbookDao, spellDao); + + service.findAllSpellbooks(); + verify(spellbookDao).findAll(); + verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); + } + + @Test + public void testFindAllSpells() throws Exception { + final WizardDao wizardDao = mock(WizardDao.class); + final SpellbookDao spellbookDao = mock(SpellbookDao.class); + final SpellDao spellDao = mock(SpellDao.class); + + final MagicServiceImpl service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + verifyZeroInteractions(wizardDao, spellbookDao, spellDao); + + service.findAllSpells(); + verify(spellDao).findAll(); + verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); + } + + @Test + public void testFindWizardsWithSpellbook() throws Exception { + final String bookname = "bookname"; + final Spellbook spellbook = mock(Spellbook.class); + final Set wizards = new HashSet<>(); + wizards.add(mock(Wizard.class)); + wizards.add(mock(Wizard.class)); + wizards.add(mock(Wizard.class)); + + when(spellbook.getWizards()).thenReturn(wizards); + + final SpellbookDao spellbookDao = mock(SpellbookDao.class); + when(spellbookDao.findByName(eq(bookname))).thenReturn(spellbook); + + final WizardDao wizardDao = mock(WizardDao.class); + final SpellDao spellDao = mock(SpellDao.class); + + + final MagicServiceImpl service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + verifyZeroInteractions(wizardDao, spellbookDao, spellDao, spellbook); + + final List result = service.findWizardsWithSpellbook(bookname); + verify(spellbookDao).findByName(eq(bookname)); + verify(spellbook).getWizards(); + + assertNotNull(result); + assertEquals(3, result.size()); + + verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); + } + + @Test + public void testFindWizardsWithSpell() throws Exception { + final Set wizards = new HashSet<>(); + wizards.add(mock(Wizard.class)); + wizards.add(mock(Wizard.class)); + wizards.add(mock(Wizard.class)); + + final Spellbook spellbook = mock(Spellbook.class); + when(spellbook.getWizards()).thenReturn(wizards); + + final SpellbookDao spellbookDao = mock(SpellbookDao.class); + final WizardDao wizardDao = mock(WizardDao.class); + + final Spell spell = mock(Spell.class); + when(spell.getSpellbook()).thenReturn(spellbook); + + final String spellName = "spellname"; + final SpellDao spellDao = mock(SpellDao.class); + when(spellDao.findByName(eq(spellName))).thenReturn(spell); + + final MagicServiceImpl service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + verifyZeroInteractions(wizardDao, spellbookDao, spellDao, spellbook); + + final List result = service.findWizardsWithSpell(spellName); + verify(spellDao).findByName(eq(spellName)); + verify(spellbook).getWizards(); + + assertNotNull(result); + assertEquals(3, result.size()); + + verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); + } + +} diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java new file mode 100644 index 000000000000..99a8e142f252 --- /dev/null +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java @@ -0,0 +1,35 @@ +package com.iluwatar.servicelayer.spell; + +import com.iluwatar.servicelayer.common.BaseDaoTest; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/28/15 - 11:02 PM + * + * @author Jeroen Meulemeester + */ +public class SpellDaoImplTest extends BaseDaoTest { + + public SpellDaoImplTest() { + super(Spell::new, new SpellDaoImpl()); + } + + @Test + public void testFindByName() throws Exception { + final SpellDaoImpl dao = getDao(); + final List allSpells = dao.findAll(); + for (final Spell spell : allSpells) { + final Spell spellByName = dao.findByName(spell.getName()); + assertNotNull(spellByName); + assertEquals(spell.getId(), spellByName.getId()); + assertEquals(spell.getName(), spellByName.getName()); + } + } + +} diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java new file mode 100644 index 000000000000..fda46009ef61 --- /dev/null +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java @@ -0,0 +1,35 @@ +package com.iluwatar.servicelayer.spellbook; + +import com.iluwatar.servicelayer.common.BaseDaoTest; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/28/15 - 11:44 PM + * + * @author Jeroen Meulemeester + */ +public class SpellbookDaoImplTest extends BaseDaoTest { + + public SpellbookDaoImplTest() { + super(Spellbook::new, new SpellbookDaoImpl()); + } + + @Test + public void testFindByName() throws Exception { + final SpellbookDaoImpl dao = getDao(); + final List allBooks = dao.findAll(); + for (final Spellbook book : allBooks) { + final Spellbook spellByName = dao.findByName(book.getName()); + assertNotNull(spellByName); + assertEquals(book.getId(), spellByName.getId()); + assertEquals(book.getName(), spellByName.getName()); + } + } + +} diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java new file mode 100644 index 000000000000..1812f4c366e1 --- /dev/null +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java @@ -0,0 +1,35 @@ +package com.iluwatar.servicelayer.wizard; + +import com.iluwatar.servicelayer.common.BaseDaoTest; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/28/15 - 11:46 PM + * + * @author Jeroen Meulemeester + */ +public class WizardDaoImplTest extends BaseDaoTest { + + public WizardDaoImplTest() { + super(Wizard::new, new WizardDaoImpl()); + } + + @Test + public void testFindByName() throws Exception { + final WizardDaoImpl dao = getDao(); + final List allWizards = dao.findAll(); + for (final Wizard spell : allWizards) { + final Wizard byName = dao.findByName(spell.getName()); + assertNotNull(byName); + assertEquals(spell.getId(), byName.getId()); + assertEquals(spell.getName(), byName.getName()); + } + } + +} diff --git a/service-locator/index.md b/service-locator/index.md index 03c432749c1a..af4d8c3ac822 100644 --- a/service-locator/index.md +++ b/service-locator/index.md @@ -4,15 +4,20 @@ title: Service Locator folder: service-locator permalink: /patterns/service-locator/ categories: Structural -tags: Java +tags: + - Java + - Difficulty-Beginner + - Performance --- -**Intent:** Encapsulate the processes involved in obtaining a service with a +## Intent +Encapsulate the processes involved in obtaining a service with a strong abstraction layer. ![alt text](./etc/service-locator.png "Service Locator") -**Applicability:** The service locator pattern is applicable whenever we want +## Applicability +The service locator pattern is applicable whenever we want to locate/fetch various services using JNDI which, typically, is a redundant and expensive lookup. The service Locator pattern addresses this expensive lookup by making use of caching techniques ie. for the very first time a @@ -21,12 +26,12 @@ the relevant service and then finally caches this service object. Now, further lookups of the same service via Service Locator is done in its cache which improves the performance of application to great extent. -**Typical Use Case:** +## Typical Use Case * when network hits are expensive and time consuming * lookups of services are done quite frequently * large number of services are being used -**Credits:** +## Credits * [J2EE Design Patterns](http://www.amazon.com/J2EE-Design-Patterns-William-Crawford/dp/0596004273/ref=sr_1_2) diff --git a/service-locator/pom.xml b/service-locator/pom.xml index f04f9199ee65..09acb03b8eeb 100644 --- a/service-locator/pom.xml +++ b/service-locator/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT service-locator diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java index f2d338cdc428..f2dd31221bf5 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java @@ -12,6 +12,9 @@ public class ServiceImpl implements Service { private final String serviceName; private final int id; + /** + * Constructor + */ public ServiceImpl(String serviceName) { // set the service name this.serviceName = serviceName; diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java index 6df74f84e873..6ec51b9898f0 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java @@ -10,6 +10,9 @@ public class ServiceLocator { private static ServiceCache serviceCache = new ServiceCache(); + private ServiceLocator() { + } + /** * Fetch the service with the name param from the cache first, if no service is found, lookup the * service from the {@link InitContext} and then add the newly created service into the cache map @@ -29,7 +32,9 @@ public static Service getService(String serviceJndiName) { */ InitContext ctx = new InitContext(); serviceObj = (Service) ctx.lookup(serviceJndiName); - serviceCache.addService(serviceObj); + if (serviceObj != null) { // Only cache a service if it actually exists + serviceCache.addService(serviceObj); + } return serviceObj; } } diff --git a/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java b/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java index ab1549182d48..0ed27656c048 100644 --- a/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java +++ b/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.servicelocator.App; - /** * * Application test diff --git a/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java b/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java new file mode 100644 index 000000000000..ce54e054fc9d --- /dev/null +++ b/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java @@ -0,0 +1,46 @@ +package com.iluwatar.servicelocator; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/29/15 - 19:07 PM + * + * @author Jeroen Meulemeester + */ +public class ServiceLocatorTest { + + /** + * Verify if we just receive 'null' when requesting a non-existing service + */ + @Test + public void testGetNonExistentService() { + assertNull(ServiceLocator.getService("fantastic/unicorn/service")); + assertNull(ServiceLocator.getService("another/fantastic/unicorn/service")); + } + + /** + * Verify if we get the same cached instance when requesting the same service twice + */ + @Test + public void testServiceCache() { + final String[] serviceNames = new String[]{ + "jndi/serviceA", "jndi/serviceB" + }; + + for (final String serviceName : serviceNames) { + final Service service = ServiceLocator.getService(serviceName); + assertNotNull(service); + assertEquals(serviceName, service.getName()); + assertTrue(service.getId() > 0); // The id is generated randomly, but the minimum value is '1' + assertSame(service, ServiceLocator.getService(serviceName)); + } + + } + +} \ No newline at end of file diff --git a/singleton/index.md b/singleton/index.md index 18c137448509..dcbd63902c93 100644 --- a/singleton/index.md +++ b/singleton/index.md @@ -10,26 +10,28 @@ tags: - Difficulty-Beginner --- -**Intent:** Ensure a class only has one instance, and provide a global point of +## Intent +Ensure a class only has one instance, and provide a global point of access to it. ![alt text](./etc/singleton_1.png "Singleton") -**Applicability:** Use the Singleton pattern when +## Applicability +Use the Singleton pattern when * there must be exactly one instance of a class, and it must be accessible to clients from a well-known access point * when the sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code -**Typical Use Case:** +## Typical Use Case * the logging class * managing a connection to a database * file manager -**Real world examples:** +## Real world examples * [java.lang.Runtime#getRuntime()](http://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#getRuntime%28%29) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/singleton/pom.xml b/singleton/pom.xml index de316c34c980..e08ffec868f1 100644 --- a/singleton/pom.xml +++ b/singleton/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT singleton diff --git a/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java index 585b11e61758..f8b7e170f42f 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java @@ -8,7 +8,7 @@ public final class IvoryTower { /** * Static to class instance of the class. */ - private static final IvoryTower instance = new IvoryTower(); + private static final IvoryTower INSTANCE = new IvoryTower(); /** * Private constructor so nobody can instantiate the class. @@ -21,6 +21,6 @@ private IvoryTower() {} * @return instance of the singleton. */ public static IvoryTower getInstance() { - return instance; + return INSTANCE; } } diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java index 1aca15b30a61..ab39a652dcdb 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java @@ -11,14 +11,14 @@ */ public class ThreadSafeDoubleCheckLocking { - private static volatile ThreadSafeDoubleCheckLocking INSTANCE; + private static volatile ThreadSafeDoubleCheckLocking instance; /** * private constructor to prevent client from instantiating. */ private ThreadSafeDoubleCheckLocking() { // to prevent instantiating by Reflection call - if (INSTANCE != null) { + if (instance != null) { throw new IllegalStateException("Already initialized."); } } @@ -31,12 +31,12 @@ private ThreadSafeDoubleCheckLocking() { public static ThreadSafeDoubleCheckLocking getInstance() { // local variable increases performance by 25 percent // Joshua Bloch "Effective Java, Second Edition", p. 283-284 - ThreadSafeDoubleCheckLocking result = INSTANCE; + ThreadSafeDoubleCheckLocking result = instance; if (result == null) { synchronized (ThreadSafeDoubleCheckLocking.class) { - result = INSTANCE; + result = instance; if (result == null) { - INSTANCE = result = new ThreadSafeDoubleCheckLocking(); + instance = result = new ThreadSafeDoubleCheckLocking(); } } } diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java index 98281b4c8fa4..e679220163e7 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java @@ -16,7 +16,7 @@ private ThreadSafeLazyLoadedIvoryTower() {} /** * The instance gets created only when it is called for first time. Lazy-loading */ - public synchronized static ThreadSafeLazyLoadedIvoryTower getInstance() { + public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() { if (instance == null) { instance = new ThreadSafeLazyLoadedIvoryTower(); diff --git a/singleton/src/test/java/com/iluwatar/singleton/AppTest.java b/singleton/src/test/java/com/iluwatar/singleton/AppTest.java index 4957eec6b7c8..232de4e40d5f 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/AppTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.singleton.App; - /** * * Application test diff --git a/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java new file mode 100644 index 000000000000..49c65c716af0 --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.singleton; + +/** + * Date: 12/29/15 - 19:20 PM + * + * @author Jeroen Meulemeester + */ +public class EnumIvoryTowerTest extends SingletonTest { + + /** + * Create a new singleton test instance using the given 'getInstance' method + */ + public EnumIvoryTowerTest() { + super(() -> EnumIvoryTower.INSTANCE); + } + +} diff --git a/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java b/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java new file mode 100644 index 000000000000..60ae4798dd34 --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.singleton; + +/** + * Date: 12/29/15 - 19:22 PM + * + * @author Jeroen Meulemeester + */ +public class InitializingOnDemandHolderIdiomTest extends SingletonTest { + + /** + * Create a new singleton test instance using the given 'getInstance' method + */ + public InitializingOnDemandHolderIdiomTest() { + super(InitializingOnDemandHolderIdiom::getInstance); + } + +} \ No newline at end of file diff --git a/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java new file mode 100644 index 000000000000..e9a222aef3fd --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.singleton; + +/** + * Date: 12/29/15 - 19:23 PM + * + * @author Jeroen Meulemeester + */ +public class IvoryTowerTest extends SingletonTest { + + /** + * Create a new singleton test instance using the given 'getInstance' method + */ + public IvoryTowerTest() { + super(IvoryTower::getInstance); + } + +} \ No newline at end of file diff --git a/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java b/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java deleted file mode 100644 index 3afc1bf14744..000000000000 --- a/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.iluwatar.singleton; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.*; - -import static org.junit.Assert.assertEquals; - -/** - * This class provides several test case that test singleton construction. - * - * The first proves that multiple calls to the singleton getInstance object are the same when called - * in the SAME thread. The second proves that multiple calls to the singleton getInstance object are - * the same when called in the DIFFERENT thread. - * - */ -public class LazyLoadedSingletonThreadSafetyTest { - - private static final int NUM_THREADS = 5; - private List threadObjects = Collections - .synchronizedList(new ArrayList<>()); - - // NullObject class so Callable has to return something - private class NullObject { - private NullObject() {} - } - - @Test - public void test_MultipleCallsReturnTheSameObjectInSameThread() { - // Create several instances in the same calling thread - ThreadSafeLazyLoadedIvoryTower instance1 = ThreadSafeLazyLoadedIvoryTower.getInstance(); - ThreadSafeLazyLoadedIvoryTower instance2 = ThreadSafeLazyLoadedIvoryTower.getInstance(); - ThreadSafeLazyLoadedIvoryTower instance3 = ThreadSafeLazyLoadedIvoryTower.getInstance(); - // now check they are equal - assertEquals(instance1, instance1); - assertEquals(instance1, instance2); - assertEquals(instance2, instance3); - assertEquals(instance1, instance3); - } - - @Test - public void test_MultipleCallsReturnTheSameObjectInDifferentThreads() - throws InterruptedException, ExecutionException { - {// create several threads and inside each callable instantiate the singleton class - ExecutorService executorService = Executors.newSingleThreadExecutor(); - - List> threadList = new ArrayList<>(); - for (int i = 0; i < NUM_THREADS; i++) { - threadList.add(new SingletonCreatingThread()); - } - - ExecutorService service = Executors.newCachedThreadPool(); - List> results = service.invokeAll(threadList); - - // wait for all of the threads to complete - for (Future res : results) { - res.get(); - } - - // tidy up the executor - executorService.shutdown(); - } - {// now check the contents that were added to threadObjects by each thread - assertEquals(NUM_THREADS, threadObjects.size()); - assertEquals(threadObjects.get(0), threadObjects.get(1)); - assertEquals(threadObjects.get(1), threadObjects.get(2)); - assertEquals(threadObjects.get(2), threadObjects.get(3)); - assertEquals(threadObjects.get(3), threadObjects.get(4)); - } - } - - private class SingletonCreatingThread implements Callable { - @Override - public NullObject call() { - // instantiate the thread safety class and add to list to test afterwards - ThreadSafeLazyLoadedIvoryTower instance = ThreadSafeLazyLoadedIvoryTower.getInstance(); - threadObjects.add(instance); - return new NullObject();// return null object (cannot return Void) - } - } -} diff --git a/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java b/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java new file mode 100644 index 000000000000..6c6c4a3f44d9 --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java @@ -0,0 +1,88 @@ +package com.iluwatar.singleton; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Supplier; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +/** + * This class provides several test case that test singleton construction. + * + * The first proves that multiple calls to the singleton getInstance object are the same when called + * in the SAME thread. The second proves that multiple calls to the singleton getInstance object are + * the same when called in the DIFFERENT thread. + * + * Date: 12/29/15 - 19:25 PM + * + * @author Jeroen Meulemeester + * @author Richard Jones + */ +public abstract class SingletonTest { + + /** + * The singleton's getInstance method + */ + private final Supplier singletonInstanceMethod; + + /** + * Create a new singleton test instance using the given 'getInstance' method + * + * @param singletonInstanceMethod The singleton's getInstance method + */ + public SingletonTest(final Supplier singletonInstanceMethod) { + this.singletonInstanceMethod = singletonInstanceMethod; + } + + /** + * Test the singleton in a non-concurrent setting + */ + @Test + public void testMultipleCallsReturnTheSameObjectInSameThread() { + // Create several instances in the same calling thread + S instance1 = this.singletonInstanceMethod.get(); + S instance2 = this.singletonInstanceMethod.get(); + S instance3 = this.singletonInstanceMethod.get(); + // now check they are equal + assertSame(instance1, instance2); + assertSame(instance1, instance3); + assertSame(instance2, instance3); + } + + /** + * Test singleton instance in a concurrent setting + */ + @Test(timeout = 10000) + public void testMultipleCallsReturnTheSameObjectInDifferentThreads() throws Exception { + + // Create 10000 tasks and inside each callable instantiate the singleton class + final List> tasks = new ArrayList<>(); + for (int i = 0; i < 10000; i++) { + tasks.add(this.singletonInstanceMethod::get); + } + + // Use up to 8 concurrent threads to handle the tasks + final ExecutorService executorService = Executors.newFixedThreadPool(8); + final List> results = executorService.invokeAll(tasks); + + // wait for all of the threads to complete + final S expectedInstance = this.singletonInstanceMethod.get(); + for (Future res : results) { + final S instance = res.get(); + assertNotNull(instance); + assertSame(expectedInstance, instance); + } + + // tidy up the executor + executorService.shutdown(); + + } + +} diff --git a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java new file mode 100644 index 000000000000..f40f0cbc71b9 --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.singleton; + +/** + * Date: 12/29/15 - 19:26 PM + * + * @author Jeroen Meulemeester + */ +public class ThreadSafeDoubleCheckLockingTest extends SingletonTest { + + /** + * Create a new singleton test instance using the given 'getInstance' method + */ + public ThreadSafeDoubleCheckLockingTest() { + super(ThreadSafeDoubleCheckLocking::getInstance); + } + +} \ No newline at end of file diff --git a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java new file mode 100644 index 000000000000..8f2a5e6e1346 --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.singleton; + +/** + * Date: 12/29/15 - 19:26 PM + * + * @author Jeroen Meulemeester + */ +public class ThreadSafeLazyLoadedIvoryTowerTest extends SingletonTest { + + /** + * Create a new singleton test instance using the given 'getInstance' method + */ + public ThreadSafeLazyLoadedIvoryTowerTest() { + super(ThreadSafeLazyLoadedIvoryTower::getInstance); + } + +} diff --git a/specification/index.md b/specification/index.md index 6b76d51026ba..df6a4c3ebd31 100644 --- a/specification/index.md +++ b/specification/index.md @@ -4,21 +4,25 @@ title: Specification folder: specification permalink: /patterns/specification/ categories: Behavioral -tags: Java +tags: + - Java + - Difficulty-Beginner --- -**Intent:** Specification pattern separates the statement of how to match a +## Intent +Specification pattern separates the statement of how to match a candidate, from the candidate object that it is matched against. As well as its usefulness in selection, it is also valuable for validation and for building to order ![alt text](./etc/specification.png "Specification") -**Applicability:** Use the Specification pattern when +## Applicability +Use the Specification pattern when * you need to select a subset of objects based on some criteria, and to refresh the selection at various times * you need to check that only suitable objects are used for a certain role (validation) -**Credits:** +## Credits * [Martin Fowler - Specifications](http://martinfowler.com/apsupp/spec.pdf) diff --git a/specification/pom.xml b/specification/pom.xml index bf69e481e5fb..64caa16b0f37 100644 --- a/specification/pom.xml +++ b/specification/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT specification @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/specification/src/main/java/com/iluwatar/specification/app/App.java b/specification/src/main/java/com/iluwatar/specification/app/App.java index d755d6c2eaef..373a24f92032 100644 --- a/specification/src/main/java/com/iluwatar/specification/app/App.java +++ b/specification/src/main/java/com/iluwatar/specification/app/App.java @@ -31,6 +31,9 @@ */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { // initialize creatures list List creatures = diff --git a/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java b/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java index 2ec3ccf55cab..f02befb73a75 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java @@ -16,6 +16,9 @@ public abstract class AbstractCreature implements Creature { private Movement movement; private Color color; + /** + * Constructor + */ public AbstractCreature(String name, Size size, Movement movement, Color color) { this.name = name; this.size = size; diff --git a/specification/src/test/java/com/iluwatar/specification/app/AppTest.java b/specification/src/test/java/com/iluwatar/specification/app/AppTest.java index fe613eab7c4e..b1bf00c24178 100644 --- a/specification/src/test/java/com/iluwatar/specification/app/AppTest.java +++ b/specification/src/test/java/com/iluwatar/specification/app/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.specification.app.App; - /** * * Application test diff --git a/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java b/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java new file mode 100644 index 000000000000..0548788a4cef --- /dev/null +++ b/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java @@ -0,0 +1,111 @@ +package com.iluwatar.specification.creature; + +import com.iluwatar.specification.property.Color; +import com.iluwatar.specification.property.Movement; +import com.iluwatar.specification.property.Size; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/29/15 - 7:47 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class CreatureTest { + + /** + * @return The tested {@link Creature} instance and its expected specs + */ + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList( + new Object[]{new Dragon(), "Dragon", Size.LARGE, Movement.FLYING, Color.RED}, + new Object[]{new Goblin(), "Goblin", Size.SMALL, Movement.WALKING, Color.GREEN}, + new Object[]{new KillerBee(), "KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT}, + new Object[]{new Octopus(), "Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK}, + new Object[]{new Shark(), "Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT}, + new Object[]{new Troll(), "Troll", Size.LARGE, Movement.WALKING, Color.DARK} + ); + } + + /** + * The tested creature + */ + private final Creature testedCreature; + + /** + * The expected name of the tested creature + */ + private final String name; + + /** + * The expected size of the tested creature + */ + private final Size size; + + /** + * The expected movement type of the tested creature + */ + private final Movement movement; + + /** + * The expected color of the tested creature + */ + private final Color color; + + /** + * @param testedCreature The tested creature + * @param name The expected name of the creature + * @param size The expected size of the creature + * @param movement The expected movement type of the creature + * @param color The expected color of the creature + */ + public CreatureTest(final Creature testedCreature, final String name, final Size size, + final Movement movement, final Color color) { + this.testedCreature = testedCreature; + this.name = name; + this.size = size; + this.movement = movement; + this.color = color; + } + + + @Test + public void testGetName() throws Exception { + assertEquals(this.name, this.testedCreature.getName()); + } + + @Test + public void testGetSize() throws Exception { + assertEquals(this.size, this.testedCreature.getSize()); + } + + @Test + public void testGetMovement() throws Exception { + assertEquals(this.movement, this.testedCreature.getMovement()); + } + + @Test + public void testGetColor() throws Exception { + assertEquals(this.color, this.testedCreature.getColor()); + } + + @Test + public void testToString() throws Exception { + final String toString = this.testedCreature.toString(); + assertNotNull(toString); + assertEquals( + String.format("%s [size=%s, movement=%s, color=%s]", name, size, movement, color), + toString + ); + } +} \ No newline at end of file diff --git a/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java new file mode 100644 index 000000000000..894f6c58e67f --- /dev/null +++ b/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java @@ -0,0 +1,37 @@ +package com.iluwatar.specification.selector; + +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Color; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Date: 12/29/15 - 7:35 PM + * + * @author Jeroen Meulemeester + */ +public class ColorSelectorTest { + + /** + * Verify if the color selector gives the correct results + */ + @Test + public void testColor() { + final Creature greenCreature = mock(Creature.class); + when(greenCreature.getColor()).thenReturn(Color.GREEN); + + final Creature redCreature = mock(Creature.class); + when(redCreature.getColor()).thenReturn(Color.RED); + + final ColorSelector greenSelector = new ColorSelector(Color.GREEN); + assertTrue(greenSelector.test(greenCreature)); + assertFalse(greenSelector.test(redCreature)); + + } + +} \ No newline at end of file diff --git a/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java new file mode 100644 index 000000000000..c2a251b5a627 --- /dev/null +++ b/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.specification.selector; + +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Color; +import com.iluwatar.specification.property.Movement; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Date: 12/29/15 - 7:37 PM + * + * @author Jeroen Meulemeester + */ +public class MovementSelectorTest { + + /** + * Verify if the movement selector gives the correct results + */ + @Test + public void testMovement() { + final Creature swimmingCreature = mock(Creature.class); + when(swimmingCreature.getMovement()).thenReturn(Movement.SWIMMING); + + final Creature flyingCreature = mock(Creature.class); + when(flyingCreature.getMovement()).thenReturn(Movement.FLYING); + + final MovementSelector swimmingSelector = new MovementSelector(Movement.SWIMMING); + assertTrue(swimmingSelector.test(swimmingCreature)); + assertFalse(swimmingSelector.test(flyingCreature)); + + } + +} \ No newline at end of file diff --git a/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java new file mode 100644 index 000000000000..d2a534c1815d --- /dev/null +++ b/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java @@ -0,0 +1,36 @@ +package com.iluwatar.specification.selector; + +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Size; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Date: 12/29/15 - 7:43 PM + * + * @author Jeroen Meulemeester + */ +public class SizeSelectorTest { + + /** + * Verify if the size selector gives the correct results + */ + @Test + public void testMovement() { + final Creature normalCreature = mock(Creature.class); + when(normalCreature.getSize()).thenReturn(Size.NORMAL); + + final Creature smallCreature = mock(Creature.class); + when(smallCreature.getSize()).thenReturn(Size.SMALL); + + final SizeSelector normalSelector = new SizeSelector(Size.NORMAL); + assertTrue(normalSelector.test(normalCreature)); + assertFalse(normalSelector.test(smallCreature)); + } + +} diff --git a/state/index.md b/state/index.md index 3beeb480a8a7..f5cb189fde44 100644 --- a/state/index.md +++ b/state/index.md @@ -10,18 +10,21 @@ tags: - Gang Of Four --- -**Also known as:** Objects for States +## Also known as +Objects for States -**Intent:** Allow an object to alter its behavior when its internal state +## Intent +Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. ![alt text](./etc/state_1.png "State") -**Applicability:** Use the State pattern in either of the following cases +## Applicability +Use the State pattern in either of the following cases * an object's behavior depends on its state, and it must change its behavior at run-time depending on that state * operations have large, multipart conditional statements that depend on the object's state. This state is usually represented by one or more enumerated constants. Often, several operations will contain this same conditional structure. The State pattern puts each branch of the conditional in a separate class. This lets you treat the object's state as an object in its own right that can vary independently from other objects. -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/state/pom.xml b/state/pom.xml index 2bd9d9168f4e..4c0dbe8d352f 100644 --- a/state/pom.xml +++ b/state/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT state @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/state/src/main/java/com/iluwatar/state/App.java b/state/src/main/java/com/iluwatar/state/App.java index 2013466e093e..63b59ad59a4d 100644 --- a/state/src/main/java/com/iluwatar/state/App.java +++ b/state/src/main/java/com/iluwatar/state/App.java @@ -13,6 +13,9 @@ */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { Mammoth mammoth = new Mammoth(); diff --git a/state/src/main/java/com/iluwatar/state/Mammoth.java b/state/src/main/java/com/iluwatar/state/Mammoth.java index 8269ecb3da28..92f4d7188250 100644 --- a/state/src/main/java/com/iluwatar/state/Mammoth.java +++ b/state/src/main/java/com/iluwatar/state/Mammoth.java @@ -13,6 +13,9 @@ public Mammoth() { state = new PeacefulState(this); } + /** + * Makes time pass for the mammoth + */ public void timePasses() { if (state.getClass().equals(PeacefulState.class)) { changeStateTo(new AngryState(this)); diff --git a/state/src/test/java/com/iluwatar/state/AppTest.java b/state/src/test/java/com/iluwatar/state/AppTest.java index 0961a1c261b0..d0359273913e 100644 --- a/state/src/test/java/com/iluwatar/state/AppTest.java +++ b/state/src/test/java/com/iluwatar/state/AppTest.java @@ -2,10 +2,8 @@ import org.junit.Test; -import com.iluwatar.state.App; - /** - * + * * Application test * */ diff --git a/state/src/test/java/com/iluwatar/state/MammothTest.java b/state/src/test/java/com/iluwatar/state/MammothTest.java new file mode 100644 index 000000000000..4f7224208a34 --- /dev/null +++ b/state/src/test/java/com/iluwatar/state/MammothTest.java @@ -0,0 +1,90 @@ +package com.iluwatar.state; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import java.io.PrintStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; + +/** + * Date: 12/29/15 - 8:27 PM + * + * @author Jeroen Meulemeester + */ +public class MammothTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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); + } + + /** + * Switch to a complete mammoth 'mood'-cycle and verify if the observed mood matches the expected + * value. + */ + @Test + public void testTimePasses() { + final InOrder inOrder = Mockito.inOrder(this.stdOutMock); + final Mammoth mammoth = new Mammoth(); + + mammoth.observe(); + inOrder.verify(this.stdOutMock).println("The mammoth is calm and peaceful."); + inOrder.verifyNoMoreInteractions(); + + mammoth.timePasses(); + inOrder.verify(this.stdOutMock).println("The mammoth gets angry!"); + inOrder.verifyNoMoreInteractions(); + + mammoth.observe(); + inOrder.verify(this.stdOutMock).println("The mammoth is furious!"); + inOrder.verifyNoMoreInteractions(); + + mammoth.timePasses(); + inOrder.verify(this.stdOutMock).println("The mammoth calms down."); + inOrder.verifyNoMoreInteractions(); + + mammoth.observe(); + inOrder.verify(this.stdOutMock).println("The mammoth is calm and peaceful."); + inOrder.verifyNoMoreInteractions(); + + } + + /** + * Verify if {@link Mammoth#toString()} gives the expected value + */ + @Test + public void testToString() { + final String toString = new Mammoth().toString(); + assertNotNull(toString); + assertEquals("The mammoth", toString); + } + +} \ No newline at end of file diff --git a/step-builder/index.md b/step-builder/index.md index 76647935884d..bc636e37ae06 100644 --- a/step-builder/index.md +++ b/step-builder/index.md @@ -4,16 +4,20 @@ title: Step Builder folder: step-builder permalink: /patterns/step-builder/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Intermediate --- -**Intent:** An extension of the Builder pattern that fully guides the user through the creation of the object with no chances of confusion. +## Intent +An extension of the Builder pattern that fully guides the user through the creation of the object with no chances of confusion. The user experience will be much more improved by the fact that he will only see the next step methods available, NO build method until is the right time to build the object. ![alt text](./etc/step-builder.png "Step Builder") -**Applicability:** Use the Step Builder pattern when the algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled the construction process must allow different representations for the object that's constructed when in the process of constructing the order is important. +## Applicability +Use the Step Builder pattern when the algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled the construction process must allow different representations for the object that's constructed when in the process of constructing the order is important. -**Credits:** +## Credits * [Marco Castigliego - Step Builder](http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html) diff --git a/step-builder/pom.xml b/step-builder/pom.xml index d31192323b88..a00d7e8d6793 100644 --- a/step-builder/pom.xml +++ b/step-builder/pom.xml @@ -6,7 +6,7 @@ java-design-patterns com.iluwatar - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT step-builder diff --git a/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java b/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java index 3bf7b9a688ee..a839cd49ec1e 100644 --- a/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java +++ b/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java @@ -11,7 +11,7 @@ * methods available, NO build method until is the right time to build the object. * *

- * Implementation
+ * Implementation *

    * The concept is simple: * diff --git a/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java b/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java new file mode 100644 index 000000000000..b26635416665 --- /dev/null +++ b/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java @@ -0,0 +1,155 @@ +package com.iluwatar.stepbuilder; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/29/15 - 9:21 PM + * + * @author Jeroen Meulemeester + */ +public class CharacterStepBuilderTest { + + /** + * Build a new wizard {@link Character} and verify if it has the expected attributes + */ + @Test + public void testBuildWizard() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Merlin") + .wizardClass("alchemist") + .withSpell("poison") + .withAbility("invisibility") + .withAbility("wisdom") + .noMoreAbilities() + .build(); + + assertEquals("Merlin", character.getName()); + assertEquals("alchemist", character.getWizardClass()); + assertEquals("poison", character.getSpell()); + assertNotNull(character.toString()); + + final List abilities = character.getAbilities(); + assertNotNull(abilities); + assertEquals(2, abilities.size()); + assertTrue(abilities.contains("invisibility")); + assertTrue(abilities.contains("wisdom")); + + } + + /** + * Build a new wizard {@link Character} without spell or abilities and verify if it has the + * expected attributes + */ + @Test + public void testBuildPoorWizard() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Merlin") + .wizardClass("alchemist") + .noSpell() + .build(); + + assertEquals("Merlin", character.getName()); + assertEquals("alchemist", character.getWizardClass()); + assertNull(character.getSpell()); + assertNull(character.getAbilities()); + assertNotNull(character.toString()); + + } + + /** + * Build a new wizard {@link Character} and verify if it has the expected attributes + */ + @Test + public void testBuildWeakWizard() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Merlin") + .wizardClass("alchemist") + .withSpell("poison") + .noAbilities() + .build(); + + assertEquals("Merlin", character.getName()); + assertEquals("alchemist", character.getWizardClass()); + assertEquals("poison", character.getSpell()); + assertNull(character.getAbilities()); + assertNotNull(character.toString()); + + } + + + /** + * Build a new warrior {@link Character} and verify if it has the expected attributes + */ + @Test + public void testBuildWarrior() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Cuauhtemoc") + .fighterClass("aztec") + .withWeapon("spear") + .withAbility("speed") + .withAbility("strength") + .noMoreAbilities() + .build(); + + assertEquals("Cuauhtemoc", character.getName()); + assertEquals("aztec", character.getFighterClass()); + assertEquals("spear", character.getWeapon()); + assertNotNull(character.toString()); + + final List abilities = character.getAbilities(); + assertNotNull(abilities); + assertEquals(2, abilities.size()); + assertTrue(abilities.contains("speed")); + assertTrue(abilities.contains("strength")); + + } + + /** + * Build a new wizard {@link Character} without weapon and abilities and verify if it has the + * expected attributes + */ + @Test + public void testBuildPoorWarrior() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Poor warrior") + .fighterClass("none") + .noWeapon() + .build(); + + assertEquals("Poor warrior", character.getName()); + assertEquals("none", character.getFighterClass()); + assertNull(character.getWeapon()); + assertNull(character.getAbilities()); + assertNotNull(character.toString()); + + } + + /** + * Build a new warrior {@link Character} without any abilities, but with a weapon and verify if it + * has the expected attributes + */ + @Test + public void testBuildWeakWarrior() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Weak warrior") + .fighterClass("none") + .withWeapon("Slingshot") + .noAbilities() + .build(); + + assertEquals("Weak warrior", character.getName()); + assertEquals("none", character.getFighterClass()); + assertEquals("Slingshot", character.getWeapon()); + assertNull(character.getAbilities()); + assertNotNull(character.toString()); + + } + +} \ No newline at end of file diff --git a/strategy/index.md b/strategy/index.md index 288276015e6c..9b35b806d3b3 100644 --- a/strategy/index.md +++ b/strategy/index.md @@ -10,21 +10,24 @@ tags: - Gang Of Four --- -**Also known as:** Policy +## Also known as +Policy -**Intent:** Define a family of algorithms, encapsulate each one, and make them +## Intent +Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. ![alt text](./etc/strategy_1.png "Strategy") -**Applicability:** Use the Strategy pattern when +## Applicability +Use the Strategy pattern when * many related classes differ only in their behavior. Strategies provide a way to configure a class either one of many behaviors * you need different variants of an algorithm. for example, you might define algorithms reflecting different space/time trade-offs. Strategies can be used when these variants are implemented as a class hierarchy of algorithms * an algorithm uses data that clients shouldn't know about. Use the Strategy pattern to avoid exposing complex, algorithm-specific data structures * a class defines many behaviors, and these appear as multiple conditional statements in its operations. Instead of many conditionals, move related conditional branches into their own Strategy class -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/strategy/pom.xml b/strategy/pom.xml index 4b4448ffe22b..b194365f1a5e 100644 --- a/strategy/pom.xml +++ b/strategy/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT strategy @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/strategy/src/main/java/com/iluwatar/strategy/App.java b/strategy/src/main/java/com/iluwatar/strategy/App.java index 272e56cf9e51..e2bcfef8a35e 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/App.java +++ b/strategy/src/main/java/com/iluwatar/strategy/App.java @@ -5,6 +5,10 @@ * The Strategy pattern (also known as the policy pattern) is a software design pattern that enables * an algorithm's behavior to be selected at runtime. *

    + * Before Java 8 the Strategies needed to be separate classes forcing the developer + * to write lots of boilerplate code. With modern Java it is easy to pass behavior + * with method references and lambdas making the code shorter and more readable. + *

    * In this example ({@link DragonSlayingStrategy}) encapsulates an algorithm. The containing object * ({@link DragonSlayer}) can alter its behavior by changing its strategy. * @@ -17,6 +21,7 @@ public class App { * @param args command line args */ public static void main(String[] args) { + // GoF Strategy pattern System.out.println("Green dragon spotted ahead!"); DragonSlayer dragonSlayer = new DragonSlayer(new MeleeStrategy()); dragonSlayer.goToBattle(); @@ -26,5 +31,19 @@ public static void main(String[] args) { System.out.println("Black dragon lands before you."); dragonSlayer.changeStrategy(new SpellStrategy()); dragonSlayer.goToBattle(); + + // Java 8 Strategy pattern + System.out.println("Green dragon spotted ahead!"); + dragonSlayer = new DragonSlayer( + () -> System.out.println("With your Excalibur you severe the dragon's head!")); + dragonSlayer.goToBattle(); + System.out.println("Red dragon emerges."); + dragonSlayer.changeStrategy(() -> System.out.println( + "You shoot the dragon with the magical crossbow and it falls dead on the ground!")); + dragonSlayer.goToBattle(); + System.out.println("Black dragon lands before you."); + dragonSlayer.changeStrategy(() -> System.out.println( + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); + dragonSlayer.goToBattle(); } } diff --git a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java index e17e5fbf00bf..aa57c34f0fc0 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java @@ -5,6 +5,7 @@ * Strategy interface. * */ +@FunctionalInterface public interface DragonSlayingStrategy { void execute(); diff --git a/strategy/src/test/java/com/iluwatar/strategy/AppTest.java b/strategy/src/test/java/com/iluwatar/strategy/AppTest.java index d3c970b8952f..88ee28be40a0 100644 --- a/strategy/src/test/java/com/iluwatar/strategy/AppTest.java +++ b/strategy/src/test/java/com/iluwatar/strategy/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.strategy.App; - /** * * Application test diff --git a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java new file mode 100644 index 000000000000..907d65ac4a3c --- /dev/null +++ b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java @@ -0,0 +1,48 @@ +package com.iluwatar.strategy; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/29/15 - 10:50 PM + * + * @author Jeroen Meulemeester + */ +public class DragonSlayerTest { + + /** + * Verify if the dragon slayer uses the strategy during battle + */ + @Test + public void testGoToBattle() { + final DragonSlayingStrategy strategy = mock(DragonSlayingStrategy.class); + final DragonSlayer dragonSlayer = new DragonSlayer(strategy); + + dragonSlayer.goToBattle(); + verify(strategy).execute(); + verifyNoMoreInteractions(strategy); + } + + /** + * Verify if the dragon slayer uses the new strategy during battle after a change of strategy + */ + @Test + public void testChangeStrategy() throws Exception { + final DragonSlayingStrategy initialStrategy = mock(DragonSlayingStrategy.class); + final DragonSlayer dragonSlayer = new DragonSlayer(initialStrategy); + + dragonSlayer.goToBattle(); + verify(initialStrategy).execute(); + + final DragonSlayingStrategy newStrategy = mock(DragonSlayingStrategy.class); + dragonSlayer.changeStrategy(newStrategy); + + dragonSlayer.goToBattle(); + verify(newStrategy).execute(); + + verifyNoMoreInteractions(initialStrategy, newStrategy); + } +} \ No newline at end of file diff --git a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java new file mode 100644 index 000000000000..f9d18e22c6b7 --- /dev/null +++ b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java @@ -0,0 +1,104 @@ +package com.iluwatar.strategy; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Collection; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/29/15 - 10:58 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class DragonSlayingStrategyTest { + + /** + * @return The test parameters for each cycle + */ + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList( + new Object[]{ + new MeleeStrategy(), + "With your Excalibur you severe the dragon's head!" + }, + new Object[]{ + new ProjectileStrategy(), + "You shoot the dragon with the magical crossbow and it falls dead on the ground!" + }, + new Object[]{ + new SpellStrategy(), + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!" + } + ); + } + + /** + * The tested strategy + */ + private final DragonSlayingStrategy strategy; + + /** + * The expected action on the std-out + */ + private final String expectedResult; + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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; + + /** + * Create a new test instance for the given strategy + * + * @param strategy The tested strategy + * @param expectedResult The expected result + */ + public DragonSlayingStrategyTest(final DragonSlayingStrategy strategy, final String expectedResult) { + this.strategy = strategy; + this.expectedResult = expectedResult; + } + + /** + * 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 if executing the strategy gives the correct response + */ + @Test + public void testExecute() { + this.strategy.execute(); + verify(this.stdOutMock).println(this.expectedResult); + verifyNoMoreInteractions(this.stdOutMock); + } + +} \ No newline at end of file diff --git a/template-method/index.md b/template-method/index.md index 8b8b7878e290..ad972f06bce5 100644 --- a/template-method/index.md +++ b/template-method/index.md @@ -10,18 +10,20 @@ tags: - Gang Of Four --- -**Intent:** Define the skeleton of an algorithm in an operation, deferring some +## Intent +Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure. ![alt text](./etc/template-method_1.png "Template Method") -**Applicability:** The Template Method pattern should be used +## Applicability +The Template Method pattern should be used * to implement the invariant parts of an algorithm once and leave it up to subclasses to implement the behavior that can vary * when common behavior among subclasses should be factored and localized in a common class to avoid code duplication. This is good example of "refactoring to generalize" as described by Opdyke and Johnson. You first identify the differences in the existing code and then separate the differences into new operations. Finally, you replace the differing code with a template method that calls one of these new operations * to control subclasses extensions. You can define a template method that calls "hook" operations at specific points, thereby permitting extensions only at those points -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/template-method/pom.xml b/template-method/pom.xml index ecc0c4efa757..ef298020346a 100644 --- a/template-method/pom.xml +++ b/template-method/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT template-method @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java b/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java index bad3d790f857..096d51b4e52e 100644 --- a/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java +++ b/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java @@ -13,6 +13,9 @@ public abstract class StealingMethod { protected abstract void stealTheItem(String target); + /** + * Steal + */ public void steal() { String target = pickTarget(); System.out.println("The target has been chosen as " + target + "."); diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java index ddc46de5b4ac..80e867327cad 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.templatemethod.App; - /** * * Application test diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java new file mode 100644 index 000000000000..be049720f45d --- /dev/null +++ b/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java @@ -0,0 +1,50 @@ +package com.iluwatar.templatemethod; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/29/15 - 18:15 PM + * + * @author Jeroen Meulemeester + */ +public class HalflingThiefTest { + + /** + * Verify if the thief uses the provided stealing method + */ + @Test + public void testSteal() { + final StealingMethod method = mock(StealingMethod.class); + final HalflingThief thief = new HalflingThief(method); + + thief.steal(); + verify(method).steal(); + + verifyNoMoreInteractions(method); + } + + /** + * Verify if the thief uses the provided stealing method, and the new method after changing it + */ + @Test + public void testChangeMethod() { + final StealingMethod initialMethod = mock(StealingMethod.class); + final HalflingThief thief = new HalflingThief(initialMethod); + + thief.steal(); + verify(initialMethod).steal(); + + final StealingMethod newMethod = mock(StealingMethod.class); + thief.changeMethod(newMethod); + + thief.steal(); + verify(newMethod).steal(); + + verifyNoMoreInteractions(initialMethod, newMethod); + + } +} \ No newline at end of file diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java new file mode 100644 index 000000000000..86fc2591d7a9 --- /dev/null +++ b/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java @@ -0,0 +1,23 @@ +package com.iluwatar.templatemethod; + +/** + * Date: 12/30/15 - 18:12 PM + * + * @author Jeroen Meulemeester + */ +public class HitAndRunMethodTest extends StealingMethodTest { + + /** + * Create a new test for the {@link HitAndRunMethod} + */ + public HitAndRunMethodTest() { + super( + new HitAndRunMethod(), + "old goblin woman", + "The target has been chosen as old goblin woman.", + "Approach the old goblin woman from behind.", + "Grab the handbag and run away fast!" + ); + } + +} \ No newline at end of file diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java new file mode 100644 index 000000000000..61143a15d42c --- /dev/null +++ b/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java @@ -0,0 +1,142 @@ +package com.iluwatar.templatemethod; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import java.io.PrintStream; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/30/15 - 18:12 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StealingMethodTest { + + /** + * The tested stealing method + */ + private final M method; + + /** + * The expected target + */ + private final String expectedTarget; + + /** + * The expected target picking result + */ + private final String expectedTargetResult; + + /** + * The expected confusion method + */ + private final String expectedConfuseMethod; + + /** + * The expected stealing method + */ + private final String expectedStealMethod; + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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; + + /** + * Create a new test for the given stealing method, together with the expected results + * + * @param method The tested stealing method + * @param expectedTarget The expected target name + * @param expectedTargetResult The expected target picking result + * @param expectedConfuseMethod The expected confusion method + * @param expectedStealMethod The expected stealing method + */ + public StealingMethodTest(final M method, String expectedTarget, final String expectedTargetResult, + final String expectedConfuseMethod, final String expectedStealMethod) { + + this.method = method; + this.expectedTarget = expectedTarget; + this.expectedTargetResult = expectedTargetResult; + this.expectedConfuseMethod = expectedConfuseMethod; + this.expectedStealMethod = expectedStealMethod; + } + + /** + * 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); + } + + /** + * Verify if the thief picks the correct target + */ + @Test + public void testPickTarget() { + assertEquals(expectedTarget, this.method.pickTarget()); + } + + /** + * Verify if the target confusing step goes as planned + */ + @Test + public void testConfuseTarget() { + verifyZeroInteractions(this.stdOutMock); + + this.method.confuseTarget(this.expectedTarget); + verify(this.stdOutMock).println(this.expectedConfuseMethod); + verifyNoMoreInteractions(this.stdOutMock); + } + + /** + * Verify if the stealing step goes as planned + */ + @Test + public void testStealTheItem() { + verifyZeroInteractions(this.stdOutMock); + + this.method.stealTheItem(this.expectedTarget); + verify(this.stdOutMock).println(this.expectedStealMethod); + verifyNoMoreInteractions(this.stdOutMock); + } + + /** + * Verify if the complete steal process goes as planned + */ + @Test + public void testSteal() { + final InOrder inOrder = inOrder(this.stdOutMock); + + this.method.steal(); + + inOrder.verify(this.stdOutMock).println(this.expectedTargetResult); + inOrder.verify(this.stdOutMock).println(this.expectedConfuseMethod); + inOrder.verify(this.stdOutMock).println(this.expectedStealMethod); + inOrder.verifyNoMoreInteractions(); + } + +} \ No newline at end of file diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java new file mode 100644 index 000000000000..8b3681a76396 --- /dev/null +++ b/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java @@ -0,0 +1,23 @@ +package com.iluwatar.templatemethod; + +/** + * Date: 12/30/15 - 18:19 PM + * + * @author Jeroen Meulemeester + */ +public class SubtleMethodTest extends StealingMethodTest { + + /** + * Create a new test for the {@link SubtleMethod} + */ + public SubtleMethodTest() { + super( + new SubtleMethod(), + "shop keeper", + "The target has been chosen as shop keeper.", + "Approach the shop keeper with tears running and hug him!", + "While in close contact grab the shop keeper's wallet." + ); + } + +} \ No newline at end of file diff --git a/thread-pool/index.md b/thread-pool/index.md index d9f00f428a49..9806fa8e0b31 100644 --- a/thread-pool/index.md +++ b/thread-pool/index.md @@ -4,10 +4,14 @@ title: Thread Pool folder: thread-pool permalink: /patterns/thread-pool/ categories: Concurrency -tags: Java +tags: + - Java + - Difficulty-Intermediate + - Performance --- -**Intent:** It is often the case that tasks to be executed are short-lived and +## Intent +It is often the case that tasks to be executed are short-lived and the number of tasks is large. Creating a new thread for each task would make the system spend more time creating and destroying the threads than executing the actual tasks. Thread Pool solves this problem by reusing existing threads @@ -15,6 +19,7 @@ and eliminating the latency of creating new threads. ![alt text](./etc/thread-pool.png "Thread Pool") -**Applicability:** Use the Thread Pool pattern when +## Applicability +Use the Thread Pool pattern when * you have a large number of short-lived tasks to be executed in parallel diff --git a/thread-pool/pom.xml b/thread-pool/pom.xml index f79388008746..5965b46bb8a9 100644 --- a/thread-pool/pom.xml +++ b/thread-pool/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT thread-pool @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/App.java b/thread-pool/src/main/java/com/iluwatar/threadpool/App.java index 75971cd184c0..1833f3950062 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/App.java +++ b/thread-pool/src/main/java/com/iluwatar/threadpool/App.java @@ -65,6 +65,7 @@ public static void main(String[] args) { // All tasks were executed, now shutdown executor.shutdown(); while (!executor.isTerminated()) { + Thread.yield(); } System.out.println("Program finished"); } diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java b/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java index f1247101c2f3..3a8464092447 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java +++ b/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java @@ -7,7 +7,7 @@ */ public class CoffeeMakingTask extends Task { - private static final int TIME_PER_CUP = 300; + private static final int TIME_PER_CUP = 100; public CoffeeMakingTask(int numCups) { super(numCups * TIME_PER_CUP); diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java b/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java index a90bf4becdcf..2be941406e8d 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java +++ b/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java @@ -7,7 +7,7 @@ */ public class PotatoPeelingTask extends Task { - private static final int TIME_PER_POTATO = 500; + private static final int TIME_PER_POTATO = 200; public PotatoPeelingTask(int numPotatoes) { super(numPotatoes * TIME_PER_POTATO); diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java b/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java index 12fecbbd0ea6..2426948b3293 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java +++ b/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java @@ -1,19 +1,21 @@ package com.iluwatar.threadpool; +import java.util.concurrent.atomic.AtomicInteger; + /** - * + * * Abstract base class for tasks * */ public abstract class Task { - private static int nextId = 1; + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); private final int id; private final int timeMs; public Task(final int timeMs) { - this.id = nextId++; + this.id = ID_GENERATOR.incrementAndGet(); this.timeMs = timeMs; } diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java index c725983e26d7..f0f7b74bb624 100644 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.threadpool.App; - /** * Application test * diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java new file mode 100644 index 000000000000..ab3d47d9aedc --- /dev/null +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.threadpool; + +/** + * Date: 12/30/15 - 18:23 PM + * + * @author Jeroen Meulemeester + */ +public class CoffeeMakingTaskTest extends TaskTest { + + /** + * Create a new test instance + */ + public CoffeeMakingTaskTest() { + super(CoffeeMakingTask::new, 100); + } + +} diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java new file mode 100644 index 000000000000..4f9b1496c725 --- /dev/null +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.threadpool; + +/** + * Date: 12/30/15 - 18:23 PM + * + * @author Jeroen Meulemeester + */ +public class PotatoPeelingTaskTest extends TaskTest { + + /** + * Create a new test instance + */ + public PotatoPeelingTaskTest() { + super(PotatoPeelingTask::new, 200); + } + +} \ No newline at end of file diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java new file mode 100644 index 000000000000..f1ef8160f4a5 --- /dev/null +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java @@ -0,0 +1,121 @@ +package com.iluwatar.threadpool; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/30/15 - 18:22 PM + * + * @author Jeroen Meulemeester + */ +public abstract class TaskTest { + + /** + * The number of tasks used during the concurrency test + */ + private static final int TASK_COUNT = 128 * 1024; + + /** + * The number of threads used during the concurrency test + */ + private static final int THREAD_COUNT = 8; + + /** + * The task factory, used to create new test items + */ + private final Function factory; + + /** + * The expected time needed to run the task 1 single time, in milli seconds + */ + private final int expectedExecutionTime; + + /** + * Create a new test instance + * + * @param factory The task factory, used to create new test items + * @param expectedExecutionTime The expected time needed to run the task 1 time, in milli seconds + */ + public TaskTest(final Function factory, final int expectedExecutionTime) { + this.factory = factory; + this.expectedExecutionTime = expectedExecutionTime; + } + + /** + * Verify if the generated id is unique for each task, even if the tasks are created in separate + * threads + */ + @Test(timeout = 10000) + public void testIdGeneration() throws Exception { + final ExecutorService service = Executors.newFixedThreadPool(THREAD_COUNT); + + final List> tasks = new ArrayList<>(); + for (int i = 0; i < TASK_COUNT; i++) { + tasks.add(() -> factory.apply(1).getId()); + } + + final List ids = service.invokeAll(tasks) + .stream() + .map(TaskTest::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + service.shutdownNow(); + + final long uniqueIdCount = ids.stream() + .distinct() + .count(); + + assertEquals(TASK_COUNT, ids.size()); + assertEquals(TASK_COUNT, uniqueIdCount); + + } + + /** + * Verify if the time per execution of a task matches the actual time required to execute the task + * a given number of times + */ + @Test + public void testTimeMs() { + for (int i = 0; i < 10; i++) { + assertEquals(this.expectedExecutionTime * i, this.factory.apply(i).getTimeMs()); + } + } + + /** + * Verify if the task has some sort of {@link T#toString()}, different from 'null' + */ + @Test + public void testToString() { + assertNotNull(this.factory.apply(0).toString()); + } + + /** + * Extract the result from a future or returns 'null' when an exception occurred + * + * @param future The future we want the result from + * @param The result type + * @return The result or 'null' when a checked exception occurred + */ + private static O get(Future future) { + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + return null; + } + } + +} \ No newline at end of file diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java new file mode 100644 index 000000000000..53a1d8694345 --- /dev/null +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java @@ -0,0 +1,31 @@ +package com.iluwatar.threadpool; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/30/15 - 18:21 PM + * + * @author Jeroen Meulemeester + */ +public class WorkerTest { + + /** + * Verify if a worker does the actual job + */ + @Test + public void testRun() { + final Task task = mock(Task.class); + final Worker worker = new Worker(task); + verifyZeroInteractions(task); + + worker.run(); + verify(task).getTimeMs(); + verifyNoMoreInteractions(task); + } + +} \ No newline at end of file diff --git a/tolerant-reader/index.md b/tolerant-reader/index.md index b2bfd376a387..be0085f2c571 100644 --- a/tolerant-reader/index.md +++ b/tolerant-reader/index.md @@ -4,20 +4,24 @@ title: Tolerant Reader folder: tolerant-reader permalink: /patterns/tolerant-reader/ categories: Integration -tags: Java +tags: + - Java + - Difficulty-Beginner --- -**Intent:** Tolerant Reader is an integration pattern that helps creating +## Intent +Tolerant Reader is an integration pattern that helps creating robust communication systems. The idea is to be as tolerant as possible when reading data from another service. This way, when the communication schema changes, the readers must not break. ![alt text](./etc/tolerant-reader.png "Tolerant Reader") -**Applicability:** Use the Tolerant Reader pattern when +## Applicability +Use the Tolerant Reader pattern when * the communication schema can evolve and change and yet the receiving side should not break -**Credits:** +## Credits * [Martin Fowler - Tolerant Reader](http://martinfowler.com/bliki/TolerantReader.html) diff --git a/tolerant-reader/pom.xml b/tolerant-reader/pom.xml index d32006ed754a..c7677b934a3e 100644 --- a/tolerant-reader/pom.xml +++ b/tolerant-reader/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT tolerant-reader diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java index eb73fb7f336a..242b71390415 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java @@ -19,6 +19,9 @@ */ public class App { + /** + * Program entry point + */ public static void main(String[] args) throws IOException, ClassNotFoundException { // Write V1 RainbowFish fishV1 = new RainbowFish("Zed", 10, 11, 12); diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java index 74c4526a0505..d12ed4dbfb7a 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java @@ -16,6 +16,9 @@ public class RainbowFish implements Serializable { private int lengthMeters; private int weightTons; + /** + * Constructor + */ public RainbowFish(String name, int age, int lengthMeters, int weightTons) { this.name = name; this.age = age; diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java index 3929e06e7c43..5d2a13735fba 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java @@ -18,12 +18,11 @@ */ public class RainbowFishSerializer { + private RainbowFishSerializer() { + } + /** * Write V1 RainbowFish to file - * - * @param rainbowFish - * @param filename - * @throws IOException */ public static void writeV1(RainbowFish rainbowFish, String filename) throws IOException { Map map = new HashMap<>(); @@ -40,10 +39,6 @@ public static void writeV1(RainbowFish rainbowFish, String filename) throws IOEx /** * Write V2 RainbowFish to file - * - * @param rainbowFish - * @param filename - * @throws IOException */ public static void writeV2(RainbowFishV2 rainbowFish, String filename) throws IOException { Map map = new HashMap<>(); @@ -63,17 +58,11 @@ public static void writeV2(RainbowFishV2 rainbowFish, String filename) throws IO /** * Read V1 RainbowFish from file - * - * @param filename - * @return - * @throws IOException - * @throws ClassNotFoundException */ public static RainbowFish readV1(String filename) throws IOException, ClassNotFoundException { - Map map = null; FileInputStream fileIn = new FileInputStream(filename); ObjectInputStream objIn = new ObjectInputStream(fileIn); - map = (Map) objIn.readObject(); + Map map = (Map) objIn.readObject(); objIn.close(); fileIn.close(); return new RainbowFish(map.get("name"), Integer.parseInt(map.get("age")), Integer.parseInt(map diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java index 6146946e12ac..2c72bee4d38e 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java @@ -17,6 +17,9 @@ public RainbowFishV2(String name, int age, int lengthMeters, int weightTons) { super(name, age, lengthMeters, weightTons); } + /** + * Constructor + */ public RainbowFishV2(String name, int age, int lengthMeters, int weightTons, boolean sleeping, boolean hungry, boolean angry) { this(name, age, lengthMeters, weightTons); diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java index ceb1c3f666dc..c7906adb214d 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java @@ -1,13 +1,11 @@ package com.iluwatar.tolerantreader; -import java.io.File; -import java.io.IOException; - import org.junit.After; import org.junit.Before; import org.junit.Test; -import com.iluwatar.tolerantreader.App; +import java.io.File; +import java.io.IOException; /** * diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java new file mode 100644 index 000000000000..5f7ca02628b7 --- /dev/null +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java @@ -0,0 +1,68 @@ +package com.iluwatar.tolerantreader; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; + +/** + * Date: 12/30/15 - 18:39 PM + * + * @author Jeroen Meulemeester + */ +public class RainbowFishSerializerTest { + + /** + * Create a temporary folder, used to generate files in during this test + */ + @Rule + public final TemporaryFolder testFolder = new TemporaryFolder(); + + /** + * Rainbow fish version 1 used during the tests + */ + private static final RainbowFish V1 = new RainbowFish("version1", 1, 2, 3); + + /** + * Rainbow fish version 2 used during the tests + */ + private static final RainbowFishV2 V2 = new RainbowFishV2("version2", 4, 5, 6, true, false, true); + + /** + * Verify if a fish, written as version 1 can be read back as version 1 + */ + @Test + public void testWriteV1ReadV1() throws Exception { + final File outputFile = this.testFolder.newFile(); + RainbowFishSerializer.writeV1(V1, outputFile.getPath()); + + final RainbowFish fish = RainbowFishSerializer.readV1(outputFile.getPath()); + assertNotSame(V1, fish); + assertEquals(V1.getName(), fish.getName()); + assertEquals(V1.getAge(), fish.getAge()); + assertEquals(V1.getLengthMeters(), fish.getLengthMeters()); + assertEquals(V1.getWeightTons(), fish.getWeightTons()); + + } + + /** + * Verify if a fish, written as version 2 can be read back as version 1 + */ + @Test + public void testWriteV2ReadV1() throws Exception { + final File outputFile = this.testFolder.newFile(); + RainbowFishSerializer.writeV2(V2, outputFile.getPath()); + + final RainbowFish fish = RainbowFishSerializer.readV1(outputFile.getPath()); + assertNotSame(V2, fish); + assertEquals(V2.getName(), fish.getName()); + assertEquals(V2.getAge(), fish.getAge()); + assertEquals(V2.getLengthMeters(), fish.getLengthMeters()); + assertEquals(V2.getWeightTons(), fish.getWeightTons()); + } + +} \ No newline at end of file diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java new file mode 100644 index 000000000000..0f7df25c8c01 --- /dev/null +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java @@ -0,0 +1,26 @@ +package com.iluwatar.tolerantreader; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/30/15 - 18:34 PM + * + * @author Jeroen Meulemeester + */ +public class RainbowFishTest { + + /** + * Verify if the getters of a {@link RainbowFish} return the expected values + */ + @Test + public void testValues() { + final RainbowFish fish = new RainbowFish("name", 1, 2, 3); + assertEquals("name", fish.getName()); + assertEquals(1, fish.getAge()); + assertEquals(2, fish.getLengthMeters()); + assertEquals(3, fish.getWeightTons()); + } + +} \ No newline at end of file diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java new file mode 100644 index 000000000000..5e8bdcef57b4 --- /dev/null +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java @@ -0,0 +1,29 @@ +package com.iluwatar.tolerantreader; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/30/15 - 18:35 PM + * + * @author Jeroen Meulemeester + */ +public class RainbowFishV2Test { + + /** + * Verify if the getters of a {@link RainbowFish} return the expected values + */ + @Test + public void testValues() { + final RainbowFishV2 fish = new RainbowFishV2("name", 1, 2, 3, false, true, false); + assertEquals("name", fish.getName()); + assertEquals(1, fish.getAge()); + assertEquals(2, fish.getLengthMeters()); + assertEquals(3, fish.getWeightTons()); + assertEquals(false, fish.getSleeping()); + assertEquals(true, fish.getHungry()); + assertEquals(false, fish.getAngry()); + } + +} \ No newline at end of file diff --git a/twin/index.md b/twin/index.md index 475437754d13..3795236bb528 100644 --- a/twin/index.md +++ b/twin/index.md @@ -4,21 +4,23 @@ title: Twin folder: twin permalink: /patterns/twin/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Intermediate --- -**Intent:** Twin pattern is a design pattern which provides a standard solution to simulate multiple +## Intent + Twin pattern is a design pattern which provides a standard solution to simulate multiple inheritance in java - - ![alt text](./etc/twin.png "Twin") -**Applicability:** Use the Twin idiom when +## Applicability +Use the Twin idiom when * to simulate multiple inheritance in a language that does not support this feature. * to avoid certain problems of multiple inheritance such as name clashes. -**Credits:** +## Credits -* [Twin – A Design Pattern for Modeling Multiple Inheritance](http://www.ssw.uni-linz.ac.at/Research/Papers/Moe99/Paper.pdf) \ No newline at end of file +* [Twin – A Design Pattern for Modeling Multiple Inheritance](http://www.ssw.uni-linz.ac.at/Research/Papers/Moe99/Paper.pdf) diff --git a/twin/pom.xml b/twin/pom.xml index c2a4b131b610..46e8de15aa49 100644 --- a/twin/pom.xml +++ b/twin/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT twin @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/twin/src/main/java/com/iluwatar/twin/App.java b/twin/src/main/java/com/iluwatar/twin/App.java index eaa21a849586..cb971c490238 100644 --- a/twin/src/main/java/com/iluwatar/twin/App.java +++ b/twin/src/main/java/com/iluwatar/twin/App.java @@ -41,6 +41,6 @@ public static void main(String[] args) throws Exception { } private static void waiting() throws Exception { - Thread.sleep(2500); + Thread.sleep(750); } } diff --git a/twin/src/main/java/com/iluwatar/twin/BallThread.java b/twin/src/main/java/com/iluwatar/twin/BallThread.java index dae537e64ffd..2d9e7c41acb1 100644 --- a/twin/src/main/java/com/iluwatar/twin/BallThread.java +++ b/twin/src/main/java/com/iluwatar/twin/BallThread.java @@ -19,18 +19,20 @@ public void setTwin(BallItem twin) { this.twin = twin; } + /** + * Run the thread + */ public void run() { while (isRunning) { - while (!isSuspended) { + if (!isSuspended) { twin.draw(); twin.move(); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - + } + try { + Thread.sleep(250); + } catch (InterruptedException e) { + throw new RuntimeException(e); } } } diff --git a/twin/src/main/java/com/iluwatar/twin/GameItem.java b/twin/src/main/java/com/iluwatar/twin/GameItem.java index d797eda95651..e98202d0ce5d 100644 --- a/twin/src/main/java/com/iluwatar/twin/GameItem.java +++ b/twin/src/main/java/com/iluwatar/twin/GameItem.java @@ -9,9 +9,6 @@ public abstract class GameItem { /** * Template method, do some common logic before draw - * - * @param other - * @return */ public void draw() { System.out.println("draw"); diff --git a/twin/src/test/java/com/iluwatar/twin/BallItemTest.java b/twin/src/test/java/com/iluwatar/twin/BallItemTest.java new file mode 100644 index 000000000000..ca1da7ac8d71 --- /dev/null +++ b/twin/src/test/java/com/iluwatar/twin/BallItemTest.java @@ -0,0 +1,62 @@ +package com.iluwatar.twin; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/30/15 - 18:44 PM + * + * @author Jeroen Meulemeester + */ +public class BallItemTest extends StdOutTest { + + @Test + public void testClick() { + final BallThread ballThread = mock(BallThread.class); + final BallItem ballItem = new BallItem(); + ballItem.setTwin(ballThread); + + final InOrder inOrder = inOrder(ballThread); + + for (int i = 0; i < 10; i++) { + ballItem.click(); + inOrder.verify(ballThread).suspendMe(); + + ballItem.click(); + inOrder.verify(ballThread).resumeMe(); + } + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testDoDraw() { + final BallItem ballItem = new BallItem(); + final BallThread ballThread = mock(BallThread.class); + ballItem.setTwin(ballThread); + + ballItem.draw(); + verify(getStdOutMock()).println("draw"); + verify(getStdOutMock()).println("doDraw"); + + verifyNoMoreInteractions(ballThread, getStdOutMock()); + } + + @Test + public void testMove() { + final BallItem ballItem = new BallItem(); + final BallThread ballThread = mock(BallThread.class); + ballItem.setTwin(ballThread); + + ballItem.move(); + verify(getStdOutMock()).println("move"); + + verifyNoMoreInteractions(ballThread, getStdOutMock()); + } + +} \ No newline at end of file diff --git a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java new file mode 100644 index 000000000000..7e0bdc11e7e5 --- /dev/null +++ b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java @@ -0,0 +1,89 @@ +package com.iluwatar.twin; + +import org.junit.Test; + +import static java.lang.Thread.UncaughtExceptionHandler; +import static java.lang.Thread.sleep; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/30/15 - 18:55 PM + * + * @author Jeroen Meulemeester + */ +public class BallThreadTest { + + /** + * Verify if the {@link BallThread} can be resumed + */ + @Test(timeout = 5000) + public void testSuspend() throws Exception { + final BallThread ballThread = new BallThread(); + + final BallItem ballItem = mock(BallItem.class); + ballThread.setTwin(ballItem); + + ballThread.start(); + + verify(ballItem, timeout(2000).atLeastOnce()).draw(); + verify(ballItem, timeout(2000).atLeastOnce()).move(); + ballThread.suspendMe(); + + sleep(1000); + + ballThread.stopMe(); + ballThread.join(); + + verifyNoMoreInteractions(ballItem); + } + + /** + * Verify if the {@link BallThread} can be resumed + */ + @Test(timeout = 5000) + public void testResume() throws Exception { + final BallThread ballThread = new BallThread(); + + final BallItem ballItem = mock(BallItem.class); + ballThread.setTwin(ballItem); + + ballThread.suspendMe(); + ballThread.start(); + + sleep(1000); + + verifyZeroInteractions(ballItem); + + ballThread.resumeMe(); + verify(ballItem, timeout(2000).atLeastOnce()).draw(); + verify(ballItem, timeout(2000).atLeastOnce()).move(); + + ballThread.stopMe(); + ballThread.join(); + + verifyNoMoreInteractions(ballItem); + } + + /** + * Verify if the {@link BallThread} is interruptible + */ + @Test(timeout = 5000) + public void testInterrupt() throws Exception { + final BallThread ballThread = new BallThread(); + final UncaughtExceptionHandler exceptionHandler = mock(UncaughtExceptionHandler.class); + ballThread.setUncaughtExceptionHandler(exceptionHandler); + ballThread.setTwin(mock(BallItem.class)); + ballThread.start(); + ballThread.interrupt(); + ballThread.join(); + + verify(exceptionHandler).uncaughtException(eq(ballThread), any(RuntimeException.class)); + verifyNoMoreInteractions(exceptionHandler); + } +} \ No newline at end of file diff --git a/twin/src/test/java/com/iluwatar/twin/StdOutTest.java b/twin/src/test/java/com/iluwatar/twin/StdOutTest.java new file mode 100644 index 000000000000..f506886e18dc --- /dev/null +++ b/twin/src/test/java/com/iluwatar/twin/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.twin; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/visitor/index.md b/visitor/index.md index 5f32edbbdfd8..c1e24a624c23 100644 --- a/visitor/index.md +++ b/visitor/index.md @@ -6,26 +6,28 @@ permalink: /patterns/visitor/ categories: Behavioral tags: - Java - - Difficulty-Expert + - Difficulty-Intermediate - Gang Of Four --- -**Intent:** Represent an operation to be performed on the elements of an object +## Intent +Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. ![alt text](./etc/visitor_1.png "Visitor") -**Applicability:** Use the Visitor pattern when +## Applicability +Use the Visitor pattern when * an object structure contains many classes of objects with differing interfaces, and you want to perform operations on these objects that depend on their concrete classes * many distinct and unrelated operations need to be performed on objects in an object structure, and you want to avoid "polluting" their classes with these operations. Visitor lets you keep related operations together by defining them in one class. When the object structure is shared by many applications, use Visitor to put operations in just those applications that need them * the classes defining the object structure rarely change, but you often want to define new operations over the structure. Changing the object structure classes requires redefining the interface to all visitors, which is potentially costly. If the object structure classes change often, then it's probably better to define the operations in those classes -**Real world examples:** +## Real world examples * [Apache Wicket](https://github.com/apache/wicket) component tree, see [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) -**Credits** +## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/visitor/pom.xml b/visitor/pom.xml index b33f9975b815..a53e2bcc5318 100644 --- a/visitor/pom.xml +++ b/visitor/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT visitor @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/visitor/src/main/java/com/iluwatar/visitor/Unit.java b/visitor/src/main/java/com/iluwatar/visitor/Unit.java index 9fb52f6e0c72..300a6299bc39 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Unit.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Unit.java @@ -13,6 +13,9 @@ public Unit(Unit... children) { this.children = children; } + /** + * Accept visitor + */ public void accept(UnitVisitor visitor) { for (Unit child : children) { child.accept(visitor); diff --git a/visitor/src/test/java/com/iluwatar/visitor/AppTest.java b/visitor/src/test/java/com/iluwatar/visitor/AppTest.java index 912f1a22881d..573a11532505 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/AppTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/AppTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import com.iluwatar.visitor.App; - /** * * Application test diff --git a/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java b/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java new file mode 100644 index 000000000000..bbf6c7963e54 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java @@ -0,0 +1,25 @@ +package com.iluwatar.visitor; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +/** + * Date: 12/30/15 - 19:45 PM + * + * @author Jeroen Meulemeester + */ +public class CommanderTest extends UnitTest { + + /** + * Create a new test instance for the given {@link Commander} + */ + public CommanderTest() { + super(Commander::new); + } + + @Override + void verifyVisit(Commander unit, UnitVisitor mockedVisitor) { + verify(mockedVisitor).visitCommander(eq(unit)); + } + +} \ No newline at end of file diff --git a/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java new file mode 100644 index 000000000000..ac296c332b66 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.visitor; + +import java.util.Optional; + +/** + * Date: 12/30/15 - 18:43 PM + * + * @author Jeroen Meulemeester + */ +public class CommanderVisitorTest extends VisitorTest { + + /** + * Create a new test instance for the given visitor + */ + public CommanderVisitorTest() { + super( + new CommanderVisitor(), + Optional.of("Good to see you commander"), + Optional.empty(), + Optional.empty() + ); + } + +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java b/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java new file mode 100644 index 000000000000..d0e6d3db2c4c --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java @@ -0,0 +1,25 @@ +package com.iluwatar.visitor; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +/** + * Date: 12/30/15 - 19:45 PM + * + * @author Jeroen Meulemeester + */ +public class SergeantTest extends UnitTest { + + /** + * Create a new test instance for the given {@link Sergeant} + */ + public SergeantTest() { + super(Sergeant::new); + } + + @Override + void verifyVisit(Sergeant unit, UnitVisitor mockedVisitor) { + verify(mockedVisitor).visitSergeant(eq(unit)); + } + +} \ No newline at end of file diff --git a/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java new file mode 100644 index 000000000000..54e274bc7c9b --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.visitor; + +import java.util.Optional; + +/** + * Date: 12/30/15 - 18:36 PM + * + * @author Jeroen Meulemeester + */ +public class SergeantVisitorTest extends VisitorTest { + + /** + * Create a new test instance for the given visitor + */ + public SergeantVisitorTest() { + super( + new SergeantVisitor(), + Optional.empty(), + Optional.of("Hello sergeant"), + Optional.empty() + ); + } + +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java b/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java new file mode 100644 index 000000000000..e9aa5460880d --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java @@ -0,0 +1,25 @@ +package com.iluwatar.visitor; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +/** + * Date: 12/30/15 - 19:45 PM + * + * @author Jeroen Meulemeester + */ +public class SoldierTest extends UnitTest { + + /** + * Create a new test instance for the given {@link Soldier} + */ + public SoldierTest() { + super(Soldier::new); + } + + @Override + void verifyVisit(Soldier unit, UnitVisitor mockedVisitor) { + verify(mockedVisitor).visitSoldier(eq(unit)); + } + +} \ No newline at end of file diff --git a/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java new file mode 100644 index 000000000000..a5f16e9e391c --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.visitor; + +import java.util.Optional; + +/** + * Date: 12/30/15 - 18:59 PM + * + * @author Jeroen Meulemeester + */ +public class SoldierVisitorTest extends VisitorTest { + + /** + * Create a new test instance for the given visitor + */ + public SoldierVisitorTest() { + super( + new SoldierVisitor(), + Optional.empty(), + Optional.empty(), + Optional.of("Greetings soldier") + ); + } + +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/StdOutTest.java b/visitor/src/test/java/com/iluwatar/visitor/StdOutTest.java new file mode 100644 index 000000000000..2c54994bb8c0 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.visitor; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to 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); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java b/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java new file mode 100644 index 000000000000..291ab544a4f7 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java @@ -0,0 +1,60 @@ +package com.iluwatar.visitor; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.function.Function; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/30/15 - 18:59 PM + * + * @author Jeroen Meulemeester + */ +public abstract class UnitTest { + + /** + * Factory to create new instances of the tested unit + */ + private final Function factory; + + /** + * Create a new test instance for the given unit type {@link U} + * + * @param factory Factory to create new instances of the tested unit + */ + public UnitTest(final Function factory) { + this.factory = factory; + } + + @Test + public void testAccept() throws Exception { + final Unit[] children = new Unit[5]; + Arrays.setAll(children, (i) -> mock(Unit.class)); + + final U unit = this.factory.apply(children); + final UnitVisitor visitor = mock(UnitVisitor.class); + unit.accept(visitor); + verifyVisit(unit, visitor); + + for (final Unit child : children) { + verify(child).accept(eq(visitor)); + } + + verifyNoMoreInteractions(children); + verifyNoMoreInteractions(visitor); + } + + /** + * Verify if the correct visit method is called on the mock, depending on the tested instance + * + * @param unit The tested unit instance + * @param mockedVisitor The mocked {@link UnitVisitor} who should have gotten a visit by the unit + */ + abstract void verifyVisit(final U unit, final UnitVisitor mockedVisitor); + +} \ No newline at end of file diff --git a/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java new file mode 100644 index 000000000000..7bd9f03c0503 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java @@ -0,0 +1,80 @@ +package com.iluwatar.visitor; + +import org.junit.Test; + +import java.util.Optional; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/30/15 - 18:59 PM + * + * @author Jeroen Meulemeester + */ +public abstract class VisitorTest extends StdOutTest { + + /** + * The tested visitor instance + */ + private final V visitor; + + /** + * The optional expected response when being visited by a commander + */ + private final Optional commanderResponse; + + /** + * The optional expected response when being visited by a sergeant + */ + private final Optional sergeantResponse; + + /** + * The optional expected response when being visited by a soldier + */ + private final Optional soldierResponse; + + /** + * Create a new test instance for the given visitor + * + * @param commanderResponse The optional expected response when being visited by a commander + * @param sergeantResponse The optional expected response when being visited by a sergeant + * @param soldierResponse The optional expected response when being visited by a soldier + */ + public VisitorTest(final V visitor, final Optional commanderResponse, + final Optional sergeantResponse, final Optional soldierResponse) { + + this.visitor = visitor; + this.commanderResponse = commanderResponse; + this.sergeantResponse = sergeantResponse; + this.soldierResponse = soldierResponse; + } + + @Test + public void testVisitCommander() { + this.visitor.visitCommander(new Commander()); + if (this.commanderResponse.isPresent()) { + verify(getStdOutMock()).println(this.commanderResponse.get()); + } + verifyNoMoreInteractions(getStdOutMock()); + } + + @Test + public void testVisitSergeant() { + this.visitor.visitSergeant(new Sergeant()); + if (this.sergeantResponse.isPresent()) { + verify(getStdOutMock()).println(this.sergeantResponse.get()); + } + verifyNoMoreInteractions(getStdOutMock()); + } + + @Test + public void testVisitSoldier() { + this.visitor.visitSoldier(new Soldier()); + if (this.soldierResponse.isPresent()) { + verify(getStdOutMock()).println(this.soldierResponse.get()); + } + verifyNoMoreInteractions(getStdOutMock()); + } + +}