diff --git a/.all-contributorsrc b/.all-contributorsrc index 03d451d9a..6c2bbadbc 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -747,7 +747,8 @@ "avatar_url": "https://avatars1.githubusercontent.com/u/7418012?v=4", "profile": "https://github.com/hbothra15", "contributions": [ - "code" + "code", + "design" ] }, { @@ -948,6 +949,325 @@ "code", "review" ] + }, + { + "login": "sivasubramanim", + "name": "Sivasubramani M", + "avatar_url": "https://avatars2.githubusercontent.com/u/51107434?v=4", + "profile": "https://github.com/sivasubramanim", + "contributions": [ + "code" + ] + }, + { + "login": "d4gg4d", + "name": "Sami Airaksinen", + "avatar_url": "https://avatars2.githubusercontent.com/u/99457?v=4", + "profile": "https://github.com/d4gg4d", + "contributions": [ + "code" + ] + }, + { + "login": "vertti", + "name": "Janne Sinivirta", + "avatar_url": "https://avatars0.githubusercontent.com/u/557751?v=4", + "profile": "https://github.com/vertti", + "contributions": [ + "code" + ] + }, + { + "login": "Bobo1239", + "name": "Boris-Chengbiao Zhou", + "avatar_url": "https://avatars1.githubusercontent.com/u/2302947?v=4", + "profile": "https://github.com/Bobo1239", + "contributions": [ + "content" + ] + }, + { + "login": "Jahhein", + "name": "Jacob Hein", + "avatar_url": "https://avatars2.githubusercontent.com/u/10779515?v=4", + "profile": "https://jahhein.github.io", + "contributions": [ + "content" + ] + }, + { + "login": "iamrichardjones", + "name": "Richard Jones", + "avatar_url": "https://avatars3.githubusercontent.com/u/14842151?v=4", + "profile": "https://github.com/iamrichardjones", + "contributions": [ + "content" + ] + }, + { + "login": "rachelcarmena", + "name": "Rachel M. Carmena", + "avatar_url": "https://avatars0.githubusercontent.com/u/22792183?v=4", + "profile": "https://rachelcarmena.github.io", + "contributions": [ + "content" + ] + }, + { + "login": "zd-zero", + "name": "Zaerald Denze Lungos", + "avatar_url": "https://avatars0.githubusercontent.com/u/21978370?v=4", + "profile": "https://zd-zero.github.io", + "contributions": [ + "content" + ] + }, + { + "login": "webpro", + "name": "Lars Kappert", + "avatar_url": "https://avatars1.githubusercontent.com/u/456426?v=4", + "profile": "https://webpro.nl", + "contributions": [ + "content" + ] + }, + { + "login": "xiaod-dev", + "name": "Mike Liu", + "avatar_url": "https://avatars2.githubusercontent.com/u/21277644?v=4", + "profile": "https://xiaod.info", + "contributions": [ + "translation" + ] + }, + { + "login": "charlesfinley", + "name": "Matt Dolan", + "avatar_url": "https://avatars1.githubusercontent.com/u/6307904?v=4", + "profile": "https://github.com/charlesfinley", + "contributions": [ + "code", + "review" + ] + }, + { + "login": "MananS77", + "name": "Manan", + "avatar_url": "https://avatars3.githubusercontent.com/u/21033516?v=4", + "profile": "https://github.com/MananS77", + "contributions": [ + "review" + ] + }, + { + "login": "nishant", + "name": "Nishant Arora", + "avatar_url": "https://avatars2.githubusercontent.com/u/15331971?v=4", + "profile": "https://github.com/nishant", + "contributions": [ + "code" + ] + }, + { + "login": "raja-peeyush-kumar-singh", + "name": "Peeyush", + "avatar_url": "https://avatars0.githubusercontent.com/u/5496024?v=4", + "profile": "https://github.com/raja-peeyush-kumar-singh", + "contributions": [ + "code" + ] + }, + { + "login": "ravening", + "name": "Rakesh", + "avatar_url": "https://avatars1.githubusercontent.com/u/10645273?v=4", + "profile": "https://github.com/ravening", + "contributions": [ + "code", + "review" + ] + }, + { + "login": "vINCENT8888801", + "name": "Wei Seng", + "avatar_url": "https://avatars0.githubusercontent.com/u/8037883?v=4", + "profile": "https://github.com/vINCENT8888801", + "contributions": [ + "code" + ] + }, + { + "login": "ashishtrivedi16", + "name": "Ashish Trivedi", + "avatar_url": "https://avatars3.githubusercontent.com/u/23194128?v=4", + "profile": "https://www.linkedin.com/in/ashish-trivedi-218379135/", + "contributions": [ + "code" + ] + }, + { + "login": "RayYH", + "name": "洪月阳", + "avatar_url": "https://avatars1.githubusercontent.com/u/41055099?v=4", + "profile": "https://rayyounghong.com", + "contributions": [ + "code" + ] + }, + { + "login": "xdvrx1", + "name": "xdvrx1", + "avatar_url": "https://avatars0.githubusercontent.com/u/47092464?v=4", + "profile": "https://xdvrx1.github.io/", + "contributions": [ + "review", + "ideas" + ] + }, + { + "login": "ohbus", + "name": "Subhrodip Mohanta", + "avatar_url": "https://avatars0.githubusercontent.com/u/13291222?v=4", + "profile": "http://subho.xyz", + "contributions": [ + "code", + "review" + ] + }, + { + "login": "nahteb", + "name": "Bethan Palmer", + "avatar_url": "https://avatars3.githubusercontent.com/u/13121570?v=4", + "profile": "https://github.com/nahteb", + "contributions": [ + "code" + ] + }, + { + "login": "ToxicDreamz", + "name": "Toxic Dreamz", + "avatar_url": "https://avatars0.githubusercontent.com/u/45225562?v=4", + "profile": "https://github.com/ToxicDreamz", + "contributions": [ + "code" + ] + }, + { + "login": "edycutjong", + "name": "Edy Cu Tjong", + "avatar_url": "https://avatars1.githubusercontent.com/u/1098102?v=4", + "profile": "http://www.edycutjong.com", + "contributions": [ + "doc" + ] + }, + { + "login": "mkrzywanski", + "name": "Michał Krzywański", + "avatar_url": "https://avatars0.githubusercontent.com/u/15279585?v=4", + "profile": "https://github.com/mkrzywanski", + "contributions": [ + "code" + ] + }, + { + "login": "stefanbirkner", + "name": "Stefan Birkner", + "avatar_url": "https://avatars1.githubusercontent.com/u/711349?v=4", + "profile": "https://www.stefan-birkner.de", + "contributions": [ + "code" + ] + }, + { + "login": "fedorskvorcov", + "name": "Fedor Skvorcov", + "avatar_url": "https://avatars3.githubusercontent.com/u/43882212?v=4", + "profile": "https://github.com/fedorskvorcov", + "contributions": [ + "code" + ] + }, + { + "login": "samilAyoub", + "name": "samilAyoub", + "avatar_url": "https://avatars0.githubusercontent.com/u/61546990?v=4", + "profile": "https://github.com/samilAyoub", + "contributions": [ + "code" + ] + }, + { + "login": "vdlald", + "name": "Vladislav Golubinov", + "avatar_url": "https://avatars0.githubusercontent.com/u/29997701?v=4", + "profile": "https://github.com/vdlald", + "contributions": [ + "code" + ] + }, + { + "login": "swarajsaaj", + "name": "Swaraj", + "avatar_url": "https://avatars2.githubusercontent.com/u/6285049?v=4", + "profile": "https://github.com/swarajsaaj", + "contributions": [ + "code" + ] + }, + { + "login": "ChFlick", + "name": "Christoph Flick", + "avatar_url": "https://avatars0.githubusercontent.com/u/4465376?v=4", + "profile": "http://christophflick.de", + "contributions": [ + "doc" + ] + }, + { + "login": "Ascenio", + "name": "Ascênio", + "avatar_url": "https://avatars1.githubusercontent.com/u/7662016?v=4", + "profile": "https://github.com/Ascenio", + "contributions": [ + "review" + ] + }, + { + "login": "dsibilio", + "name": "Domenico Sibilio", + "avatar_url": "https://avatars2.githubusercontent.com/u/24280982?v=4", + "profile": "https://www.linkedin.com/in/domenico-sibilio/", + "contributions": [ + "doc" + ] + }, + { + "login": "akashchandwani", + "name": "Akash Chandwani", + "avatar_url": "https://avatars2.githubusercontent.com/u/3483277?v=4", + "profile": "https://github.com/akashchandwani", + "contributions": [ + "review" + ] + }, + { + "login": "manannikov", + "name": "Pavlo Manannikov", + "avatar_url": "https://avatars2.githubusercontent.com/u/7019769?v=4", + "profile": "http://www.linkedin.com/in/manannikov", + "contributions": [ + "code" + ] + }, + { + "login": "eimanip", + "name": "Eiman", + "avatar_url": "https://avatars0.githubusercontent.com/u/20307301?v=4", + "profile": "https://github.com/eimanip", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 4, diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml new file mode 100644 index 000000000..048a1bc80 --- /dev/null +++ b/.github/workflows/maven-ci.yml @@ -0,0 +1,80 @@ +# +# The MIT License +# Copyright © 2014-2019 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI + +on: + push: + branches: [ master ] + + +jobs: + + build: + + runs-on: ubuntu-20.04 + + steps: + + - name: Checkout Code + uses: actions/checkout@v2 + with: + # Disabling shallow clone for improving relevancy of SonarQube reporting + fetch-depth: 0 + + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Cache SonarCloud packages + uses: actions/cache@v2 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Cache Maven dependencies + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Some tests need screen access + - name: Install xvfb + run: sudo apt-get install -y xvfb + + # The SonarQube analysis is only for the master branch of the main repository. + # SonarQube scan does not work for forked repositories try changing it to xvfb-run mvn clean verify + # See https://jira.sonarsource.com/browse/MMF-1371 + - name: Build with Maven and run SonarQube analysis + run: xvfb-run mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + env: + # These two env variables are needed for sonar analysis + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/maven.yml b/.github/workflows/maven-pr-builder.yml similarity index 72% rename from .github/workflows/maven.yml rename to .github/workflows/maven-pr-builder.yml index d18cad280..a451f4c86 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven-pr-builder.yml @@ -24,37 +24,41 @@ # This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven -name: Java CI with Maven +name: Java PR Builder on: - push: - branches: [ master ] pull_request: branches: [ master ] + types: [ opened, reopened, synchronize, labeled, unlabeled ] jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - name: Checkout Code + uses: actions/checkout@v2 + - name: Set up JDK 11 uses: actions/setup-java@v1 with: java-version: 11 + + - name: Cache Maven Dependecies + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + # Some tests need screen access - name: Install xvfb - run: sudo apt-get install xvfb - # SonarQube scan does not work for forked repositories + run: sudo apt-get install -y xvfb + + # This worflow is only for building Pull Requests, the master branch runs Sonar analysis on the main repository. + # SonarQube scan does not work for forked repositories. # See https://jira.sonarsource.com/browse/MMF-1371 - name: Build with Maven - if: github.ref != 'refs/heads/master' run: xvfb-run mvn clean verify - - name: Build with Maven and run SonarQube analysis - if: github.ref == 'refs/heads/master' - run: xvfb-run mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar - env: - # These two env variables are needed for sonar analysis - GITHUB_TOKEN: ${{ secrets.REPOSITORY_ACCESS_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/README.md b/README.md index f67e40b8b..4211060c5 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,9 @@ ![Java CI with Maven](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI%20with%20Maven/badge.svg) [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![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) -[![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=alert_status)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![All Contributors](https://img.shields.io/badge/all_contributors-104-orange.svg?style=flat-square)](#contributors-) @@ -30,7 +31,7 @@ This site showcases Java Design Patterns. The solutions have been developed by experienced programmers and architects from the open source community. The patterns can be browsed by their high level descriptions or by looking at their source code. The source code examples are well commented and can be thought as -programming tutorials how to implement a specific pattern. We use the most +programming tutorials on how to implement a specific pattern. We use the most popular battle-proven open source Java technologies. Before you dive into the material, you should be familiar with various @@ -190,7 +191,7 @@ This project is licensed under the terms of the MIT license.
George Mavroeidis

💻 -
Hemant Bothra

💻 +
Hemant Bothra

💻 🎨
Kevin Peters

💻
George Aristy

💻 @@ -224,6 +225,59 @@ This project is licensed under the terms of the MIT license.
Aditya Pal

💻
grzesiekkedzior

💻 👀 + +
Sivasubramani M

💻 +
Sami Airaksinen

💻 +
Janne Sinivirta

💻 +
Boris-Chengbiao Zhou

🖋 + + +
Jacob Hein

🖋 +
Richard Jones

🖋 +
Rachel M. Carmena

🖋 +
Zaerald Denze Lungos

🖋 + + +
Lars Kappert

🖋 +
Mike Liu

🌍 +
Matt Dolan

💻 👀 +
Manan

👀 + + +
Nishant Arora

💻 +
Peeyush

💻 +
Rakesh

💻 👀 +
Wei Seng

💻 + + +
Ashish Trivedi

💻 +
洪月阳

💻 +
xdvrx1

👀 🤔 +
Subhrodip Mohanta

💻 👀 + + +
Bethan Palmer

💻 +
Toxic Dreamz

💻 +
Edy Cu Tjong

📖 +
Michał Krzywański

💻 + + +
Stefan Birkner

💻 +
Fedor Skvorcov

💻 +
samilAyoub

💻 +
Vladislav Golubinov

💻 + + +
Swaraj

💻 +
Christoph Flick

📖 +
Ascênio

👀 +
Domenico Sibilio

📖 + + +
Akash Chandwani

👀 +
Pavlo Manannikov

💻 +
Eiman

💻 + diff --git a/abstract-document/README.md b/abstract-document/README.md index 15f5c6c85..cbb35cee8 100644 --- a/abstract-document/README.md +++ b/abstract-document/README.md @@ -9,21 +9,182 @@ tags: --- ## Intent -Achieve flexibility of untyped languages and keep the type-safety + +Use dynamic properties and achieve flexibility of untyped languages while keeping type-safety. + +## Explanation + +The Abstract Document pattern enables handling additional, non-static properties. This pattern +uses concept of traits to enable type safety and separate properties of different classes into +set of interfaces. + +Real world example + +> Consider a car that consists of multiple parts. However we don't know if the specific car really has all the parts, or just some of them. Our cars are dynamic and extremely flexible. + +In plain words + +> Abstract Document pattern allows attaching properties to objects without them knowing about it. + +Wikipedia says + +> An object-oriented structural design pattern for organizing objects in loosely typed key-value stores and exposing +the data using typed views. The purpose of the pattern is to achieve a high degree of flexibility between components +in a strongly typed language where new properties can be added to the object-tree on the fly, without losing the +support of type-safety. The pattern makes use of traits to separate different properties of a class into different +interfaces. + +**Programmatic Example** + +Let's first define the base classes `Document` and `AbstractDocument`. They basically make the object hold a property +map and any amount of child objects. + +```java +public interface Document { + + Void put(String key, Object value); + + Object get(String key); + + Stream children(String key, Function, T> constructor); +} + +public abstract class AbstractDocument implements Document { + + private final Map properties; + + protected AbstractDocument(Map properties) { + Objects.requireNonNull(properties, "properties map is required"); + this.properties = properties; + } + + @Override + public Void put(String key, Object value) { + properties.put(key, value); + return null; + } + + @Override + public Object get(String key) { + return properties.get(key); + } + + @Override + public Stream children(String key, Function, T> constructor) { + return Stream.ofNullable(get(key)) + .filter(Objects::nonNull) + .map(el -> (List>) el) + .findAny() + .stream() + .flatMap(Collection::stream) + .map(constructor); + } + ... +} +``` +Next we define an enum `Property` and a set of interfaces for type, price, model and parts. This allows us to create +static looking interface to our `Car` class. + +```java +public enum Property { + + PARTS, TYPE, PRICE, MODEL +} + +public interface HasType extends Document { + + default Optional getType() { + return Optional.ofNullable((String) get(Property.TYPE.toString())); + } +} + +public interface HasPrice extends Document { + + default Optional getPrice() { + return Optional.ofNullable((Number) get(Property.PRICE.toString())); + } +} +public interface HasModel extends Document { + + default Optional getModel() { + return Optional.ofNullable((String) get(Property.MODEL.toString())); + } +} + +public interface HasParts extends Document { + + default Stream getParts() { + return children(Property.PARTS.toString(), Part::new); + } +} +``` + +Now we are ready to introduce the `Car`. + +```java +public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { + + public Car(Map properties) { + super(properties); + } +} +``` + +And finally here's how we construct and use the `Car` in a full example. + +```java + LOGGER.info("Constructing parts and car"); + + var wheelProperties = Map.of( + Property.TYPE.toString(), "wheel", + Property.MODEL.toString(), "15C", + Property.PRICE.toString(), 100L); + + var doorProperties = Map.of( + Property.TYPE.toString(), "door", + Property.MODEL.toString(), "Lambo", + Property.PRICE.toString(), 300L); + + var carProperties = Map.of( + Property.MODEL.toString(), "300SL", + Property.PRICE.toString(), 10000L, + Property.PARTS.toString(), List.of(wheelProperties, doorProperties)); + + var car = new Car(carProperties); + + LOGGER.info("Here is our car:"); + LOGGER.info("-> model: {}", car.getModel().orElseThrow()); + LOGGER.info("-> price: {}", car.getPrice().orElseThrow()); + LOGGER.info("-> parts: "); + car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}", + p.getType().orElse(null), + p.getModel().orElse(null), + p.getPrice().orElse(null)) + ); + + // Constructing parts and car + // Here is our car: + // model: 300SL + // price: 10000 + // parts: + // wheel/15C/100 + // door/Lambo/300 +``` ## Class diagram + ![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain") - ## Applicability + Use the Abstract Document Pattern when -* there is a need to add new properties on the fly -* you want a flexible way to organize domain in tree like structure -* you want more loosely coupled system - +* There is a need to add new properties on the fly +* You want a flexible way to organize domain in tree like structure +* You want more loosely coupled system ## Credits * [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern) * [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf) +* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://www.amazon.com/gp/product/0470059028/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0470059028&linkId=e3aacaea7017258acf184f9f3283b492) diff --git a/abstract-document/pom.xml b/abstract-document/pom.xml index 5a19c3c81..15e7cc54d 100644 --- a/abstract-document/pom.xml +++ b/abstract-document/pom.xml @@ -21,7 +21,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT abstract-document diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java index b881ee7ac..d13021e72 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java @@ -43,9 +43,11 @@ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** - * Executes the App. + * Program entry point. + * + * @param args command line args */ - public App() { + public static void main(String[] args) { LOGGER.info("Constructing parts and car"); var wheelProperties = Map.of( @@ -75,14 +77,4 @@ public class App { p.getPrice().orElse(null)) ); } - - /** - * Program entry point. - * - * @param args command line args - */ - public static void main(String[] args) { - new App(); - } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java index 8ecfa85fb..54f308ccf 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java @@ -32,7 +32,6 @@ import java.util.stream.Stream; */ public interface HasParts extends Document { - default Stream getParts() { return children(Property.PARTS.toString(), Part::new); } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java index 9a95f2a51..a50c725c3 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java @@ -32,7 +32,6 @@ import java.util.Optional; */ public interface HasPrice extends Document { - default Optional getPrice() { return Optional.ofNullable((Number) get(Property.PRICE.toString())); } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java index b1d5bd6b5..2722564d5 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java @@ -32,7 +32,6 @@ import java.util.Optional; */ public interface HasType extends Document { - default Optional getType() { return Optional.ofNullable((String) get(Property.TYPE.toString())); } diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java index d7fe5688d..13db318e4 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java @@ -40,14 +40,14 @@ public class AbstractDocumentTest { private static final String KEY = "key"; private static final String VALUE = "value"; - private class DocumentImplementation extends AbstractDocument { + private static class DocumentImplementation extends AbstractDocument { DocumentImplementation(Map properties) { super(properties); } } - private DocumentImplementation document = new DocumentImplementation(new HashMap<>()); + private final DocumentImplementation document = new DocumentImplementation(new HashMap<>()); @Test public void shouldPutAndGetValue() { diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java index aed63f303..dca4f040f 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java @@ -25,14 +25,23 @@ package com.iluwatar.abstractdocument; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Simple App test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ @Test - public void shouldExecuteAppWithoutException() { - App.main(null); + void shouldExecuteAppWithoutException() { + assertDoesNotThrow(() -> App.main(null)); } } diff --git a/abstract-factory/README.md b/abstract-factory/README.md index fba3460c1..22edb81e0 100644 --- a/abstract-factory/README.md +++ b/abstract-factory/README.md @@ -9,16 +9,19 @@ tags: --- ## Also known as + Kit ## Intent + Provide an interface for creating families of related or dependent objects without specifying their concrete classes. ## Explanation + Real world example -> To create a kingdom we need objects with common theme. Elven kingdom needs an Elven king, Elven castle and Elven army whereas Orcish kingdom needs an Orcish king, Orcish castle and Orcish army. There is a dependency between the objects in the kingdom. +> To create a kingdom we need objects with a common theme. Elven kingdom needs an Elven king, Elven castle and Elven army whereas Orcish kingdom needs an Orcish king, Orcish castle and Orcish army. There is a dependency between the objects in the kingdom. In plain words @@ -30,15 +33,18 @@ Wikipedia says **Programmatic Example** -Translating the kingdom example above. First of all we have some interfaces and implementation for the objects in the kingdom +Translating the kingdom example above. First of all we have some interfaces and implementation for the objects in the +kingdom. ```java public interface Castle { String getDescription(); } + public interface King { String getDescription(); } + public interface Army { String getDescription(); } @@ -66,7 +72,7 @@ public class ElfArmy implements Army { } } -// Orcish implementations similarly... +// Orcish implementations similarly -> ... ``` @@ -112,9 +118,17 @@ var castle = factory.createCastle(); var king = factory.createKing(); var army = factory.createArmy(); -castle.getDescription(); // Output: This is the Elven castle! -king.getDescription(); // Output: This is the Elven king! -army.getDescription(); // Output: This is the Elven Army! +castle.getDescription(); +king.getDescription(); +army.getDescription(); +``` + +Program output: + +```java +This is the Elven castle! +This is the Elven king! +This is the Elven Army! ``` Now, we can design a factory for our different kingdom factories. In this example, we created FactoryMaker, responsible for returning an instance of either ElfKingdomFactory or OrcKingdomFactory. @@ -156,46 +170,52 @@ public static void main(String[] args) { ``` ## Class diagram + ![alt text](./etc/abstract-factory.urm.png "Abstract Factory class diagram") ## 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 -* the lifetime of the dependency is conceptually shorter than the lifetime of the consumer. -* you need a run-time value to construct a particular dependency -* you want to decide which product to call from a family at runtime. -* you need to supply one or more parameters only known at run-time before you can resolve a dependency. -* when you need consistency among products -* you don’t want to change existing code when adding new products or families of products to the program. +* The system should be independent of how its products are created, composed and represented +* The system should be configured with one of multiple families of products +* The 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 +* The lifetime of the dependency is conceptually shorter than the lifetime of the consumer. +* You need a run-time value to construct a particular dependency +* You want to decide which product to call from a family at runtime. +* You need to supply one or more parameters only known at run-time before you can resolve a dependency. +* When you need consistency among products +* You don’t want to change existing code when adding new products or families of products to the program. -## Use Cases: +Example use cases -* Selecting to call the appropriate implementation of FileSystemAcmeService or DatabaseAcmeService or NetworkAcmeService at runtime. -* Unit test case writing becomes much easier +* Selecting to call to the appropriate implementation of FileSystemAcmeService or DatabaseAcmeService or NetworkAcmeService at runtime. +* Unit test case writing becomes much easier * UI tools for different OS ## Consequences: -* Dependency injection in java hides the service class dependencies that can lead to runtime errors that would have been caught at compile time. +* Dependency injection in java hides the service class dependencies that can lead to runtime errors that would have been caught at compile time. * While the pattern is great when creating predefined objects, adding the new ones might be challenging. -* The code may become more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern. - +* The code becomes more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern. ## Tutorial + * [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java) - -## Real world examples +## Known uses * [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html) * [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--) * [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--) +## Related patterns + +[Factory Method](https://java-design-patterns.com/patterns/factory-method/) +[Factory Kit](https://java-design-patterns.com/patterns/factory-kit/) + ## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) diff --git a/abstract-factory/pom.xml b/abstract-factory/pom.xml index 614b26232..2328c60a8 100644 --- a/abstract-factory/pom.xml +++ b/abstract-factory/pom.xml @@ -22,7 +22,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.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 e158ece74..baf2d7569 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java @@ -23,7 +23,6 @@ package com.iluwatar.abstractfactory; -import com.iluwatar.abstractfactory.App.FactoryMaker.KingdomType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,84 +40,14 @@ import org.slf4j.LoggerFactory; * 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 { +public class App implements Runnable { - private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + private static Logger log = LoggerFactory.getLogger(App.class); - private King king; - private Castle castle; - private Army army; + private final Kingdom kingdom = new Kingdom(); - /** - * Creates kingdom. - */ - public void createKingdom(final KingdomFactory factory) { - setKing(factory.createKing()); - setCastle(factory.createCastle()); - setArmy(factory.createArmy()); - } - - King getKing(final KingdomFactory factory) { - return factory.createKing(); - } - - public King getKing() { - return king; - } - - private void setKing(final King king) { - this.king = king; - } - - Castle getCastle(final KingdomFactory factory) { - return factory.createCastle(); - } - - public Castle getCastle() { - return castle; - } - - private void setCastle(final Castle castle) { - this.castle = castle; - } - - Army getArmy(final KingdomFactory factory) { - return factory.createArmy(); - } - - public Army getArmy() { - return army; - } - - private void setArmy(final Army army) { - this.army = army; - } - - /** - * The factory of kingdom factories. - */ - public static class FactoryMaker { - - /** - * Enumeration for the different types of Kingdoms. - */ - public enum KingdomType { - ELF, ORC - } - - /** - * The factory method to create KingdomFactory concrete objects. - */ - public static KingdomFactory makeFactory(KingdomType type) { - switch (type) { - case ELF: - return new ElfKingdomFactory(); - case ORC: - return new OrcKingdomFactory(); - default: - throw new IllegalArgumentException("KingdomType not supported."); - } - } + public Kingdom getKingdom() { + return kingdom; } /** @@ -127,19 +56,33 @@ public class App { * @param args command line args */ public static void main(String[] args) { - var app = new App(); + app.run(); + } - LOGGER.info("Elf Kingdom"); - app.createKingdom(FactoryMaker.makeFactory(KingdomType.ELF)); - LOGGER.info(app.getArmy().getDescription()); - LOGGER.info(app.getCastle().getDescription()); - LOGGER.info(app.getKing().getDescription()); + @Override + public void run() { + log.info("Elf Kingdom"); + createKingdom(Kingdom.FactoryMaker.KingdomType.ELF); + log.info(kingdom.getArmy().getDescription()); + log.info(kingdom.getCastle().getDescription()); + log.info(kingdom.getKing().getDescription()); - LOGGER.info("Orc Kingdom"); - app.createKingdom(FactoryMaker.makeFactory(KingdomType.ORC)); - LOGGER.info(app.getArmy().getDescription()); - LOGGER.info(app.getCastle().getDescription()); - LOGGER.info(app.getKing().getDescription()); + log.info("Orc Kingdom"); + createKingdom(Kingdom.FactoryMaker.KingdomType.ORC); + log.info(kingdom.getArmy().getDescription()); + log.info(kingdom.getCastle().getDescription()); + log.info(kingdom.getKing().getDescription()); + } + + /** + * Creates kingdom. + * @param kingdomType type of Kingdom + */ + public void createKingdom(final Kingdom.FactoryMaker.KingdomType kingdomType) { + final KingdomFactory kingdomFactory = Kingdom.FactoryMaker.makeFactory(kingdomType); + kingdom.setKing(kingdomFactory.createKing()); + kingdom.setCastle(kingdomFactory.createCastle()); + kingdom.setArmy(kingdomFactory.createArmy()); } } \ No newline at end of file diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java new file mode 100644 index 000000000..cffcabf68 --- /dev/null +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java @@ -0,0 +1,82 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.abstractfactory; + +public class Kingdom { + + private King king; + private Castle castle; + private Army army; + + public King getKing() { + return king; + } + + public Castle getCastle() { + return castle; + } + + public Army getArmy() { + return army; + } + + public void setKing(King king) { + this.king = king; + } + + public void setCastle(Castle castle) { + this.castle = castle; + } + + public void setArmy(Army army) { + this.army = army; + } + + /** + * The factory of kingdom factories. + */ + public static class FactoryMaker { + + /** + * Enumeration for the different types of Kingdoms. + */ + public enum KingdomType { + ELF, ORC + } + + /** + * The factory method to create KingdomFactory concrete objects. + */ + public static KingdomFactory makeFactory(KingdomType type) { + switch (type) { + case ELF: + return new ElfKingdomFactory(); + case ORC: + return new OrcKingdomFactory(); + default: + throw new IllegalArgumentException("KingdomType not supported."); + } + } + } +} diff --git a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java index be83cc315..17c09be88 100644 --- a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java +++ b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java @@ -23,65 +23,71 @@ package com.iluwatar.abstractfactory; +import org.junit.jupiter.api.Test; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.iluwatar.abstractfactory.App.FactoryMaker; -import com.iluwatar.abstractfactory.App.FactoryMaker.KingdomType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - /** * Test for abstract factory. */ public class AbstractFactoryTest { - private App app = new App(); - private KingdomFactory elfFactory; - private KingdomFactory orcFactory; - - @BeforeEach - public void setUp() { - elfFactory = FactoryMaker.makeFactory(KingdomType.ELF); - orcFactory = FactoryMaker.makeFactory(KingdomType.ORC); - } + private final App app = new App(); @Test public void king() { - final var elfKing = app.getKing(elfFactory); + app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF); + final var kingdom = app.getKingdom(); + + final var elfKing = kingdom.getKing(); assertTrue(elfKing instanceof ElfKing); assertEquals(ElfKing.DESCRIPTION, elfKing.getDescription()); - final var orcKing = app.getKing(orcFactory); + + app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC); + final var orcKing = kingdom.getKing(); assertTrue(orcKing instanceof OrcKing); assertEquals(OrcKing.DESCRIPTION, orcKing.getDescription()); } @Test public void castle() { - final var elfCastle = app.getCastle(elfFactory); + app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF); + final var kingdom = app.getKingdom(); + + final var elfCastle = kingdom.getCastle(); assertTrue(elfCastle instanceof ElfCastle); assertEquals(ElfCastle.DESCRIPTION, elfCastle.getDescription()); - final var orcCastle = app.getCastle(orcFactory); + + app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC); + final var orcCastle = kingdom.getCastle(); assertTrue(orcCastle instanceof OrcCastle); assertEquals(OrcCastle.DESCRIPTION, orcCastle.getDescription()); } @Test public void army() { - final var elfArmy = app.getArmy(elfFactory); + app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF); + final var kingdom = app.getKingdom(); + + final var elfArmy = kingdom.getArmy(); assertTrue(elfArmy instanceof ElfArmy); assertEquals(ElfArmy.DESCRIPTION, elfArmy.getDescription()); - final var orcArmy = app.getArmy(orcFactory); + + app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC); + final var orcArmy = kingdom.getArmy(); assertTrue(orcArmy instanceof OrcArmy); assertEquals(OrcArmy.DESCRIPTION, orcArmy.getDescription()); } @Test public void createElfKingdom() { - app.createKingdom(elfFactory); - final var king = app.getKing(); - final var castle = app.getCastle(); - final var army = app.getArmy(); + app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF); + final var kingdom = app.getKingdom(); + + final var king = kingdom.getKing(); + final var castle = kingdom.getCastle(); + final var army = kingdom.getArmy(); assertTrue(king instanceof ElfKing); assertEquals(ElfKing.DESCRIPTION, king.getDescription()); assertTrue(castle instanceof ElfCastle); @@ -92,10 +98,12 @@ public class AbstractFactoryTest { @Test public void createOrcKingdom() { - app.createKingdom(orcFactory); - final var king = app.getKing(); - final var castle = app.getCastle(); - final var army = app.getArmy(); + app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC); + final var kingdom = app.getKingdom(); + + final var king = kingdom.getKing(); + final var castle = kingdom.getCastle(); + final var army = kingdom.getArmy(); assertTrue(king instanceof OrcKing); assertEquals(OrcKing.DESCRIPTION, king.getDescription()); assertTrue(castle instanceof OrcCastle); diff --git a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java index 4036cc9b8..238ff76d0 100644 --- a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java +++ b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java @@ -25,12 +25,23 @@ package com.iluwatar.abstractfactory; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Abstract Factory example runs without errors. */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/acyclic-visitor/README.md b/acyclic-visitor/README.md index f293e4393..835a5743e 100644 --- a/acyclic-visitor/README.md +++ b/acyclic-visitor/README.md @@ -9,12 +9,126 @@ tags: --- ## Intent -Allow new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating the troublesome dependency cycles that are inherent to the GOF VISITOR Pattern. + +Allow new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating +the troublesome dependency cycles that are inherent to the GoF Visitor Pattern. + +## Explanation + +Real world example + +> We have a hierarchy of modem classes. The modems in this hierarchy need to be visited by an external algorithm based +> on filtering criteria (is it Unix or DOS compatible modem). + +In plain words + +> Acyclic Visitor allows functions to be added to existing class hierarchies without modifying the hierarchies. + +[WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) says + +> The Acyclic Visitor pattern allows new functions to be added to existing class hierarchies without affecting those +> hierarchies, and without creating the dependency cycles that are inherent to the GangOfFour VisitorPattern. + +**Programmatic Example** + +Here's the `Modem` hierarchy. + +```java +public abstract class Modem { + public abstract void accept(ModemVisitor modemVisitor); +} + +public class Zoom extends Modem { + ... + @Override + public void accept(ModemVisitor modemVisitor) { + if (modemVisitor instanceof ZoomVisitor) { + ((ZoomVisitor) modemVisitor).visit(this); + } else { + LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem"); + } + } +} + +public class Hayes extends Modem { + ... + @Override + public void accept(ModemVisitor modemVisitor) { + if (modemVisitor instanceof HayesVisitor) { + ((HayesVisitor) modemVisitor).visit(this); + } else { + LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem"); + } + } +} +``` + +Next we introduce the `ModemVisitor` hierarchy. + +```java +public interface ModemVisitor { +} + +public interface HayesVisitor extends ModemVisitor { + void visit(Hayes hayes); +} + +public interface ZoomVisitor extends ModemVisitor { + void visit(Zoom zoom); +} + +public interface AllModemVisitor extends ZoomVisitor, HayesVisitor { +} + +public class ConfigureForDosVisitor implements AllModemVisitor { + ... + @Override + public void visit(Hayes hayes) { + LOGGER.info(hayes + " used with Dos configurator."); + } + @Override + public void visit(Zoom zoom) { + LOGGER.info(zoom + " used with Dos configurator."); + } +} + +public class ConfigureForUnixVisitor implements ZoomVisitor { + ... + @Override + public void visit(Zoom zoom) { + LOGGER.info(zoom + " used with Unix configurator."); + } +} +``` + +Finally, here are the visitors in action. + +```java + var conUnix = new ConfigureForUnixVisitor(); + var conDos = new ConfigureForDosVisitor(); + var zoom = new Zoom(); + var hayes = new Hayes(); + hayes.accept(conDos); + zoom.accept(conDos); + hayes.accept(conUnix); + zoom.accept(conUnix); +``` + +Program output: + +``` + // Hayes modem used with Dos configurator. + // Zoom modem used with Dos configurator. + // Only HayesVisitor is allowed to visit Hayes modem + // Zoom modem used with Unix configurator. +``` ## Class diagram + ![alt text](./etc/acyclic-visitor.png "Acyclic Visitor") ## Applicability + This pattern can be used: * When you need to add a new function to an existing hierarchy without the need to alter or affect that hierarchy. @@ -24,6 +138,7 @@ This pattern can be used: * When the recompilation, relinking, retesting or redistribution of the derivatives of Element is very expensive. ## Consequences + The good: * No dependency cycles between class hierarchies. @@ -32,11 +147,14 @@ The good: The bad: -* Violates the principle of least surprise or Liskov's Substitution principle by showing that it can accept all visitors but actually only being interested in particular visitors. +* Violates [Liskov's Substitution Principle](https://java-design-patterns.com/principles/#liskov-substitution-principle) by showing that it can accept all visitors but actually only being interested in particular visitors. * Parallel hierarchy of visitors has to be created for all members in visitable class hierarchy. ## Related patterns -* [Visitor Pattern](../visitor/) + +* [Visitor Pattern](https://java-design-patterns.com/patterns/visitor/) ## Credits -* [Acyclic Visitor](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf) + +* [Acyclic Visitor by Robert C. Martin](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf) +* [Acyclic Visitor in WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) diff --git a/acyclic-visitor/etc/Acyclic Visitor.ucls b/acyclic-visitor/etc/Acyclic Visitor.ucls deleted file mode 100644 index 03b6c77dd..000000000 --- a/acyclic-visitor/etc/Acyclic Visitor.ucls +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/acyclic-visitor/etc/acyclic-visitor.png b/acyclic-visitor/etc/acyclic-visitor.png index 636532c4d..7b4df13d8 100644 Binary files a/acyclic-visitor/etc/acyclic-visitor.png and b/acyclic-visitor/etc/acyclic-visitor.png differ diff --git a/acyclic-visitor/pom.xml b/acyclic-visitor/pom.xml index 24bab933a..8c53b8750 100644 --- a/acyclic-visitor/pom.xml +++ b/acyclic-visitor/pom.xml @@ -21,7 +21,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT acyclic-visitor diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java index 4b9a7ec6c..842779fff 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java @@ -25,13 +25,23 @@ package com.iluwatar.acyclicvisitor; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that the Acyclic Visitor example runs without errors. */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } \ No newline at end of file diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitorTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitorTest.java index 8847a131e..79097a454 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitorTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitorTest.java @@ -37,7 +37,7 @@ import uk.org.lidalia.slf4jtest.TestLoggerFactory; */ public class ConfigureForDosVisitorTest { - private TestLogger logger = TestLoggerFactory.getTestLogger(ConfigureForDosVisitor.class); + private final TestLogger logger = TestLoggerFactory.getTestLogger(ConfigureForDosVisitor.class); @Test public void testVisitForZoom() { diff --git a/adapter/README.md b/adapter/README.md index b36558cbc..aef4cdb69 100644 --- a/adapter/README.md +++ b/adapter/README.md @@ -12,9 +12,8 @@ tags: Wrapper ## 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. +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. ## Explanation @@ -56,7 +55,7 @@ And captain expects an implementation of `RowingBoat` interface to be able to mo ```java public class Captain { - private RowingBoat rowingBoat; + private final RowingBoat rowingBoat; // default constructor and setter for rowingBoat public Captain(RowingBoat rowingBoat) { this.rowingBoat = rowingBoat; @@ -75,7 +74,7 @@ public class FishingBoatAdapter implements RowingBoat { private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoatAdapter.class); - private FishingBoat boat; + private final FishingBoat boat; public FishingBoatAdapter() { boat = new FishingBoat(); diff --git a/adapter/pom.xml b/adapter/pom.xml index 4c725def8..a4d8d7b12 100644 --- a/adapter/pom.xml +++ b/adapter/pom.xml @@ -22,7 +22,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT adapter diff --git a/adapter/src/main/java/com/iluwatar/adapter/FishingBoatAdapter.java b/adapter/src/main/java/com/iluwatar/adapter/FishingBoatAdapter.java index 5ccde5c53..39a9adab4 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/FishingBoatAdapter.java +++ b/adapter/src/main/java/com/iluwatar/adapter/FishingBoatAdapter.java @@ -29,7 +29,7 @@ package com.iluwatar.adapter; */ public class FishingBoatAdapter implements RowingBoat { - private FishingBoat boat; + private final FishingBoat boat; public FishingBoatAdapter() { boat = new FishingBoat(); diff --git a/adapter/src/test/java/com/iluwatar/adapter/AppTest.java b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java index 3bf8e1010..3748c64f6 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AppTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java @@ -25,12 +25,23 @@ package com.iluwatar.adapter; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Adapter example runs without errors. */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/aggregator-microservices/README.md b/aggregator-microservices/README.md index 74a411fc5..b1bd5d762 100644 --- a/aggregator-microservices/README.md +++ b/aggregator-microservices/README.md @@ -6,20 +6,97 @@ permalink: /patterns/aggregator-microservices/ categories: Architectural tags: - Cloud distributed +- Decoupling +- Microservices --- ## Intent -The user makes a single call to the Aggregator, and the aggregator then calls each relevant microservice and collects -the data, apply business logic to it, and further publish is as a REST Endpoint. -More variations of the aggregator are: -- Proxy Microservice Design Pattern: A different microservice is called upon the business need. -- Chained Microservice Design Pattern: In this case each microservice is dependent/ chained to a series -of other microservices. +The user makes a single call to the aggregator service, and the aggregator then calls each relevant microservice. + +## Explanation + +Real world example + +> Our web marketplace needs information about products and their current inventory. It makes a call to an aggregator +> service which in turn calls the product information microservice and product inventory microservice returning the +> combined information. + +In plain words + +> Aggregator Microservice collects pieces of data from various microservices and returns an aggregate for processing. + +Stack Overflow says + +> Aggregator Microservice invokes multiple services to achieve the functionality required by the application. + +**Programmatic Example** + +Let's start from the data model. Here's our `Product`. + +```java +public class Product { + private String title; + private int productInventories; + // getters and setters -> + ... +} +``` + +Next we can introduce our `Aggregator` microservice. It contains clients `ProductInformationClient` and +`ProductInventoryClient` for calling respective microservices. + +```java +@RestController +public class Aggregator { + + @Resource + private ProductInformationClient informationClient; + + @Resource + private ProductInventoryClient inventoryClient; + + @RequestMapping(path = "/product", method = RequestMethod.GET) + public Product getProduct() { + + var product = new Product(); + var productTitle = informationClient.getProductTitle(); + var productInventory = inventoryClient.getProductInventories(); + + //Fallback to error message + product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed")); + + //Fallback to default error inventory + product.setProductInventories(requireNonNullElse(productInventory, -1)); + + return product; + } +} +``` + +Here's the essence of information microservice implementation. Inventory microservice is similar, it just returns +inventory counts. + +```java +@RestController +public class InformationController { + @RequestMapping(value = "/information", method = RequestMethod.GET) + public String getProductTitle() { + return "The Product Title."; + } +} +``` + +Now calling our `Aggregator` REST API returns the product information. + +```bash +curl http://localhost:50004/product +{"title":"The Product Title.","productInventories":5} +``` ## Class diagram -![alt text](./etc/aggregator-microservice.png "Aggregator Microservice") +![alt text](./aggregator-service/etc/aggregator-service.png "Aggregator Microservice") ## Applicability @@ -28,3 +105,5 @@ Use the Aggregator Microservices pattern when you need a unified API for various ## Credits * [Microservice Design Patterns](http://web.archive.org/web/20190705163602/http://blog.arungupta.me/microservice-design-patterns/) +* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=8b4e570267bc5fb8b8189917b461dc60) +* [Architectural Patterns: Uncover essential patterns in the most indispensable realm of enterprise architecture](https://www.amazon.com/gp/product/B077T7V8RC/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B077T7V8RC&linkId=c34d204bfe1b277914b420189f09c1a4) diff --git a/aggregator-microservices/aggregator-service/etc/aggregator-service.png b/aggregator-microservices/aggregator-service/etc/aggregator-service.png new file mode 100644 index 000000000..75ee82328 Binary files /dev/null and b/aggregator-microservices/aggregator-service/etc/aggregator-service.png differ diff --git a/aggregator-microservices/aggregator-service/pom.xml b/aggregator-microservices/aggregator-service/pom.xml index f4482d0e3..b7c2bac66 100644 --- a/aggregator-microservices/aggregator-service/pom.xml +++ b/aggregator-microservices/aggregator-service/pom.xml @@ -20,7 +20,7 @@ aggregator-microservices com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 aggregator-service diff --git a/aggregator-microservices/etc/aggregator-microservice.png b/aggregator-microservices/etc/aggregator-microservice.png deleted file mode 100644 index ad344a7e1..000000000 Binary files a/aggregator-microservices/etc/aggregator-microservice.png and /dev/null differ diff --git a/aggregator-microservices/information-microservice/pom.xml b/aggregator-microservices/information-microservice/pom.xml index f99d26b65..71935757f 100644 --- a/aggregator-microservices/information-microservice/pom.xml +++ b/aggregator-microservices/information-microservice/pom.xml @@ -20,7 +20,7 @@ aggregator-microservices com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/aggregator-microservices/inventory-microservice/pom.xml b/aggregator-microservices/inventory-microservice/pom.xml index f7899aa8f..6c2726a35 100644 --- a/aggregator-microservices/inventory-microservice/pom.xml +++ b/aggregator-microservices/inventory-microservice/pom.xml @@ -20,7 +20,7 @@ aggregator-microservices com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 inventory-microservice diff --git a/aggregator-microservices/pom.xml b/aggregator-microservices/pom.xml index a63ec2f12..9e66e9be0 100644 --- a/aggregator-microservices/pom.xml +++ b/aggregator-microservices/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 aggregator-microservices diff --git a/ambassador/README.md b/ambassador/README.md index 11abfaf88..dfba18649 100644 --- a/ambassador/README.md +++ b/ambassador/README.md @@ -10,28 +10,37 @@ tags: --- ## Intent + Provide a helper service instance on a client and offload common functionality away from a shared resource. ## Explanation + Real world example -> A remote service has many clients accessing a function it provides. The service is a legacy application and is impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request frequency should be implemented along with latency checks and client-side logging. +> A remote service has many clients accessing a function it provides. The service is a legacy application and is +> impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request +> frequency should be implemented along with latency checks and client-side logging. In plain words -> Using the ambassador pattern, we can implement less-frequent polling from clients along with latency checks and logging. +> With the Ambassador pattern, we can implement less-frequent polling from clients along with latency checks and +> logging. Microsoft documentation states -> An ambassador service can be thought of as an out-of-process proxy that is co-located with the client. This pattern can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, or other applications that are difficult to modify, in order to extend their networking capabilities. It can also enable a specialized team to implement those features. +> An ambassador service can be thought of as an out-of-process proxy which is co-located with the client. This pattern +> can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, +> security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, +> or other applications that are difficult to modify, in order to extend their networking capabilities. It can also +> enable a specialized team to implement those features. **Programmatic Example** -With the above example in mind we will imitate the functionality in a simple manner. We have an interface implemented by the remote service as well as the ambassador service: +With the above introduction in mind we will imitate the functionality in this example. We have an interface implemented +by the remote service as well as the ambassador service: ```java interface RemoteServiceInterface { - long doRemoteFunction(int value) throws Exception; } ``` @@ -136,7 +145,7 @@ public class Client { } ``` -And here are two clients using the service. +Here are two clients using the service. ```java public class App { @@ -149,13 +158,29 @@ public class App { } ``` +Here's the output for running the example: + +```java +Time taken (ms): 111 +Service result: 120 +Time taken (ms): 931 +Failed to reach remote: (1) +Time taken (ms): 665 +Failed to reach remote: (2) +Time taken (ms): 538 +Failed to reach remote: (3) +Service result: -1 +``` + ## Class diagram + ![alt text](./etc/ambassador.urm.png "Ambassador class diagram") ## Applicability -Ambassador is applicable when working with a legacy remote service that cannot -be modified or would be extremely difficult to modify. Connectivity features can -be implemented on the client avoiding the need for changes on the remote service. + +Ambassador is applicable when working with a legacy remote service which cannot be modified or would be extremely +difficult to modify. Connectivity features can be implemented on the client avoiding the need for changes on the remote +service. * Ambassador provides a local interface for a remote service. * Ambassador provides logging, circuit breaking, retries and security on the client. @@ -168,10 +193,14 @@ be implemented on the client avoiding the need for changes on the remote service * Offload remote service tasks * Facilitate network connection -## Real world examples +## Known uses * [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador) +## Related patterns + +* [Proxy](https://java-design-patterns.com/patterns/proxy/) + ## Credits * [Ambassador pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador) diff --git a/ambassador/pom.xml b/ambassador/pom.xml index 6d6a9894d..0e5e208d5 100644 --- a/ambassador/pom.xml +++ b/ambassador/pom.xml @@ -20,7 +20,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 ambassador diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java index a80806851..e2877b683 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java @@ -62,7 +62,7 @@ public class RemoteService implements RemoteServiceInterface { * * @param value integer value to be multiplied. * @return if waitTime is less than {@link RemoteService#THRESHOLD}, it returns value * 10, - * otherwise {@link RemoteServiceInterface#FAILURE}. + * otherwise {@link RemoteServiceStatus#FAILURE}. */ @Override public long doRemoteFunction(int value) { @@ -74,6 +74,7 @@ public class RemoteService implements RemoteServiceInterface { } catch (InterruptedException e) { LOGGER.error("Thread sleep state interrupted", e); } - return waitTime <= THRESHOLD ? value * 10 : FAILURE; + return waitTime <= THRESHOLD ? value * 10 + : RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue(); } } diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java index 013015936..eadb981a2 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java @@ -27,7 +27,6 @@ package com.iluwatar.ambassador; * Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}). */ interface RemoteServiceInterface { - int FAILURE = -1; - long doRemoteFunction(int value) throws Exception; + long doRemoteFunction(int value); } diff --git a/command/src/main/java/com/iluwatar/command/InvisibilitySpell.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java similarity index 65% rename from command/src/main/java/com/iluwatar/command/InvisibilitySpell.java rename to ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java index 3e0f7bbf4..5d830e0fc 100644 --- a/command/src/main/java/com/iluwatar/command/InvisibilitySpell.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java @@ -21,37 +21,28 @@ * THE SOFTWARE. */ -package com.iluwatar.command; +package com.iluwatar.ambassador; /** - * InvisibilitySpell is a concrete command. + * Holds information regarding the status of the Remote Service. + * + *

This Enum replaces the integer value previously + * stored in {@link RemoteServiceInterface} as SonarCloud was identifying + * it as an issue. All test cases have been checked after changes, + * without failures.

*/ -public class InvisibilitySpell extends Command { - private Target target; +public enum RemoteServiceStatus { + FAILURE(-1) + ; - @Override - public void execute(Target target) { - target.setVisibility(Visibility.INVISIBLE); - this.target = target; + private final long remoteServiceStatusValue; + + RemoteServiceStatus(long remoteServiceStatusValue) { + this.remoteServiceStatusValue = remoteServiceStatusValue; } - @Override - public void undo() { - if (target != null) { - target.setVisibility(Visibility.VISIBLE); - } - } - - @Override - public void redo() { - if (target != null) { - target.setVisibility(Visibility.INVISIBLE); - } - } - - @Override - public String toString() { - return "Invisibility spell"; + public long getRemoteServiceStatusValue() { + return remoteServiceStatusValue; } } diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java b/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java index a9d34581c..57e444d88 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java @@ -23,6 +23,7 @@ package com.iluwatar.ambassador; +import static com.iluwatar.ambassador.RemoteServiceStatus.FAILURE; import static java.lang.Thread.sleep; import org.slf4j.Logger; @@ -58,14 +59,14 @@ public class ServiceAmbassador implements RemoteServiceInterface { private long safeCall(int value) { var retries = 0; - var result = (long) FAILURE; + var result = FAILURE.getRemoteServiceStatusValue(); for (int i = 0; i < RETRIES; i++) { if (retries >= RETRIES) { - return FAILURE; + return FAILURE.getRemoteServiceStatusValue(); } - if ((result = checkLatency(value)) == FAILURE) { + if ((result = checkLatency(value)) == FAILURE.getRemoteServiceStatusValue()) { LOGGER.info("Failed to reach remote: (" + (i + 1) + ")"); retries++; try { diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java index c9a4d09b6..186f13715 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java @@ -25,13 +25,23 @@ package com.iluwatar.ambassador; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ class AppTest { + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ + @Test - void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java index 12a93a1cb..bab319bee 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java @@ -37,6 +37,6 @@ class ClientTest { Client client = new Client(); var result = client.useService(10); - assertTrue(result == 100 || result == RemoteService.FAILURE); + assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue()); } } diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java index 3cfea2623..374a909f3 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java @@ -37,7 +37,7 @@ class RemoteServiceTest { void testFailedCall() { var remoteService = new RemoteService(new StaticRandomProvider(0.21)); var result = remoteService.doRemoteFunction(10); - assertEquals(RemoteServiceInterface.FAILURE, result); + assertEquals(RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue(), result); } @Test @@ -48,7 +48,7 @@ class RemoteServiceTest { } private static class StaticRandomProvider implements RandomProvider { - private double value; + private final double value; StaticRandomProvider(double value) { this.value = value; diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java index 8eb55b30a..48d128115 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java @@ -35,6 +35,6 @@ class ServiceAmbassadorTest { @Test void test() { long result = new ServiceAmbassador().doRemoteFunction(10); - assertTrue(result == 100 || result == RemoteServiceInterface.FAILURE); + assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue()); } } diff --git a/api-gateway/README.md b/api-gateway/README.md index 0d67b8d9b..66cdb20af 100644 --- a/api-gateway/README.md +++ b/api-gateway/README.md @@ -12,49 +12,53 @@ tags: ## Intent -Aggregate calls to microservices in a single location: the API Gateway. The user makes a single call to the API Gateway, -and the API Gateway then calls each relevant microservice. +Aggregate calls to microservices in a single location, the API Gateway. The user makes a single call +to the API Gateway, and the API Gateway then calls each relevant microservice. ## Explanation -With the Microservices pattern, a client may need data from multiple different microservices. If the client called each -microservice directly, that could contribute to longer load times, since the client would have to make a network request -for each microservice called. Moreover, having the client call each microservice directly ties the client to that -microservice - if the internal implementations of the microservices change (for example, if two microservices are -combined sometime in the future) or if the location (host and port) of a microservice changes, then every client that +With the Microservices pattern, a client may need data from multiple different microservices. If the +client called each microservice directly, that could contribute to longer load times, since the +client would have to make a network request for each microservice called. Moreover, having the +client call each microservice directly ties the client to that microservice - if the internal +implementations of the microservices change (for example, if two microservices are combined sometime +in the future) or if the location (host and port) of a microservice changes, then every client that makes use of those microservices must be updated. -The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway pattern, an additional -entity (the API Gateway) is placed between the client and the microservices. The job of the API Gateway is to aggregate -the calls to the microservices. Rather than the client calling each microservice individually, the client calls the -API Gateway a single time. The API Gateway then calls each of the microservices that the client needs. +The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway +pattern, an additional entity (the API Gateway) is placed between the client and the microservices. +The job of the API Gateway is to aggregate the calls to the microservices. Rather than the client +calling each microservice individually, the client calls the API Gateway a single time. The API +Gateway then calls each of the microservices that the client needs. Real world example -> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system the API Gateway makes -calls to the Image and Price microservices. +> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system +> the API Gateway makes calls to the Image and Price microservices. In plain words -> For a system implemented using microservices architecture, API Gateway is the single entry point that aggregates the -calls to the individual microservices. +> For a system implemented using microservices architecture, API Gateway is the single entry point +> that aggregates the calls to the individual microservices. Wikipedia says -> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling and security -policies, passes requests to the back-end service and then passes the response back to the requester. A gateway often -includes a transformation engine to orchestrate and modify the requests and responses on the fly. A gateway can also -provide functionality such as collecting analytics data and providing caching. The gateway can provide functionality to -support authentication, authorization, security, audit and regulatory compliance. +> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling +> and security policies, passes requests to the back-end service and then passes the response back +> to the requester. A gateway often includes a transformation engine to orchestrate and modify the +> requests and responses on the fly. A gateway can also provide functionality such as collecting +> analytics data and providing caching. The gateway can provide functionality to support +> authentication, authorization, security, audit and regulatory compliance. **Programmatic Example** -This implementation shows what the API Gateway pattern could look like for an e-commerce site. The `ApiGateway` makes -calls to the Image and Price microservices using the `ImageClientImpl` and `PriceClientImpl` respectively. Customers -viewing the site on a desktop device can see both price information and an image of a product, so the `ApiGateway` calls -both of the microservices and aggregates the data in the `DesktopProduct` model. However, mobile users only see price -information; they do not see a product image. For mobile users, the `ApiGateway` only retrieves price information, which -it uses to populate the `MobileProduct`. +This implementation shows what the API Gateway pattern could look like for an e-commerce site. The +`ApiGateway` makes calls to the Image and Price microservices using the `ImageClientImpl` and +`PriceClientImpl` respectively. Customers viewing the site on a desktop device can see both price +information and an image of a product, so the `ApiGateway` calls both of the microservices and +aggregates the data in the `DesktopProduct` model. However, mobile users only see price information; +they do not see a product image. For mobile users, the `ApiGateway` only retrieves price +information, which it uses to populate the `MobileProduct`. Here's the Image microservice implementation. @@ -64,7 +68,6 @@ public interface ImageClient { } public class ImageClientImpl implements ImageClient { - @Override public String getImagePath() { var httpClient = HttpClient.newHttpClient(); @@ -114,7 +117,7 @@ public class PriceClientImpl implements PriceClient { } ``` -And here we can see how API Gateway maps the requests to the microservices. +Here we can see how API Gateway maps the requests to the microservices. ```java public class ApiGateway { diff --git a/api-gateway/api-gateway-service/pom.xml b/api-gateway/api-gateway-service/pom.xml index 744023e90..c5ca117b6 100644 --- a/api-gateway/api-gateway-service/pom.xml +++ b/api-gateway/api-gateway-service/pom.xml @@ -29,7 +29,7 @@ api-gateway com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 api-gateway-service diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java index 52dd065ff..6fea815fc 100644 --- a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java +++ b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java @@ -23,11 +23,16 @@ package com.iluwatar.api.gateway; +import static org.slf4j.LoggerFactory.getLogger; + import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; + +import org.slf4j.Logger; import org.springframework.stereotype.Component; /** @@ -35,6 +40,8 @@ import org.springframework.stereotype.Component; */ @Component public class ImageClientImpl implements ImageClient { + private static final Logger LOGGER = getLogger(ImageClientImpl.class); + /** * Makes a simple HTTP Get request to the Image microservice. * @@ -49,12 +56,26 @@ public class ImageClientImpl implements ImageClient { .build(); try { + LOGGER.info("Sending request to fetch image path"); var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); + logResponse(httpResponse); return httpResponse.body(); } catch (IOException | InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Failure occurred while getting image path", e); } return null; } + + private void logResponse(HttpResponse httpResponse) { + if (isSuccessResponse(httpResponse.statusCode())) { + LOGGER.info("Image path received successfully"); + } else { + LOGGER.warn("Image path request failed"); + } + } + + private boolean isSuccessResponse(int responseCode) { + return responseCode >= 200 && responseCode <= 299; + } } diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java index 0dc44a51b..f773d0d54 100644 --- a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java +++ b/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java @@ -23,18 +23,26 @@ package com.iluwatar.api.gateway; +import static org.slf4j.LoggerFactory.getLogger; + import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; + +import org.slf4j.Logger; import org.springframework.stereotype.Component; + /** * An adapter to communicate with the Price microservice. */ @Component public class PriceClientImpl implements PriceClient { + private static final Logger LOGGER = getLogger(PriceClientImpl.class); + /** * Makes a simple HTTP Get request to the Price microservice. * @@ -49,12 +57,26 @@ public class PriceClientImpl implements PriceClient { .build(); try { + LOGGER.info("Sending request to fetch price info"); var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); + logResponse(httpResponse); return httpResponse.body(); } catch (IOException | InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Failure occurred while getting price info", e); } return null; } + + private void logResponse(HttpResponse httpResponse) { + if (isSuccessResponse(httpResponse.statusCode())) { + LOGGER.info("Price info received successfully"); + } else { + LOGGER.warn("Price info request failed"); + } + } + + private boolean isSuccessResponse(int responseCode) { + return responseCode >= 200 && responseCode <= 299; + } } diff --git a/api-gateway/image-microservice/pom.xml b/api-gateway/image-microservice/pom.xml index 27ef343af..821857449 100644 --- a/api-gateway/image-microservice/pom.xml +++ b/api-gateway/image-microservice/pom.xml @@ -29,7 +29,7 @@ api-gateway com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 image-microservice diff --git a/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java b/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java index b1f6dd3f7..a96ef4f41 100644 --- a/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java +++ b/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java @@ -23,15 +23,20 @@ package com.iluwatar.image.microservice; +import static org.slf4j.LoggerFactory.getLogger; + +import org.slf4j.Logger; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; + /** * Exposes the Image microservice's endpoints. */ @RestController public class ImageController { + private static final Logger LOGGER = getLogger(ImageController.class); /** * An endpoint for a user to retrieve an image path. @@ -40,6 +45,7 @@ public class ImageController { */ @RequestMapping(value = "/image-path", method = RequestMethod.GET) public String getImagePath() { + LOGGER.info("Successfully found image path"); return "/product-image.png"; } } diff --git a/api-gateway/pom.xml b/api-gateway/pom.xml index 63a986996..8ed569412 100644 --- a/api-gateway/pom.xml +++ b/api-gateway/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 api-gateway diff --git a/api-gateway/price-microservice/pom.xml b/api-gateway/price-microservice/pom.xml index e7b144c58..dc3591bf3 100644 --- a/api-gateway/price-microservice/pom.xml +++ b/api-gateway/price-microservice/pom.xml @@ -29,7 +29,7 @@ api-gateway com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java b/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java index cf2f5eb4f..dbcd59952 100644 --- a/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java +++ b/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java @@ -23,15 +23,20 @@ package com.iluwatar.price.microservice; +import static org.slf4j.LoggerFactory.getLogger; + +import org.slf4j.Logger; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; + /** * Exposes the Price microservice's endpoints. */ @RestController public class PriceController { + private static final Logger LOGGER = getLogger(PriceController.class); /** * An endpoint for a user to retrieve a product's price. @@ -40,6 +45,7 @@ public class PriceController { */ @RequestMapping(value = "/price", method = RequestMethod.GET) public String getPrice() { + LOGGER.info("Successfully found price info"); return "20"; } } diff --git a/arrange-act-assert/README.md b/arrange-act-assert/README.md index 02b7ee8b7..6b3cb4058 100644 --- a/arrange-act-assert/README.md +++ b/arrange-act-assert/README.md @@ -9,22 +9,137 @@ tags: --- ## Also known as + Given/When/Then ## Intent -The Arrange/Act/Assert (AAA) is a pattern for organizing unit tests. + +Arrange/Act/Assert (AAA) is a pattern for organizing unit tests. It breaks tests down into three clear and distinct steps: + 1. Arrange: Perform the setup and initialization required for the test. 2. Act: Take action(s) required for the test. 3. Assert: Verify the outcome(s) of the test. +## Explanation + +This pattern has several significant benefits. It creates a clear separation between a test's +setup, operations, and results. This structure makes the code easier to read and understand. If +you place the steps in order and format your code to separate them, you can scan a test and +quickly comprehend what it does. + +It also enforces a certain degree of discipline when you write your tests. You have to think +clearly about the three steps your test will perform. It makes tests more natural to write at +the same time since you already have an outline. + +Real world example + +> We need to write comprehensive and clear unit test suite for a class. + +In plain words + +> Arrange/Act/Assert is a testing pattern that organizes tests into three clear steps for easy +> maintenance. + +WikiWikiWeb says + +> Arrange/Act/Assert is a pattern for arranging and formatting code in UnitTest methods. + +**Programmatic Example** + +Let's first introduce our `Cash` class to be unit tested. + +```java +public class Cash { + + private int amount; + + Cash(int amount) { + this.amount = amount; + } + + void plus(int addend) { + amount += addend; + } + + boolean minus(int subtrahend) { + if (amount >= subtrahend) { + amount -= subtrahend; + return true; + } else { + return false; + } + } + + int count() { + return amount; + } +} +``` + +Then we write our unit tests according to Arrange/Act/Assert pattern. Notice the clearly +separated steps for each unit test. + +```java +public class CashAAATest { + + @Test + public void testPlus() { + //Arrange + var cash = new Cash(3); + //Act + cash.plus(4); + //Assert + assertEquals(7, cash.count()); + } + + @Test + public void testMinus() { + //Arrange + var cash = new Cash(8); + //Act + var result = cash.minus(5); + //Assert + assertTrue(result); + assertEquals(3, cash.count()); + } + + @Test + public void testInsufficientMinus() { + //Arrange + var cash = new Cash(1); + //Act + var result = cash.minus(6); + //Assert + assertFalse(result); + assertEquals(1, cash.count()); + } + + @Test + public void testUpdate() { + //Arrange + var cash = new Cash(5); + //Act + cash.plus(6); + var result = cash.minus(3); + //Assert + assertTrue(result); + assertEquals(8, cash.count()); + } +} +``` + ## Applicability + Use Arrange/Act/Assert pattern when -* you need to structure your unit tests so they're easier to read, maintain, and enhance. +* You need to structure your unit tests so that they're easier to read, maintain, and enhance. ## Credits * [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx) * [Bill Wake: 3A – Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/) * [Martin Fowler: GivenWhenThen](https://martinfowler.com/bliki/GivenWhenThen.html) +* [xUnit Test Patterns: Refactoring Test Code](https://www.amazon.com/gp/product/0131495054/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0131495054&linkId=99701e8f4af2f7e8dd50d720c9b63dbf) +* [Unit Testing Principles, Practices, and Patterns](https://www.amazon.com/gp/product/1617296279/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617296279&linkId=74c75cf22a63c3e4758ae08aa0a0cc35) +* [Test Driven Development: By Example](https://www.amazon.com/gp/product/0321146530/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321146530&linkId=5c63a93d8c1175b84ca5087472ef0e05) diff --git a/arrange-act-assert/pom.xml b/arrange-act-assert/pom.xml index bb0387e7a..4b0d57ebf 100644 --- a/arrange-act-assert/pom.xml +++ b/arrange-act-assert/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java index d7841843d..115dd3a11 100644 --- a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java +++ b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java @@ -60,7 +60,7 @@ public class CashAAATest { //Act cash.plus(4); //Assert - assertEquals(cash.count(), 7); + assertEquals(7, cash.count()); } @Test @@ -71,7 +71,7 @@ public class CashAAATest { var result = cash.minus(5); //Assert assertTrue(result); - assertEquals(cash.count(), 3); + assertEquals(3, cash.count()); } @Test @@ -82,7 +82,7 @@ public class CashAAATest { var result = cash.minus(6); //Assert assertFalse(result); - assertEquals(cash.count(), 1); + assertEquals(1, cash.count()); } @Test @@ -94,6 +94,6 @@ public class CashAAATest { var result = cash.minus(3); //Assert assertTrue(result); - assertEquals(cash.count(), 8); + assertEquals(8, cash.count()); } } diff --git a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java index 3f8c33d5e..564b923e7 100644 --- a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java +++ b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java @@ -44,16 +44,16 @@ public class CashAntiAAATest { var cash = new Cash(3); //test plus cash.plus(4); - assertEquals(cash.count(), 7); + assertEquals(7, cash.count()); //test minus cash = new Cash(8); assertTrue(cash.minus(5)); - assertEquals(cash.count(), 3); + assertEquals(3, cash.count()); assertFalse(cash.minus(6)); - assertEquals(cash.count(), 3); + assertEquals(3, cash.count()); //test update cash.plus(5); assertTrue(cash.minus(5)); - assertEquals(cash.count(), 3); + assertEquals(3, cash.count()); } } diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml index 46aa9d354..b4f7dd670 100644 --- a/async-method-invocation/pom.xml +++ b/async-method-invocation/pom.xml @@ -22,7 +22,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT async-method-invocation 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 7bdf84171..e430e9ce4 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 @@ -100,7 +100,7 @@ public class ThreadAsyncExecutor implements AsyncExecutor { void setValue(T value) { this.value = value; this.state = COMPLETED; - this.callback.ifPresent(ac -> ac.onComplete(value, Optional.empty())); + this.callback.ifPresent(ac -> ac.onComplete(value, Optional.empty())); synchronized (lock) { lock.notifyAll(); } diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java index 830e66a2d..5dfe901e8 100644 --- a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java @@ -25,12 +25,24 @@ package com.iluwatar.async.method.invocation; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ + @Test - void test() throws Exception { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); + } } diff --git a/balking/pom.xml b/balking/pom.xml index 964531692..0a86bc430 100644 --- a/balking/pom.xml +++ b/balking/pom.xml @@ -20,7 +20,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/balking/src/test/java/com/iluwatar/balking/AppTest.java b/balking/src/test/java/com/iluwatar/balking/AppTest.java index 8c75a1f62..b12b6e1ec 100644 --- a/balking/src/test/java/com/iluwatar/balking/AppTest.java +++ b/balking/src/test/java/com/iluwatar/balking/AppTest.java @@ -24,15 +24,26 @@ package com.iluwatar.balking; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ class AppTest { + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ + @Test - void main() { - App.main(); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow((Executable) App::main); } } \ No newline at end of file diff --git a/bridge/README.md b/bridge/README.md index 82c2f2735..12a6358f5 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -9,20 +9,26 @@ tags: --- ## Also known as + Handle/Body ## Intent + Decouple an abstraction from its implementation so that the two can vary independently. ## Explanation Real world example -> Consider you have a weapon with different enchantments and you are supposed to allow mixing different weapons with different enchantments. What would you do? Create multiple copies of each of the weapons for each of the enchantments or would you just create separate enchantment and set it for the weapon as needed? Bridge pattern allows you to do the second. +> Consider you have a weapon with different enchantments, and you are supposed to allow mixing +> different weapons with different enchantments. What would you do? Create multiple copies of each +> of the weapons for each of the enchantments or would you just create separate enchantment and set +> it for the weapon as needed? Bridge pattern allows you to do the second. In Plain Words -> Bridge pattern is about preferring composition over inheritance. Implementation details are pushed from a hierarchy to another object with a separate hierarchy. +> Bridge pattern is about preferring composition over inheritance. Implementation details are pushed +> from a hierarchy to another object with a separate hierarchy. Wikipedia says @@ -30,7 +36,7 @@ Wikipedia says **Programmatic Example** -Translating our weapon example from above. Here we have the `Weapon` hierarchy +Translating our weapon example from above. Here we have the `Weapon` hierarchy: ```java public interface Weapon { @@ -105,7 +111,7 @@ public class Hammer implements Weapon { } ``` -And the separate enchantment hierarchy +Here's the separate enchantment hierarchy: ```java public interface Enchantment { @@ -151,7 +157,7 @@ public class SoulEatingEnchantment implements Enchantment { } ``` -And both the hierarchies in action +Here are both hierarchies in action: ```java var enchantedSword = new Sword(new SoulEatingEnchantment()); @@ -178,18 +184,21 @@ hammer.unwield(); ``` ## Class diagram + ![alt text](./etc/bridge.urm.png "Bridge class diagram") ## 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 -* changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled. -* you have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies -* you want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien's String class, in which multiple objects can share the same string representation. +* You want to 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. +* Changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled. +* You have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies. +* You 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. ## Tutorial + * [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java) ## Credits diff --git a/bridge/pom.xml b/bridge/pom.xml index 0664bc9b5..41271814c 100644 --- a/bridge/pom.xml +++ b/bridge/pom.xml @@ -22,7 +22,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT bridge diff --git a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java index d3edbb27c..026f0954c 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java @@ -25,12 +25,22 @@ package com.iluwatar.bridge; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ + @Test - void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/builder/README.md b/builder/README.md index bb7426e35..008854ed7 100644 --- a/builder/README.md +++ b/builder/README.md @@ -9,36 +9,47 @@ tags: --- ## Intent -Separate the construction of a complex object from its -representation so that the same construction process can create different -representations. + +Separate the construction of a complex object from its representation so that the same construction +process can create different representations. ## Explanation Real world example -> Imagine a character generator for a role playing game. The easiest option is to let computer create the character for you. But if you want to select the character details like profession, gender, hair color etc. the character generation becomes a step-by-step process that completes when all the selections are ready. +> Imagine a character generator for a role-playing game. The easiest option is to let the computer +> create the character for you. If you want to manually select the character details like +> profession, gender, hair color etc. the character generation becomes a step-by-step process that +> completes when all the selections are ready. In plain words -> Allows you to create different flavors of an object while avoiding constructor pollution. Useful when there could be several flavors of an object. Or when there are a lot of steps involved in creation of an object. +> Allows you to create different flavors of an object while avoiding constructor pollution. Useful +> when there could be several flavors of an object. Or when there are a lot of steps involved in +> creation of an object. Wikipedia says -> The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern. +> The builder pattern is an object creation software design pattern with the intentions of finding +> a solution to the telescoping constructor anti-pattern. -Having said that let me add a bit about what telescoping constructor anti-pattern is. At one point or the other we have all seen a constructor like below: +Having said that let me add a bit about what telescoping constructor anti-pattern is. At one point +or the other, we have all seen a constructor like below: ```java public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { } ``` -As you can see the number of constructor parameters can quickly get out of hand and it might become difficult to understand the arrangement of parameters. Plus this parameter list could keep on growing if you would want to add more options in future. This is called telescoping constructor anti-pattern. +As you can see the number of constructor parameters can quickly get out of hand, and it may become +difficult to understand the arrangement of parameters. Plus this parameter list could keep on +growing if you would want to add more options in the future. This is called telescoping constructor +anti-pattern. **Programmatic Example** -The sane alternative is to use the Builder pattern. First of all we have our hero that we want to create +The sane alternative is to use the Builder pattern. First of all we have our hero that we want to +create: ```java public final class Hero { @@ -60,7 +71,7 @@ public final class Hero { } ``` -And then we have the builder +Then we have the builder: ```java public static class Builder { @@ -105,20 +116,22 @@ And then we have the builder } ``` -And then it can be used as: +Then it can be used as: ```java var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); ``` ## Class diagram + ![alt text](./etc/builder.urm.png "Builder class diagram") ## 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 +* 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 diff --git a/builder/pom.xml b/builder/pom.xml index dab9c66a7..6aa467f9e 100644 --- a/builder/pom.xml +++ b/builder/pom.xml @@ -22,7 +22,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT builder diff --git a/builder/src/test/java/com/iluwatar/builder/AppTest.java b/builder/src/test/java/com/iluwatar/builder/AppTest.java index 941f62f75..29b6ecb15 100644 --- a/builder/src/test/java/com/iluwatar/builder/AppTest.java +++ b/builder/src/test/java/com/iluwatar/builder/AppTest.java @@ -25,12 +25,23 @@ package com.iluwatar.builder; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ + @Test - void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/business-delegate/pom.xml b/business-delegate/pom.xml index 26987c73a..a46e856a9 100644 --- a/business-delegate/pom.xml +++ b/business-delegate/pom.xml @@ -22,7 +22,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT business-delegate diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java index dcf4ce6b2..2c13bc149 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java @@ -28,7 +28,7 @@ package com.iluwatar.business.delegate; */ public class Client { - private BusinessDelegate businessDelegate; + private final BusinessDelegate businessDelegate; public Client(BusinessDelegate businessDelegate) { this.businessDelegate = businessDelegate; diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java index 87fd1562d..c0f02b5e3 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java @@ -28,5 +28,5 @@ package com.iluwatar.business.delegate; */ public enum ServiceType { - EJB, JMS; + EJB, JMS } diff --git a/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java b/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java index 48e756acb..6c57e145e 100644 --- a/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java +++ b/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java @@ -27,13 +27,23 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Business Delegate example runs without errors. */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ + @Test - public void test() throws IOException { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/bytecode/pom.xml b/bytecode/pom.xml index f6be69cee..d21064be4 100644 --- a/bytecode/pom.xml +++ b/bytecode/pom.xml @@ -20,7 +20,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/App.java b/bytecode/src/main/java/com/iluwatar/bytecode/App.java index 04f473cee..b85d8ef05 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/App.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/App.java @@ -58,12 +58,14 @@ public class App { var vm = new VirtualMachine(); vm.getWizards()[0] = wizard; - interpretInstruction("LITERAL 0", vm); - interpretInstruction("LITERAL 0", vm); + String literal = "LITERAL 0"; + + interpretInstruction(literal, vm); + interpretInstruction(literal, vm); interpretInstruction("GET_HEALTH", vm); - interpretInstruction("LITERAL 0", vm); + interpretInstruction(literal, vm); interpretInstruction("GET_AGILITY", vm); - interpretInstruction("LITERAL 0", vm); + interpretInstruction(literal, vm); interpretInstruction("GET_WISDOM ", vm); interpretInstruction("ADD", vm); interpretInstruction("LITERAL 2", vm); diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java index 5afc2fb93..c45301c29 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java @@ -30,9 +30,9 @@ import java.util.Stack; */ public class VirtualMachine { - private Stack stack = new Stack<>(); + private final Stack stack = new Stack<>(); - private Wizard[] wizards = new Wizard[2]; + private final Wizard[] wizards = new Wizard[2]; /** * Constructor. diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java index 59962d39e..31060b683 100644 --- a/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java +++ b/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java @@ -25,13 +25,22 @@ package com.iluwatar.bytecode; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java index 61a316f5a..4518ca310 100644 --- a/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java +++ b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java @@ -104,7 +104,7 @@ public class VirtualMachineTest { bytecode[2] = LITERAL.getIntValue(); bytecode[3] = 50; // health amount bytecode[4] = SET_HEALTH.getIntValue(); - bytecode[5] = LITERAL.getIntValue();; + bytecode[5] = LITERAL.getIntValue(); bytecode[6] = wizardNumber; bytecode[7] = GET_HEALTH.getIntValue(); diff --git a/caching/pom.xml b/caching/pom.xml index 704dd78d0..627f76e98 100644 --- a/caching/pom.xml +++ b/caching/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT caching diff --git a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java index 6bc6dbd77..84b8307f3 100644 --- a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java +++ b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java @@ -29,7 +29,7 @@ package com.iluwatar.caching; public enum CachingPolicy { THROUGH("through"), AROUND("around"), BEHIND("behind"), ASIDE("aside"); - private String policy; + private final String policy; CachingPolicy(String policy) { this.policy = policy; diff --git a/caching/src/test/java/com/iluwatar/caching/AppTest.java b/caching/src/test/java/com/iluwatar/caching/AppTest.java index 831cfe493..20a03282d 100644 --- a/caching/src/test/java/com/iluwatar/caching/AppTest.java +++ b/caching/src/test/java/com/iluwatar/caching/AppTest.java @@ -27,12 +27,23 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Caching example runs without errors. */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/callback/README.md b/callback/README.md index 78766cc15..34543f0bb 100644 --- a/callback/README.md +++ b/callback/README.md @@ -9,14 +9,16 @@ tags: --- ## 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. + +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. ## Explanation Real world example -> We need to be notified after executing task has finished. We pass a callback method for the executor and wait for it to call back on us. +> We need to be notified after executing task has finished. We pass a callback method for +> the executor and wait for it to call back on us. In plain words @@ -24,7 +26,9 @@ In plain words Wikipedia says -> In computer programming, a callback, also known as a "call-after" function, is any executable code that is passed as an argument to other code; that other code is expected to call back (execute) the argument at a given time. +> In computer programming, a callback, also known as a "call-after" function, is any executable +> code that is passed as an argument to other code; that other code is expected to call +> back (execute) the argument at a given time. **Programmatic Example** @@ -61,7 +65,7 @@ public final class SimpleTask extends Task { } ``` -Finally here's how we execute a task and receive a callback when it's finished. +Finally, here's how we execute a task and receive a callback when it's finished. ```java var task = new SimpleTask(); @@ -69,13 +73,15 @@ Finally here's how we execute a task and receive a callback when it's finished. ``` ## Class diagram + ![alt text](./etc/callback.png "Callback") ## 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 -* [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. +* [CyclicBarrier](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html#CyclicBarrier%28int,%20java.lang.Runnable%29) constructor can accept a callback that will be triggered every time a barrier is tripped. diff --git a/callback/etc/callback.png b/callback/etc/callback.png index a81745871..7b499f79f 100644 Binary files a/callback/etc/callback.png and b/callback/etc/callback.png differ diff --git a/callback/etc/callback.urm.puml b/callback/etc/callback.urm.puml index a666a4fdb..2d213eda8 100644 --- a/callback/etc/callback.urm.puml +++ b/callback/etc/callback.urm.puml @@ -8,11 +8,6 @@ package com.iluwatar.callback { interface Callback { + call() {abstract} } - class LambdasApp { - - LOGGER : Logger {static} - - LambdasApp() - + main(args : String[]) {static} - } class SimpleTask { - LOGGER : Logger {static} + SimpleTask() diff --git a/callback/pom.xml b/callback/pom.xml index c156527f5..a74b653e1 100644 --- a/callback/pom.xml +++ b/callback/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT callback diff --git a/callback/src/main/java/com/iluwatar/callback/module-info.java b/callback/src/main/java/com/iluwatar/callback/module-info.java deleted file mode 100644 index 21a7a732b..000000000 --- a/callback/src/main/java/com/iluwatar/callback/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.callback { - requires org.slf4j; -} \ No newline at end of file diff --git a/callback/src/test/java/com/iluwatar/callback/AppTest.java b/callback/src/test/java/com/iluwatar/callback/AppTest.java index c1f466dee..3a70df7e0 100644 --- a/callback/src/test/java/com/iluwatar/callback/AppTest.java +++ b/callback/src/test/java/com/iluwatar/callback/AppTest.java @@ -25,12 +25,23 @@ package com.iluwatar.callback; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Callback example runs without errors. */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/chain/README.md b/chain/README.md index cdc5966bd..8b0508833 100644 --- a/chain/README.md +++ b/chain/README.md @@ -9,27 +9,32 @@ tags: --- ## 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. +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. ## Explanation Real world example -> The Orc King gives loud orders to his army. The closest one to react is the commander, then officer and then soldier. The commander, officer and soldier here form a chain of responsibility. +> The Orc King gives loud orders to his army. The closest one to react is the commander, then +> officer and then soldier. The commander, officer and soldier here form a chain of responsibility. In plain words -> It helps building a chain of objects. Request enters from one end and keeps going from object to object till it finds the suitable handler. +> It helps to build a chain of objects. A request enters from one end and keeps going from an object +> to another until it finds a suitable handler. Wikipedia says -> In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. +> In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of +> a source of command objects and a series of processing objects. Each processing object contains +> logic that defines the types of command objects that it can handle; the rest are passed to the +> next processing object in the chain. **Programmatic Example** -Translating our example with orcs from above. First we have the request class +Translating our example with the orcs from above. First we have the `Request` class: ```java public class Request { @@ -65,7 +70,7 @@ Then the request handler hierarchy ```java public abstract class RequestHandler { private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class); - private RequestHandler next; + private final RequestHandler next; public RequestHandler(RequestHandler next) { this.next = next; @@ -140,14 +145,16 @@ king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); // Orc so ``` ## Class diagram + ![alt text](./etc/chain.urm.png "Chain of Responsibility class diagram") ## 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 +* 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 diff --git a/chain/pom.xml b/chain/pom.xml index cf70ad1e8..9a7097e6d 100644 --- a/chain/pom.xml +++ b/chain/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT chain diff --git a/chain/src/main/java/com/iluwatar/chain/RequestHandler.java b/chain/src/main/java/com/iluwatar/chain/RequestHandler.java index 7923f03a6..4778ecf91 100644 --- a/chain/src/main/java/com/iluwatar/chain/RequestHandler.java +++ b/chain/src/main/java/com/iluwatar/chain/RequestHandler.java @@ -33,7 +33,7 @@ public abstract class RequestHandler { private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class); - private RequestHandler next; + private final RequestHandler next; public RequestHandler(RequestHandler next) { this.next = next; diff --git a/chain/src/main/java/com/iluwatar/chain/module-info.java b/chain/src/main/java/com/iluwatar/chain/module-info.java deleted file mode 100644 index 4f11ab327..000000000 --- a/chain/src/main/java/com/iluwatar/chain/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.chain { - requires org.slf4j; -} \ No newline at end of file diff --git a/chain/src/test/java/com/iluwatar/chain/AppTest.java b/chain/src/test/java/com/iluwatar/chain/AppTest.java index 164ff9bfe..6cd696517 100644 --- a/chain/src/test/java/com/iluwatar/chain/AppTest.java +++ b/chain/src/test/java/com/iluwatar/chain/AppTest.java @@ -25,13 +25,23 @@ package com.iluwatar.chain; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + * throws an exception. + */ @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/circuit-breaker/README.md b/circuit-breaker/README.md index ce280a570..f1792cb24 100644 --- a/circuit-breaker/README.md +++ b/circuit-breaker/README.md @@ -12,167 +12,304 @@ tags: ## Intent -Handle costly remote *procedure/service* calls in such a way that the failure of a **single** service/component cannot bring the whole application down, and we can reconnect to the service as soon as possible. +Handle costly remote service calls in such a way that the failure of a single service/component +cannot bring the whole application down, and we can reconnect to the service as soon as possible. ## Explanation Real world example -> Imagine a Web App that has both local (example: files and images) and remote (example: database entries) to serve. The database might not be responding due to a variety of reasons, so if the application keeps trying to read from the database using multiple threads/processes, soon all of them will hang and our entire web application will crash. We should be able to detect this situation and show the user an appropriate message so that he/she can explore other parts of the app unaffected by the database failure without any problem. +> Imagine a web application that has both local files/images and remote services that are used for +> fetching data. These remote services may be either healthy and responsive at times, or may become +> slow and unresponsive at some point of time due to variety of reasons. So if one of the remote +> services is slow or not responding successfully, our application will try to fetch response from +> the remote service using multiple threads/processes, soon all of them will hang (also called +> [thread starvation](https://en.wikipedia.org/wiki/Starvation_(computer_science))) causing our entire web application to crash. We should be able to detect +> this situation and show the user an appropriate message so that he/she can explore other parts of +> the app unaffected by the remote service failure. Meanwhile, the other services that are working +> normally, should keep functioning unaffected by this failure. In plain words -> Allows us to save resources when we know a remote service failed. Useful when all parts of our application are highly decoupled from each other, and failure of one component doesn't mean the other parts will stop working. +> Circuit Breaker allows graceful handling of failed remote services. It's especially useful when +> all parts of our application are highly decoupled from each other, and failure of one component +> doesn't mean the other parts will stop working. Wikipedia says -> **Circuit breaker** is a design pattern used in modern software development. It is used to detect failures and encapsulates the logic of preventing a failure from constantly recurring, during maintenance, temporary external system failure or unexpected system difficulties. - -So, how does this all come together? +> Circuit breaker is a design pattern used in modern software development. It is used to detect +> failures and encapsulates the logic of preventing a failure from constantly recurring, during +> maintenance, temporary external system failure or unexpected system difficulties. ## Programmatic Example -With the above example in mind we will imitate the functionality in a simple manner. We have two services: A *monitoring service* which will mimic the web app and will make both **local** and **remote** calls. + +So, how does this all come together? With the above example in mind we will imitate the +functionality in a simple example. A monitoring service mimics the web app and makes both local and +remote calls. The service architecture is as follows: ![alt text](./etc/ServiceDiagram.PNG "Service Diagram") -In terms of code, the End user application is: +In terms of code, the end user application is: ```java public class App { - + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); - + + /** + * Program entry point. + * + * @param args command line args + */ public static void main(String[] args) { - var obj = new MonitoringService(); - var circuitBreaker = new CircuitBreaker(3000, 1, 2000 * 1000 * 1000); + var serverStartTime = System.nanoTime(); - while (true) { - LOGGER.info(obj.localResourceResponse()); - LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime)); - LOGGER.info(circuitBreaker.getState()); - try { - Thread.sleep(5 * 1000); - } catch (InterruptedException e) { - LOGGER.error(e.getMessage()); - } + + var delayedService = new DelayedRemoteService(serverStartTime, 5); + var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2, + 2000 * 1000 * 1000); + + var quickService = new QuickRemoteService(); + var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2, + 2000 * 1000 * 1000); + + //Create an object of monitoring service which makes both local and remote calls + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, + quickServiceCircuitBreaker); + + //Fetch response from local resource + LOGGER.info(monitoringService.localResourceResponse()); + + //Fetch response from delayed service 2 times, to meet the failure threshold + LOGGER.info(monitoringService.delayedServiceResponse()); + LOGGER.info(monitoringService.delayedServiceResponse()); + + //Fetch current state of delayed service circuit breaker after crossing failure threshold limit + //which is OPEN now + LOGGER.info(delayedServiceCircuitBreaker.getState()); + + //Meanwhile, the delayed service is down, fetch response from the healthy quick service + LOGGER.info(monitoringService.quickServiceResponse()); + LOGGER.info(quickServiceCircuitBreaker.getState()); + + //Wait for the delayed service to become responsive + try { + LOGGER.info("Waiting for delayed service to become responsive"); + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); } + //Check the state of delayed circuit breaker, should be HALF_OPEN + LOGGER.info(delayedServiceCircuitBreaker.getState()); + + //Fetch response from delayed service, which should be healthy by now + LOGGER.info(monitoringService.delayedServiceResponse()); + //As successful response is fetched, it should be CLOSED again. + LOGGER.info(delayedServiceCircuitBreaker.getState()); } } ``` -The monitoring service is: +The monitoring service: -``` java +```java public class MonitoringService { + private final CircuitBreaker delayedService; + + private final CircuitBreaker quickService; + + public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) { + this.delayedService = delayedService; + this.quickService = quickService; + } + + //Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic public String localResourceResponse() { return "Local Service is working"; } - public String remoteResourceResponse(CircuitBreaker circuitBreaker, long serverStartTime) { + /** + * Fetch response from the delayed service (with some simulated startup time). + * + * @return response string + */ + public String delayedServiceResponse() { try { - return circuitBreaker.call("delayedService", serverStartTime); - } catch (Exception e) { + return this.delayedService.attemptRequest(); + } catch (RemoteServiceException e) { + return e.getMessage(); + } + } + + /** + * Fetches response from a healthy service without any failure. + * + * @return response string + */ + public String quickServiceResponse() { + try { + return this.quickService.attemptRequest(); + } catch (RemoteServiceException e) { return e.getMessage(); } } } ``` -As it can be seen, it does the call to get local resources directly, but it wraps the call to remote (costly) service in a circuit breaker object, which prevents faults as follows: +As it can be seen, it does the call to get local resources directly, but it wraps the call to +remote (costly) service in a circuit breaker object, which prevents faults as follows: ```java -public class CircuitBreaker { +public class DefaultCircuitBreaker implements CircuitBreaker { + private final long timeout; private final long retryTimePeriod; + private final RemoteService service; long lastFailureTime; + private String lastFailureResponse; int failureCount; private final int failureThreshold; private State state; private final long futureTime = 1000 * 1000 * 1000 * 1000; - CircuitBreaker(long timeout, int failureThreshold, long retryTimePeriod) { + /** + * Constructor to create an instance of Circuit Breaker. + * + * @param timeout Timeout for the API request. Not necessary for this simple example + * @param failureThreshold Number of failures we receive from the depended service before changing + * state to 'OPEN' + * @param retryTimePeriod Time period after which a new request is made to remote service for + * status check. + */ + DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold, + long retryTimePeriod) { + this.service = serviceToCall; + // We start in a closed state hoping that everything is fine this.state = State.CLOSED; this.failureThreshold = failureThreshold; + // Timeout for the API request. + // Used to break the calls made to remote resource if it exceeds the limit this.timeout = timeout; this.retryTimePeriod = retryTimePeriod; + //An absurd amount of time in future which basically indicates the last failure never happened this.lastFailureTime = System.nanoTime() + futureTime; this.failureCount = 0; } - - private void reset() { + + // Reset everything to defaults + @Override + public void recordSuccess() { this.failureCount = 0; - this.lastFailureTime = System.nanoTime() + futureTime; + this.lastFailureTime = System.nanoTime() + futureTime; this.state = State.CLOSED; } - private void recordFailure() { + @Override + public void recordFailure(String response) { failureCount = failureCount + 1; this.lastFailureTime = System.nanoTime(); + // Cache the failure response for returning on open state + this.lastFailureResponse = response; } - - protected void setState() { - if (failureCount > failureThreshold) { + + // Evaluate the current state based on failureThreshold, failureCount and lastFailureTime. + protected void evaluateState() { + if (failureCount >= failureThreshold) { //Then something is wrong with remote service if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) { + //We have waited long enough and should try checking if service is up state = State.HALF_OPEN; } else { + //Service would still probably be down state = State.OPEN; } } else { + //Everything is working fine state = State.CLOSED; } } - + + @Override public String getState() { + evaluateState(); return state.name(); } - - public void setStateForBypass(State state) { + + /** + * Break the circuit beforehand if it is known service is down Or connect the circuit manually if + * service comes online before expected. + * + * @param state State at which circuit is in + */ + @Override + public void setState(State state) { this.state = state; + switch (state) { + case OPEN: + this.failureCount = failureThreshold; + this.lastFailureTime = System.nanoTime(); + break; + case HALF_OPEN: + this.failureCount = failureThreshold; + this.lastFailureTime = System.nanoTime() - retryTimePeriod; + break; + default: + this.failureCount = 0; + } } - - public String call(String serviceToCall, long serverStartTime) throws Exception { - setState(); + + /** + * Executes service call. + * + * @return Value from the remote resource, stale response or a custom exception + */ + @Override + public String attemptRequest() throws RemoteServiceException { + evaluateState(); if (state == State.OPEN) { - return "This is stale response from API"; + // return cached response if the circuit is in OPEN state + return this.lastFailureResponse; } else { - if (serviceToCall.equals("delayedService")) { - var delayedService = new DelayedService(20); - var response = delayedService.response(serverStartTime); - if (response.split(" ")[3].equals("working")) { - reset(); - return response; - } else { - recordFailure(); - throw new Exception("Remote service not responding"); - } - } else { - throw new Exception("Unknown Service Name"); + // Make the API request if the circuit is not OPEN + try { + //In a real application, this would be run in a thread and the timeout + //parameter of the circuit breaker would be utilized to know if service + //is working. Here, we simulate that based on server response itself + var response = service.call(); + // Yay!! the API responded fine. Let's reset everything. + recordSuccess(); + return response; + } catch (RemoteServiceException ex) { + recordFailure(ex.getMessage()); + throw ex; } } } } ``` -How does the above pattern prevent failures? Let's understand via this finite state machine implemented by it. +How does the above pattern prevent failures? Let's understand via this finite state machine +implemented by it. ![alt text](./etc/StateDiagram.PNG "State Diagram") -- We initialize the Circuit Breaker object with certain parameters: **timeout**, **failureThreshold** and **retryTimePeriod** which help determine how resilient the API is. -- Initially, we are in the **closed** state and the remote call to API happens. +- We initialize the Circuit Breaker object with certain parameters: `timeout`, `failureThreshold` and `retryTimePeriod` which help determine how resilient the API is. +- Initially, we are in the `closed` state and nos remote calls to the API have occurred. - Every time the call succeeds, we reset the state to as it was in the beginning. -- If the number of failures cross a certain threshold, we move to the **open** state, which acts just like an open circuit and prevents remote service calls from being made, thus saving resources. (Here, we return the response called ```stale response from API```) -- Once we exceed the retry timeout period, we move to the **half-open** state and make another call to the remote service again to check if the service is working so that we can serve fresh content. A *failure* sets it back to **open** state and another attempt is made after retry timeout period, while a *success* sets it to **closed** state so that everything starts working normally again. +- If the number of failures cross a certain threshold, we move to the `open` state, which acts just like an open circuit and prevents remote service calls from being made, thus saving resources. (Here, we return the response called ```stale response from API```) +- Once we exceed the retry timeout period, we move to the `half-open` state and make another call to the remote service again to check if the service is working so that we can serve fresh content. A failure sets it back to `open` state and another attempt is made after retry timeout period, while a success sets it to `closed` state so that everything starts working normally again. ## Class diagram + ![alt text](./etc/circuit-breaker.urm.png "Circuit Breaker class diagram") ## Applicability + Use the Circuit Breaker pattern when - Building a fault-tolerant application where failure of some services shouldn't bring the entire application down. -- Building an continuously incremental/continuous delivery application, as some of it's components can be upgraded without shutting it down entirely. +- Building a continuously running (always-on) application, so that its components can be upgraded without shutting it down entirely. ## Related Patterns diff --git a/circuit-breaker/etc/ServiceDiagram.PNG b/circuit-breaker/etc/ServiceDiagram.PNG index 661f1a105..885320a4d 100644 Binary files a/circuit-breaker/etc/ServiceDiagram.PNG and b/circuit-breaker/etc/ServiceDiagram.PNG differ diff --git a/circuit-breaker/etc/circuit-breaker.urm.png b/circuit-breaker/etc/circuit-breaker.urm.png index 9278ce216..fc90318ec 100644 Binary files a/circuit-breaker/etc/circuit-breaker.urm.png and b/circuit-breaker/etc/circuit-breaker.urm.png differ diff --git a/circuit-breaker/etc/circuit-breaker.urm.puml b/circuit-breaker/etc/circuit-breaker.urm.puml index 214719002..951ec02d3 100644 --- a/circuit-breaker/etc/circuit-breaker.urm.puml +++ b/circuit-breaker/etc/circuit-breaker.urm.puml @@ -5,32 +5,52 @@ package com.iluwatar.circuitbreaker { + App() + main(args : String[]) {static} } - class CircuitBreaker { + interface CircuitBreaker { + + attemptRequest() : String {abstract} + + getState() : String {abstract} + + recordFailure(String) {abstract} + + recordSuccess() {abstract} + + setState(State) {abstract} + } + class DefaultCircuitBreaker { ~ failureCount : int - failureThreshold : int - futureTime : long + - lastFailureResponse : String ~ lastFailureTime : long - retryTimePeriod : long + - service : RemoteService - state : State - timeout : long - ~ CircuitBreaker(timeout : long, failureThreshold : int, retryTimePeriod : long) - + call(serviceToCall : String, serverStartTime : long) : String + ~ DefaultCircuitBreaker(serviceToCall : RemoteService, timeout : long, failureThreshold : int, retryTimePeriod : long) + + attemptRequest() : String + # evaluateState() + getState() : String - - recordFailure() - - reset() - # setState() - + setStateForBypass(state : State) + + recordFailure(response : String) + + recordSuccess() + + setState(state : State) } - class DelayedService { + class DelayedRemoteService { - delay : int - + DelayedService() - + DelayedService(delay : int) - + response(serverStartTime : long) : String + - serverStartTime : long + + DelayedRemoteService() + + DelayedRemoteService(serverStartTime : long, delay : int) + + call() : String } class MonitoringService { - + MonitoringService() + - delayedService : CircuitBreaker + - quickService : CircuitBreaker + + MonitoringService(delayedService : CircuitBreaker, quickService : CircuitBreaker) + + delayedServiceResponse() : String + localResourceResponse() : String - + remoteResourceResponse(circuitBreaker : CircuitBreaker, serverStartTime : long) : String + + quickServiceResponse() : String + } + class QuickRemoteService { + + QuickRemoteService() + + call() : String + } + interface RemoteService { + + call() : String {abstract} } enum State { + CLOSED {static} @@ -40,5 +60,10 @@ package com.iluwatar.circuitbreaker { + values() : State[] {static} } } -CircuitBreaker --> "-state" State +DefaultCircuitBreaker --> "-state" State +MonitoringService --> "-delayedService" CircuitBreaker +DefaultCircuitBreaker --> "-service" RemoteService +DefaultCircuitBreaker ..|> CircuitBreaker +DelayedRemoteService ..|> RemoteService +QuickRemoteService ..|> RemoteService @enduml \ No newline at end of file diff --git a/circuit-breaker/pom.xml b/circuit-breaker/pom.xml index fd9f85675..083c527fa 100644 --- a/circuit-breaker/pom.xml +++ b/circuit-breaker/pom.xml @@ -27,7 +27,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT circuit-breaker diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java index c3465d801..302930585 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java @@ -36,17 +36,18 @@ import org.slf4j.LoggerFactory; * operational again, so that we can use it *

*

- * In this example, the circuit breaker pattern is demonstrated by using two services: {@link - * MonitoringService} and {@link DelayedService}. The monitoring service is responsible for calling - * two services: a local service and a remote service {@link DelayedService} , and by using the - * circuit breaker construction we ensure that if the call to remote service is going to fail, we - * are going to save our resources and not make the function call at all, by wrapping our call to - * the remote service in the circuit breaker object. + * In this example, the circuit breaker pattern is demonstrated by using three services: {@link + * DelayedRemoteService}, {@link QuickRemoteService} and {@link MonitoringService}. The monitoring + * service is responsible for calling three services: a local service, a quick remove service + * {@link QuickRemoteService} and a delayed remote service {@link DelayedRemoteService} , and by + * using the circuit breaker construction we ensure that if the call to remote service is going to + * fail, we are going to save our resources and not make the function call at all, by wrapping our + * call to the remote services in the {@link DefaultCircuitBreaker} implementation object. *

*

- * This works as follows: The {@link CircuitBreaker} object can be in one of three states: - * Open, Closed and Half-Open, which represents the real world circuits. If the - * state is closed (initial), we assume everything is alright and perform the function call. + * This works as follows: The {@link DefaultCircuitBreaker} object can be in one of three states: + * Open, Closed and Half-Open, which represents the real world circuits. If + * the state is closed (initial), we assume everything is alright and perform the function call. * However, every time the call fails, we note it and once it crosses a threshold, we set the state * to Open, preventing any further calls to the remote server. Then, after a certain retry period * (during which we expect thee service to recover), we make another call to the remote server and @@ -63,22 +64,50 @@ public class App { * * @param args command line args */ - @SuppressWarnings("squid:S2189") public static void main(String[] args) { - //Create an object of monitoring service which makes both local and remote calls - var obj = new MonitoringService(); - //Set the circuit Breaker parameters - var circuitBreaker = new CircuitBreaker(3000, 1, 2000 * 1000 * 1000); + var serverStartTime = System.nanoTime(); - while (true) { - LOGGER.info(obj.localResourceResponse()); - LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime)); - LOGGER.info(circuitBreaker.getState()); - try { - Thread.sleep(5 * 1000); - } catch (InterruptedException e) { - LOGGER.error(e.getMessage()); - } + + var delayedService = new DelayedRemoteService(serverStartTime, 5); + var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2, + 2000 * 1000 * 1000); + + var quickService = new QuickRemoteService(); + var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2, + 2000 * 1000 * 1000); + + //Create an object of monitoring service which makes both local and remote calls + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, + quickServiceCircuitBreaker); + + //Fetch response from local resource + LOGGER.info(monitoringService.localResourceResponse()); + + //Fetch response from delayed service 2 times, to meet the failure threshold + LOGGER.info(monitoringService.delayedServiceResponse()); + LOGGER.info(monitoringService.delayedServiceResponse()); + + //Fetch current state of delayed service circuit breaker after crossing failure threshold limit + //which is OPEN now + LOGGER.info(delayedServiceCircuitBreaker.getState()); + + //Meanwhile, the delayed service is down, fetch response from the healthy quick service + LOGGER.info(monitoringService.quickServiceResponse()); + LOGGER.info(quickServiceCircuitBreaker.getState()); + + //Wait for the delayed service to become responsive + try { + LOGGER.info("Waiting for delayed service to become responsive"); + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); } + //Check the state of delayed circuit breaker, should be HALF_OPEN + LOGGER.info(delayedServiceCircuitBreaker.getState()); + + //Fetch response from delayed service, which should be healthy by now + LOGGER.info(monitoringService.delayedServiceResponse()); + //As successful response is fetched, it should be CLOSED again. + LOGGER.info(delayedServiceCircuitBreaker.getState()); } } diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java index 18268b1ce..92884258b 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java @@ -24,114 +24,22 @@ package com.iluwatar.circuitbreaker; /** - * The circuit breaker class with all configurations. + * The Circuit breaker interface. */ -public class CircuitBreaker { - private final long timeout; - private final long retryTimePeriod; - long lastFailureTime; - int failureCount; - private final int failureThreshold; - private State state; - private final long futureTime = 1000 * 1000 * 1000 * 1000; +public interface CircuitBreaker { - /** - * Constructor to create an instance of Circuit Breaker. - * - * @param timeout Timeout for the API request. Not necessary for this simple example - * @param failureThreshold Number of failures we receive from the depended service before changing - * state to 'OPEN' - * @param retryTimePeriod Time period after which a new request is made to remote service for - * status check. - */ - CircuitBreaker(long timeout, int failureThreshold, long retryTimePeriod) { - // We start in a closed state hoping that everything is fine - this.state = State.CLOSED; - this.failureThreshold = failureThreshold; - // Timeout for the API request. - // Used to break the calls made to remote resource if it exceeds the limit - this.timeout = timeout; - this.retryTimePeriod = retryTimePeriod; - //An absurd amount of time in future which basically indicates the last failure never happened - this.lastFailureTime = System.nanoTime() + futureTime; - this.failureCount = 0; - } + // Success response. Reset everything to defaults + void recordSuccess(); - //Reset everything to defaults - private void reset() { - this.failureCount = 0; - this.lastFailureTime = System.nanoTime() + futureTime; - this.state = State.CLOSED; - } + // Failure response. Handle accordingly with response and change state if required. + void recordFailure(String response); - private void recordFailure() { - failureCount = failureCount + 1; - this.lastFailureTime = System.nanoTime(); - } + // Get the current state of circuit breaker + String getState(); - protected void setState() { - if (failureCount > failureThreshold) { //Then something is wrong with remote service - if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) { - //We have waited long enough and should try checking if service is up - state = State.HALF_OPEN; - } else { - //Service would still probably be down - state = State.OPEN; - } - } else { - //Everything is working fine - state = State.CLOSED; - } - } + // Set the specific state manually. + void setState(State state); - public String getState() { - return state.name(); - } - - /** - * Break the circuit beforehand if it is known service is down Or connect the circuit manually if - * service comes online before expected. - * - * @param state State at which circuit is in - */ - public void setStateForBypass(State state) { - this.state = state; - } - - /** - * Executes service call. - * - * @param serviceToCall The name of the service in String. Can be changed to data URLs in case - * of web applications - * @param serverStartTime Time at which actual server was started which makes calls to this - * service - * @return Value from the remote resource, stale response or a custom exception - */ - public String call(String serviceToCall, long serverStartTime) throws Exception { - setState(); - if (state == State.OPEN) { - // return cached response if no the circuit is in OPEN state - return "This is stale response from API"; - } else { - // Make the API request if the circuit is not OPEN - if (serviceToCall.equals("delayedService")) { - var delayedService = new DelayedService(20); - var response = delayedService.response(serverStartTime); - //In a real application, this would be run in a thread and the timeout - //parameter of the circuit breaker would be utilized to know if service - //is working. Here, we simulate that based on server response itself - if (response.split(" ")[3].equals("working")) { - // Yay!! the API responded fine. Let's reset everything. - reset(); - return response; - } else { - // Uh-oh!! the call still failed. Let's update that in our records. - recordFailure(); - throw new Exception("Remote service not responding"); - } - } else { - throw new Exception("Unknown Service Name"); - } - } - } -} \ No newline at end of file + // Attempt to fetch response from the remote service. + String attemptRequest() throws RemoteServiceException; +} diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java new file mode 100644 index 000000000..7e45f5709 --- /dev/null +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java @@ -0,0 +1,155 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +/** + * The delay based Circuit breaker implementation that works in a + * CLOSED->OPEN-(retry_time_period)->HALF_OPEN->CLOSED flow with some retry time period for failed + * services and a failure threshold for service to open circuit. + */ +public class DefaultCircuitBreaker implements CircuitBreaker { + + private final long timeout; + private final long retryTimePeriod; + private final RemoteService service; + long lastFailureTime; + private String lastFailureResponse; + int failureCount; + private final int failureThreshold; + private State state; + private final long futureTime = 1000 * 1000 * 1000 * 1000; + + /** + * Constructor to create an instance of Circuit Breaker. + * + * @param timeout Timeout for the API request. Not necessary for this simple example + * @param failureThreshold Number of failures we receive from the depended service before changing + * state to 'OPEN' + * @param retryTimePeriod Time period after which a new request is made to remote service for + * status check. + */ + DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold, + long retryTimePeriod) { + this.service = serviceToCall; + // We start in a closed state hoping that everything is fine + this.state = State.CLOSED; + this.failureThreshold = failureThreshold; + // Timeout for the API request. + // Used to break the calls made to remote resource if it exceeds the limit + this.timeout = timeout; + this.retryTimePeriod = retryTimePeriod; + //An absurd amount of time in future which basically indicates the last failure never happened + this.lastFailureTime = System.nanoTime() + futureTime; + this.failureCount = 0; + } + + // Reset everything to defaults + @Override + public void recordSuccess() { + this.failureCount = 0; + this.lastFailureTime = System.nanoTime() + futureTime; + this.state = State.CLOSED; + } + + @Override + public void recordFailure(String response) { + failureCount = failureCount + 1; + this.lastFailureTime = System.nanoTime(); + // Cache the failure response for returning on open state + this.lastFailureResponse = response; + } + + // Evaluate the current state based on failureThreshold, failureCount and lastFailureTime. + protected void evaluateState() { + if (failureCount >= failureThreshold) { //Then something is wrong with remote service + if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) { + //We have waited long enough and should try checking if service is up + state = State.HALF_OPEN; + } else { + //Service would still probably be down + state = State.OPEN; + } + } else { + //Everything is working fine + state = State.CLOSED; + } + } + + @Override + public String getState() { + evaluateState(); + return state.name(); + } + + /** + * Break the circuit beforehand if it is known service is down Or connect the circuit manually if + * service comes online before expected. + * + * @param state State at which circuit is in + */ + @Override + public void setState(State state) { + this.state = state; + switch (state) { + case OPEN: + this.failureCount = failureThreshold; + this.lastFailureTime = System.nanoTime(); + break; + case HALF_OPEN: + this.failureCount = failureThreshold; + this.lastFailureTime = System.nanoTime() - retryTimePeriod; + break; + default: + this.failureCount = 0; + } + } + + /** + * Executes service call. + * + * @return Value from the remote resource, stale response or a custom exception + */ + @Override + public String attemptRequest() throws RemoteServiceException { + evaluateState(); + if (state == State.OPEN) { + // return cached response if the circuit is in OPEN state + return this.lastFailureResponse; + } else { + // Make the API request if the circuit is not OPEN + try { + //In a real application, this would be run in a thread and the timeout + //parameter of the circuit breaker would be utilized to know if service + //is working. Here, we simulate that based on server response itself + var response = service.call(); + // Yay!! the API responded fine. Let's reset everything. + recordSuccess(); + return response; + } catch (RemoteServiceException ex) { + recordFailure(ex.getMessage()); + throw ex; + } + } + } +} diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java similarity index 81% rename from circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedService.java rename to circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java index 13861923b..f4add64fc 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedService.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java @@ -27,7 +27,9 @@ package com.iluwatar.circuitbreaker; * This simulates the remote service It responds only after a certain timeout period (default set to * 20 seconds). */ -public class DelayedService { +public class DelayedRemoteService implements RemoteService { + + private final long serverStartTime; private final int delay; /** @@ -35,22 +37,23 @@ public class DelayedService { * * @param delay the delay after which service would behave properly, in seconds */ - public DelayedService(int delay) { + public DelayedRemoteService(long serverStartTime, int delay) { + this.serverStartTime = serverStartTime; this.delay = delay; } - public DelayedService() { - this.delay = 60; + public DelayedRemoteService() { + this.serverStartTime = System.nanoTime(); + this.delay = 20; } /** * Responds based on delay, current time and server start time if the service is down / working. * - * @param serverStartTime Time at which actual server was started which makes calls to this - * service * @return The state of the service */ - public String response(long serverStartTime) { + @Override + public String call() throws RemoteServiceException { var currentTime = System.nanoTime(); //Since currentTime and serverStartTime are both in nanoseconds, we convert it to //seconds by diving by 10e9 and ensure floating point division by multiplying it @@ -58,9 +61,8 @@ public class DelayedService { //send the reply if ((currentTime - serverStartTime) * 1.0 / (1000 * 1000 * 1000) < delay) { //Can use Thread.sleep() here to block and simulate a hung server - return "Delayed service is down"; - } else { - return "Delayed service is working"; + throw new RemoteServiceException("Delayed service is down"); } + return "Delayed service is working"; } } diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java index e91367175..3fb8d8396 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java @@ -24,28 +24,47 @@ package com.iluwatar.circuitbreaker; /** - * The service class which makes local and remote calls Uses {@link CircuitBreaker} object to ensure - * remote calls don't use up resources. + * The service class which makes local and remote calls Uses {@link DefaultCircuitBreaker} object to + * ensure remote calls don't use up resources. */ public class MonitoringService { + private final CircuitBreaker delayedService; + + private final CircuitBreaker quickService; + + public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) { + this.delayedService = delayedService; + this.quickService = quickService; + } + //Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic public String localResourceResponse() { return "Local Service is working"; } /** - * Try to get result from remote server. + * Fetch response from the delayed service (with some simulated startup time). * - * @param circuitBreaker The circuitBreaker object with all parameters - * @param serverStartTime Time at which actual server was started which makes calls to this - * service - * @return result from the remote response or exception raised by it. + * @return response string */ - public String remoteResourceResponse(CircuitBreaker circuitBreaker, long serverStartTime) { + public String delayedServiceResponse() { try { - return circuitBreaker.call("delayedService", serverStartTime); - } catch (Exception e) { + return this.delayedService.attemptRequest(); + } catch (RemoteServiceException e) { + return e.getMessage(); + } + } + + /** + * Fetches response from a healthy service without any failure. + * + * @return response string + */ + public String quickServiceResponse() { + try { + return this.quickService.attemptRequest(); + } catch (RemoteServiceException e) { return e.getMessage(); } } diff --git a/command/src/main/java/com/iluwatar/command/Command.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java similarity index 80% rename from command/src/main/java/com/iluwatar/command/Command.java rename to circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java index 85deff74e..1b73bc0e3 100644 --- a/command/src/main/java/com/iluwatar/command/Command.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java @@ -1,40 +1,35 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.command; - -/** - * Interface for Commands. - */ -public abstract class Command { - - public abstract void execute(Target target); - - public abstract void undo(); - - public abstract void redo(); - - @Override - public abstract String toString(); - -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +/** + * A quick response remote service, that responds healthy without any delay or failure. + */ +public class QuickRemoteService implements RemoteService { + + @Override + public String call() throws RemoteServiceException { + return "Quick Service is working"; + } +} diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java new file mode 100644 index 000000000..3c2fd9c78 --- /dev/null +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java @@ -0,0 +1,34 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +/** + * The Remote service interface, used by {@link CircuitBreaker} for fetching response from remote + * services. + */ +public interface RemoteService { + + //Fetch response from remote service. + String call() throws RemoteServiceException; +} diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java new file mode 100644 index 000000000..b34f0b3fe --- /dev/null +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java @@ -0,0 +1,34 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +/** + * Exception thrown when {@link RemoteService} does not respond successfully. + */ +public class RemoteServiceException extends Exception { + + public RemoteServiceException(String message) { + super(message); + } +} diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java new file mode 100644 index 000000000..d28d316c8 --- /dev/null +++ b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java @@ -0,0 +1,135 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * App Test showing usage of circuit breaker. + */ +public class AppTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppTest.class); + + //Startup delay for delayed service (in seconds) + private static final int STARTUP_DELAY = 4; + + //Number of failed requests for circuit breaker to open + private static final int FAILURE_THRESHOLD = 1; + + //Time period in seconds for circuit breaker to retry service + private static final int RETRY_PERIOD = 2; + + private MonitoringService monitoringService; + + private CircuitBreaker delayedServiceCircuitBreaker; + + private CircuitBreaker quickServiceCircuitBreaker; + + /** + * Setup the circuit breakers and services, where {@link DelayedRemoteService} will be start with + * a delay of 4 seconds and a {@link QuickRemoteService} responding healthy. Both services are + * wrapped in a {@link DefaultCircuitBreaker} implementation with failure threshold of 1 failure + * and retry time period of 2 seconds. + */ + @BeforeEach + public void setupCircuitBreakers() { + var delayedService = new DelayedRemoteService(System.nanoTime(), STARTUP_DELAY); + //Set the circuit Breaker parameters + delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, + FAILURE_THRESHOLD, + RETRY_PERIOD * 1000 * 1000 * 1000); + + var quickService = new QuickRemoteService(); + //Set the circuit Breaker parameters + quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, FAILURE_THRESHOLD, + RETRY_PERIOD * 1000 * 1000 * 1000); + + monitoringService = new MonitoringService(delayedServiceCircuitBreaker, + quickServiceCircuitBreaker); + + } + + @Test + public void testFailure_OpenStateTransition() { + //Calling delayed service, which will be unhealthy till 4 seconds + assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); + //As failure threshold is "1", the circuit breaker is changed to OPEN + assertEquals("OPEN", delayedServiceCircuitBreaker.getState()); + //As circuit state is OPEN, we expect a quick fallback response from circuit breaker. + assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); + + //Meanwhile, the quick service is responding and the circuit state is CLOSED + assertEquals("Quick Service is working", monitoringService.quickServiceResponse()); + assertEquals("CLOSED", quickServiceCircuitBreaker.getState()); + + } + + @Test + public void testFailure_HalfOpenStateTransition() { + //Calling delayed service, which will be unhealthy till 4 seconds + assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); + //As failure threshold is "1", the circuit breaker is changed to OPEN + assertEquals("OPEN", delayedServiceCircuitBreaker.getState()); + + //Waiting for recovery period of 2 seconds for circuit breaker to retry service. + try { + LOGGER.info("Waiting 2s for delayed service to become responsive"); + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + //After 2 seconds, the circuit breaker should move to "HALF_OPEN" state and retry fetching response from service again + assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState()); + + } + + @Test + public void testRecovery_ClosedStateTransition() { + //Calling delayed service, which will be unhealthy till 4 seconds + assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); + //As failure threshold is "1", the circuit breaker is changed to OPEN + assertEquals("OPEN", delayedServiceCircuitBreaker.getState()); + + //Waiting for 4 seconds, which is enough for DelayedService to become healthy and respond successfully. + try { + LOGGER.info("Waiting 4s for delayed service to become responsive"); + Thread.sleep(4000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + //As retry period is 2 seconds (<4 seconds of wait), hence the circuit breaker should be back in HALF_OPEN state. + assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState()); + //Check the success response from delayed service. + assertEquals("Delayed service is working", monitoringService.delayedServiceResponse()); + //As the response is success, the state should be CLOSED + assertEquals("CLOSED", delayedServiceCircuitBreaker.getState()); + } + +} diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/CircuitBreakerTest.java b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java similarity index 70% rename from circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/CircuitBreakerTest.java rename to circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java index 98b59a6ae..4d300b36f 100644 --- a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/CircuitBreakerTest.java +++ b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java @@ -25,56 +25,60 @@ package com.iluwatar.circuitbreaker; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.rmi.Remote; import org.junit.jupiter.api.Test; /** * Circuit Breaker test */ -public class CircuitBreakerTest { +public class DefaultCircuitBreakerTest { //long timeout, int failureThreshold, long retryTimePeriod @Test - public void testSetState() { - var circuitBreaker = new CircuitBreaker(1, 1, 100); + public void testEvaluateState() { + var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 100); //Right now, failureCountfailureThreshold, and lastFailureTime is nearly equal to current time, //state should be half-open assertEquals(circuitBreaker.getState(), "HALF_OPEN"); //Since failureCount>failureThreshold, and lastFailureTime is much lesser current time, //state should be open circuitBreaker.lastFailureTime = System.nanoTime() - 1000 * 1000 * 1000 * 1000; - circuitBreaker.setState(); + circuitBreaker.evaluateState(); assertEquals(circuitBreaker.getState(), "OPEN"); //Now set it back again to closed to test idempotency circuitBreaker.failureCount = 0; - circuitBreaker.setState(); + circuitBreaker.evaluateState(); assertEquals(circuitBreaker.getState(), "CLOSED"); } @Test public void testSetStateForBypass() { - var circuitBreaker = new CircuitBreaker(1, 1, 100); + var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 2000 * 1000 * 1000); //Right now, failureCount { + var obj = new DelayedRemoteService(); + obj.call(); + }); + } + + /** + * Testing server started in past (2 seconds ago) and with a simulated delay of 1 second. + * + * @throws RemoteServiceException + */ + @Test + public void testParameterizedConstructor() throws RemoteServiceException { + var obj = new DelayedRemoteService(System.nanoTime()-2000*1000*1000,1); + assertEquals("Delayed service is working",obj.call()); + } +} diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java index 71ede3e1c..f6b802b96 100644 --- a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java +++ b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java @@ -35,28 +35,45 @@ public class MonitoringServiceTest { //long timeout, int failureThreshold, long retryTimePeriod @Test public void testLocalResponse() { - var monitoringService = new MonitoringService(); + var monitoringService = new MonitoringService(null,null); var response = monitoringService.localResourceResponse(); assertEquals(response, "Local Service is working"); } @Test - public void testRemoteResponse() { - var monitoringService = new MonitoringService(); - var circuitBreaker = new CircuitBreaker(1, 1, 100); + public void testDelayedRemoteResponseSuccess() { + var delayedService = new DelayedRemoteService(System.nanoTime()-2*1000*1000*1000, 2); + var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, + 1, + 2 * 1000 * 1000 * 1000); + + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null); //Set time in past to make the server work - var serverStartTime = System.nanoTime() / 10; - var response = monitoringService.remoteResourceResponse(circuitBreaker, serverStartTime); + var response = monitoringService.delayedServiceResponse(); assertEquals(response, "Delayed service is working"); } @Test - public void testRemoteResponse2() { - var monitoringService = new MonitoringService(); - var circuitBreaker = new CircuitBreaker(1, 1, 100); + public void testDelayedRemoteResponseFailure() { + var delayedService = new DelayedRemoteService(System.nanoTime(), 2); + var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, + 1, + 2 * 1000 * 1000 * 1000); + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null); //Set time as current time as initially server fails - var serverStartTime = System.nanoTime(); - var response = monitoringService.remoteResourceResponse(circuitBreaker, serverStartTime); - assertEquals(response, "Remote service not responding"); + var response = monitoringService.delayedServiceResponse(); + assertEquals(response, "Delayed service is down"); + } + + @Test + public void testQuickRemoteServiceResponse() { + var delayedService = new QuickRemoteService(); + var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, + 1, + 2 * 1000 * 1000 * 1000); + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null); + //Set time as current time as initially server fails + var response = monitoringService.delayedServiceResponse(); + assertEquals(response, "Quick Service is working"); } } diff --git a/collection-pipeline/pom.xml b/collection-pipeline/pom.xml index 6d8d467ad..08c41880b 100644 --- a/collection-pipeline/pom.xml +++ b/collection-pipeline/pom.xml @@ -27,7 +27,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT collection-pipeline diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java index 2828cffd4..cffdc7c82 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java @@ -87,10 +87,7 @@ public class Car { } else if (!model.equals(other.model)) { return false; } - if (year != other.year) { - return false; - } - return true; + return year == other.year; } public String getMake() { diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java index 2e564b701..3e25f6993 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java @@ -29,7 +29,7 @@ import java.util.List; * A Person class that has the list of cars that the person owns and use. */ public class Person { - private List cars; + private final List cars; /** * Constructor to create an instance of person. diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/module-info.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/module-info.java deleted file mode 100644 index f8bd30a68..000000000 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.collectionpipeline { - requires org.slf4j; -} \ No newline at end of file diff --git a/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java b/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java index 6bf373e81..cedc492b9 100644 --- a/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java +++ b/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java @@ -37,7 +37,7 @@ import org.slf4j.LoggerFactory; public class AppTest { private static final Logger LOGGER = LoggerFactory.getLogger(AppTest.class); - private List cars = CarFactory.createCars(); + private final List cars = CarFactory.createCars(); @Test public void testGetModelsAfter2000UsingFor() { diff --git a/combinator/pom.xml b/combinator/pom.xml index 3edfa7580..4886873cd 100644 --- a/combinator/pom.xml +++ b/combinator/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT combinator @@ -39,6 +39,12 @@ junit test + + + org.junit.jupiter + junit-jupiter-engine + test + diff --git a/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java b/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java index f42b46c14..6e7b4f63f 100644 --- a/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java +++ b/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java @@ -25,12 +25,19 @@ package com.iluwatar.combinator; import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; public class CombinatorAppTest { + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link CombinatorApp#main(String[])} + * throws an exception. + */ + @Test - public void main() { - CombinatorApp.main(new String[]{}); + public void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> CombinatorApp.main(new String[]{})); } } \ No newline at end of file diff --git a/command/README.md b/command/README.md index 02a290e4d..08783679b 100644 --- a/command/README.md +++ b/command/README.md @@ -9,15 +9,20 @@ tags: --- ## Also known as + Action, Transaction ## Intent -Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. + +Encapsulate a request as an object, thereby letting you parameterize clients with different +requests, queue or log requests, and support undoable operations. ## Explanation Real world example -> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one. The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses the spells one by one. Each spell here is a command object that can be undone. +> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one. +> The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses +> the spells one by one. Each spell here is a command object that can be undone. In plain words @@ -25,34 +30,32 @@ In plain words Wikipedia says -> In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. +> In object-oriented programming, the command pattern is a behavioral design pattern in which an +> object is used to encapsulate all information needed to perform an action or trigger an event at +> a later time. **Programmatic Example** -Here's the sample code with wizard and goblin. Let's start from the wizard class. +Here's the sample code with wizard and goblin. Let's start from the `Wizard` class. ```java public class Wizard { - private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class); - - private Deque undoStack = new LinkedList<>(); - private Deque redoStack = new LinkedList<>(); + private final Deque undoStack = new LinkedList<>(); + private final Deque redoStack = new LinkedList<>(); public Wizard() {} - public void castSpell(Command command, Target target) { - LOGGER.info("{} casts {} at {}", this, command, target); - command.execute(target); - undoStack.offerLast(command); + public void castSpell(Runnable runnable) { + runnable.run(); + undoStack.offerLast(runnable); } public void undoLastSpell() { if (!undoStack.isEmpty()) { var previousSpell = undoStack.pollLast(); redoStack.offerLast(previousSpell); - LOGGER.info("{} undoes {}", this, previousSpell); - previousSpell.undo(); + previousSpell.run(); } } @@ -60,8 +63,7 @@ public class Wizard { if (!redoStack.isEmpty()) { var previousSpell = redoStack.pollLast(); undoStack.offerLast(previousSpell); - LOGGER.info("{} redoes {}", this, previousSpell); - previousSpell.redo(); + previousSpell.run(); } } @@ -72,85 +74,7 @@ public class Wizard { } ``` -Next we present the spell hierarchy. - -```java -public abstract class Command { - - public abstract void execute(Target target); - - public abstract void undo(); - - public abstract void redo(); - - @Override - public abstract String toString(); -} - -public class InvisibilitySpell extends Command { - - private Target target; - - @Override - public void execute(Target target) { - target.setVisibility(Visibility.INVISIBLE); - this.target = target; - } - - @Override - public void undo() { - if (target != null) { - target.setVisibility(Visibility.VISIBLE); - } - } - - @Override - public void redo() { - if (target != null) { - target.setVisibility(Visibility.INVISIBLE); - } - } - - @Override - public String toString() { - return "Invisibility spell"; - } -} - -public class ShrinkSpell extends Command { - - private Size oldSize; - private Target target; - - @Override - public void execute(Target target) { - oldSize = target.getSize(); - target.setSize(Size.SMALL); - this.target = target; - } - - @Override - public void undo() { - if (oldSize != null && target != null) { - var temp = target.getSize(); - target.setSize(oldSize); - oldSize = temp; - } - } - - @Override - public void redo() { - undo(); - } - - @Override - public String toString() { - return "Shrink spell"; - } -} -``` - -And last we have the goblin who's the target of the spells. +Next, we have the goblin who's the target of the spells. ```java public abstract class Target { @@ -197,47 +121,110 @@ public class Goblin extends Target { return "Goblin"; } + public void changeSize() { + var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; + setSize(oldSize); + } + + public void changeVisibility() { + var visible = getVisibility() == Visibility.INVISIBLE + ? Visibility.VISIBLE : Visibility.INVISIBLE; + setVisibility(visible); + } } ``` -Finally here's the whole example in action. +Finally we have the wizard in main function who casts spell + +```java +public static void main(String[] args) { + var wizard = new Wizard(); + var goblin = new Goblin(); + + // casts shrink/unshrink spell + wizard.castSpell(goblin::changeSize); + + // casts visible/invisible spell + wizard.castSpell(goblin::changeVisibility); + + // undo and redo casts + wizard.undoLastSpell(); + wizard.redoLastSpell(); +``` + +Here's the whole example in action. ```java var wizard = new Wizard(); var goblin = new Goblin(); + goblin.printStatus(); -// Goblin, [size=normal] [visibility=visible] -wizard.castSpell(new ShrinkSpell(), goblin); -// Wizard casts Shrink spell at Goblin +wizard.castSpell(goblin::changeSize); goblin.printStatus(); -// Goblin, [size=small] [visibility=visible] -wizard.castSpell(new InvisibilitySpell(), goblin); -// Wizard casts Invisibility spell at Goblin + +wizard.castSpell(goblin::changeVisibility); goblin.printStatus(); -// Goblin, [size=small] [visibility=invisible] + wizard.undoLastSpell(); -// Wizard undoes Invisibility spell goblin.printStatus(); -// Goblin, [size=small] [visibility=visible] + +wizard.undoLastSpell(); +goblin.printStatus(); + +wizard.redoLastSpell(); +goblin.printStatus(); + +wizard.redoLastSpell(); +goblin.printStatus(); +``` + +Here's the program output: + +```java +Goblin, [size=normal] [visibility=visible] +Goblin, [size=small] [visibility=visible] +Goblin, [size=small] [visibility=invisible] +Goblin, [size=small] [visibility=visible] +Goblin, [size=normal] [visibility=visible] +Goblin, [size=small] [visibility=visible] +Goblin, [size=small] [visibility=invisible] ``` ## Class diagram + ![alt text](./etc/command.png "Command") ## 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 -* support undo. The Command's execute operation can store state for reversing its effects in the command itself. The Command interface must have an added Unexecute operation that reverses the effects of a previous call to execute. Executed commands are stored in a history list. Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling unexecute and execute, respectively -* 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 +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. +* Support undo. The Command's execute operation can store state for reversing its effects in the +command itself. The Command interface must have an added un-execute operation that reverses the +effects of a previous call to execute. The executed commands are stored in a history list. +Unlimited-level undo and redo is achieved by traversing this list backwards and forwards calling +un-execute and execute, respectively. +* 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 -* to keep a history of requests -* implement callback functionality -* implement the undo functionality +* To keep a history of requests +* Implement callback functionality +* Implement the undo functionality ## Real world examples diff --git a/command/etc/command.png b/command/etc/command.png index 81b47d6d0..0f026464e 100644 Binary files a/command/etc/command.png and b/command/etc/command.png differ diff --git a/command/etc/command.ucls b/command/etc/command.ucls index f0e0857d2..afc7e1762 100644 --- a/command/etc/command.ucls +++ b/command/etc/command.ucls @@ -1,116 +1,89 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/command/etc/command.urm.puml b/command/etc/command.urm.puml index a8b773418..f85949c56 100644 --- a/command/etc/command.urm.puml +++ b/command/etc/command.urm.puml @@ -4,33 +4,11 @@ package com.iluwatar.command { + App() + main(args : String[]) {static} } - abstract class Command { - + Command() - + execute(Target) {abstract} - + redo() {abstract} - + toString() : String {abstract} - + undo() {abstract} - } class Goblin { + Goblin() + toString() : String - } - class InvisibilitySpell { - - target : Target - + InvisibilitySpell() - + execute(target : Target) - + redo() - + toString() : String - + undo() - } - class ShrinkSpell { - - oldSize : Size - - target : Target - + ShrinkSpell() - + execute(target : Target) - + redo() - + toString() : String - + undo() + + changeSize() + + changeVisibility() } enum Size { + NORMAL {static} @@ -62,22 +40,19 @@ package com.iluwatar.command { } class Wizard { - LOGGER : Logger {static} - - redoStack : Deque - - undoStack : Deque + - redoStack : Deque + - undoStack : Deque + Wizard() - + castSpell(command : Command, target : Target) + + castSpell(Runnable : runnable) + redoLastSpell() + toString() : String + undoLastSpell() } } Target --> "-size" Size -Wizard --> "-undoStack" Command -ShrinkSpell --> "-oldSize" Size -InvisibilitySpell --> "-target" Target -ShrinkSpell --> "-target" Target +Wizard --> "-changeSize" Goblin +Wizard --> "-changeVisibility" Goblin Target --> "-visibility" Visibility -Goblin --|> Target -InvisibilitySpell --|> Command -ShrinkSpell --|> Command -@enduml \ No newline at end of file +Goblin --|> Target +App --> "castSpell" Wizard +@enduml diff --git a/command/pom.xml b/command/pom.xml index 50a14c45f..3869d4b44 100644 --- a/command/pom.xml +++ b/command/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT command diff --git a/command/src/main/java/com/iluwatar/command/App.java b/command/src/main/java/com/iluwatar/command/App.java index b4e54fd97..77396d37a 100644 --- a/command/src/main/java/com/iluwatar/command/App.java +++ b/command/src/main/java/com/iluwatar/command/App.java @@ -30,12 +30,10 @@ package com.iluwatar.command; * *

Four terms always associated with the command pattern are command, receiver, invoker and * client. A command object (spell) knows about the receiver (target) and invokes a method of the - * receiver. Values for parameters of the receiver method are stored in the command. The receiver - * then does the work. An invoker object (wizard) knows how to execute a command, and optionally - * does bookkeeping about the command execution. The invoker does not know anything about a concrete - * command, it knows only about command interface. Both an invoker object and several command - * objects are held by a client object (app). The client decides which commands to execute at which - * points. To execute a command, it passes the command object to the invoker object. + * receiver. An invoker object (wizard) receives a reference to the command to be executed and + * optionally does bookkeeping about the command execution. The invoker does not know anything + * about how the command is executed. The client decides which commands to execute at which + * points. To execute a command, it passes a reference of the function to the invoker object. * *

In other words, in this example the wizard casts spells on the goblin. The wizard keeps track * of the previous spells cast, so it is easy to undo them. In addition, the wizard keeps track of @@ -54,10 +52,10 @@ public class App { goblin.printStatus(); - wizard.castSpell(new ShrinkSpell(), goblin); + wizard.castSpell(goblin::changeSize); goblin.printStatus(); - wizard.castSpell(new InvisibilitySpell(), goblin); + wizard.castSpell(goblin::changeVisibility); goblin.printStatus(); wizard.undoLastSpell(); diff --git a/command/src/main/java/com/iluwatar/command/Goblin.java b/command/src/main/java/com/iluwatar/command/Goblin.java index 72ddc43b5..e430b65b5 100644 --- a/command/src/main/java/com/iluwatar/command/Goblin.java +++ b/command/src/main/java/com/iluwatar/command/Goblin.java @@ -37,5 +37,4 @@ public class Goblin extends Target { public String toString() { return "Goblin"; } - } diff --git a/command/src/main/java/com/iluwatar/command/Size.java b/command/src/main/java/com/iluwatar/command/Size.java index ae327d8b1..c9aeb7017 100644 --- a/command/src/main/java/com/iluwatar/command/Size.java +++ b/command/src/main/java/com/iluwatar/command/Size.java @@ -1,43 +1,43 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.command; - -/** - * Enumeration for target size. - */ -public enum Size { - - SMALL("small"), NORMAL("normal"); - - private String title; - - Size(String title) { - this.title = title; - } - - @Override - public String toString() { - return title; - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.command; + +/** + * Enumeration for target size. + */ +public enum Size { + + SMALL("small"), NORMAL("normal"); + + private final String title; + + Size(String title) { + this.title = title; + } + + @Override + public String toString() { + return title; + } +} diff --git a/command/src/main/java/com/iluwatar/command/Target.java b/command/src/main/java/com/iluwatar/command/Target.java index f5ac4344c..419ad6f54 100644 --- a/command/src/main/java/com/iluwatar/command/Target.java +++ b/command/src/main/java/com/iluwatar/command/Target.java @@ -62,4 +62,21 @@ public abstract class Target { public void printStatus() { LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); } + + /** + * Changes the size of the target. + */ + public void changeSize() { + var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; + setSize(oldSize); + } + + /** + * Changes the visibility of the target. + */ + public void changeVisibility() { + var visible = getVisibility() == Visibility.INVISIBLE + ? Visibility.VISIBLE : Visibility.INVISIBLE; + setVisibility(visible); + } } diff --git a/command/src/main/java/com/iluwatar/command/Visibility.java b/command/src/main/java/com/iluwatar/command/Visibility.java index 3c48990a0..8fe0ce7bb 100644 --- a/command/src/main/java/com/iluwatar/command/Visibility.java +++ b/command/src/main/java/com/iluwatar/command/Visibility.java @@ -1,43 +1,43 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.command; - -/** - * Enumeration for target visibility. - */ -public enum Visibility { - - VISIBLE("visible"), INVISIBLE("invisible"); - - private String title; - - Visibility(String title) { - this.title = title; - } - - @Override - public String toString() { - return title; - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.command; + +/** + * Enumeration for target visibility. + */ +public enum Visibility { + + VISIBLE("visible"), INVISIBLE("invisible"); + + private final String title; + + Visibility(String title) { + this.title = title; + } + + @Override + public String toString() { + return title; + } +} diff --git a/command/src/main/java/com/iluwatar/command/Wizard.java b/command/src/main/java/com/iluwatar/command/Wizard.java index e0b973265..f63831c07 100644 --- a/command/src/main/java/com/iluwatar/command/Wizard.java +++ b/command/src/main/java/com/iluwatar/command/Wizard.java @@ -25,30 +25,24 @@ package com.iluwatar.command; import java.util.Deque; import java.util.LinkedList; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Wizard is the invoker of the commands. */ public class Wizard { - private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class); - - private Deque undoStack = new LinkedList<>(); - private Deque redoStack = new LinkedList<>(); + private final Deque undoStack = new LinkedList<>(); + private final Deque redoStack = new LinkedList<>(); public Wizard() { - // comment to ignore sonar issue: LEVEL critical } /** * Cast spell. */ - public void castSpell(Command command, Target target) { - LOGGER.info("{} casts {} at {}", this, command, target); - command.execute(target); - undoStack.offerLast(command); + public void castSpell(Runnable runnable) { + runnable.run(); + undoStack.offerLast(runnable); } /** @@ -58,8 +52,7 @@ public class Wizard { if (!undoStack.isEmpty()) { var previousSpell = undoStack.pollLast(); redoStack.offerLast(previousSpell); - LOGGER.info("{} undoes {}", this, previousSpell); - previousSpell.undo(); + previousSpell.run(); } } @@ -70,8 +63,7 @@ public class Wizard { if (!redoStack.isEmpty()) { var previousSpell = redoStack.pollLast(); undoStack.offerLast(previousSpell); - LOGGER.info("{} redoes {}", this, previousSpell); - previousSpell.redo(); + previousSpell.run(); } } diff --git a/command/src/main/java/com/iluwatar/command/module-info.java b/command/src/main/java/com/iluwatar/command/module-info.java deleted file mode 100644 index 0e0c0b31f..000000000 --- a/command/src/main/java/com/iluwatar/command/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.command { - requires org.slf4j; -} \ No newline at end of file diff --git a/command/src/test/java/com/iluwatar/command/AppTest.java b/command/src/test/java/com/iluwatar/command/AppTest.java index cf691aba3..73d098fa3 100644 --- a/command/src/test/java/com/iluwatar/command/AppTest.java +++ b/command/src/test/java/com/iluwatar/command/AppTest.java @@ -25,12 +25,21 @@ package com.iluwatar.command; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Command example runs without errors. */ -public class AppTest { +class AppTest { + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/command/src/test/java/com/iluwatar/command/CommandTest.java b/command/src/test/java/com/iluwatar/command/CommandTest.java index 81f556010..76b8c9e58 100644 --- a/command/src/test/java/com/iluwatar/command/CommandTest.java +++ b/command/src/test/java/com/iluwatar/command/CommandTest.java @@ -56,10 +56,10 @@ public class CommandTest { var wizard = new Wizard(); var goblin = new Goblin(); - wizard.castSpell(new ShrinkSpell(), goblin); + wizard.castSpell(goblin::changeSize); verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.VISIBLE); - wizard.castSpell(new InvisibilitySpell(), goblin); + wizard.castSpell(goblin::changeVisibility); verifyGoblin(goblin, GOBLIN, Size.SMALL, Visibility.INVISIBLE); wizard.undoLastSpell(); diff --git a/commander/pom.xml b/commander/pom.xml index 7ab29e421..baabc04fc 100644 --- a/commander/pom.xml +++ b/commander/pom.xml @@ -27,7 +27,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT commander diff --git a/commander/src/main/java/com/iluwatar/commander/Commander.java b/commander/src/main/java/com/iluwatar/commander/Commander.java index 41779c076..2909f9304 100644 --- a/commander/src/main/java/com/iluwatar/commander/Commander.java +++ b/commander/src/main/java/com/iluwatar/commander/Commander.java @@ -36,9 +36,11 @@ import com.iluwatar.commander.queue.QueueDatabase; import com.iluwatar.commander.queue.QueueTask; import com.iluwatar.commander.queue.QueueTask.TaskType; import com.iluwatar.commander.shippingservice.ShippingService; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + /** *

Commander pattern is used to handle all issues that can come up while making a * distributed transaction. The idea is to have a commander, which coordinates the execution of all @@ -159,8 +161,8 @@ public class Commander { private void sendPaymentRequest(Order order) { if (System.currentTimeMillis() - order.createdTime >= this.paymentTime) { - if (order.paid.equals(PaymentStatus.Trying)) { - order.paid = PaymentStatus.NotDone; + if (order.paid.equals(PaymentStatus.TRYING)) { + order.paid = PaymentStatus.NOT_DONE; sendPaymentFailureMessage(order); LOG.error("Order " + order.id + ": Payment time for order over, failed and returning.."); } //if succeeded or failed, would have been dequeued, no attempt to make payment @@ -172,15 +174,15 @@ public class Commander { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { LOG.debug("Order " + order.id + ": Error in connecting to payment service," - + " trying again.."); + + " trying again.."); } else { LOG.debug("Order " + order.id + ": Error in creating payment request.."); } throw l.remove(0); } - if (order.paid.equals(PaymentStatus.Trying)) { + if (order.paid.equals(PaymentStatus.TRYING)) { var transactionId = paymentService.receiveRequest(order.price); - order.paid = PaymentStatus.Done; + order.paid = PaymentStatus.DONE; LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId); if (!finalSiteMsgShown) { LOG.info("Payment made successfully, thank you for shopping with us!!"); @@ -193,26 +195,26 @@ public class Commander { if (PaymentDetailsErrorException.class.isAssignableFrom(err.getClass())) { if (!finalSiteMsgShown) { LOG.info("There was an error in payment. Your account/card details " - + "may have been incorrect. " - + "Meanwhile, your order has been converted to COD and will be shipped."); + + "may have been incorrect. " + + "Meanwhile, your order has been converted to COD and will be shipped."); finalSiteMsgShown = true; } LOG.error("Order " + order.id + ": Payment details incorrect, failed.."); - o.paid = PaymentStatus.NotDone; + o.paid = PaymentStatus.NOT_DONE; sendPaymentFailureMessage(o); } else { - if (o.messageSent.equals(MessageSent.NoneSent)) { + if (o.messageSent.equals(MessageSent.NONE_SENT)) { if (!finalSiteMsgShown) { LOG.info("There was an error in payment. We are on it, and will get back to you " - + "asap. Don't worry, your order has been placed and will be shipped."); + + "asap. Don't worry, your order has been placed and will be shipped."); finalSiteMsgShown = true; } LOG.warn("Order " + order.id + ": Payment error, going to queue.."); sendPaymentPossibleErrorMsg(o); } - if (o.paid.equals(PaymentStatus.Trying) && System - .currentTimeMillis() - o.createdTime < paymentTime) { - var qt = new QueueTask(o, TaskType.Payment, -1); + if (o.paid.equals(PaymentStatus.TRYING) && System + .currentTimeMillis() - o.createdTime < paymentTime) { + var qt = new QueueTask(o, TaskType.PAYMENT, -1); updateQueue(qt); } } @@ -234,12 +236,12 @@ public class Commander { // additional check not needed LOG.trace("Order " + qt.order.id + ": Queue time for order over, failed.."); return; - } else if (qt.taskType.equals(TaskType.Payment) && !qt.order.paid.equals(PaymentStatus.Trying) - || qt.taskType.equals(TaskType.Messaging) && (qt.messageType == 1 - && !qt.order.messageSent.equals(MessageSent.NoneSent) - || qt.order.messageSent.equals(MessageSent.PaymentFail) - || qt.order.messageSent.equals(MessageSent.PaymentSuccessful)) - || qt.taskType.equals(TaskType.EmployeeDb) && qt.order.addedToEmployeeHandle) { + } else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING) + || qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1 + && !qt.order.messageSent.equals(MessageSent.NONE_SENT) + || qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL) + || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) + || qt.taskType.equals(TaskType.EMPLOYEE_DB) && qt.order.addedToEmployeeHandle) { LOG.trace("Order " + qt.order.id + ": Not queueing task since task already done.."); return; } @@ -256,8 +258,8 @@ public class Commander { tryDoingTasksInQueue(); }; Retry.HandleErrorIssue handleError = (qt1, err) -> { - if (qt1.taskType.equals(TaskType.Payment)) { - qt1.order.paid = PaymentStatus.NotDone; + if (qt1.taskType.equals(TaskType.PAYMENT)) { + qt1.order.paid = PaymentStatus.NOT_DONE; sendPaymentFailureMessage(qt1.order); LOG.error("Order " + qt1.order.id + ": Unable to enqueue payment task," + " payment failed.."); @@ -331,35 +333,9 @@ public class Commander { } var list = messagingService.exceptionsList; Thread t = new Thread(() -> { - Retry.Operation op = (l) -> { - if (!l.isEmpty()) { - if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug("Order " + order.id + ": Error in connecting to messaging service " - + "(Payment Success msg), trying again.."); - } else { - LOG.debug("Order " + order.id + ": Error in creating Payment Success" - + " messaging request.."); - } - throw l.remove(0); - } - if (!order.messageSent.equals(MessageSent.PaymentFail) - && !order.messageSent.equals(MessageSent.PaymentSuccessful)) { - var requestId = messagingService.receiveRequest(2); - order.messageSent = MessageSent.PaymentSuccessful; - LOG.info("Order " + order.id + ": Payment Success message sent," - + " request Id: " + requestId); - } - }; + Retry.Operation op = handleSuccessMessageRetryOperation(order); Retry.HandleErrorIssue handleError = (o, err) -> { - if ((o.messageSent.equals(MessageSent.NoneSent) || o.messageSent - .equals(MessageSent.PaymentTrying)) - && System.currentTimeMillis() - o.createdTime < messageTime) { - var qt = new QueueTask(order, TaskType.Messaging, 2); - updateQueue(qt); - LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to" - + " queue task and add to employee handle.."); - employeeHandleIssue(order); - } + handleSuccessMessageErrorIssue(order, o); }; var r = new Retry<>(op, handleError, numOfRetries, retryDuration, e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); @@ -372,6 +348,40 @@ public class Commander { t.start(); } + private void handleSuccessMessageErrorIssue(Order order, Order o) { + if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent + .equals(MessageSent.PAYMENT_TRYING)) + && System.currentTimeMillis() - o.createdTime < messageTime) { + var qt = new QueueTask(order, TaskType.MESSAGING, 2); + updateQueue(qt); + LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to" + + " queue task and add to employee handle.."); + employeeHandleIssue(order); + } + } + + private Retry.Operation handleSuccessMessageRetryOperation(Order order) { + return (l) -> { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug("Order " + order.id + ": Error in connecting to messaging service " + + "(Payment Success msg), trying again.."); + } else { + LOG.debug("Order " + order.id + ": Error in creating Payment Success" + + " messaging request.."); + } + throw l.remove(0); + } + if (!order.messageSent.equals(MessageSent.PAYMENT_FAIL) + && !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { + var requestId = messagingService.receiveRequest(2); + order.messageSent = MessageSent.PAYMENT_SUCCESSFUL; + LOG.info("Order " + order.id + ": Payment Success message sent," + + " request Id: " + requestId); + } + }; + } + private void sendPaymentFailureMessage(Order order) { if (System.currentTimeMillis() - order.createdTime >= this.messageTime) { LOG.trace("Order " + order.id + ": Message time for order over, returning.."); @@ -380,34 +390,10 @@ public class Commander { var list = messagingService.exceptionsList; var t = new Thread(() -> { Retry.Operation op = (l) -> { - if (!l.isEmpty()) { - if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug("Order " + order.id + ": Error in connecting to messaging service " - + "(Payment Failure msg), trying again.."); - } else { - LOG.debug("Order " + order.id + ": Error in creating Payment Failure" - + " message request.."); - } - throw l.remove(0); - } - if (!order.messageSent.equals(MessageSent.PaymentFail) - && !order.messageSent.equals(MessageSent.PaymentSuccessful)) { - var requestId = messagingService.receiveRequest(0); - order.messageSent = MessageSent.PaymentFail; - LOG.info("Order " + order.id + ": Payment Failure message sent successfully," - + " request Id: " + requestId); - } + handlePaymentFailureRetryOperation(order, l); }; Retry.HandleErrorIssue handleError = (o, err) -> { - if ((o.messageSent.equals(MessageSent.NoneSent) || o.messageSent - .equals(MessageSent.PaymentTrying)) - && System.currentTimeMillis() - o.createdTime < messageTime) { - var qt = new QueueTask(order, TaskType.Messaging, 0); - updateQueue(qt); - LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, " - + "trying to queue task and add to employee handle.."); - employeeHandleIssue(o); - } + handlePaymentErrorIssue(order, o); }; var r = new Retry<>(op, handleError, numOfRetries, retryDuration, e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); @@ -420,6 +406,38 @@ public class Commander { t.start(); } + private void handlePaymentErrorIssue(Order order, Order o) { + if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent + .equals(MessageSent.PAYMENT_TRYING)) + && System.currentTimeMillis() - o.createdTime < messageTime) { + var qt = new QueueTask(order, TaskType.MESSAGING, 0); + updateQueue(qt); + LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, " + + "trying to queue task and add to employee handle.."); + employeeHandleIssue(o); + } + } + + private void handlePaymentFailureRetryOperation(Order order, List l) throws Exception { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug("Order " + order.id + ": Error in connecting to messaging service " + + "(Payment Failure msg), trying again.."); + } else { + LOG.debug("Order " + order.id + ": Error in creating Payment Failure" + + " message request.."); + } + throw l.remove(0); + } + if (!order.messageSent.equals(MessageSent.PAYMENT_FAIL) + && !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { + var requestId = messagingService.receiveRequest(0); + order.messageSent = MessageSent.PAYMENT_FAIL; + LOG.info("Order " + order.id + ": Payment Failure message sent successfully," + + " request Id: " + requestId); + } + } + private void sendPaymentPossibleErrorMsg(Order order) { if (System.currentTimeMillis() - order.createdTime >= this.messageTime) { LOG.trace("Message time for order over, returning.."); @@ -428,34 +446,10 @@ public class Commander { var list = messagingService.exceptionsList; var t = new Thread(() -> { Retry.Operation op = (l) -> { - if (!l.isEmpty()) { - if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug("Order " + order.id + ": Error in connecting to messaging service " - + "(Payment Error msg), trying again.."); - } else { - LOG.debug("Order " + order.id + ": Error in creating Payment Error" - + " messaging request.."); - } - throw l.remove(0); - } - if (order.paid.equals(PaymentStatus.Trying) && order.messageSent - .equals(MessageSent.NoneSent)) { - var requestId = messagingService.receiveRequest(1); - order.messageSent = MessageSent.PaymentTrying; - LOG.info("Order " + order.id + ": Payment Error message sent successfully," - + " request Id: " + requestId); - } + handlePaymentPossibleErrorMsgRetryOperation(order, l); }; Retry.HandleErrorIssue handleError = (o, err) -> { - if (o.messageSent.equals(MessageSent.NoneSent) && order.paid - .equals(PaymentStatus.Trying) - && System.currentTimeMillis() - o.createdTime < messageTime) { - var qt = new QueueTask(order, TaskType.Messaging, 1); - updateQueue(qt); - LOG.warn("Order " + order.id + ": Error in sending Payment Error message, " - + "trying to queue task and add to employee handle.."); - employeeHandleIssue(o); - } + handlePaymentPossibleErrorMsgErrorIssue(order, o); }; var r = new Retry<>(op, handleError, numOfRetries, retryDuration, e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); @@ -468,6 +462,39 @@ public class Commander { t.start(); } + private void handlePaymentPossibleErrorMsgErrorIssue(Order order, Order o) { + if (o.messageSent.equals(MessageSent.NONE_SENT) && order.paid + .equals(PaymentStatus.TRYING) + && System.currentTimeMillis() - o.createdTime < messageTime) { + var qt = new QueueTask(order, TaskType.MESSAGING, 1); + updateQueue(qt); + LOG.warn("Order " + order.id + ": Error in sending Payment Error message, " + + "trying to queue task and add to employee handle.."); + employeeHandleIssue(o); + } + } + + private void handlePaymentPossibleErrorMsgRetryOperation(Order order, List l) + throws Exception { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug("Order " + order.id + ": Error in connecting to messaging service " + + "(Payment Error msg), trying again.."); + } else { + LOG.debug("Order " + order.id + ": Error in creating Payment Error" + + " messaging request.."); + } + throw l.remove(0); + } + if (order.paid.equals(PaymentStatus.TRYING) && order.messageSent + .equals(MessageSent.NONE_SENT)) { + var requestId = messagingService.receiveRequest(1); + order.messageSent = MessageSent.PAYMENT_TRYING; + LOG.info("Order " + order.id + ": Payment Error message sent successfully," + + " request Id: " + requestId); + } + } + private void employeeHandleIssue(Order order) { if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) { LOG.trace("Order " + order.id + ": Employee handle time for order over, returning.."); @@ -490,7 +517,7 @@ public class Commander { Retry.HandleErrorIssue handleError = (o, err) -> { if (!o.addedToEmployeeHandle && System .currentTimeMillis() - order.createdTime < employeeTime) { - var qt = new QueueTask(order, TaskType.EmployeeDb, -1); + var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1); updateQueue(qt); LOG.warn("Order " + order.id + ": Error in adding to employee db," + " trying to queue task.."); @@ -520,21 +547,21 @@ public class Commander { LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType() + " does not need to be done anymore (timeout), dequeue.."); } else { - if (qt.taskType.equals(TaskType.Payment)) { - if (!qt.order.paid.equals(PaymentStatus.Trying)) { + if (qt.taskType.equals(TaskType.PAYMENT)) { + if (!qt.order.paid.equals(PaymentStatus.TRYING)) { tryDequeue(); LOG.trace("Order " + qt.order.id + ": This payment task already done, dequeueing.."); } else { sendPaymentRequest(qt.order); LOG.debug("Order " + qt.order.id + ": Trying to connect to payment service.."); } - } else if (qt.taskType.equals(TaskType.Messaging)) { - if (qt.order.messageSent.equals(MessageSent.PaymentFail) - || qt.order.messageSent.equals(MessageSent.PaymentSuccessful)) { + } else if (qt.taskType.equals(TaskType.MESSAGING)) { + if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL) + || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { tryDequeue(); LOG.trace("Order " + qt.order.id + ": This messaging task already done, dequeue.."); - } else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NoneSent) - || !qt.order.paid.equals(PaymentStatus.Trying))) { + } else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT) + || !qt.order.paid.equals(PaymentStatus.TRYING))) { tryDequeue(); LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done," + " dequeue.."); @@ -548,7 +575,7 @@ public class Commander { sendSuccessMessage(qt.order); LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service.."); } - } else if (qt.taskType.equals(TaskType.EmployeeDb)) { + } else if (qt.taskType.equals(TaskType.EMPLOYEE_DB)) { if (qt.order.addedToEmployeeHandle) { tryDequeue(); LOG.trace("Order " + qt.order.id + ": This employee handle task already done," diff --git a/commander/src/main/java/com/iluwatar/commander/Order.java b/commander/src/main/java/com/iluwatar/commander/Order.java index 87a9f794a..f736aa47c 100644 --- a/commander/src/main/java/com/iluwatar/commander/Order.java +++ b/commander/src/main/java/com/iluwatar/commander/Order.java @@ -33,11 +33,11 @@ import java.util.Random; public class Order { //can store all transactions ids also enum PaymentStatus { - NotDone, Trying, Done + NOT_DONE, TRYING, DONE } enum MessageSent { - NoneSent, PaymentFail, PaymentTrying, PaymentSuccessful + NONE_SENT, PAYMENT_FAIL, PAYMENT_TRYING, PAYMENT_SUCCESSFUL } final User user; @@ -65,8 +65,8 @@ public class Order { //can store all transactions ids also } this.id = id; USED_IDS.put(this.id, true); - this.paid = PaymentStatus.Trying; - this.messageSent = MessageSent.NoneSent; + this.paid = PaymentStatus.TRYING; + this.messageSent = MessageSent.NONE_SENT; this.addedToEmployeeHandle = false; } diff --git a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java index 496bb545a..69ebc1fd9 100644 --- a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java @@ -33,7 +33,7 @@ import java.util.Hashtable; */ public class EmployeeDatabase extends Database { - private Hashtable data; + private final Hashtable data; public EmployeeDatabase() { this.data = new Hashtable<>(); diff --git a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java index fbba52cac..22ad733cb 100644 --- a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java @@ -33,7 +33,7 @@ import java.util.Hashtable; */ public class MessagingDatabase extends Database { - private Hashtable data; + private final Hashtable data; public MessagingDatabase() { this.data = new Hashtable<>(); diff --git a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java index 3fb385757..e353a4c7c 100644 --- a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java +++ b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java @@ -38,7 +38,7 @@ public class MessagingService extends Service { private static final Logger LOGGER = LoggerFactory.getLogger(MessagingService.class); enum MessageToSend { - PaymentFail, PaymentTrying, PaymentSuccessful + PAYMENT_FAIL, PAYMENT_TRYING, PAYMENT_SUCCESSFUL } class MessageRequest { @@ -63,11 +63,11 @@ public class MessagingService extends Service { var id = generateId(); MessageToSend msg; if (messageToSend == 0) { - msg = MessageToSend.PaymentFail; + msg = MessageToSend.PAYMENT_FAIL; } else if (messageToSend == 1) { - msg = MessageToSend.PaymentTrying; + msg = MessageToSend.PAYMENT_TRYING; } else { //messageToSend == 2 - msg = MessageToSend.PaymentSuccessful; + msg = MessageToSend.PAYMENT_SUCCESSFUL; } var req = new MessageRequest(id, msg); return updateDb(req); @@ -84,10 +84,10 @@ public class MessagingService extends Service { } String sendMessage(MessageToSend m) { - if (m.equals(MessageToSend.PaymentSuccessful)) { + if (m.equals(MessageToSend.PAYMENT_SUCCESSFUL)) { return "Msg: Your order has been placed and paid for successfully!" + " Thank you for shopping with us!"; - } else if (m.equals(MessageToSend.PaymentTrying)) { + } else if (m.equals(MessageToSend.PAYMENT_TRYING)) { return "Msg: There was an error in your payment process," + " we are working on it and will return back to you shortly." + " Meanwhile, your order has been placed and will be shipped."; diff --git a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java index 644979883..bf9e846bb 100644 --- a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java @@ -34,7 +34,7 @@ import java.util.Hashtable; public class PaymentDatabase extends Database { - private Hashtable data; + private final Hashtable data; public PaymentDatabase() { this.data = new Hashtable<>(); diff --git a/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java b/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java index 91a7966f7..003a7da46 100644 --- a/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java @@ -35,7 +35,7 @@ import java.util.List; public class QueueDatabase extends Database { - private Queue data; + private final Queue data; public List exceptionsList; public QueueDatabase(Exception... exc) { diff --git a/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java b/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java index a27dd62b8..341eb628c 100644 --- a/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java +++ b/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java @@ -36,7 +36,7 @@ public class QueueTask { */ public enum TaskType { - Messaging, Payment, EmployeeDb + MESSAGING, PAYMENT, EMPLOYEE_DB } public Order order; @@ -68,7 +68,7 @@ public class QueueTask { * @return String representing type of task */ public String getType() { - if (!this.taskType.equals(TaskType.Messaging)) { + if (!this.taskType.equals(TaskType.MESSAGING)) { return this.taskType.toString(); } else { if (this.messageType == 0) { diff --git a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java index 305122db2..abaf27c9d 100644 --- a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java @@ -34,7 +34,7 @@ import java.util.Hashtable; public class ShippingDatabase extends Database { - private Hashtable data; + private final Hashtable data; public ShippingDatabase() { this.data = new Hashtable<>(); diff --git a/composite/README.md b/composite/README.md index 25b553b76..b7aaa69df 100644 --- a/composite/README.md +++ b/composite/README.md @@ -9,15 +9,17 @@ tags: --- ## Intent -Compose objects into tree structures to represent part-whole -hierarchies. Composite lets clients treat individual objects and compositions -of objects uniformly. + +Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients +treat individual objects and compositions of objects uniformly. ## Explanation Real world example -> Every sentence is composed of words which are in turn composed of characters. Each of these objects is printable and they can have something printed before or after them like sentence always ends with full stop and word always has space before it +> Every sentence is composed of words which are in turn composed of characters. Each of these +> objects is printable and they can have something printed before or after them like sentence always +> ends with full stop and word always has space before it. In plain words @@ -25,16 +27,21 @@ In plain words Wikipedia says -> In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly. +> In software engineering, the composite pattern is a partitioning design pattern. The composite +> pattern describes that a group of objects is to be treated in the same way as a single instance of +> an object. The intent of a composite is to "compose" objects into tree structures to represent +> part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects +> and compositions uniformly. **Programmatic Example** -Taking our sentence example from above. Here we have the base class and different printable types +Taking our sentence example from above. Here we have the base class `LetterComposite` and the +different printable types `Letter`, `Word` and `Sentence`. ```java public abstract class LetterComposite { - private List children = new ArrayList<>(); + private final List children = new ArrayList<>(); public void add(LetterComposite letter) { children.add(letter); @@ -59,7 +66,7 @@ public abstract class LetterComposite { public class Letter extends LetterComposite { - private char character; + private final char character; public Letter(char c) { this.character = c; @@ -102,7 +109,7 @@ public class Sentence extends LetterComposite { } ``` -Then we have a messenger to carry messages +Then we have a messenger to carry messages: ```java public class Messenger { @@ -143,7 +150,7 @@ public class Messenger { } ``` -And then it can be used as +And then it can be used as: ```java var orcMessage = new Messenger().messageFromOrcs(); @@ -153,13 +160,16 @@ elfMessage.print(); // Much wind pours from your mouth. ``` ## Class diagram + ![alt text](./etc/composite.urm.png "Composite class diagram") ## 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 +* 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 diff --git a/composite/pom.xml b/composite/pom.xml index c16b95c13..6f7147482 100644 --- a/composite/pom.xml +++ b/composite/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT composite diff --git a/composite/src/main/java/com/iluwatar/composite/Letter.java b/composite/src/main/java/com/iluwatar/composite/Letter.java index ab2d496ea..00b1a9639 100644 --- a/composite/src/main/java/com/iluwatar/composite/Letter.java +++ b/composite/src/main/java/com/iluwatar/composite/Letter.java @@ -28,7 +28,7 @@ package com.iluwatar.composite; */ public class Letter extends LetterComposite { - private char character; + private final char character; public Letter(char c) { this.character = c; diff --git a/composite/src/main/java/com/iluwatar/composite/LetterComposite.java b/composite/src/main/java/com/iluwatar/composite/LetterComposite.java index 25808c468..0daf88222 100644 --- a/composite/src/main/java/com/iluwatar/composite/LetterComposite.java +++ b/composite/src/main/java/com/iluwatar/composite/LetterComposite.java @@ -1,58 +1,58 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.composite; - -import java.util.ArrayList; -import java.util.List; - -/** - * Composite interface. - */ -public abstract class LetterComposite { - - private List children = new ArrayList<>(); - - public void add(LetterComposite letter) { - children.add(letter); - } - - public int count() { - return children.size(); - } - - protected void printThisBefore() { - } - - protected void printThisAfter() { - } - - /** - * Print. - */ - public void print() { - printThisBefore(); - children.forEach(LetterComposite::print); - printThisAfter(); - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.composite; + +import java.util.ArrayList; +import java.util.List; + +/** + * Composite interface. + */ +public abstract class LetterComposite { + + private final List children = new ArrayList<>(); + + public void add(LetterComposite letter) { + children.add(letter); + } + + public int count() { + return children.size(); + } + + protected void printThisBefore() { + } + + protected void printThisAfter() { + } + + /** + * Print. + */ + public void print() { + printThisBefore(); + children.forEach(LetterComposite::print); + printThisAfter(); + } +} diff --git a/composite/src/main/java/com/iluwatar/composite/module-info.java b/composite/src/main/java/com/iluwatar/composite/module-info.java deleted file mode 100644 index d75a7b8f8..000000000 --- a/composite/src/main/java/com/iluwatar/composite/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.composite { - requires org.slf4j; -} \ No newline at end of file diff --git a/composite/src/test/java/com/iluwatar/composite/AppTest.java b/composite/src/test/java/com/iluwatar/composite/AppTest.java index 5eb8c35c7..c82056a51 100644 --- a/composite/src/test/java/com/iluwatar/composite/AppTest.java +++ b/composite/src/test/java/com/iluwatar/composite/AppTest.java @@ -23,15 +23,23 @@ package com.iluwatar.composite; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Application test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + Assertions.assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/converter/README.md b/converter/README.md index 3be8ce331..02b172149 100644 --- a/converter/README.md +++ b/converter/README.md @@ -9,16 +9,19 @@ tags: --- ## Intent -The purpose of the Converter Pattern is to provide a generic, common way of bidirectional + +The purpose of the Converter pattern is to provide a generic, common way of bidirectional conversion between corresponding types, allowing a clean implementation in which the types do not -need to be aware of each other. Moreover, the Converter Pattern introduces bidirectional collection +need to be aware of each other. Moreover, the Converter pattern introduces bidirectional collection mapping, reducing a boilerplate code to minimum. ## Explanation Real world example -> In real world applications it is often the case that database layer consists of entities that need to be mapped into DTOs for use on the business logic layer. Similar mapping is done for potentially huge amount of classes and we need a generic way to achieve this. +> In real world applications it is often the case that database layer consists of entities that need +> to be mapped into DTOs for use on the business logic layer. Similar mapping is done for +> potentially huge amount of classes and we need a generic way to achieve this. In plain words @@ -26,7 +29,8 @@ In plain words **Programmatic Example** -We need a generic solution for the mapping problem. To achieve this, let's introduce a generic converter. +We need a generic solution for the mapping problem. To achieve this, let's introduce a generic +converter. ```java public class Converter { @@ -77,7 +81,7 @@ public class UserConverter extends Converter { } ``` -Now mapping between User and UserDto becomes trivial. +Now mapping between `User` and `UserDto` becomes trivial. ```java var userConverter = new UserConverter(); @@ -86,14 +90,18 @@ var user = userConverter.convertFromDto(dtoUser); ``` ## Class diagram + ![alt text](./etc/converter.png "Converter Pattern") ## Applicability + Use the Converter Pattern in the following situations: -* When you have types that logically correspond which other and you need to convert entities between them -* When you want to provide different ways of types conversions depending on a context -* Whenever you introduce a DTO (Data transfer object), you will probably need to convert it into the domain equivalence +* When you have types that logically correspond with each other and you need to convert entities +between them. +* When you want to provide different ways of types conversions depending on the context. +* Whenever you introduce a DTO (Data transfer object), you will probably need to convert it into the +domain equivalence. ## Credits diff --git a/converter/pom.xml b/converter/pom.xml index 56eb2ccdb..1bed5e973 100644 --- a/converter/pom.xml +++ b/converter/pom.xml @@ -20,7 +20,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT converter 4.0.0 diff --git a/converter/src/main/java/com/iluwatar/converter/User.java b/converter/src/main/java/com/iluwatar/converter/User.java index 637d77a25..2c1ba9ff0 100644 --- a/converter/src/main/java/com/iluwatar/converter/User.java +++ b/converter/src/main/java/com/iluwatar/converter/User.java @@ -29,10 +29,10 @@ import java.util.Objects; * User class. */ public class User { - private String firstName; - private String lastName; - private boolean isActive; - private String userId; + private final String firstName; + private final String lastName; + private final boolean isActive; + private final String userId; /** * Constructor. diff --git a/converter/src/main/java/com/iluwatar/converter/UserDto.java b/converter/src/main/java/com/iluwatar/converter/UserDto.java index e75aaab8c..67a886087 100644 --- a/converter/src/main/java/com/iluwatar/converter/UserDto.java +++ b/converter/src/main/java/com/iluwatar/converter/UserDto.java @@ -30,10 +30,10 @@ import java.util.Objects; */ public class UserDto { - private String firstName; - private String lastName; - private boolean isActive; - private String email; + private final String firstName; + private final String lastName; + private final boolean isActive; + private final String email; /** * Constructor. diff --git a/converter/src/main/java/com/iluwatar/converter/module-info.java b/converter/src/main/java/com/iluwatar/converter/module-info.java deleted file mode 100644 index d83a43c6b..000000000 --- a/converter/src/main/java/com/iluwatar/converter/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.converter { - requires org.slf4j; -} \ No newline at end of file diff --git a/converter/src/test/java/com/iluwatar/converter/AppTest.java b/converter/src/test/java/com/iluwatar/converter/AppTest.java index ed53c6863..7a99fe6ae 100644 --- a/converter/src/test/java/com/iluwatar/converter/AppTest.java +++ b/converter/src/test/java/com/iluwatar/converter/AppTest.java @@ -25,14 +25,24 @@ package com.iluwatar.converter; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * App running test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void testMain() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/converter/src/test/java/com/iluwatar/converter/ConverterTest.java b/converter/src/test/java/com/iluwatar/converter/ConverterTest.java index d9e4e418b..46aca82a7 100644 --- a/converter/src/test/java/com/iluwatar/converter/ConverterTest.java +++ b/converter/src/test/java/com/iluwatar/converter/ConverterTest.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test; */ public class ConverterTest { - private UserConverter userConverter = new UserConverter(); + private final UserConverter userConverter = new UserConverter(); /** * Tests whether a converter created of opposite functions holds equality as a bijection. diff --git a/cqrs/pom.xml b/cqrs/pom.xml index b3a0303e6..1838ed599 100644 --- a/cqrs/pom.xml +++ b/cqrs/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT cqrs diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java index ba08811e7..e402adad8 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java @@ -34,7 +34,7 @@ import org.hibernate.SessionFactory; */ public class CommandServiceImpl implements ICommandService { - private SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + private final SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); private Author getAuthorByUsername(String username) { Author author; diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java b/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java index 9b008402e..d30c0f386 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java @@ -38,7 +38,7 @@ import org.hibernate.transform.Transformers; */ public class QueryServiceImpl implements IQueryService { - private SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + private final SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); @Override public Author getAuthorByUsername(String username) { diff --git a/dao/README.md b/dao/README.md index 4b65679c4..29e64a74f 100644 --- a/dao/README.md +++ b/dao/README.md @@ -9,13 +9,15 @@ tags: --- ## Intent + Object provides an abstract interface to some type of database or other persistence mechanism. ## Explanation Real world example -> There's a set of customers that need to be persisted to database. Additionally we need the whole set of CRUD (create/read/update/delete) operations so we can operate on customers easily. +> There's a set of customers that need to be persisted to database. Additionally we need the whole +> set of CRUD (create/read/update/delete) operations so we can operate on customers easily. In plain words @@ -23,11 +25,12 @@ In plain words Wikipedia says -> In computer software, a data access object (DAO) is a pattern that provides an abstract interface to some type of database or other persistence mechanism. +> In computer software, a data access object (DAO) is a pattern that provides an abstract interface +> to some type of database or other persistence mechanism. **Programmatic Example** -Walking through our customers example, here's the basic Customer entity. +Walking through our customers example, here's the basic `Customer` entity. ```java public class Customer { @@ -41,60 +44,13 @@ public class Customer { this.firstName = firstName; this.lastName = lastName; } - - public int getId() { - return id; - } - - public void setId(final int id) { - this.id = id; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(final String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(final String lastName) { - this.lastName = lastName; - } - - @Override - public String toString() { - return "Customer{" + "id=" + getId() + ", firstName='" + getFirstName() + '\'' + ", lastName='" - + getLastName() + '\'' + '}'; - } - - @Override - public boolean equals(final Object that) { - var isEqual = false; - if (this == that) { - isEqual = true; - } else if (that != null && getClass() == that.getClass()) { - final var customer = (Customer) that; - if (getId() == customer.getId()) { - isEqual = true; - } - } - return isEqual; - } - - @Override - public int hashCode() { - return getId(); - } + // getters and setters -> + ... } ``` -Here's the DAO interface and two different implementations for it. InMemoryCustomerDao keeps a simple map of customers -in memory while DBCustomerDao is the real RDBMS implementation. +Here's the `CustomerDao` interface and two different implementations for it. `InMemoryCustomerDao` +keeps a simple map of customers in memory while `DBCustomerDao` is the real RDBMS implementation. ```java public interface CustomerDao { @@ -112,37 +68,10 @@ public interface CustomerDao { public class InMemoryCustomerDao implements CustomerDao { - private Map idToCustomer = new HashMap<>(); + private final Map idToCustomer = new HashMap<>(); - @Override - public Stream getAll() { - return idToCustomer.values().stream(); - } - - @Override - public Optional getById(final int id) { - return Optional.ofNullable(idToCustomer.get(id)); - } - - @Override - public boolean add(final Customer customer) { - if (getById(customer.getId()).isPresent()) { - return false; - } - - idToCustomer.put(customer.getId(), customer); - return true; - } - - @Override - public boolean update(final Customer customer) { - return idToCustomer.replace(customer.getId(), customer) != null; - } - - @Override - public boolean delete(final Customer customer) { - return idToCustomer.remove(customer.getId()) != null; - } + // implement the interface using the map + ... } public class DbCustomerDao implements CustomerDao { @@ -155,121 +84,8 @@ public class DbCustomerDao implements CustomerDao { this.dataSource = dataSource; } - @Override - public Stream getAll() throws Exception { - try { - var connection = getConnection(); - var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); - var resultSet = statement.executeQuery(); - return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, - Spliterator.ORDERED) { - - @Override - public boolean tryAdvance(Consumer action) { - try { - if (!resultSet.next()) { - return false; - } - action.accept(createCustomer(resultSet)); - return true; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - }, false).onClose(() -> mutedClose(connection, statement, resultSet)); - } catch (SQLException e) { - throw new CustomException(e.getMessage(), e); - } - } - - private Connection getConnection() throws SQLException { - return dataSource.getConnection(); - } - - private void mutedClose(Connection connection, PreparedStatement statement, ResultSet resultSet) { - try { - resultSet.close(); - statement.close(); - connection.close(); - } catch (SQLException e) { - LOGGER.info("Exception thrown " + e.getMessage()); - } - } - - private Customer createCustomer(ResultSet resultSet) throws SQLException { - return new Customer(resultSet.getInt("ID"), - resultSet.getString("FNAME"), - resultSet.getString("LNAME")); - } - - @Override - public Optional getById(int id) throws Exception { - - ResultSet resultSet = null; - - try (var connection = getConnection(); - var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) { - - statement.setInt(1, id); - resultSet = statement.executeQuery(); - if (resultSet.next()) { - return Optional.of(createCustomer(resultSet)); - } else { - return Optional.empty(); - } - } catch (SQLException ex) { - throw new CustomException(ex.getMessage(), ex); - } finally { - if (resultSet != null) { - resultSet.close(); - } - } - } - - @Override - public boolean add(Customer customer) throws Exception { - if (getById(customer.getId()).isPresent()) { - return false; - } - - try (var connection = getConnection(); - var statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) { - statement.setInt(1, customer.getId()); - statement.setString(2, customer.getFirstName()); - statement.setString(3, customer.getLastName()); - statement.execute(); - return true; - } catch (SQLException ex) { - throw new CustomException(ex.getMessage(), ex); - } - } - - @Override - public boolean update(Customer customer) throws Exception { - try (var connection = getConnection(); - var statement = - connection - .prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) { - statement.setString(1, customer.getFirstName()); - statement.setString(2, customer.getLastName()); - statement.setInt(3, customer.getId()); - return statement.executeUpdate() > 0; - } catch (SQLException ex) { - throw new CustomException(ex.getMessage(), ex); - } - } - - @Override - public boolean delete(Customer customer) throws Exception { - try (var connection = getConnection(); - var statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) { - statement.setInt(1, customer.getId()); - return statement.executeUpdate() > 0; - } catch (SQLException ex) { - throw new CustomException(ex.getMessage(), ex); - } - } -} + // implement the interface using the data source + ... ``` Finally here's how we use our DAO to manage customers. @@ -301,15 +117,45 @@ Finally here's how we use our DAO to manage customers. deleteSchema(dataSource); ``` +The program output: + +```java +customerDao.getAllCustomers(): +Customer{id=1, firstName='Adam', lastName='Adamson'} +Customer{id=2, firstName='Bob', lastName='Bobson'} +Customer{id=3, firstName='Carl', lastName='Carlson'} +customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}] +customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@7cef4e59 +customerDao.getAllCustomers(): +Customer{id=1, firstName='Adam', lastName='Adamson'} +Customer{id=2, firstName='Bob', lastName='Bobson'} +Customer{id=3, firstName='Carl', lastName='Carlson'} +Customer{id=4, firstName='Daniel', lastName='Danielson'} +customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@2db0f6b2 +customerDao.getAllCustomers(): +Customer{id=1, firstName='Adam', lastName='Adamson'} +Customer{id=2, firstName='Bob', lastName='Bobson'} +Customer{id=3, firstName='Carl', lastName='Carlson'} +customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}] +customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@12c8a2c0 +customerDao.getAllCustomers(): +Customer{id=1, firstName='Adam', lastName='Adamson'} +Customer{id=2, firstName='Bob', lastName='Bobson'} +Customer{id=3, firstName='Carl', lastName='Carlson'} +Customer{id=4, firstName='Daniel', lastName='Danielson'} +customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@6ec8211c +``` ## Class diagram + ![alt text](./etc/dao.png "Data Access Object") ## 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 +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 diff --git a/dao/pom.xml b/dao/pom.xml index 7e8bd5625..32e9ef1ff 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.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 de9c7b7c1..6d578bc79 100644 --- a/dao/src/main/java/com/iluwatar/dao/App.java +++ b/dao/src/main/java/com/iluwatar/dao/App.java @@ -44,7 +44,7 @@ import org.slf4j.LoggerFactory; */ public class App { private static final String DB_URL = "jdbc:h2:~/dao"; - private static Logger log = LoggerFactory.getLogger(App.class); + private static final Logger log = LoggerFactory.getLogger(App.class); private static final String ALL_CUSTOMERS = "customerDao.getAllCustomers(): "; /** diff --git a/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java b/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java index 6dbfa367a..0a3bd40e3 100644 --- a/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java +++ b/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java @@ -36,7 +36,7 @@ import java.util.stream.Stream; */ public class InMemoryCustomerDao implements CustomerDao { - private Map idToCustomer = new HashMap<>(); + private final Map idToCustomer = new HashMap<>(); /** * An eagerly evaluated stream of customers stored in memory. diff --git a/dao/src/main/java/com/iluwatar/dao/module-info.java b/dao/src/main/java/com/iluwatar/dao/module-info.java deleted file mode 100644 index 08e4f662e..000000000 --- a/dao/src/main/java/com/iluwatar/dao/module-info.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.dao { - requires org.slf4j; - requires java.sql; - requires h2; - requires java.naming; -} \ No newline at end of file diff --git a/dao/src/test/java/com/iluwatar/dao/AppTest.java b/dao/src/test/java/com/iluwatar/dao/AppTest.java index edfcf7cd0..e6d41fc8a 100644 --- a/dao/src/test/java/com/iluwatar/dao/AppTest.java +++ b/dao/src/test/java/com/iluwatar/dao/AppTest.java @@ -25,12 +25,22 @@ package com.iluwatar.dao; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that DAO example runs without errors. */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + @Test - public void test() throws Exception { - App.main(new String[]{}); + void shouldExecuteDaoWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java b/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java index b7a0b9769..8155cda79 100644 --- a/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java +++ b/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java @@ -50,7 +50,7 @@ public class DbCustomerDaoTest { private static final String DB_URL = "jdbc:h2:~/dao"; private DbCustomerDao dao; - private Customer existingCustomer = new Customer(1, "Freddy", "Krueger"); + private final Customer existingCustomer = new Customer(1, "Freddy", "Krueger"); /** * Creates customers schema. diff --git a/data-bus/pom.xml b/data-bus/pom.xml index e67135ae0..4db738307 100644 --- a/data-bus/pom.xml +++ b/data-bus/pom.xml @@ -33,7 +33,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT data-bus diff --git a/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java b/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java index 5a8218225..d77d56b9f 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java +++ b/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java @@ -41,7 +41,7 @@ public class MessageCollectorMember implements Member { private final String name; - private List messages = new ArrayList<>(); + private final List messages = new ArrayList<>(); public MessageCollectorMember(String name) { this.name = name; diff --git a/data-locality/pom.xml b/data-locality/pom.xml index 660daa9b7..88fd96c64 100644 --- a/data-locality/pom.xml +++ b/data-locality/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT data-locality diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java index 5b1be9e35..40acb2f71 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java @@ -43,6 +43,6 @@ public class AiComponent implements Component { @Override public void render() { - + // Do Nothing. } } diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java index 616ebf801..c85bd1e68 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java @@ -40,7 +40,7 @@ public class AiComponentManager { private final int numEntities; - private static final Component[] AI_COMPONENTS = new AiComponent[MAX_ENTITIES]; + private final Component[] aiComponents = new AiComponent[MAX_ENTITIES]; public AiComponentManager(int numEntities) { this.numEntities = numEntities; @@ -51,7 +51,7 @@ public class AiComponentManager { */ public void start() { LOGGER.info("Start AI Game Component"); - IntStream.range(0, numEntities).forEach(i -> AI_COMPONENTS[i] = new AiComponent()); + IntStream.range(0, numEntities).forEach(i -> aiComponents[i] = new AiComponent()); } /** @@ -60,7 +60,7 @@ public class AiComponentManager { public void update() { LOGGER.info("Update AI Game Component"); IntStream.range(0, numEntities) - .filter(i -> AI_COMPONENTS.length > i && AI_COMPONENTS[i] != null) - .forEach(i -> AI_COMPONENTS[i].update()); + .filter(i -> aiComponents.length > i && aiComponents[i] != null) + .forEach(i -> aiComponents[i].update()); } } diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java index 61ba4ebdd..155793c88 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java @@ -40,7 +40,7 @@ public class PhysicsComponentManager { private final int numEntities; - private static final Component[] PHYSICS_COMPONENTS = new PhysicsComponent[MAX_ENTITIES]; + private final Component[] physicsComponents = new PhysicsComponent[MAX_ENTITIES]; public PhysicsComponentManager(int numEntities) { this.numEntities = numEntities; @@ -51,7 +51,7 @@ public class PhysicsComponentManager { */ public void start() { LOGGER.info("Start Physics Game Component "); - IntStream.range(0, numEntities).forEach(i -> PHYSICS_COMPONENTS[i] = new PhysicsComponent()); + IntStream.range(0, numEntities).forEach(i -> physicsComponents[i] = new PhysicsComponent()); } @@ -62,7 +62,7 @@ public class PhysicsComponentManager { LOGGER.info("Update Physics Game Component "); // Process physics. IntStream.range(0, numEntities) - .filter(i -> PHYSICS_COMPONENTS.length > i && PHYSICS_COMPONENTS[i] != null) - .forEach(i -> PHYSICS_COMPONENTS[i].update()); + .filter(i -> physicsComponents.length > i && physicsComponents[i] != null) + .forEach(i -> physicsComponents[i].update()); } } diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java index f8c4b3522..be1d3c2e9 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java @@ -40,7 +40,7 @@ public class RenderComponentManager { private final int numEntities; - private static final Component[] RENDER_COMPONENTS = new RenderComponent[MAX_ENTITIES]; + private final Component[] renderComponents = new RenderComponent[MAX_ENTITIES]; public RenderComponentManager(int numEntities) { this.numEntities = numEntities; @@ -51,7 +51,7 @@ public class RenderComponentManager { */ public void start() { LOGGER.info("Start Render Game Component "); - IntStream.range(0, numEntities).forEach(i -> RENDER_COMPONENTS[i] = new RenderComponent()); + IntStream.range(0, numEntities).forEach(i -> renderComponents[i] = new RenderComponent()); } @@ -62,7 +62,7 @@ public class RenderComponentManager { LOGGER.info("Update Render Game Component "); // Process Render. IntStream.range(0, numEntities) - .filter(i -> RENDER_COMPONENTS.length > i && RENDER_COMPONENTS[i] != null) - .forEach(i -> RENDER_COMPONENTS[i].render()); + .filter(i -> renderComponents.length > i && renderComponents[i] != null) + .forEach(i -> renderComponents[i].render()); } } diff --git a/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java b/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java index 3371be4c1..b7d1f8961 100644 --- a/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java +++ b/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java @@ -26,16 +26,22 @@ package com.iluwatar.data.locality; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Test Game Application */ class ApplicationTest { /** - * Test run + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link Application#main(String[])} + * throws an exception. */ + @Test - void main() { - Application.main(new String[] {}); + void shouldExecuteGameApplicationWithoutException() { + assertDoesNotThrow(() -> Application.main(new String[] {})); } } \ No newline at end of file diff --git a/data-mapper/pom.xml b/data-mapper/pom.xml index 64f03a186..cf17de69b 100644 --- a/data-mapper/pom.xml +++ b/data-mapper/pom.xml @@ -28,7 +28,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT data-mapper diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/App.java b/data-mapper/src/main/java/com/iluwatar/datamapper/App.java index 9bfc32952..09c027401 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/App.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/App.java @@ -1,83 +1,83 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.datamapper; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The Data Mapper (DM) is a layer of software that separates the in-memory objects from the - * database. Its responsibility is to transfer data between the two and also to isolate them from - * each other. With Data Mapper the in-memory objects needn't know even that there's a database - * present; they need no SQL interface code, and certainly no knowledge of the database schema. (The - * database schema is always ignorant of the objects that use it.) Since it's a form of Mapper , - * Data Mapper itself is even unknown to the domain layer. - * - *

The below example demonstrates basic CRUD operations: Create, Read, Update, and Delete. - */ -public final class App { - - private static Logger log = LoggerFactory.getLogger(App.class); - private static final String STUDENT_STRING = "App.main(), student : "; - - - /** - * Program entry point. - * - * @param args command line args. - */ - public static void main(final String... args) { - - /* Create new data mapper for type 'first' */ - final var mapper = new StudentDataMapperImpl(); - - /* Create new student */ - var student = new Student(1, "Adam", 'A'); - - /* Add student in respectibe store */ - mapper.insert(student); - - log.debug(STUDENT_STRING + student + ", is inserted"); - - /* Find this student */ - final var studentToBeFound = mapper.find(student.getStudentId()); - - log.debug(STUDENT_STRING + studentToBeFound + ", is searched"); - - /* Update existing student object */ - student = new Student(student.getStudentId(), "AdamUpdated", 'A'); - - /* Update student in respectibe db */ - mapper.update(student); - - log.debug(STUDENT_STRING + student + ", is updated"); - log.debug(STUDENT_STRING + student + ", is going to be deleted"); - - /* Delete student in db */ - mapper.delete(student); - } - - private App() { - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.datamapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Data Mapper (DM) is a layer of software that separates the in-memory objects from the + * database. Its responsibility is to transfer data between the two and also to isolate them from + * each other. With Data Mapper the in-memory objects needn't know even that there's a database + * present; they need no SQL interface code, and certainly no knowledge of the database schema. (The + * database schema is always ignorant of the objects that use it.) Since it's a form of Mapper , + * Data Mapper itself is even unknown to the domain layer. + * + *

The below example demonstrates basic CRUD operations: Create, Read, Update, and Delete. + */ +public final class App { + + private static final Logger log = LoggerFactory.getLogger(App.class); + private static final String STUDENT_STRING = "App.main(), student : "; + + + /** + * Program entry point. + * + * @param args command line args. + */ + public static void main(final String... args) { + + /* Create new data mapper for type 'first' */ + final var mapper = new StudentDataMapperImpl(); + + /* Create new student */ + var student = new Student(1, "Adam", 'A'); + + /* Add student in respectibe store */ + mapper.insert(student); + + log.debug(STUDENT_STRING + student + ", is inserted"); + + /* Find this student */ + final var studentToBeFound = mapper.find(student.getStudentId()); + + log.debug(STUDENT_STRING + studentToBeFound + ", is searched"); + + /* Update existing student object */ + student = new Student(student.getStudentId(), "AdamUpdated", 'A'); + + /* Update student in respectibe db */ + mapper.update(student); + + log.debug(STUDENT_STRING + student + ", is updated"); + log.debug(STUDENT_STRING + student + ", is going to be deleted"); + + /* Delete student in db */ + mapper.delete(student); + } + + private App() { + } +} diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java index 85ad4aa8d..7abe04e3f 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java @@ -33,7 +33,7 @@ import java.util.Optional; public final class StudentDataMapperImpl implements StudentDataMapper { /* Note: Normally this would be in the form of an actual database */ - private List students = new ArrayList<>(); + private final List students = new ArrayList<>(); @Override public Optional find(int studentId) { diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/module-info.java b/data-mapper/src/main/java/com/iluwatar/datamapper/module-info.java deleted file mode 100644 index 7abd78826..000000000 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.datamapper { - requires org.slf4j; -} \ No newline at end of file diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java index ec1d71be4..ab74edd6c 100644 --- a/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java @@ -24,14 +24,25 @@ package com.iluwatar.datamapper; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; /** * Tests that Data-Mapper example runs without errors. */ -public final class AppTest { +final class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void test() { - App.main(); + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow((Executable) App::main); } } diff --git a/data-transfer-object/README.md b/data-transfer-object/README.md index e9286ce03..72e2e240e 100644 --- a/data-transfer-object/README.md +++ b/data-transfer-object/README.md @@ -9,13 +9,16 @@ tags: --- ## Intent -Pass data with multiple attributes in one shot from client to server, to avoid multiple calls to remote server. + +Pass data with multiple attributes in one shot from client to server, to avoid multiple calls to +remote server. ## Explanation Real world example -> We need to fetch information about customers from remote database. Instead of querying the attributes one at a time, we use DTOs to transfer all the relevant attributes in a single shot. +> We need to fetch information about customers from remote database. Instead of querying the +> attributes one at a time, we use DTOs to transfer all the relevant attributes in a single shot. In plain words @@ -23,16 +26,17 @@ In plain words Wikipedia says -> In the field of programming a data transfer object (DTO) is an object that carries data between processes. The -motivation for its use is that communication between processes is usually done resorting to remote interfaces -(e.g., web services), where each call is an expensive operation. Because the majority of the cost of each call is -related to the round-trip time between the client and the server, one way of reducing the number of calls is to use an -object (the DTO) that aggregates the data that would have been transferred by the several calls, but that is served by -one call only. +> In the field of programming a data transfer object (DTO) is an object that carries data between +> processes. The motivation for its use is that communication between processes is usually done +> resorting to remote interfaces (e.g. web services), where each call is an expensive operation. +> Because the majority of the cost of each call is related to the round-trip time between the client +> and the server, one way of reducing the number of calls is to use an object (the DTO) that +> aggregates the data that would have been transferred by the several calls, but that is served by +> one call only. **Programmatic Example** -Let's first introduce our simple customer DTO class. +Let's first introduce our simple `CustomerDTO` class. ```java public class CustomerDto { @@ -60,11 +64,11 @@ public class CustomerDto { } ``` -Customer resource class acts as the server for customer information. +`CustomerResource` class acts as the server for customer information. ```java public class CustomerResource { - private List customers; + private final List customers; public CustomerResource(List customers) { this.customers = customers; @@ -94,10 +98,12 @@ Now fetching customer information is easy since we have the DTOs. ``` ## Class diagram + ![alt text](./etc/data-transfer-object.urm.png "data-transfer-object") ## Applicability -Use the Data Transfer Object pattern when + +Use the Data Transfer Object pattern when: * The client is asking for multiple information. And the information is related. * When you want to boost the performance to get resources. diff --git a/data-transfer-object/pom.xml b/data-transfer-object/pom.xml index 459b1ab1e..2529b3756 100644 --- a/data-transfer-object/pom.xml +++ b/data-transfer-object/pom.xml @@ -28,7 +28,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT data-transfer-object diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerResource.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerResource.java index 7e4b8340d..d0a153f6f 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerResource.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/CustomerResource.java @@ -30,7 +30,7 @@ import java.util.List; * has all customer details. */ public class CustomerResource { - private List customers; + private final List customers; /** * Initialise resource with existing customers. diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/module-info.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/module-info.java deleted file mode 100644 index 25685d4d0..000000000 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.datatransfer { - requires org.slf4j; -} \ No newline at end of file diff --git a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java index 3a58d0c54..68a8b9444 100644 --- a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java +++ b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java @@ -25,9 +25,19 @@ package com.iluwatar.datatransfer; import org.junit.jupiter.api.Test; -public class AppTest { +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + @Test - public void test() throws Exception { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/decorator/README.md b/decorator/README.md index a9dd5d745..7ac0bb94c 100644 --- a/decorator/README.md +++ b/decorator/README.md @@ -10,30 +10,39 @@ tags: --- ## Also known as + Wrapper ## Intent -Attach additional responsibilities to an object dynamically. -Decorators provide a flexible alternative to subclassing for extending -functionality. + +Attach additional responsibilities to an object dynamically. Decorators provide a flexible +alternative to subclassing for extending functionality. ## Explanation Real world example -> There is an angry troll living in the nearby hills. Usually it goes bare handed but sometimes it has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it dynamically with a suitable weapon. +> There is an angry troll living in the nearby hills. Usually it goes bare handed but sometimes it +> has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it +> dynamically with a suitable weapon. In plain words -> Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping them in an object of a decorator class. +> Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping +> them in an object of a decorator class. Wikipedia says -> In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern. +> In object-oriented programming, the decorator pattern is a design pattern that allows behavior to +> be added to an individual object, either statically or dynamically, without affecting the behavior +> of other objects from the same class. The decorator pattern is often useful for adhering to the +> Single Responsibility Principle, as it allows functionality to be divided between classes with +> unique areas of concern. **Programmatic Example** -Let's take the troll example. First of all we have a simple troll implementing the troll interface +Let's take the troll example. First of all we have a `SimpleTroll` implementing the `Troll` +interface: ```java public interface Troll { @@ -63,14 +72,14 @@ public class SimpleTroll implements Troll { } ``` -Next we want to add club for the troll. We can do it dynamically by using a decorator +Next we want to add club for the troll. We can do it dynamically by using a decorator: ```java public class ClubbedTroll implements Troll { private static final Logger LOGGER = LoggerFactory.getLogger(ClubbedTroll.class); - private Troll decorated; + private final Troll decorated; public ClubbedTroll(Troll decorated) { this.decorated = decorated; @@ -94,7 +103,7 @@ public class ClubbedTroll implements Troll { } ``` -Here's the troll in action +Here's the troll in action: ```java // simple troll @@ -108,20 +117,36 @@ clubbedTroll.attack(); // The troll tries to grab you! The troll swings at you w clubbedTroll.fleeBattle(); // The troll shrieks in horror and runs away! ``` +Program output: + +```java +The troll tries to grab you! +The troll shrieks in horror and runs away! +The troll tries to grab you! The troll swings at you with a club! +The troll shrieks in horror and runs away! +``` + ## Class diagram + ![alt text](./etc/decorator.urm.png "Decorator pattern class diagram") ## 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 +Decorator is used 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. ## Tutorial + * [Decorator Pattern Tutorial](https://www.journaldev.com/1540/decorator-design-pattern-in-java-example) ## Real world examples + * [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html), [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) * [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-) diff --git a/decorator/pom.xml b/decorator/pom.xml index c7e1a4d8d..b075704c8 100644 --- a/decorator/pom.xml +++ b/decorator/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT decorator diff --git a/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java b/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java index 70fd15489..74a1434e1 100644 --- a/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java +++ b/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java @@ -33,7 +33,7 @@ public class ClubbedTroll implements Troll { private static final Logger LOGGER = LoggerFactory.getLogger(ClubbedTroll.class); - private Troll decorated; + private final Troll decorated; public ClubbedTroll(Troll decorated) { this.decorated = decorated; diff --git a/decorator/src/main/java/com/iluwatar/decorator/module-info.java b/decorator/src/main/java/com/iluwatar/decorator/module-info.java deleted file mode 100644 index 50d17f022..000000000 --- a/decorator/src/main/java/com/iluwatar/decorator/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.decorator { - requires org.slf4j; -} \ No newline at end of file diff --git a/decorator/src/test/java/com/iluwatar/decorator/AppTest.java b/decorator/src/test/java/com/iluwatar/decorator/AppTest.java index e8d4c8505..792d61233 100644 --- a/decorator/src/test/java/com/iluwatar/decorator/AppTest.java +++ b/decorator/src/test/java/com/iluwatar/decorator/AppTest.java @@ -25,13 +25,22 @@ package com.iluwatar.decorator; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java b/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java index c9f62407c..a398135e6 100644 --- a/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java +++ b/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java @@ -68,7 +68,7 @@ public class SimpleTrollTest { private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); diff --git a/delegation/pom.xml b/delegation/pom.xml index 63cd91842..d7ad81362 100644 --- a/delegation/pom.xml +++ b/delegation/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/delegation/src/main/java/com/iluwatar/delegation/module-info.java b/delegation/src/main/java/com/iluwatar/delegation/module-info.java deleted file mode 100644 index 156477cde..000000000 --- a/delegation/src/main/java/com/iluwatar/delegation/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.delegation { - requires org.slf4j; -} \ No newline at end of file diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java index 2865c76c1..8e20c9032 100644 --- a/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java @@ -25,14 +25,23 @@ package com.iluwatar.delegation.simple; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application Test Entry */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java index 2da1e0571..8aefc4b56 100644 --- a/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java @@ -86,7 +86,7 @@ public class DelegateTest { */ private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); diff --git a/dependency-injection/README.md b/dependency-injection/README.md index abf647b50..e5bf93387 100644 --- a/dependency-injection/README.md +++ b/dependency-injection/README.md @@ -9,15 +9,19 @@ tags: --- ## 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 behavior, which allows program designs to be loosely coupled and -to follow the inversion of control and single responsibility principles. + +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 behavior, +which allows program designs to be loosely coupled and to follow the inversion of control and single +responsibility principles. ## Explanation + Real world example -> The old wizard likes to fill his pipe and smoke tobacco once in a while. However, he doesn't want to depend on a single tobacco brand only but likes to be able to enjoy them all interchangeably. +> The old wizard likes to fill his pipe and smoke tobacco once in a while. However, he doesn't want +> to depend on a single tobacco brand only but likes to be able to enjoy them all interchangeably. In plain words @@ -25,11 +29,12 @@ In plain words Wikipedia says -> In software engineering, dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies. +> In software engineering, dependency injection is a technique in which an object receives other +> objects that it depends on. These other objects are called dependencies. **Programmatic Example** -Let's first introduce the tobacco brands. +Let's first introduce the `Tobacco` interface and the concrete brands. ```java public abstract class Tobacco { @@ -52,7 +57,7 @@ public class OldTobyTobacco extends Tobacco { } ``` -Next here's the wizard class hierarchy. +Next here's the `Wizard` class hierarchy. ```java public interface Wizard { @@ -62,7 +67,7 @@ public interface Wizard { public class AdvancedWizard implements Wizard { - private Tobacco tobacco; + private final Tobacco tobacco; public AdvancedWizard(Tobacco tobacco) { this.tobacco = tobacco; @@ -83,13 +88,15 @@ And lastly we can show how easy it is to give the old wizard any brand of tobacc ``` ## Class diagram + ![alt text](./etc/dependency-injection.png "Dependency Injection") ## 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 +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. ## Credits diff --git a/dependency-injection/pom.xml b/dependency-injection/pom.xml index 9baffe382..f3aaba520 100644 --- a/dependency-injection/pom.xml +++ b/dependency-injection/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT dependency-injection diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedWizard.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedWizard.java index e0c952186..f0ff2da94 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedWizard.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedWizard.java @@ -29,7 +29,7 @@ package com.iluwatar.dependency.injection; */ public class AdvancedWizard implements Wizard { - private Tobacco tobacco; + private final Tobacco tobacco; public AdvancedWizard(Tobacco tobacco) { this.tobacco = tobacco; diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/GuiceWizard.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/GuiceWizard.java index 319a635eb..d769ffd46 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/GuiceWizard.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/GuiceWizard.java @@ -31,7 +31,7 @@ import javax.inject.Inject; */ public class GuiceWizard implements Wizard { - private Tobacco tobacco; + private final Tobacco tobacco; @Inject public GuiceWizard(Tobacco tobacco) { diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SimpleWizard.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SimpleWizard.java index 40bca0ffb..0136ff69f 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SimpleWizard.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SimpleWizard.java @@ -29,7 +29,7 @@ package com.iluwatar.dependency.injection; */ public class SimpleWizard implements Wizard { - private OldTobyTobacco tobacco = new OldTobyTobacco(); + private final OldTobyTobacco tobacco = new OldTobyTobacco(); public void smoke() { tobacco.smoke(this); 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 51115496d..52508814a 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 @@ -25,13 +25,22 @@ package com.iluwatar.dependency.injection; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java index 9d0ad1b3b..d91099af9 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java @@ -37,7 +37,7 @@ import java.util.List; */ public class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); diff --git a/dirty-flag/pom.xml b/dirty-flag/pom.xml index c014cd41e..b796ab37a 100644 --- a/dirty-flag/pom.xml +++ b/dirty-flag/pom.xml @@ -29,11 +29,10 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT - com.iluwatar dirty-flag - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT dirty-flag http://maven.apache.org @@ -45,6 +44,12 @@ junit-jupiter-engine test + + org.mockito + mockito-junit-jupiter + 3.5.0 + test + diff --git a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java index db60924c1..1d4fbfa75 100644 --- a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java +++ b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java @@ -34,7 +34,7 @@ import java.util.List; public class World { private List countries; - private DataFetcher df; + private final DataFetcher df; public World() { this.countries = new ArrayList(); diff --git a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/module-info.java b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/module-info.java deleted file mode 100644 index bf47d2cd7..000000000 --- a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.dirtyflag { - requires org.slf4j; -} \ No newline at end of file diff --git a/dirty-flag/src/test/java/org/dirty/flag/AppTest.java b/dirty-flag/src/test/java/org/dirty/flag/AppTest.java index 1b604898b..82c7fea9b 100644 --- a/dirty-flag/src/test/java/org/dirty/flag/AppTest.java +++ b/dirty-flag/src/test/java/org/dirty/flag/AppTest.java @@ -26,12 +26,22 @@ package org.dirty.flag; import com.iluwatar.dirtyflag.App; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Dirty-Flag example runs without errors. */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java b/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java index 6a3274a45..9af9664d6 100644 --- a/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java +++ b/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java @@ -23,29 +23,27 @@ package org.dirty.flag; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - import com.iluwatar.dirtyflag.DataFetcher; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Application test */ -public class DirtyFlagTest { +class DirtyFlagTest { @Test - public void testIsDirty() { + void testIsDirty() { var df = new DataFetcher(); var countries = df.fetch(); - assertFalse(countries.isEmpty()); + Assertions.assertFalse(countries.isEmpty()); } @Test - public void testIsNotDirty() { + void testIsNotDirty() { var df = new DataFetcher(); df.fetch(); var countries = df.fetch(); - assertTrue(countries.isEmpty()); + Assertions.assertTrue(countries.isEmpty()); } } diff --git a/double-buffer/pom.xml b/double-buffer/pom.xml index 084cbc8c9..cc4032074 100644 --- a/double-buffer/pom.xml +++ b/double-buffer/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 @@ -44,6 +44,11 @@ org.apache.commons commons-lang3 + + org.junit.jupiter + junit-jupiter-engine + test + diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java index 5f683cf1e..4b974a2e8 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java @@ -33,7 +33,7 @@ public class FrameBuffer implements Buffer { public static final int WIDTH = 10; public static final int HEIGHT = 8; - private Pixel[] pixels = new Pixel[WIDTH * HEIGHT]; + private final Pixel[] pixels = new Pixel[WIDTH * HEIGHT]; public FrameBuffer() { clearAll(); diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java index 501797743..54f130b1d 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java @@ -31,7 +31,7 @@ public enum Pixel { WHITE(0), BLACK(1); - private int color; + private final int color; Pixel(int color) { this.color = color; diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java index 2c1503918..8ee72ded4 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java @@ -35,7 +35,7 @@ public class Scene { private static final Logger LOGGER = LoggerFactory.getLogger(Scene.class); - private Buffer[] frameBuffers; + private final Buffer[] frameBuffers; private int current; diff --git a/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java b/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java index eb89a4044..6612d2b00 100644 --- a/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java +++ b/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java @@ -25,14 +25,23 @@ package com.iluwatar.doublebuffer; import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * App unit test. */ public class AppTest { + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + @Test - public void testMain() { - App.main(new String[]{}); + public void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/double-checked-locking/pom.xml b/double-checked-locking/pom.xml index a77546386..04aba2260 100644 --- a/double-checked-locking/pom.xml +++ b/double-checked-locking/pom.xml @@ -27,7 +27,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT double-checked-locking diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/module-info.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/module-info.java deleted file mode 100644 index 4f4216ea7..000000000 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.doublecheckedlocking { - requires org.slf4j; -} \ No newline at end of file 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 6eac88fcd..e24e51094 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 @@ -25,13 +25,23 @@ package com.iluwatar.doublechecked.locking; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } 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 index e8ea7c6f8..fe0cbf5e9 100644 --- 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 @@ -109,7 +109,7 @@ public class InventoryTest { private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); diff --git a/double-dispatch/pom.xml b/double-dispatch/pom.xml index 9582797a2..275b505a5 100644 --- a/double-dispatch/pom.xml +++ b/double-dispatch/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT double-dispatch 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 bd832287c..ea18ca3dc 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java @@ -28,10 +28,10 @@ package com.iluwatar.doubledispatch; */ public class Rectangle { - private int left; - private int top; - private int right; - private int bottom; + private final int left; + private final int top; + private final int right; + private final int bottom; /** * Constructor. diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java index cc61edcdc..5b6535752 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java @@ -45,7 +45,7 @@ public class SpaceStationMir extends GameObject { @Override public void collisionResolve(FlamingAsteroid asteroid) { - LOGGER.info(AppConstants.HITS, " {} is damaged! {} is set on fire!", asteroid.getClass() + LOGGER.info(AppConstants.HITS + " {} is damaged! {} is set on fire!", asteroid.getClass() .getSimpleName(), this.getClass().getSimpleName(), this.getClass().getSimpleName(), this.getClass() .getSimpleName()); @@ -55,14 +55,14 @@ public class SpaceStationMir extends GameObject { @Override public void collisionResolve(Meteoroid meteoroid) { - LOGGER.info(AppConstants.HITS, " {} is damaged!", meteoroid.getClass().getSimpleName(), + LOGGER.info(AppConstants.HITS + " {} is damaged!", meteoroid.getClass().getSimpleName(), this.getClass().getSimpleName(), this.getClass().getSimpleName()); setDamaged(true); } @Override public void collisionResolve(SpaceStationMir mir) { - LOGGER.info(AppConstants.HITS, " {} is damaged!", mir.getClass().getSimpleName(), + LOGGER.info(AppConstants.HITS + " {} is damaged!", mir.getClass().getSimpleName(), this.getClass().getSimpleName(), this.getClass().getSimpleName()); setDamaged(true); } diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/module-info.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/module-info.java deleted file mode 100644 index b1bc2e824..000000000 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.doubledispatch { - requires org.slf4j; -} \ No newline at end of file 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 67ca00c56..e5df7a2be 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java @@ -25,13 +25,23 @@ package com.iluwatar.doubledispatch; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/eip-aggregator/pom.xml b/eip-aggregator/pom.xml index 578d1bbf2..efee153a3 100644 --- a/eip-aggregator/pom.xml +++ b/eip-aggregator/pom.xml @@ -31,7 +31,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT diff --git a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java index ed604e8c2..3da3b3e66 100644 --- a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java +++ b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java @@ -25,13 +25,22 @@ package com.iluwatar.eip.aggregator; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Test for App class */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void testMain() throws Exception { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/eip-message-channel/pom.xml b/eip-message-channel/pom.xml index bea72b1f9..12fe153e3 100644 --- a/eip-message-channel/pom.xml +++ b/eip-message-channel/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT eip-message-channel diff --git a/eip-message-channel/src/main/java/com/iluwatar/eip/message/channel/module-info.java b/eip-message-channel/src/main/java/com/iluwatar/eip/message/channel/module-info.java deleted file mode 100644 index b904ee1c8..000000000 --- a/eip-message-channel/src/main/java/com/iluwatar/eip/message/channel/module-info.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.eipmessagechannel { - requires org.slf4j; - requires camel.core; -} \ No newline at end of file diff --git a/eip-message-channel/src/test/java/com/iluwatar/eip/message/channel/AppTest.java b/eip-message-channel/src/test/java/com/iluwatar/eip/message/channel/AppTest.java index 9f11c0209..14cdc5c65 100644 --- a/eip-message-channel/src/test/java/com/iluwatar/eip/message/channel/AppTest.java +++ b/eip-message-channel/src/test/java/com/iluwatar/eip/message/channel/AppTest.java @@ -25,13 +25,22 @@ package com.iluwatar.eip.message.channel; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void test() throws Exception { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/eip-publish-subscribe/pom.xml b/eip-publish-subscribe/pom.xml index e7b5462b6..f354b1ee3 100644 --- a/eip-publish-subscribe/pom.xml +++ b/eip-publish-subscribe/pom.xml @@ -28,7 +28,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT eip-publish-subscribe diff --git a/eip-publish-subscribe/src/main/java/com/iluwatar/eip/publish/subscribe/module-info.java b/eip-publish-subscribe/src/main/java/com/iluwatar/eip/publish/subscribe/module-info.java deleted file mode 100644 index 50eab8360..000000000 --- a/eip-publish-subscribe/src/main/java/com/iluwatar/eip/publish/subscribe/module-info.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.eippublishsubscribe { - requires org.slf4j; - requires camel.core; -} \ No newline at end of file diff --git a/eip-publish-subscribe/src/test/java/com/iluwatar/eip/publish/subscribe/AppTest.java b/eip-publish-subscribe/src/test/java/com/iluwatar/eip/publish/subscribe/AppTest.java index 107e954ed..f910d0abe 100644 --- a/eip-publish-subscribe/src/test/java/com/iluwatar/eip/publish/subscribe/AppTest.java +++ b/eip-publish-subscribe/src/test/java/com/iluwatar/eip/publish/subscribe/AppTest.java @@ -25,13 +25,22 @@ package com.iluwatar.eip.publish.subscribe; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void test() throws Exception { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/eip-splitter/pom.xml b/eip-splitter/pom.xml index 9c06f3f8d..5b4758a9e 100644 --- a/eip-splitter/pom.xml +++ b/eip-splitter/pom.xml @@ -31,7 +31,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT diff --git a/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java b/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java index 1a7dfcb0a..d5936282e 100644 --- a/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java +++ b/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java @@ -25,13 +25,22 @@ package com.iluwatar.eip.splitter; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Test for App class */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void testMain() throws Exception { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/eip-wire-tap/pom.xml b/eip-wire-tap/pom.xml index 06cbc33db..332861547 100644 --- a/eip-wire-tap/pom.xml +++ b/eip-wire-tap/pom.xml @@ -31,7 +31,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT diff --git a/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java b/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java index 31154043c..8be8bcbe7 100644 --- a/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java +++ b/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java @@ -25,13 +25,22 @@ package com.iluwatar.eip.wiretap; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Test for App class */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void testMain() throws Exception { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/event-aggregator/pom.xml b/event-aggregator/pom.xml index 5553de2e3..813521c48 100644 --- a/event-aggregator/pom.xml +++ b/event-aggregator/pom.xml @@ -28,7 +28,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT event-aggregator diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java index 7a125c042..91bb020ee 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java @@ -31,7 +31,7 @@ public enum Event { STARK_SIGHTED("Stark sighted"), WARSHIPS_APPROACHING("Warships approaching"), TRAITOR_DETECTED( "Traitor detected"); - private String description; + private final String description; Event(String description) { this.description = description; diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java index 9985cee60..7d3f32a68 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java @@ -31,7 +31,7 @@ import java.util.List; */ public abstract class EventEmitter { - private List observers; + private final List observers; public EventEmitter() { observers = new LinkedList<>(); diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java index 9ec61339c..1e0ce9491 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java @@ -36,7 +36,7 @@ public enum Weekday { SATURDAY("Saturday"), SUNDAY("Sunday"); - private String description; + private final String description; Weekday(String description) { this.description = description; diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/module-info.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/module-info.java deleted file mode 100644 index 93ebd3173..000000000 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.eventaggregator { - requires org.slf4j; -} \ No newline at end of file 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 a65424023..e56bc9d7f 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 @@ -25,13 +25,22 @@ package com.iluwatar.event.aggregator; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } 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 index a8bb6cbaa..f8aa5cb37 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java @@ -74,7 +74,7 @@ public class KingJoffreyTest { } private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); diff --git a/event-asynchronous/pom.xml b/event-asynchronous/pom.xml index 001b3b9a8..06921a100 100644 --- a/event-asynchronous/pom.xml +++ b/event-asynchronous/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT event-asynchronous diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java index 6925a2ffd..68c4c9781 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java @@ -33,9 +33,9 @@ public class Event implements IEvent, Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(Event.class); - private int eventId; - private int eventTime; - private boolean isSynchronous; + private final int eventId; + private final int eventTime; + private final boolean isSynchronous; private Thread thread; private boolean isComplete = false; private ThreadCompleteListener eventListener; diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java index 14d28860b..55671fd82 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java +++ b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java @@ -43,8 +43,8 @@ public class EventManager implements ThreadCompleteListener { public static final int MAX_ID = MAX_RUNNING_EVENTS; public static final int MAX_EVENT_TIME = 1800; // in seconds / 30 minutes. private int currentlyRunningSyncEvent = -1; - private Random rand; - private Map eventPool; + private final Random rand; + private final Map eventPool; private static final String DOES_NOT_EXIST = " does not exist."; diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/module-info.java b/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/module-info.java deleted file mode 100644 index aa9b6c29d..000000000 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.eventasynchronous { - requires org.slf4j; -} \ No newline at end of file diff --git a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java b/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java index 76554d6b1..638e77f87 100644 --- a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java +++ b/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java @@ -25,12 +25,22 @@ package com.iluwatar.event.asynchronous; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that EventAsynchronous example runs without errors. */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/event-driven-architecture/pom.xml b/event-driven-architecture/pom.xml index 17d2795c4..c40a8821b 100644 --- a/event-driven-architecture/pom.xml +++ b/event-driven-architecture/pom.xml @@ -31,7 +31,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT event-driven-architecture 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 index c18426c95..dd5e65a9a 100644 --- 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 @@ -32,7 +32,7 @@ import com.iluwatar.eda.model.User; */ public class UserCreatedEvent extends AbstractEvent { - private User user; + private final User user; public UserCreatedEvent(User user) { this.user = 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 index 59583053c..05370c6a6 100644 --- 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 @@ -32,7 +32,7 @@ import com.iluwatar.eda.model.User; */ public class UserUpdatedEvent extends AbstractEvent { - private User user; + private final User user; public UserUpdatedEvent(User user) { this.user = 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 index dd72c1e93..74a7ee145 100644 --- 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 @@ -32,7 +32,7 @@ import java.util.Map; */ public class EventDispatcher { - private Map, Handler> handlers; + private final Map, Handler> handlers; public EventDispatcher() { handlers = new HashMap<>(); 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 index 1492c175c..0c9f12501 100644 --- 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 @@ -32,7 +32,7 @@ import com.iluwatar.eda.event.UserUpdatedEvent; */ public class User { - private String username; + private final String username; public User(String username) { this.username = username; diff --git a/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java b/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java index 0f1720363..eb944a22c 100644 --- a/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java +++ b/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java @@ -25,12 +25,22 @@ package com.iluwatar.eda; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Event Driven Architecture example runs without errors. */ -public class AppTest { +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/event-queue/pom.xml b/event-queue/pom.xml index fd8ce9902..232b9abaa 100644 --- a/event-queue/pom.xml +++ b/event-queue/pom.xml @@ -30,7 +30,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT event-queue diff --git a/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java b/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java index 4286a5ed0..a0ff5d987 100644 --- a/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java +++ b/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java @@ -49,7 +49,7 @@ public class Audio { private volatile Thread updateThread = null; - private PlayMessage[] pendingAudio = new PlayMessage[MAX_PENDING]; + private final PlayMessage[] pendingAudio = new PlayMessage[MAX_PENDING]; // Visible only for testing purposes Audio() { diff --git a/event-sourcing/README.md b/event-sourcing/README.md index 6d24a40e5..06b7ec0c2 100644 --- a/event-sourcing/README.md +++ b/event-sourcing/README.md @@ -24,11 +24,11 @@ Use the Event Sourcing pattern when ## Real world examples -* [The Lmax Architecture] (https://martinfowler.com/articles/lmax.html) +* [The Lmax Architecture](https://martinfowler.com/articles/lmax.html) ## Credits -* [Martin Fowler - Event Sourcing] (https://martinfowler.com/eaaDev/EventSourcing.html) -* [Event Sourcing | Microsoft Docs] (https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing) -* [Reference 3: Introducing Event Sourcing] (https://msdn.microsoft.com/en-us/library/jj591559.aspx) +* [Martin Fowler - Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) +* [Event Sourcing in Microsoft's documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing) +* [Reference 3: Introducing Event Sourcing](https://msdn.microsoft.com/en-us/library/jj591559.aspx) * [Event Sourcing pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing) diff --git a/event-sourcing/pom.xml b/event-sourcing/pom.xml index 0d1592abb..44af5fc5e 100644 --- a/event-sourcing/pom.xml +++ b/event-sourcing/pom.xml @@ -30,7 +30,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT event-sourcing diff --git a/execute-around/README.md b/execute-around/README.md index 2873aef9b..600e41196 100644 --- a/execute-around/README.md +++ b/execute-around/README.md @@ -9,15 +9,18 @@ tags: --- ## 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. + +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. ## Explanation Real world example -> We need to provide a class that can be used to write text strings to files. To make it easy for the user we let our service class open and close the file automatically, the user only has to specify what is written into which file. +> We need to provide a class that can be used to write text strings to files. To make it easy for +> the user we let our service class open and close the file automatically, the user only has to +> specify what is written into which file. In plain words @@ -25,7 +28,9 @@ In plain words [Stack Overflow](https://stackoverflow.com/questions/341971/what-is-the-execute-around-idiom) says -> Basically it's the pattern where you write a method to do things which are always required, e.g. resource allocation and clean-up, and make the caller pass in "what we want to do with the resource". +> Basically it's the pattern where you write a method to do things which are always required, e.g. +> resource allocation and clean-up, and make the caller pass in "what we want to do with the +> resource". **Programmatic Example** @@ -61,12 +66,15 @@ To utilize the file writer the following code is needed. ``` ## Class diagram + ![alt text](./etc/execute-around.png "Execute Around") ## 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. +* You use an API that requires methods to be called in pairs such as open/close or +allocate/deallocate. ## Credits diff --git a/execute-around/pom.xml b/execute-around/pom.xml index 1752f04f5..2d0640fe0 100644 --- a/execute-around/pom.xml +++ b/execute-around/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT execute-around diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/module-info.java b/execute-around/src/main/java/com/iluwatar/execute/around/module-info.java deleted file mode 100644 index a3e179094..000000000 --- a/execute-around/src/main/java/com/iluwatar/execute/around/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.executearound { - requires org.slf4j; -} \ No newline at end of file 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 5512ba8d2..6516ede4a 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 @@ -29,19 +29,21 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests execute-around example. */ -public class AppTest { +class AppTest { @Test - public void test() throws IOException { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } @BeforeEach @AfterEach - public void cleanup() { + void cleanup() { var file = new File("testfile.txt"); file.delete(); } diff --git a/extension-objects/pom.xml b/extension-objects/pom.xml index 0194357ed..7247a9676 100644 --- a/extension-objects/pom.xml +++ b/extension-objects/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/extension-objects/src/main/java/concreteextensions/Commander.java b/extension-objects/src/main/java/concreteextensions/Commander.java index 5a0552b20..ffd46dd25 100644 --- a/extension-objects/src/main/java/concreteextensions/Commander.java +++ b/extension-objects/src/main/java/concreteextensions/Commander.java @@ -35,7 +35,7 @@ public class Commander implements CommanderExtension { private static final Logger LOGGER = LoggerFactory.getLogger(Commander.class); - private CommanderUnit unit; + private final CommanderUnit unit; public Commander(CommanderUnit commanderUnit) { this.unit = commanderUnit; @@ -45,4 +45,8 @@ public class Commander implements CommanderExtension { public void commanderReady() { LOGGER.info("[Commander] " + unit.getName() + " is ready!"); } + + public CommanderUnit getUnit() { + return unit; + } } diff --git a/extension-objects/src/main/java/concreteextensions/Sergeant.java b/extension-objects/src/main/java/concreteextensions/Sergeant.java index a45b82f11..aea2ddaca 100644 --- a/extension-objects/src/main/java/concreteextensions/Sergeant.java +++ b/extension-objects/src/main/java/concreteextensions/Sergeant.java @@ -35,7 +35,7 @@ public class Sergeant implements SergeantExtension { private static final Logger LOGGER = LoggerFactory.getLogger(Sergeant.class); - private SergeantUnit unit; + private final SergeantUnit unit; public Sergeant(SergeantUnit sergeantUnit) { this.unit = sergeantUnit; @@ -43,6 +43,10 @@ public class Sergeant implements SergeantExtension { @Override public void sergeantReady() { - LOGGER.info("[Sergeant] " + unit.getName() + " is ready! "); + LOGGER.info("[Sergeant] " + unit.getName() + " is ready!"); + } + + public SergeantUnit getUnit() { + return unit; } } diff --git a/extension-objects/src/main/java/concreteextensions/Soldier.java b/extension-objects/src/main/java/concreteextensions/Soldier.java index b47ba595d..3ceaa7880 100644 --- a/extension-objects/src/main/java/concreteextensions/Soldier.java +++ b/extension-objects/src/main/java/concreteextensions/Soldier.java @@ -34,7 +34,7 @@ import units.SoldierUnit; public class Soldier implements SoldierExtension { private static final Logger LOGGER = LoggerFactory.getLogger(Soldier.class); - private SoldierUnit unit; + private final SoldierUnit unit; public Soldier(SoldierUnit soldierUnit) { this.unit = soldierUnit; @@ -42,6 +42,10 @@ public class Soldier implements SoldierExtension { @Override public void soldierReady() { - LOGGER.info("[Solider] " + unit.getName() + " is ready!"); + LOGGER.info("[Soldier] " + unit.getName() + " is ready!"); + } + + public SoldierUnit getUnit() { + return unit; } } diff --git a/extension-objects/src/test/java/AppTest.java b/extension-objects/src/test/java/AppTest.java index 2af33e506..321bf758f 100644 --- a/extension-objects/src/test/java/AppTest.java +++ b/extension-objects/src/test/java/AppTest.java @@ -23,13 +23,16 @@ import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Created by Srdjan on 03-May-17. */ -public class AppTest { +class AppTest { + @Test - public void main() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } \ No newline at end of file diff --git a/extension-objects/src/test/java/concreteextensions/CommanderTest.java b/extension-objects/src/test/java/concreteextensions/CommanderTest.java index 60ff614e4..0c9d00baf 100644 --- a/extension-objects/src/test/java/concreteextensions/CommanderTest.java +++ b/extension-objects/src/test/java/concreteextensions/CommanderTest.java @@ -23,17 +23,43 @@ package concreteextensions; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; import units.CommanderUnit; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + /** * Created by Srdjan on 03-May-17. + * + * Modified by ToxicDreamz on 15-Aug-20 */ -public class CommanderTest { +class CommanderTest { + @Test - public void commanderReady() { + void shouldExecuteCommanderReady() { + + Logger commanderLogger = (Logger) LoggerFactory.getLogger(Commander.class); + + ListAppender listAppender = new ListAppender<>(); + listAppender.start(); + + commanderLogger.addAppender(listAppender); + final var commander = new Commander(new CommanderUnit("CommanderUnitTest")); commander.commanderReady(); + + List logsList = listAppender.list; + assertEquals("[Commander] " + commander.getUnit().getName() + " is ready!", logsList.get(0) + .getMessage()); + assertEquals(Level.INFO, logsList.get(0) + .getLevel()); } } \ No newline at end of file diff --git a/extension-objects/src/test/java/concreteextensions/SergeantTest.java b/extension-objects/src/test/java/concreteextensions/SergeantTest.java index a5a60d914..3970a5c80 100644 --- a/extension-objects/src/test/java/concreteextensions/SergeantTest.java +++ b/extension-objects/src/test/java/concreteextensions/SergeantTest.java @@ -23,17 +23,41 @@ package concreteextensions; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; import units.SergeantUnit; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + /** * Created by Srdjan on 03-May-17. */ -public class SergeantTest { +class SergeantTest { + @Test - public void sergeantReady() { + void sergeantReady() { + + Logger sergeantLogger = (Logger) LoggerFactory.getLogger(Sergeant.class); + + ListAppender listAppender = new ListAppender<>(); + listAppender.start(); + + sergeantLogger.addAppender(listAppender); + final var sergeant = new Sergeant(new SergeantUnit("SergeantUnitTest")); sergeant.sergeantReady(); + + List logsList = listAppender.list; + assertEquals("[Sergeant] " + sergeant.getUnit().getName() + " is ready!", logsList.get(0) + .getMessage()); + assertEquals(Level.INFO, logsList.get(0) + .getLevel()); } } \ No newline at end of file diff --git a/extension-objects/src/test/java/concreteextensions/SoldierTest.java b/extension-objects/src/test/java/concreteextensions/SoldierTest.java index 89c8c2d91..98224e848 100644 --- a/extension-objects/src/test/java/concreteextensions/SoldierTest.java +++ b/extension-objects/src/test/java/concreteextensions/SoldierTest.java @@ -23,17 +23,41 @@ package concreteextensions; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; import units.SoldierUnit; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + /** * Created by Srdjan on 03-May-17. */ -public class SoldierTest { +class SoldierTest { + @Test - public void soldierReady() { + void soldierReady() { + + Logger soldierLogger = (Logger) LoggerFactory.getLogger(Soldier.class); + + ListAppender listAppender = new ListAppender<>(); + listAppender.start(); + + soldierLogger.addAppender(listAppender); + final var soldier = new Soldier(new SoldierUnit("SoldierUnitTest")); soldier.soldierReady(); + + List logsList = listAppender.list; + assertEquals("[Soldier] " + soldier.getUnit().getName() + " is ready!", logsList.get(0) + .getMessage()); + assertEquals(Level.INFO, logsList.get(0) + .getLevel()); } } \ No newline at end of file diff --git a/facade/README.md b/facade/README.md index 018c493a7..f6765e325 100644 --- a/facade/README.md +++ b/facade/README.md @@ -10,14 +10,18 @@ tags: --- ## 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. + +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. ## Explanation Real world example -> How does a goldmine work? "Well, the miners go down there and dig gold!" you say. That is what you believe because you are using a simple interface that goldmine provides on the outside, internally it has to do a lot of stuff to make it happen. This simple interface to the complex subsystem is a facade. +> How does a goldmine work? "Well, the miners go down there and dig gold!" you say. That is what you +> believe because you are using a simple interface that goldmine provides on the outside, internally +> it has to do a lot of stuff to make it happen. This simple interface to the complex subsystem is a +> facade. In plain words @@ -25,11 +29,13 @@ In plain words Wikipedia says -> A facade is an object that provides a simplified interface to a larger body of code, such as a class library. +> A facade is an object that provides a simplified interface to a larger body of code, such as a +> class library. **Programmatic Example** -Taking our goldmine example from above. Here we have the dwarven mine worker hierarchy +Let's take our goldmine example from above. Here we have the dwarven mine worker hierarchy. First +there's a base class `DwarvenMineWorker`: ```java public abstract class DwarvenMineWorker { @@ -83,11 +89,16 @@ public abstract class DwarvenMineWorker { public abstract String name(); - static enum Action { + enum Action { GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK } } +``` +Then we have the concrete dwarf classes `DwarvenTunnelDigger`, `DwarvenGoldDigger` and +`DwarvenCartOperator`: + +```java public class DwarvenTunnelDigger extends DwarvenMineWorker { private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenTunnelDigger.class); @@ -135,7 +146,7 @@ public class DwarvenCartOperator extends DwarvenMineWorker { ``` -To operate all these goldmine workers we have the facade +To operate all these goldmine workers we have the `DwarvenGoldmineFacade`: ```java public class DwarvenGoldmineFacade { @@ -168,22 +179,27 @@ public class DwarvenGoldmineFacade { } ``` -Now to use the facade +Now let's use the facade: ```java -DwarvenGoldmineFacade facade = new DwarvenGoldmineFacade(); +var facade = new DwarvenGoldmineFacade(); facade.startNewDay(); +facade.digOutGold(); +facade.endDay(); +``` + +Program output: + +```java // Dwarf gold digger wakes up. // Dwarf gold digger goes to the mine. // Dwarf cart operator wakes up. // Dwarf cart operator goes to the mine. // Dwarven tunnel digger wakes up. // Dwarven tunnel digger goes to the mine. -facade.digOutGold(); // Dwarf gold digger digs for gold. // Dwarf cart operator moves gold chunks out of the mine. // Dwarven tunnel digger creates another promising tunnel. -facade.endDay(); // Dwarf gold digger goes home. // Dwarf gold digger goes to sleep. // Dwarf cart operator goes home. @@ -193,14 +209,25 @@ facade.endDay(); ``` ## Class diagram + ![alt text](./etc/facade.urm.png "Facade pattern class diagram") ## 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, then you can simplify the dependencies between them by making them communicate with each other solely through their facades. +* 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 customization 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, then you can simplify the dependencies between them by making them +communicate with each other solely through their facades. ## Credits diff --git a/facade/pom.xml b/facade/pom.xml index a7fdb88f0..cf73e6a43 100644 --- a/facade/pom.xml +++ b/facade/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT facade diff --git a/facade/src/main/java/com/iluwatar/facade/module-info.java b/facade/src/main/java/com/iluwatar/facade/module-info.java deleted file mode 100644 index 966758790..000000000 --- a/facade/src/main/java/com/iluwatar/facade/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.facade { - requires org.slf4j; -} \ No newline at end of file diff --git a/facade/src/test/java/com/iluwatar/facade/AppTest.java b/facade/src/test/java/com/iluwatar/facade/AppTest.java index b6287b02b..7e2d389dc 100644 --- a/facade/src/test/java/com/iluwatar/facade/AppTest.java +++ b/facade/src/test/java/com/iluwatar/facade/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.facade; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java b/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java index 3b67f3754..10d6e1ecd 100644 --- a/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java +++ b/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java @@ -110,7 +110,7 @@ public class DwarvenGoldmineFacadeTest { private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); diff --git a/factory-kit/pom.xml b/factory-kit/pom.xml index 87f27b341..987bbdb24 100644 --- a/factory-kit/pom.xml +++ b/factory-kit/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT factory-kit diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/module-info.java b/factory-kit/src/main/java/com/iluwatar/factorykit/module-info.java deleted file mode 100644 index 9440571c4..000000000 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.factorykit { - requires org.slf4j; -} \ No newline at end of file diff --git a/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java b/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java index f1d3c65a2..99477aaf0 100644 --- a/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java +++ b/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java @@ -26,14 +26,16 @@ package com.iluwatar.factorykit.app; import com.iluwatar.factorykit.App; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application Test Entrypoint */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/factory-method/README.md b/factory-method/README.md index 206388537..180ddb868 100644 --- a/factory-method/README.md +++ b/factory-method/README.md @@ -10,17 +10,20 @@ tags: --- ## Also known as + Virtual Constructor ## 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. + +Define an interface for creating an object, but let subclasses decide which class to instantiate. +Factory Method lets a class defer instantiation to subclasses. ## Explanation + Real world example -> Blacksmith manufactures weapons. Elves require Elvish weapons and orcs require Orcish weapons. Depending on the customer at hand the right type of blacksmith is summoned. +> Blacksmith manufactures weapons. Elves require Elvish weapons and orcs require Orcish weapons. +> Depending on the customer at hand the right type of blacksmith is summoned. In plain words @@ -28,11 +31,16 @@ In plain words Wikipedia says -> In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor. +> In class-based programming, the factory method pattern is a creational pattern that uses factory +> methods to deal with the problem of creating objects without having to specify the exact class of +> the object that will be created. This is done by creating objects by calling a factory method +> — either specified in an interface and implemented by child classes, or implemented in a base +> class and optionally overridden by derived classes—rather than by calling a constructor. **Programmatic Example** -Taking our blacksmith example above. First of all we have a blacksmith interface and some implementations for it +Taking our blacksmith example above. First of all we have a `Blacksmith` interface and some +implementations for it: ```java public interface Blacksmith { @@ -52,24 +60,33 @@ public class OrcBlacksmith implements Blacksmith { } ``` -Now as the customers come the correct type of blacksmith is summoned and requested weapons are manufactured +When the customers come, the correct type of blacksmith is summoned and requested weapons are +manufactured: ```java var blacksmith = new ElfBlacksmith(); blacksmith.manufactureWeapon(WeaponType.SPEAR); blacksmith.manufactureWeapon(WeaponType.AXE); -// Elvish weapons are created +``` + +Program output: +```java +// Elven spear +// Elven axe ``` ## Class diagram + ![alt text](./etc/factory-method.urm.png "Factory Method pattern class diagram") ## 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 +Use the Factory Method pattern when: + +* Class cannot anticipate the class of objects it must create. +* 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. ## Real world examples diff --git a/factory-method/pom.xml b/factory-method/pom.xml index 5f0358d4d..c49fae691 100644 --- a/factory-method/pom.xml +++ b/factory-method/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT factory-method diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java b/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java index b6f29e43a..99ebcef65 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java @@ -32,7 +32,7 @@ import java.util.Map; */ public class ElfBlacksmith implements Blacksmith { - private static Map ELFARSENAL; + private static final Map ELFARSENAL; static { ELFARSENAL = new HashMap<>(WeaponType.values().length); diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java b/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java index 66a6ea7e7..208dfa277 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java @@ -28,7 +28,7 @@ package com.iluwatar.factory.method; */ public class ElfWeapon implements Weapon { - private WeaponType weaponType; + private final WeaponType weaponType; public ElfWeapon(WeaponType weaponType) { this.weaponType = weaponType; diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java b/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java index b04830085..ea99200de 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java @@ -32,7 +32,7 @@ import java.util.Map; */ public class OrcBlacksmith implements Blacksmith { - private static Map ORCARSENAL; + private static final Map ORCARSENAL; static { ORCARSENAL = new HashMap<>(WeaponType.values().length); diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java b/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java index b35adf798..af1ee5bcf 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java @@ -28,7 +28,7 @@ package com.iluwatar.factory.method; */ public class OrcWeapon implements Weapon { - private WeaponType weaponType; + private final WeaponType weaponType; public OrcWeapon(WeaponType weaponType) { this.weaponType = weaponType; diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java b/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java index 73ab10dd6..6c7c86712 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java @@ -30,7 +30,7 @@ public enum WeaponType { SHORT_SWORD("short sword"), SPEAR("spear"), AXE("axe"), UNDEFINED(""); - private String title; + private final String title; WeaponType(String title) { this.title = title; diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/module-info.java b/factory-method/src/main/java/com/iluwatar/factory/method/module-info.java deleted file mode 100644 index 4ea385c8b..000000000 --- a/factory-method/src/main/java/com/iluwatar/factory/method/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.factorymethod { - requires org.slf4j; -} \ No newline at end of file diff --git a/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java b/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java index c23295d9a..8756ba0aa 100644 --- a/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java +++ b/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java @@ -25,12 +25,15 @@ package com.iluwatar.factory.method; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Factory Method example runs without errors. */ -public class AppTest { +class AppTest { + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/factory/README.md b/factory/README.md new file mode 100644 index 000000000..a1b6208fd --- /dev/null +++ b/factory/README.md @@ -0,0 +1,133 @@ +--- +layout: pattern +title: Factory +folder: factory +permalink: /patterns/factory/ +categories: Creational +tags: + - Gang of Four +--- + +## Also known as + +* Simple Factory +* Static Factory Method + +## Intent + +Providing a static method encapsulated in a class called factory, in order to hide the +implementation logic and makes client code focus on usage rather then initialization new objects. + +## Explanation + +Real world example + +> Lets say we have a web application connected to SQLServer, but now we want to switch to Oracle. To +> do so without modifying existing source code, we need to implements Simple Factory pattern, in +> which a static method can be invoked to create connection to a given database. + +Wikipedia says + +> Factory is an object for creating other objects – formally a factory is a function or method that +> returns objects of a varying prototype or class. + +**Programmatic Example** + +We have an interface `Car` and two implementations `Ford` and `Ferrari`. + +```java +public interface Car { + String getDescription(); +} + +public class Ford implements Car { + + static final String DESCRIPTION = "This is Ford."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} + +public class Ferrari implements Car { + + static final String DESCRIPTION = "This is Ferrari."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} +``` + +Enumeration above represents types of cars that we support (`Ford` and `Ferrari`). + +```java +public enum CarType { + + FORD(Ford::new), + FERRARI(Ferrari::new); + + private final Supplier constructor; + + CarType(Supplier constructor) { + this.constructor = constructor; + } + + public Supplier getConstructor() { + return this.constructor; + } +} +``` +Then we have the static method `getCar` to create car objects encapsulated in the factory class +`CarSimpleFactory`. + +```java +public class CarsFactory { + + public static Car getCar(CarType type) { + return type.getConstructor().get(); + } +} +``` + +Now on the client code we can create different types of cars using the factory class. + +```java +var car1 = CarsFactory.getCar(CarType.FORD); +var car2 = CarsFactory.getCar(CarType.FERRARI); +LOGGER.info(car1.getDescription()); +LOGGER.info(car2.getDescription());; +``` + +Program output: + +```java +This is Ford. +This Ferrari. +``` + +## Class Diagram + +![alt text](./etc/factory.urm.png "Factory pattern class diagram") + +## Applicability + +Use the Simple Factory pattern when you only care about the creation of a object, not how to create +and manage it. + +Pros + +* Allows keeping all objects creation in one place and avoid of spreading 'new' key value across codebase. +* Allows to writs loosely coupled code. Some of its main advantages include better testability, easy-to-understand code, swappable components, scalability and isolated features. + +Cons + +* The code becomes more complicated than it should be. + +## Related patterns + +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/) +* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/) +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/) diff --git a/factory/etc/factory.urm.png b/factory/etc/factory.urm.png new file mode 100644 index 000000000..2ba39a571 Binary files /dev/null and b/factory/etc/factory.urm.png differ diff --git a/factory/etc/factory.urm.puml b/factory/etc/factory.urm.puml new file mode 100644 index 000000000..9eded6328 --- /dev/null +++ b/factory/etc/factory.urm.puml @@ -0,0 +1,35 @@ +@startuml +package com.iluwatar.factory { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + interface Car { + + getDescription() : String {abstract} + } + class CarsFactory { + + CarsFactory() + + getCar(type : CarType) : Car {static} + } + ~enum CarType { + + FERRARI {static} + + FORD {static} + + valueOf(name : String) : CarType {static} + + values() : CarType[] {static} + } + class Ferrari { + ~ DESCRIPTION : String {static} + + Ferrari() + + getDescription() : String + } + class Ford { + ~ DESCRIPTION : String {static} + + Ford() + + getDescription() : String + } +} +CarType ..+ CarsFactory +Ferrari ..|> Car +Ford ..|> Car +@enduml \ No newline at end of file diff --git a/factory/pom.xml b/factory/pom.xml new file mode 100644 index 000000000..17c6863af --- /dev/null +++ b/factory/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.24.0-SNAPSHOT + + factory + + + org.junit.jupiter + junit-jupiter-engine + test + + + junit + junit + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.factory.App + + + + + + + + + \ No newline at end of file diff --git a/factory/src/main/java/com/iluwatar/factory/App.java b/factory/src/main/java/com/iluwatar/factory/App.java new file mode 100644 index 000000000..22c50d86d --- /dev/null +++ b/factory/src/main/java/com/iluwatar/factory/App.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.factory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory is an object for creating other objects, it providing Providing a static method to + * create and return objects of varying classes, in order to hide the implementation logic + * and makes client code focus on usage rather then objects initialization and management. + * + *

In this example the CarFactory is the factory class and it provides a static method to + * create different cars. + */ + +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Program main entry point. + */ + public static void main(String[] args) { + var car1 = CarsFactory.getCar(CarType.FORD); + var car2 = CarsFactory.getCar(CarType.FERRARI); + LOGGER.info(car1.getDescription()); + LOGGER.info(car2.getDescription()); + } +} diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/module-info.java b/factory/src/main/java/com/iluwatar/factory/Car.java similarity index 90% rename from abstract-document/src/main/java/com/iluwatar/abstractdocument/module-info.java rename to factory/src/main/java/com/iluwatar/factory/Car.java index 9121f0049..22b615342 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/module-info.java +++ b/factory/src/main/java/com/iluwatar/factory/Car.java @@ -21,6 +21,13 @@ * THE SOFTWARE. */ -module com.iluwatar.abstractdocument { - requires org.slf4j; -} \ No newline at end of file +package com.iluwatar.factory; + +/** + * Car interface. + */ +public interface Car { + + String getDescription(); + +} diff --git a/factory/src/main/java/com/iluwatar/factory/CarType.java b/factory/src/main/java/com/iluwatar/factory/CarType.java new file mode 100644 index 000000000..4855cb89b --- /dev/null +++ b/factory/src/main/java/com/iluwatar/factory/CarType.java @@ -0,0 +1,45 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.factory; + +import java.util.function.Supplier; + +public enum CarType { + + /** + * Enumeration for different types of cars. + */ + FORD(Ford::new), + FERRARI(Ferrari::new); + + private final Supplier constructor; + + CarType(Supplier constructor) { + this.constructor = constructor; + } + + public Supplier getConstructor() { + return this.constructor; + } +} diff --git a/factory/src/main/java/com/iluwatar/factory/CarsFactory.java b/factory/src/main/java/com/iluwatar/factory/CarsFactory.java new file mode 100644 index 000000000..0274ecc2e --- /dev/null +++ b/factory/src/main/java/com/iluwatar/factory/CarsFactory.java @@ -0,0 +1,37 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.factory; + +/** + * Factory of cars. + */ +public class CarsFactory { + + /** + * Factory method takes as parameter a car type and initiate the appropriate class. + */ + public static Car getCar(CarType type) { + return type.getConstructor().get(); + } +} diff --git a/factory/src/main/java/com/iluwatar/factory/Ferrari.java b/factory/src/main/java/com/iluwatar/factory/Ferrari.java new file mode 100644 index 000000000..03a91ec28 --- /dev/null +++ b/factory/src/main/java/com/iluwatar/factory/Ferrari.java @@ -0,0 +1,37 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.factory; + +/** + * Ferrari implementation. + */ +public class Ferrari implements Car { + + static final String DESCRIPTION = "This is Ferrari."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} diff --git a/factory/src/main/java/com/iluwatar/factory/Ford.java b/factory/src/main/java/com/iluwatar/factory/Ford.java new file mode 100644 index 000000000..9a94bb04f --- /dev/null +++ b/factory/src/main/java/com/iluwatar/factory/Ford.java @@ -0,0 +1,37 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.factory; + +/** + * Ford implementation. + */ +public class Ford implements Car { + + static final String DESCRIPTION = "This is Ford."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} diff --git a/factory/src/test/java/com/iluwatar/factory/AppTest.java b/factory/src/test/java/com/iluwatar/factory/AppTest.java new file mode 100644 index 000000000..1bc16a734 --- /dev/null +++ b/factory/src/test/java/com/iluwatar/factory/AppTest.java @@ -0,0 +1,37 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.factory; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class AppTest { + + @Test + void shouldExecuteWithoutExceptions() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } + +} diff --git a/factory/src/test/java/com/iluwatar/factory/CarsFactoryTest.java b/factory/src/test/java/com/iluwatar/factory/CarsFactoryTest.java new file mode 100644 index 000000000..f69bd1632 --- /dev/null +++ b/factory/src/test/java/com/iluwatar/factory/CarsFactoryTest.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.factory; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class CarsFactoryTest { + + @Test + void shouldReturnFerrariInstance() { + final var ferrari = CarsFactory.getCar(CarType.FERRARI); + assertTrue(ferrari instanceof Ferrari); + } + +} diff --git a/feature-toggle/pom.xml b/feature-toggle/pom.xml index 13f646b80..1818d882d 100644 --- a/feature-toggle/pom.xml +++ b/feature-toggle/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/module-info.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/module-info.java deleted file mode 100644 index 55c2d7714..000000000 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module com.iluwatar.featuretoggle { - requires org.slf4j; -} \ No newline at end of file diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java index 6e2281b9a..ed6e69518 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java @@ -42,7 +42,7 @@ import java.util.Properties; */ public class PropertiesFeatureToggleVersion implements Service { - private boolean isEnhanced; + private final boolean isEnhanced; /** * Creates an instance of {@link PropertiesFeatureToggleVersion} using the passed {@link diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/User.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/User.java index 5c660ca59..7924f86e8 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/User.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/User.java @@ -29,7 +29,7 @@ package com.iluwatar.featuretoggle.user; */ public class User { - private String name; + private final String name; /** * Default Constructor setting the username. diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/UserGroup.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/UserGroup.java index 524ea6ef8..7b644afd7 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/UserGroup.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/UserGroup.java @@ -35,8 +35,8 @@ import java.util.List; */ public class UserGroup { - private static List freeGroup = new ArrayList<>(); - private static List paidGroup = new ArrayList<>(); + private static final List freeGroup = new ArrayList<>(); + private static final List paidGroup = new ArrayList<>(); /** diff --git a/filterer/README.md b/filterer/README.md new file mode 100644 index 000000000..89dc87e84 --- /dev/null +++ b/filterer/README.md @@ -0,0 +1,239 @@ +--- +layout: pattern +title: Filterer +folder: filterer +permalink: /patterns/filterer/ +description: Design pattern that helps container-like objects to return filtered version of themselves.# short meta description that shows in Google search results +categories: + - Functional +tags: + - Extensibility +--- + +## Name / classification + +Filterer + +## Intent + +The intent of this design pattern is to introduce a functional interface that will add a +functionality for container-like objects to easily return filtered versions of themselves. + +## Explanation + +Real world example + +> We are designing a threat (malware) detection software which can analyze target systems for +> threats that are present in it. In the design we have to take into consideration that new +> Threat types can be added later. Additionally, there is a requirement that the threat detection +> system can filter the detected threats based on different criteria (the target system acts as +> container-like object for threats). + +In plain words + +> Filterer pattern is a design pattern that helps container-like objects return filtered versions +> of themselves. + +**Programmatic Example** + +To model the threat detection example presented above we introduce `Threat` and `ThreatAwareSystem` +interfaces. + +```java +public interface Threat { + String name(); + int id(); + ThreatType type(); +} + +public interface ThreatAwareSystem { + String systemId(); + List threats(); + Filterer filtered(); + +} +``` + +Notice the `filtered` method that returns instance of `Filterer` interface which is defined as: + +```java +@FunctionalInterface +public interface Filterer { + G by(Predicate predicate); +} +``` + +It is used to fulfill the requirement for system to be able to filter itself based on threat +properties. The container-like object (`ThreatAwareSystem` in our case) needs to have a method that +returns an instance of `Filterer`. This helper interface gives ability to covariantly specify a +lower bound of contravariant `Predicate` in the subinterfaces of interfaces representing the +container-like objects. + +In our example we will be able to pass a predicate that takes `? extends Threat` object and +return `? extends ThreatAwareSystem` from `Filtered::by` method. A simple implementation +of `ThreatAwareSystem`: + +```java +public class SimpleThreatAwareSystem implements ThreatAwareSystem { + + private final String systemId; + private final ImmutableList issues; + + public SimpleThreatAwareSystem(final String systemId, final List issues) { + this.systemId = systemId; + this.issues = ImmutableList.copyOf(issues); + } + + @Override + public String systemId() { + return systemId; + } + + @Override + public List threats() { + return new ArrayList<>(issues); + } + + @Override + public Filterer filtered() { + return this::filteredGroup; + } + + private ThreatAwareSystem filteredGroup(Predicate predicate) { + return new SimpleThreatAwareSystem(this.systemId, filteredItems(predicate)); + } + + private List filteredItems(Predicate predicate) { + return this.issues.stream() + .filter(predicate) + .collect(Collectors.toList()); + } +} +``` + +The `filtered` method is overridden to filter the threats list by given predicate. + +Now if we introduce a new subtype of `Threat` interface that adds probability with which given +threat can appear: + +```java +public interface ProbableThreat extends Threat { + double probability(); +} +``` + +We can also introduce a new interface that represents a system that is aware of threats with their +probabilities: + +````java +public interface ProbabilisticThreatAwareSystem extends ThreatAwareSystem { + @Override + List threats(); + + @Override + Filterer filtered(); +} +```` + +Notice how we override the `filtered` method in `ProbabilisticThreatAwareSystem` and specify +different return covariant type by specifying different generic types. Our interfaces are clean and +not cluttered by default implementations. We we will be able to filter +`ProbabilisticThreatAwareSystem` by `ProbableThreat` properties: + +```java +public class SimpleProbabilisticThreatAwareSystem implements ProbabilisticThreatAwareSystem { + + private final String systemId; + private final ImmutableList threats; + + public SimpleProbabilisticThreatAwareSystem(final String systemId, final List threats) { + this.systemId = systemId; + this.threats = ImmutableList.copyOf(threats); + } + + @Override + public String systemId() { + return systemId; + } + + @Override + public List threats() { + return threats; + } + + @Override + public Filterer filtered() { + return this::filteredGroup; + } + + private ProbabilisticThreatAwareSystem filteredGroup(final Predicate predicate) { + return new SimpleProbabilisticThreatAwareSystem(this.systemId, filteredItems(predicate)); + } + + private List filteredItems(final Predicate predicate) { + return this.threats.stream() + .filter(predicate) + .collect(Collectors.toList()); + } +} +``` + +Now if we want filter `ThreatAwareSystem` by threat type we can do: + +```java +Threat rootkit = new SimpleThreat(ThreatType.ROOTKIT, 1, "Simple-Rootkit"); +Threat trojan = new SimpleThreat(ThreatType.TROJAN, 2, "Simple-Trojan"); +List threats = List.of(rootkit, trojan); + +ThreatAwareSystem threatAwareSystem = new SimpleThreatAwareSystem("System-1", threats); + +ThreatAwareSystem rootkitThreatAwareSystem = threatAwareSystem.filtered() + .by(threat -> threat.type() == ThreatType.ROOTKIT); +``` + +Or if we want to filter `ProbabilisticThreatAwareSystem`: + +```java +ProbableThreat malwareTroyan = new SimpleProbableThreat("Troyan-ArcBomb", 1, ThreatType.TROJAN, 0.99); +ProbableThreat rootkit = new SimpleProbableThreat("Rootkit-System", 2, ThreatType.ROOTKIT, 0.8); +List probableThreats = List.of(malwareTroyan, rootkit); + +ProbabilisticThreatAwareSystem simpleProbabilisticThreatAwareSystem =new SimpleProbabilisticThreatAwareSystem("System-1", probableThreats); + +ProbabilisticThreatAwareSystem filtered = simpleProbabilisticThreatAwareSystem.filtered() + .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); +``` + +## Class diagram + +![Filterer](./etc/filterer.png "Filterer") + +## Applicability + +Pattern can be used when working with container-like objects that use subtyping, instead of +parametrizing (generics) for extensible class structure. It enables you to easily extend filtering +ability of container-like objects as business requirements change. + +## Tutorials + +* [Article about Filterer pattern posted on it's author's blog](https://blog.tlinkowski.pl/2018/filterer-pattern/) +* [Application of Filterer pattern in domain of text analysis](https://www.javacodegeeks.com/2019/02/filterer-pattern-10-steps.html) + +## Known uses + +One of the uses is present on the blog presented in +[this](https://www.javacodegeeks.com/2019/02/filterer-pattern-10-steps.html) link. It presents how +to use `Filterer` pattern to create text issue analyzer with support for test cases used for unit +testing. + +## Consequences + +Pros: + * You can easily introduce new subtypes for container-like objects and subtypes for objects that are contained within them and still be able to filter easily be new properties of those new subtypes. + +Cons: + * Covariant return types mixed with generics can be sometimes tricky + +## Credits + +* Author of the pattern : [Tomasz Linkowski](https://tlinkowski.pl/) diff --git a/filterer/etc/filterer.png b/filterer/etc/filterer.png new file mode 100644 index 000000000..6a6eb059b Binary files /dev/null and b/filterer/etc/filterer.png differ diff --git a/filterer/etc/filterer.urm.puml b/filterer/etc/filterer.urm.puml new file mode 100644 index 000000000..c0bb0b54d --- /dev/null +++ b/filterer/etc/filterer.urm.puml @@ -0,0 +1,96 @@ +@startuml +package com.iluwatar.filterer.domain { + interface Filterer { + + by(Predicate) : G {abstract} + } +} +package com.iluwatar.filterer { + class App { + - LOGGER : Logger {static} + + App() + - filteringSimpleProbableThreats() {static} + - filteringSimpleThreats() {static} + + main(args : String[]) {static} + } +} +package com.iluwatar.filterer.threat { + interface ProbabilisticThreatAwareSystem { + + filtered() : Filterer {abstract} + + threats() : List {abstract} + } + interface ProbableThreat { + + probability() : double {abstract} + } + class SimpleProbabilisticThreatAwareSystem { + - systemId : String + - threats : ImmutableList + + SimpleProbabilisticThreatAwareSystem(systemId : String, threats : List) + + equals(o : Object) : boolean + + filtered() : Filterer + - filteredGroup(predicate : Predicate) : ProbabilisticThreatAwareSystem + - filteredItems(predicate : Predicate) : List + + hashCode() : int + + systemId() : String + + threats() : List + + toString() : String + } + class SimpleProbableThreat { + - probability : double + + SimpleProbableThreat(name : String, id : int, threatType : ThreatType, probability : double) + + equals(o : Object) : boolean + + hashCode() : int + + probability() : double + + toString() : String + } + class SimpleThreat { + - id : int + - name : String + - threatType : ThreatType + + SimpleThreat(threatType : ThreatType, id : int, name : String) + + id() : int + + name() : String + + toString() : String + + type() : ThreatType + } + class SimpleThreatAwareSystem { + - issues : ImmutableList + - systemId : String + + SimpleThreatAwareSystem(systemId : String, issues : List) + + equals(o : Object) : boolean + + filtered() : Filterer + - filteredGroup(predicate : Predicate) : ThreatAwareSystem + - filteredItems(predicate : Predicate) : List + + hashCode() : int + + systemId() : String + + threats() : List + + toString() : String + } + interface Threat { + + id() : int {abstract} + + name() : String {abstract} + + type() : ThreatType {abstract} + } + interface ThreatAwareSystem { + + filtered() : Filterer {abstract} + + systemId() : String {abstract} + + threats() : List {abstract} + } + enum ThreatType { + + ROOTKIT {static} + + TROJAN {static} + + WORM {static} + + valueOf(name : String) : ThreatType {static} + + values() : ThreatType[] {static} + } +} +SimpleThreatAwareSystem --> "-issues" Threat +SimpleThreat --> "-threatType" ThreatType +SimpleProbabilisticThreatAwareSystem --> "-threats" ProbableThreat +ProbabilisticThreatAwareSystem --|> ThreatAwareSystem +ProbableThreat --|> Threat +SimpleProbabilisticThreatAwareSystem ..|> ProbabilisticThreatAwareSystem +SimpleProbableThreat ..|> ProbableThreat +SimpleProbableThreat --|> SimpleThreat +SimpleThreat ..|> Threat +SimpleThreatAwareSystem ..|> ThreatAwareSystem +@enduml \ No newline at end of file diff --git a/filterer/pom.xml b/filterer/pom.xml new file mode 100644 index 000000000..46042c1b0 --- /dev/null +++ b/filterer/pom.xml @@ -0,0 +1,73 @@ + + + + + java-design-patterns + com.iluwatar + 1.24.0-SNAPSHOT + + 4.0.0 + + filterer + + + + com.google.guava + guava + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.filterer.App + + + + + + + + + \ No newline at end of file diff --git a/filterer/src/main/java/com/iluwatar/filterer/App.java b/filterer/src/main/java/com/iluwatar/filterer/App.java new file mode 100644 index 000000000..43de5a646 --- /dev/null +++ b/filterer/src/main/java/com/iluwatar/filterer/App.java @@ -0,0 +1,108 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer; + +import com.iluwatar.filterer.threat.ProbableThreat; +import com.iluwatar.filterer.threat.SimpleProbabilisticThreatAwareSystem; +import com.iluwatar.filterer.threat.SimpleProbableThreat; +import com.iluwatar.filterer.threat.SimpleThreat; +import com.iluwatar.filterer.threat.SimpleThreatAwareSystem; +import com.iluwatar.filterer.threat.Threat; +import com.iluwatar.filterer.threat.ThreatAwareSystem; +import com.iluwatar.filterer.threat.ThreatType; + +import java.util.List; +import java.util.function.Predicate; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This demo class represent how {@link com.iluwatar.filterer.domain.Filterer} pattern is used to + * filter container-like objects to return filtered versions of themselves. The container like + * objects are systems that are aware of threats that they can be vulnerable to. We would like + * to have a way to create copy of different system objects but with filtered threats. + * The thing is to keep it simple if we add new subtype of {@link Threat} + * (for example {@link ProbableThreat}) - we still need to be able to filter by it's properties. + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public static void main(String[] args) { + filteringSimpleThreats(); + filteringSimpleProbableThreats(); + } + + /** + * Demonstrates how to filter {@link com.iluwatar.filterer.threat.ProbabilisticThreatAwareSystem} + * based on probability property. The @{@link com.iluwatar.filterer.domain.Filterer#by(Predicate)} + * method is able to use {@link com.iluwatar.filterer.threat.ProbableThreat} + * as predicate argument. + */ + private static void filteringSimpleProbableThreats() { + LOGGER.info(" ### Filtering ProbabilisticThreatAwareSystem by probability ###"); + + var trojanArcBomb = new SimpleProbableThreat("Trojan-ArcBomb", 1, ThreatType.TROJAN, 0.99); + var rootkit = new SimpleProbableThreat("Rootkit-Kernel", 2, ThreatType.ROOTKIT, 0.8); + + List probableThreats = List.of(trojanArcBomb, rootkit); + + var probabilisticThreatAwareSystem = + new SimpleProbabilisticThreatAwareSystem("Sys-1", probableThreats); + + LOGGER.info("Filtering ProbabilisticThreatAwareSystem. Initial : " + + probabilisticThreatAwareSystem); + + //Filtering using filterer + var filteredThreatAwareSystem = probabilisticThreatAwareSystem.filtered() + .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); + + LOGGER.info("Filtered by probability = 0.99 : " + filteredThreatAwareSystem); + } + + /** + * Demonstrates how to filter {@link ThreatAwareSystem} based on startingOffset property + * of {@link SimpleThreat}. The @{@link com.iluwatar.filterer.domain.Filterer#by(Predicate)} + * method is able to use {@link Threat} as predicate argument. + */ + private static void filteringSimpleThreats() { + LOGGER.info("### Filtering ThreatAwareSystem by ThreatType ###"); + + var rootkit = new SimpleThreat(ThreatType.ROOTKIT, 1, "Simple-Rootkit"); + var trojan = new SimpleThreat(ThreatType.TROJAN, 2, "Simple-Trojan"); + List threats = List.of(rootkit, trojan); + + var threatAwareSystem = new SimpleThreatAwareSystem("Sys-1", threats); + + LOGGER.info("Filtering ThreatAwareSystem. Initial : " + threatAwareSystem); + + //Filtering using Filterer + var rootkitThreatAwareSystem = threatAwareSystem.filtered() + .by(threat -> threat.type() == ThreatType.ROOTKIT); + + LOGGER.info("Filtered by threatType = ROOTKIT : " + rootkitThreatAwareSystem); + } + +} diff --git a/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java b/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java new file mode 100644 index 000000000..17970c115 --- /dev/null +++ b/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java @@ -0,0 +1,36 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.domain; + +import java.util.function.Predicate; + +/** + * Filterer helper interface. + * @param type of the container-like object. + * @param type of the elements contained within this container-like object. + */ +@FunctionalInterface +public interface Filterer { + G by(Predicate predicate); +} \ No newline at end of file diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java new file mode 100644 index 000000000..3a2959828 --- /dev/null +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java @@ -0,0 +1,49 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.threat; + +import com.iluwatar.filterer.domain.Filterer; + +import java.util.List; + +/** + * Represents system that is aware of it's threats with given probability of their occurrence. + */ +public interface ProbabilisticThreatAwareSystem extends ThreatAwareSystem { + + /** + * {@inheritDoc} + * @return + */ + @Override + List threats(); + + /** + * {@inheritDoc} + * @return + */ + @Override + Filterer filtered(); +} + diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java new file mode 100644 index 000000000..11e61dbf6 --- /dev/null +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java @@ -0,0 +1,35 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.threat; + +/** + * Represents threat that might be a threat with given probability. + */ +public interface ProbableThreat extends Threat { + /** + * Returns probability of occurrence of given threat. + * @return probability of occurrence of given threat. + */ + double probability(); +} \ No newline at end of file diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java new file mode 100644 index 000000000..3991d975e --- /dev/null +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java @@ -0,0 +1,113 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.threat; + +import com.google.common.collect.ImmutableList; +import com.iluwatar.filterer.domain.Filterer; + +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * {@inheritDoc} + */ +public class SimpleProbabilisticThreatAwareSystem implements ProbabilisticThreatAwareSystem { + + private final String systemId; + private final ImmutableList threats; + + public SimpleProbabilisticThreatAwareSystem( + final String systemId, + final List threats + ) { + this.systemId = systemId; + this.threats = ImmutableList.copyOf(threats); + } + + /** + * {@inheritDoc} + */ + @Override + public String systemId() { + return systemId; + } + + /** + * {@inheritDoc} + */ + @Override + public List threats() { + return threats; + } + + /** + * {@inheritDoc} + */ + @Override + public Filterer filtered() { + return this::filteredGroup; + } + + private ProbabilisticThreatAwareSystem filteredGroup( + final Predicate predicate + ) { + return new SimpleProbabilisticThreatAwareSystem(this.systemId, filteredItems(predicate)); + } + + private List filteredItems( + final Predicate predicate + ) { + return this.threats.stream() + .filter(predicate) + .collect(Collectors.toList()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (SimpleProbabilisticThreatAwareSystem) o; + return systemId.equals(that.systemId) + && threats.equals(that.threats); + } + + @Override + public int hashCode() { + return Objects.hash(systemId, threats); + } + + @Override + public String toString() { + return "SimpleProbabilisticThreatAwareSystem{" + + "systemId='" + systemId + '\'' + + ", threats=" + threats + + '}'; + } +} diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java new file mode 100644 index 000000000..54da07873 --- /dev/null +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java @@ -0,0 +1,79 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.threat; + +import java.util.Objects; + +/** + * {@inheritDoc} + */ +public class SimpleProbableThreat extends SimpleThreat implements ProbableThreat { + + private final double probability; + + public SimpleProbableThreat(final String name, + final int id, + final ThreatType threatType, + final double probability + ) { + super(threatType, id, name); + this.probability = probability; + } + + /** + * {@inheritDoc} + */ + @Override + public double probability() { + return probability; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + var that = (SimpleProbableThreat) o; + return Double.compare(that.probability, probability) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), probability); + } + + @Override + public String toString() { + return "SimpleProbableThreat{" + + "probability=" + probability + + "} " + + super.toString(); + } +} diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java new file mode 100644 index 000000000..08a8b0e17 --- /dev/null +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java @@ -0,0 +1,101 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.threat; + +import java.util.Objects; + +/** + * Represents a simple threat. + */ +public class SimpleThreat implements Threat { + + private final ThreatType threatType; + private final int id; + private final String name; + + /** + * Constructor. + * + * @param threatType {@link ThreatType}. + * @param id threat id. + * @param name threat name. + */ + public SimpleThreat(final ThreatType threatType, final int id, String name) { + this.threatType = threatType; + this.id = id; + this.name = name; + } + + /** + * {@inheritDoc} + */ + @Override + public String name() { + return name; + } + + /** + * {@inheritDoc} + */ + @Override + public int id() { + return id; + } + + /** + * {@inheritDoc} + */ + @Override + public ThreatType type() { + return threatType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (SimpleThreat) o; + return id == that.id + && threatType == that.threatType + && Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(threatType, id, name); + } + + @Override + public String toString() { + return "SimpleThreat{" + + "threatType=" + threatType + + ", id=" + id + + ", name='" + name + '\'' + + '}'; + } +} diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java new file mode 100644 index 000000000..f1dec40ae --- /dev/null +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java @@ -0,0 +1,107 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.threat; + +import com.google.common.collect.ImmutableList; +import com.iluwatar.filterer.domain.Filterer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * {@inheritDoc} + */ +public class SimpleThreatAwareSystem implements ThreatAwareSystem { + + private final String systemId; + private final ImmutableList issues; + + public SimpleThreatAwareSystem(final String systemId, final List issues) { + this.systemId = systemId; + this.issues = ImmutableList.copyOf(issues); + } + + /** + * {@inheritDoc} + */ + @Override + public String systemId() { + return systemId; + } + + /** + * {@inheritDoc} + */ + @Override + public List threats() { + return new ArrayList<>(issues); + } + + /** + * {@inheritDoc} + */ + @Override + public Filterer filtered() { + return this::filteredGroup; + } + + private ThreatAwareSystem filteredGroup(Predicate predicate) { + return new SimpleThreatAwareSystem(this.systemId, filteredItems(predicate)); + } + + private List filteredItems(Predicate predicate) { + return this.issues.stream() + .filter(predicate) + .collect(Collectors.toList()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (SimpleThreatAwareSystem) o; + return systemId.equals(that.systemId) + && issues.equals(that.issues); + } + + @Override + public int hashCode() { + return Objects.hash(systemId, issues); + } + + @Override + public String toString() { + return "SimpleThreatAwareSystem{" + + "systemId='" + systemId + + '\'' + ", issues=" + issues + + '}'; + } +} diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java new file mode 100644 index 000000000..515b59332 --- /dev/null +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java @@ -0,0 +1,49 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.threat; + +/** + * Represents a threat that can be detected in given system. + */ +public interface Threat { + /** + * Returns name of the threat. + * + * @return value representing name of the threat. + */ + String name(); + + /** + * Returns unique id of the threat. + * + * @return value representing threat id. + */ + int id(); + + /** + * Returns threat type. + * @return {@link ThreatType} + */ + ThreatType type(); +} diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java new file mode 100644 index 000000000..b889d537d --- /dev/null +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java @@ -0,0 +1,55 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.threat; + +import com.iluwatar.filterer.domain.Filterer; + +import java.util.List; + +/** + * Represents system that is aware of threats that are present in it. + */ +public interface ThreatAwareSystem { + + /** + * Returns the system id. + * + * @return system id. + */ + String systemId(); + + /** + * Returns list of threats for this system. + * @return list of threats for this system. + */ + List threats(); + + /** + * Returns the instance of {@link Filterer} helper interface that allows to covariantly + * specify lower bound for predicate that we want to filter by. + * @return an instance of {@link Filterer} helper interface. + */ + Filterer filtered(); + +} diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/module-info.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java similarity index 92% rename from business-delegate/src/main/java/com/iluwatar/business/delegate/module-info.java rename to filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java index 8f331c848..5f9a152a8 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/module-info.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java @@ -21,6 +21,6 @@ * THE SOFTWARE. */ -module com.iluwatar.business.delegate { - requires org.slf4j; -} \ No newline at end of file +package com.iluwatar.filterer.threat; + +public enum ThreatType { TROJAN, WORM, ROOTKIT } diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/module-info.java b/filterer/src/test/java/com/iluwatar/filterer/AppTest.java similarity index 88% rename from acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/module-info.java rename to filterer/src/test/java/com/iluwatar/filterer/AppTest.java index 78de5a786..551ebcc18 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/module-info.java +++ b/filterer/src/test/java/com/iluwatar/filterer/AppTest.java @@ -21,6 +21,13 @@ * THE SOFTWARE. */ -module com.iluwatar.acyclicvisitor { - requires org.slf4j; +package com.iluwatar.filterer; + +import org.junit.jupiter.api.Test; + +class AppTest { + @Test + void shouldLaunchApp() { + App.main(new String[]{}); + } } \ No newline at end of file diff --git a/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java new file mode 100644 index 000000000..2f14ca057 --- /dev/null +++ b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.threat; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SimpleProbabilisticThreatAwareSystemTest { + @Test + void shouldFilterByProbability() { + //given + var trojan = new SimpleProbableThreat("Troyan-ArcBomb", 1, ThreatType.TROJAN, 0.99); + var rootkit = new SimpleProbableThreat("Rootkit-System", 2, ThreatType.ROOTKIT, 0.8); + List probableThreats = List.of(trojan, rootkit); + + var simpleProbabilisticThreatAwareSystem = + new SimpleProbabilisticThreatAwareSystem("System-1", probableThreats); + + //when + var filtered = simpleProbabilisticThreatAwareSystem.filtered() + .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); + + //then + assertEquals(filtered.threats().size(), 1); + assertEquals(filtered.threats().get(0), trojan); + } +} \ No newline at end of file diff --git a/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java new file mode 100644 index 000000000..ea918c9ec --- /dev/null +++ b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java @@ -0,0 +1,50 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.filterer.threat; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class SimpleThreatAwareSystemTest { + @Test + void shouldFilterByThreatType() { + //given + var rootkit = new SimpleThreat(ThreatType.ROOTKIT, 1, "Simple-Rootkit"); + var trojan = new SimpleThreat(ThreatType.TROJAN, 2, "Simple-Trojan"); + List threats = List.of(rootkit, trojan); + + var threatAwareSystem = new SimpleThreatAwareSystem("System-1", threats); + + //when + var rootkitThreatAwareSystem = threatAwareSystem.filtered() + .by(threat -> threat.type() == ThreatType.ROOTKIT); + + //then + assertEquals(rootkitThreatAwareSystem.threats().size(), 1); + assertEquals(rootkitThreatAwareSystem.threats().get(0), rootkit); + } +} \ No newline at end of file diff --git a/fluentinterface/README.md b/fluentinterface/README.md index 61c5f2eb5..8b3f80fae 100644 --- a/fluentinterface/README.md +++ b/fluentinterface/README.md @@ -9,23 +9,25 @@ tags: --- ## 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. + +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. ## Explanation -The Fluent Interface pattern is useful when you want to provide an easy readable, flowing API. Those interfaces tend -to mimic domain specific languages, so they can nearly be read as human languages. +The Fluent Interface pattern is useful when you want to provide an easy readable, flowing API. Those +interfaces tend to mimic domain specific languages, so they can nearly be read as human languages. A fluent interface can be implemented using any of - * Method Chaining - calling a method returns some object on which further methods can be called. - * Static Factory Methods and Imports + * Method chaining - calling a method returns some object on which further methods can be called. + * Static factory methods and imports. * Named parameters - can be simulated in Java using static factory methods. Real world example -> We need to select numbers based on different criteria from the list. It's a great chance to utilize fluent interface pattern to provide readable easy-to-use developer experience. +> We need to select numbers based on different criteria from the list. It's a great chance to +> utilize fluent interface pattern to provide readable easy-to-use developer experience. In plain words @@ -33,7 +35,9 @@ In plain words Wikipedia says -> In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL). +> In software engineering, a fluent interface is an object-oriented API whose design relies +> extensively on method chaining. Its goal is to increase code legibility by creating a +> domain-specific language (DSL). **Programmatic Example** @@ -134,29 +138,35 @@ result is printed afterwards. .first(2) .last() .ifPresent(number -> LOGGER.info("Last amongst first two negatives: {}", number)); - - // The initial list contains: 1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68. - // The first three negative values are: -61, -22, -87. - // The last two positive values are: 23, 2. - // The first even number is: 14 - // A string-mapped list of negative numbers contains: String[-61], String[-22], String[-87], String[-82], String[-98], String[-68]. - // The lazy list contains the last two of the first four positive numbers mapped to Strings: String[18], String[6]. - // Last amongst first two negatives: -22 +``` + +Program output: + +```java +The initial list contains: 1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68. +The first three negative values are: -61, -22, -87. +The last two positive values are: 23, 2. +The first even number is: 14 +A string-mapped list of negative numbers contains: String[-61], String[-22], String[-87], String[-82], String[-98], String[-68]. +The lazy list contains the last two of the first four positive numbers mapped to Strings: String[18], String[6]. +Last amongst first two negatives: -22 ``` ## Class diagram + ![Fluent Interface](./etc/fluentinterface.png "Fluent Interface") ## 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 +* You provide an API that would benefit from a DSL-like usage. +* You have objects that are difficult to configure or use. ## Known uses * [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) +* [Google Guava FluentIterable](https://github.com/google/guava/wiki/FunctionalExplained) * [JOOQ](http://www.jooq.org/doc/3.0/manual/getting-started/use-cases/jooq-as-a-standalone-sql-builder/) * [Mockito](http://mockito.org/) * [Java Hamcrest](http://code.google.com/p/hamcrest/wiki/Tutorial) diff --git a/fluentinterface/pom.xml b/fluentinterface/pom.xml index 9eb063c13..138dbfa12 100644 --- a/fluentinterface/pom.xml +++ b/fluentinterface/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 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 547c657e4..0222fc54a 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java @@ -23,8 +23,6 @@ package com.iluwatar.fluentinterface.app; -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; @@ -94,7 +92,7 @@ public class App { .filter(positives()) .first(4) .last(2) - .map(number -> "String[" + valueOf(number) + "]") + .map(number -> "String[" + number + "]") .asList(); prettyPrint("The lazy list contains the last two of the first four positive numbers " + "mapped to Strings: ", lastTwoOfFirstFourStringMapped); 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 f001c532f..517e6b778 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 @@ -70,7 +70,7 @@ public class LazyFluentIterable implements FluentIterable { return new LazyFluentIterable<>() { @Override public Iterator iterator() { - return new DecoratingIterator(iterable.iterator()) { + return new DecoratingIterator<>(iterable.iterator()) { @Override public E computeNext() { while (fromIterator.hasNext()) { @@ -107,10 +107,10 @@ public class LazyFluentIterable implements FluentIterable { */ @Override public FluentIterable first(int count) { - return new LazyFluentIterable() { + return new LazyFluentIterable<>() { @Override public Iterator iterator() { - return new DecoratingIterator(iterable.iterator()) { + return new DecoratingIterator<>(iterable.iterator()) { int currentIndex; @Override @@ -149,10 +149,10 @@ public class LazyFluentIterable implements FluentIterable { */ @Override public FluentIterable last(int count) { - return new LazyFluentIterable() { + return new LazyFluentIterable<>() { @Override public Iterator iterator() { - return new DecoratingIterator(iterable.iterator()) { + return new DecoratingIterator<>(iterable.iterator()) { private int stopIndex; private int totalElementsCount; private List list; @@ -194,11 +194,11 @@ public class LazyFluentIterable implements FluentIterable { */ @Override public FluentIterable map(Function function) { - return new LazyFluentIterable() { + return new LazyFluentIterable<>() { @Override public Iterator iterator() { - return new DecoratingIterator(null) { - Iterator oldTypeIterator = iterable.iterator(); + return new DecoratingIterator<>(null) { + final Iterator oldTypeIterator = iterable.iterator(); @Override public T computeNext() { @@ -226,7 +226,7 @@ public class LazyFluentIterable implements FluentIterable { @Override public Iterator iterator() { - return new DecoratingIterator(iterable.iterator()) { + return new DecoratingIterator<>(iterable.iterator()) { @Override public E computeNext() { return fromIterator.hasNext() ? fromIterator.next() : null; 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 6f25d8416..1b6671917 100644 --- a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java +++ b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.fluentinterface.app; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application Test Entry */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/flux/pom.xml b/flux/pom.xml index 8effd0fc9..14f4f5557 100644 --- a/flux/pom.xml +++ b/flux/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT flux diff --git a/flux/src/main/java/com/iluwatar/flux/action/Action.java b/flux/src/main/java/com/iluwatar/flux/action/Action.java index 6a5f608c2..c8e2e012b 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/Action.java +++ b/flux/src/main/java/com/iluwatar/flux/action/Action.java @@ -28,7 +28,7 @@ package com.iluwatar.flux.action; */ public abstract class Action { - private ActionType type; + private final ActionType type; public Action(ActionType type) { this.type = type; diff --git a/flux/src/main/java/com/iluwatar/flux/action/ActionType.java b/flux/src/main/java/com/iluwatar/flux/action/ActionType.java index 6399d2806..e84954efd 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/ActionType.java +++ b/flux/src/main/java/com/iluwatar/flux/action/ActionType.java @@ -28,6 +28,6 @@ package com.iluwatar.flux.action; */ public enum ActionType { - MENU_ITEM_SELECTED, CONTENT_CHANGED; + MENU_ITEM_SELECTED, CONTENT_CHANGED } diff --git a/flux/src/main/java/com/iluwatar/flux/action/Content.java b/flux/src/main/java/com/iluwatar/flux/action/Content.java index 59a63ec18..6fb2e3e0e 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/Content.java +++ b/flux/src/main/java/com/iluwatar/flux/action/Content.java @@ -31,7 +31,7 @@ public enum Content { PRODUCTS("Products - This page lists the company's products."), COMPANY( "Company - This page displays information about the company."); - private String title; + private final String title; Content(String title) { this.title = title; diff --git a/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java b/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java index 3b29b6b4e..c70561a65 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java +++ b/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java @@ -28,7 +28,7 @@ package com.iluwatar.flux.action; */ public class ContentAction extends Action { - private Content content; + private final Content content; public ContentAction(Content content) { super(ActionType.CONTENT_CHANGED); diff --git a/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java b/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java index 5ddeefde4..f833a6187 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java +++ b/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java @@ -29,7 +29,7 @@ package com.iluwatar.flux.action; */ public class MenuAction extends Action { - private MenuItem menuItem; + private final MenuItem menuItem; public MenuAction(MenuItem menuItem) { super(ActionType.MENU_ITEM_SELECTED); diff --git a/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java b/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java index f251e1dd7..90fac3e2e 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java +++ b/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java @@ -30,7 +30,7 @@ public enum MenuItem { HOME("Home"), PRODUCTS("Products"), COMPANY("Company"); - private String title; + private final String title; MenuItem(String title) { this.title = title; 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 cf09ecf68..c43d87680 100644 --- a/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java +++ b/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java @@ -39,7 +39,7 @@ public final class Dispatcher { private static Dispatcher instance = new Dispatcher(); - private List stores = new LinkedList<>(); + private final List stores = new LinkedList<>(); private Dispatcher() { } @@ -57,15 +57,10 @@ public final class Dispatcher { */ public void menuItemSelected(MenuItem menuItem) { dispatchAction(new MenuAction(menuItem)); - switch (menuItem) { - case HOME: - case PRODUCTS: - default: - dispatchAction(new ContentAction(Content.PRODUCTS)); - break; - case COMPANY: - dispatchAction(new ContentAction(Content.COMPANY)); - break; + if (menuItem == MenuItem.COMPANY) { + dispatchAction(new ContentAction(Content.COMPANY)); + } else { + dispatchAction(new ContentAction(Content.PRODUCTS)); } } diff --git a/flux/src/main/java/com/iluwatar/flux/store/Store.java b/flux/src/main/java/com/iluwatar/flux/store/Store.java index cfbdf4af5..34188fff2 100644 --- a/flux/src/main/java/com/iluwatar/flux/store/Store.java +++ b/flux/src/main/java/com/iluwatar/flux/store/Store.java @@ -33,7 +33,7 @@ import java.util.List; */ public abstract class Store { - private List views = new LinkedList<>(); + private final List views = new LinkedList<>(); public abstract void onAction(Action action); 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 8916ad4de..649708d8f 100644 --- a/flux/src/test/java/com/iluwatar/flux/app/AppTest.java +++ b/flux/src/test/java/com/iluwatar/flux/app/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.flux.app; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/flyweight/README.md b/flyweight/README.md index 7b52ef800..fbefd3740 100644 --- a/flyweight/README.md +++ b/flyweight/README.md @@ -10,25 +10,32 @@ tags: --- ## Intent -Use sharing to support large numbers of fine-grained objects -efficiently. + +Use sharing to support large numbers of fine-grained objects efficiently. ## Explanation + Real world example -> Alchemist's shop has shelves full of magic potions. Many of the potions are the same so there is no need to create new object for each of them. Instead one object instance can represent multiple shelf items so memory footprint remains small. +> Alchemist's shop has shelves full of magic potions. Many of the potions are the same so there is +> no need to create new object for each of them. Instead one object instance can represent multiple +> shelf items so memory footprint remains small. In plain words -> It is used to minimize memory usage or computational expenses by sharing as much as possible with similar objects. +> It is used to minimize memory usage or computational expenses by sharing as much as possible with +> similar objects. Wikipedia says -> In computer programming, flyweight is a software design pattern. A flyweight is an object that minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory. +> In computer programming, flyweight is a software design pattern. A flyweight is an object that +> minimizes memory use by sharing as much data as possible with other similar objects; it is a way +> to use objects in large numbers when a simple repeated representation would use an unacceptable +> amount of memory. **Programmatic example** -Translating our alchemist shop example from above. First of all we have different potion types +Translating our alchemist shop example from above. First of all we have different potion types: ```java public interface Potion { @@ -60,7 +67,7 @@ public class InvisibilityPotion implements Potion { } ``` -Then the actual Flyweight object which is the factory for creating potions +Then the actual Flyweight class `PotionFactory`, which is the factory for creating potions. ```java public class PotionFactory { @@ -96,7 +103,7 @@ public class PotionFactory { } ``` -And it can be used as below +And it can be used as below: ```java var factory = new PotionFactory(); @@ -108,19 +115,33 @@ factory.createPotion(PotionType.HOLY_WATER).drink(); // You feel blessed. (Potio factory.createPotion(PotionType.HEALING).drink(); // You feel healed. (Potion=648129364) ``` +Program output: + +```java +You become invisible. (Potion=6566818) +You feel healed. (Potion=648129364) +You become invisible. (Potion=6566818) +You feel blessed. (Potion=1104106489) +You feel blessed. (Potion=1104106489) +You feel healed. (Potion=648129364) +``` + ## Class diagram + ![alt text](./etc/flyweight.urm.png "Flyweight pattern class diagram") ## 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 -* an application uses a large number of objects -* storage costs are high because of the sheer quantity of objects -* most object state can be made extrinsic -* 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. +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: + +* An application uses a large number of objects. +* Storage costs are high because of the sheer quantity of objects. +* Most object state can be made extrinsic. +* 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 diff --git a/flyweight/pom.xml b/flyweight/pom.xml index f3a8082b5..b9ffd42ae 100644 --- a/flyweight/pom.xml +++ b/flyweight/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.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 4fa7312e5..e7af8ee00 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java @@ -34,8 +34,8 @@ public class AlchemistShop { private static final Logger LOGGER = LoggerFactory.getLogger(AlchemistShop.class); - private List topShelf; - private List bottomShelf; + private final List topShelf; + private final List bottomShelf; /** * Constructor. diff --git a/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java b/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java index 3d81a6db2..5f957c794 100644 --- a/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java +++ b/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.flyweight; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/front-controller/pom.xml b/front-controller/pom.xml index 34dabc182..a90029a82 100644 --- a/front-controller/pom.xml +++ b/front-controller/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT front-controller 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 2ea58c6a6..cf33bfb48 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 @@ -25,13 +25,15 @@ package com.iluwatar.front.controller; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java b/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java index 57cfb2454..8cbf7c631 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java @@ -36,7 +36,7 @@ import java.util.List; */ public class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); diff --git a/game-loop/README.md b/game-loop/README.md index 5f2cd9653..e967ff52d 100644 --- a/game-loop/README.md +++ b/game-loop/README.md @@ -8,31 +8,39 @@ tags: - Game programming --- -## Intent -A game loop runs continuously during gameplay. Each turn of the loop, it processes user input without blocking, updates -the game state, and renders the game. It tracks the passage of time to control the rate of gameplay. +## Intent + +A game loop runs continuously during gameplay. Each turn of the loop, it processes user input +without blocking, updates the game state, and renders the game. It tracks the passage of time to +control the rate of gameplay. This pattern decouples progression of game time from user input and processor speed. -## Applicability +## Applicability + This pattern is used in every game engine. ## Explanation + Real world example -> Game loop is the main process of all the game rendering threads. It's present in all modern games. It drives input process, internal status update, rendering, AI and all the other processes. +> Game loop is the main process of all the game rendering threads. It's present in all modern games. +> It drives input process, internal status update, rendering, AI and all the other processes. In plain words -> Game Loop pattern ensures that game time progresses in equal speed in all different hardware setups. +> Game Loop pattern ensures that game time progresses in equal speed in all different hardware +> setups. Wikipedia says -> The central component of any game, from a programming standpoint, is the game loop. The game loop allows the game to run smoothly regardless of a user's input or lack thereof. +> The central component of any game, from a programming standpoint, is the game loop. The game loop +> allows the game to run smoothly regardless of a user's input, or lack thereof. **Programmatic Example** -Let's start with something simple. Here's a bullet that will move in our game. For demonstration it's enough that it has 1-dimensional position. +Let's start with something simple. Here's `Bullet` class. Bullets will move in our game. For +demonstration purposes it's enough that it has 1-dimensional position. ```java public class Bullet { @@ -53,7 +61,7 @@ public class Bullet { } ``` -GameController is responsible for moving objects in the game. Including the aforementioned bullet. +`GameController` is responsible for moving objects in the game, including the aforementioned bullet. ```java public class GameController { @@ -75,7 +83,8 @@ public class GameController { } ``` -Now we introduce the game loop. Or actually in this demo we have 3 different game loops. +Now we introduce the game loop. Or actually in this demo we have 3 different game loops. Let's see +the base class `GameLoop` first. ```java public enum GameStatus { @@ -100,7 +109,7 @@ public abstract class GameLoop { public void run() { status = GameStatus.RUNNING; - gameThread = new Thread(() -> processGameLoop()); + gameThread = new Thread(this::processGameLoop); gameThread.start(); } @@ -128,7 +137,11 @@ public abstract class GameLoop { protected abstract void processGameLoop(); } +``` +Here's the first game loop implementation, `FrameBasedGameLoop`: + +```java public class FrameBasedGameLoop extends GameLoop { @Override @@ -144,59 +157,9 @@ public class FrameBasedGameLoop extends GameLoop { controller.moveBullet(0.5f); } } - -public class VariableStepGameLoop extends GameLoop { - - @Override - protected void processGameLoop() { - var lastFrameTime = System.currentTimeMillis(); - while (isGameRunning()) { - processInput(); - var currentFrameTime = System.currentTimeMillis(); - var elapsedTime = currentFrameTime - lastFrameTime; - update(elapsedTime); - lastFrameTime = currentFrameTime; - render(); - } - } - - protected void update(Long elapsedTime) { - controller.moveBullet(0.5f * elapsedTime / 1000); - } -} - -public class FixedStepGameLoop extends GameLoop { - - private static final long MS_PER_FRAME = 20; - - @Override - protected void processGameLoop() { - var previousTime = System.currentTimeMillis(); - var lag = 0L; - while (isGameRunning()) { - var currentTime = System.currentTimeMillis(); - var elapsedTime = currentTime - previousTime; - previousTime = currentTime; - lag += elapsedTime; - - processInput(); - - while (lag >= MS_PER_FRAME) { - update(); - lag -= MS_PER_FRAME; - } - - render(); - } - } - - protected void update() { - controller.moveBullet(0.5f * MS_PER_FRAME / 1000); - } -} ``` -Finally we can show all these game loops in action. +Finally, we show all the game loops in action. ```java try { @@ -226,10 +189,66 @@ Finally we can show all these game loops in action. } ``` +Program output: + +```java +Start frame-based game loop: +Current bullet position: 0.5 +Current bullet position: 1.0 +Current bullet position: 1.5 +Current bullet position: 2.0 +Current bullet position: 2.5 +Current bullet position: 3.0 +Current bullet position: 3.5 +Current bullet position: 4.0 +Current bullet position: 4.5 +Current bullet position: 5.0 +Current bullet position: 5.5 +Current bullet position: 6.0 +Stop frame-based game loop. +Start variable-step game loop: +Current bullet position: 6.5 +Current bullet position: 0.038 +Current bullet position: 0.084 +Current bullet position: 0.145 +Current bullet position: 0.1805 +Current bullet position: 0.28 +Current bullet position: 0.32 +Current bullet position: 0.42549998 +Current bullet position: 0.52849996 +Current bullet position: 0.57799995 +Current bullet position: 0.63199997 +Current bullet position: 0.672 +Current bullet position: 0.778 +Current bullet position: 0.848 +Current bullet position: 0.8955 +Current bullet position: 0.9635 +Stop variable-step game loop. +Start fixed-step game loop: +Current bullet position: 0.0 +Current bullet position: 1.086 +Current bullet position: 0.059999995 +Current bullet position: 0.12999998 +Current bullet position: 0.24000004 +Current bullet position: 0.33999994 +Current bullet position: 0.36999992 +Current bullet position: 0.43999985 +Current bullet position: 0.5399998 +Current bullet position: 0.65999967 +Current bullet position: 0.68999964 +Current bullet position: 0.7299996 +Current bullet position: 0.79999954 +Current bullet position: 0.89999944 +Current bullet position: 0.98999935 +Stop variable-step game loop. +``` + ## Class diagram + ![alt text](./etc/game-loop.urm.png "Game Loop pattern class diagram") -## Credits +## Credits + * [Game Programming Patterns - Game Loop](http://gameprogrammingpatterns.com/game-loop.html) * [Game Programming Patterns](https://www.amazon.com/gp/product/0990582906/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0990582906&linkId=1289749a703b3fe0e24cd8d604d7c40b) * [Game Engine Architecture, Third Edition](https://www.amazon.com/gp/product/1138035459/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1138035459&linkId=94502746617211bc40e0ef49d29333ac) diff --git a/game-loop/pom.xml b/game-loop/pom.xml index 2c2908271..6935c1fd1 100644 --- a/game-loop/pom.xml +++ b/game-loop/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 @@ -39,6 +39,12 @@ junit junit + + + org.junit.jupiter + junit-jupiter-engine + test + diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java index cbb456ccf..5f47ef1dd 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java @@ -36,7 +36,7 @@ public abstract class GameLoop { protected volatile GameStatus status; - protected GameController controller; + protected final GameController controller; private Thread gameThread; @@ -53,7 +53,7 @@ public abstract class GameLoop { */ public void run() { status = GameStatus.RUNNING; - gameThread = new Thread(() -> processGameLoop()); + gameThread = new Thread(this::processGameLoop); gameThread.start(); } diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java index 447e4f411..0c027028d 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java @@ -25,14 +25,16 @@ package com.iluwatar.gameloop; import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * App unit test class. */ public class AppTest { @Test - public void testMain() { - new App().main(new String[]{}); + public void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/guarded-suspension/pom.xml b/guarded-suspension/pom.xml index 791c696c1..f1bd31c66 100644 --- a/guarded-suspension/pom.xml +++ b/guarded-suspension/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT jar guarded-suspension diff --git a/half-sync-half-async/pom.xml b/half-sync-half-async/pom.xml index fdb37edb0..635b76172 100644 --- a/half-sync-half-async/pom.xml +++ b/half-sync-half-async/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT half-sync-half-async 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 7df2264ab..d013924cb 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 @@ -95,7 +95,7 @@ public class App { * ArithmeticSumTask. */ static class ArithmeticSumTask implements AsyncTask { - private long numberOfElements; + private final long numberOfElements; public ArithmeticSumTask(long numberOfElements) { this.numberOfElements = numberOfElements; 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 3a3bb474c..32f5e9d4a 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 @@ -48,7 +48,7 @@ public class AsynchronousService { * tasks should be performed in the background which does not affect the performance of main * thread. */ - private ExecutorService service; + private final ExecutorService service; /** * Creates an asynchronous service using {@code workQueue} as communication channel between diff --git a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java index b3cf4839b..395599927 100644 --- a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java +++ b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.halfsynchalfasync; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(null); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(null)); } } diff --git a/hexagonal/pom.xml b/hexagonal/pom.xml index 4873d0ddb..f51825d5f 100644 --- a/hexagonal/pom.xml +++ b/hexagonal/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT hexagonal diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java index 1a0fdb6b0..746b93508 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java @@ -32,7 +32,7 @@ import java.util.Map; */ public class InMemoryBank implements WireTransfers { - private static Map accounts = new HashMap<>(); + private static final Map accounts = new HashMap<>(); static { accounts diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java index 973747acc..5c0461843 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java @@ -34,7 +34,7 @@ import java.util.Optional; */ public class InMemoryTicketRepository implements LotteryTicketRepository { - private static Map tickets = new HashMap<>(); + private static final Map tickets = new HashMap<>(); @Override public Optional findById(LotteryTicketId id) { diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java index 96ab74ba3..6d6c33239 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java @@ -46,6 +46,7 @@ public class MongoTicketRepository implements LotteryTicketRepository { private static final String DEFAULT_DB = "lotteryDB"; private static final String DEFAULT_TICKETS_COLLECTION = "lotteryTickets"; private static final String DEFAULT_COUNTERS_COLLECTION = "counters"; + private static final String TICKET_ID = "ticketId"; private MongoClient mongoClient; private MongoDatabase database; @@ -93,7 +94,7 @@ public class MongoTicketRepository implements LotteryTicketRepository { } private void initCounters() { - var doc = new Document("_id", "ticketId").append("seq", 1); + var doc = new Document("_id", TICKET_ID).append("seq", 1); countersCollection.insertOne(doc); } @@ -103,7 +104,7 @@ public class MongoTicketRepository implements LotteryTicketRepository { * @return next ticket id */ public int getNextId() { - var find = new Document("_id", "ticketId"); + var find = new Document("_id", TICKET_ID); var increase = new Document("seq", 1); var update = new Document("$inc", increase); var result = countersCollection.findOneAndUpdate(find, update); @@ -131,7 +132,7 @@ public class MongoTicketRepository implements LotteryTicketRepository { @Override public Optional findById(LotteryTicketId id) { return ticketsCollection - .find(new Document("ticketId", id.getId())) + .find(new Document(TICKET_ID, id.getId())) .limit(1) .into(new ArrayList<>()) .stream() @@ -142,7 +143,7 @@ public class MongoTicketRepository implements LotteryTicketRepository { @Override public Optional save(LotteryTicket ticket) { var ticketId = getNextId(); - var doc = new Document("ticketId", ticketId); + var doc = new Document(TICKET_ID, ticketId); doc.put("email", ticket.getPlayerDetails().getEmail()); doc.put("bank", ticket.getPlayerDetails().getBankAccount()); doc.put("phone", ticket.getPlayerDetails().getPhoneNumber()); @@ -173,7 +174,7 @@ public class MongoTicketRepository implements LotteryTicketRepository { .map(Integer::parseInt) .collect(Collectors.toSet()); var lotteryNumbers = LotteryNumbers.create(numbers); - var ticketId = new LotteryTicketId(doc.getInteger("ticketId")); + var ticketId = new LotteryTicketId(doc.getInteger(TICKET_ID)); return new LotteryTicket(ticketId, playerDetails, lotteryNumbers); } } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java index 8988bba88..acdd2b8c5 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java @@ -116,7 +116,7 @@ public class LotteryNumbers { */ private static class RandomNumberGenerator { - private PrimitiveIterator.OfInt randomIterator; + private final PrimitiveIterator.OfInt randomIterator; /** * Initialize a new random number generator that generates random numbers in the range [min, diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java index dfa324449..114e78c9c 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java @@ -30,7 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ public class LotteryTicketId { - private static AtomicInteger numAllocated = new AtomicInteger(0); + private static final AtomicInteger numAllocated = new AtomicInteger(0); private final int id; public LotteryTicketId() { diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java index ba46f2d97..62fcf32b4 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java @@ -36,12 +36,15 @@ public class MongoEventLog implements LotteryEventLog { private static final String DEFAULT_DB = "lotteryDB"; private static final String DEFAULT_EVENTS_COLLECTION = "events"; + private static final String EMAIL = "email"; + private static final String PHONE = "phone"; + public static final String MESSAGE = "message"; private MongoClient mongoClient; private MongoDatabase database; private MongoCollection eventsCollection; - private StdOutEventLog stdOutEventLog = new StdOutEventLog(); + private final StdOutEventLog stdOutEventLog = new StdOutEventLog(); /** * Constructor. @@ -107,41 +110,41 @@ public class MongoEventLog implements LotteryEventLog { @Override public void ticketSubmitted(PlayerDetails details) { - var document = new Document("email", details.getEmail()); - document.put("phone", details.getPhoneNumber()); + var document = new Document(EMAIL, details.getEmail()); + document.put(PHONE, details.getPhoneNumber()); document.put("bank", details.getBankAccount()); document - .put("message", "Lottery ticket was submitted and bank account was charged for 3 credits."); + .put(MESSAGE, "Lottery ticket was submitted and bank account was charged for 3 credits."); eventsCollection.insertOne(document); stdOutEventLog.ticketSubmitted(details); } @Override public void ticketSubmitError(PlayerDetails details) { - var document = new Document("email", details.getEmail()); - document.put("phone", details.getPhoneNumber()); + var document = new Document(EMAIL, details.getEmail()); + document.put(PHONE, details.getPhoneNumber()); document.put("bank", details.getBankAccount()); - document.put("message", "Lottery ticket could not be submitted because lack of funds."); + document.put(MESSAGE, "Lottery ticket could not be submitted because lack of funds."); eventsCollection.insertOne(document); stdOutEventLog.ticketSubmitError(details); } @Override public void ticketDidNotWin(PlayerDetails details) { - var document = new Document("email", details.getEmail()); - document.put("phone", details.getPhoneNumber()); + var document = new Document(EMAIL, details.getEmail()); + document.put(PHONE, details.getPhoneNumber()); document.put("bank", details.getBankAccount()); - document.put("message", "Lottery ticket was checked and unfortunately did not win this time."); + document.put(MESSAGE, "Lottery ticket was checked and unfortunately did not win this time."); eventsCollection.insertOne(document); stdOutEventLog.ticketDidNotWin(details); } @Override public void ticketWon(PlayerDetails details, int prizeAmount) { - var document = new Document("email", details.getEmail()); - document.put("phone", details.getPhoneNumber()); + var document = new Document(EMAIL, details.getEmail()); + document.put(PHONE, details.getPhoneNumber()); document.put("bank", details.getBankAccount()); - document.put("message", String + document.put(MESSAGE, String .format("Lottery ticket won! The bank account was deposited with %d credits.", prizeAmount)); eventsCollection.insertOne(document); @@ -150,10 +153,10 @@ public class MongoEventLog implements LotteryEventLog { @Override public void prizeError(PlayerDetails details, int prizeAmount) { - var document = new Document("email", details.getEmail()); - document.put("phone", details.getPhoneNumber()); + var document = new Document(EMAIL, details.getEmail()); + document.put(PHONE, details.getPhoneNumber()); document.put("bank", details.getBankAccount()); - document.put("message", String + document.put(MESSAGE, String .format("Lottery ticket won! Unfortunately the bank credit transfer of %d failed.", prizeAmount)); eventsCollection.insertOne(document); diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/AppTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/AppTest.java index 05d51a283..d2ad89c9b 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/AppTest.java +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/AppTest.java @@ -25,13 +25,16 @@ package com.iluwatar.hexagonal; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Unit test for simple App. */ class AppTest { @Test - void testApp() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } } diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java index 6d3ba8bc5..541b2b98b 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java @@ -43,7 +43,7 @@ import org.junit.jupiter.api.Test; */ class LotteryTest { - private Injector injector; + private final Injector injector; @Inject private LotteryAdministration administration; @Inject diff --git a/intercepting-filter/pom.xml b/intercepting-filter/pom.xml index ea8597374..d8ee9985f 100644 --- a/intercepting-filter/pom.xml +++ b/intercepting-filter/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT intercepting-filter 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 656008c10..52aa890c1 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 @@ -51,11 +51,11 @@ public class Client extends JFrame { // NOSONAR private static final long serialVersionUID = 1L; private transient FilterManager filterManager; - private JLabel jl; - private JTextField[] jtFields; - private JTextArea[] jtAreas; - private JButton clearButton; - private JButton processButton; + private final JLabel jl; + private final JTextField[] jtFields; + private final JTextArea[] jtAreas; + private final JButton clearButton; + private final JButton processButton; /** * Constructor. 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 e8f3b941f..91e438882 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 @@ -30,7 +30,7 @@ package com.iluwatar.intercepting.filter; */ public class FilterManager { - private FilterChain filterChain; + private final FilterChain filterChain; public FilterManager() { filterChain = new FilterChain(); 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 08ed715b1..db552356d 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 @@ -46,9 +46,9 @@ public class Target extends JFrame { //NOSONAR private static final long serialVersionUID = 1L; - private JTable jt; - private DefaultTableModel dtm; - private JButton del; + private final JTable jt; + private final DefaultTableModel dtm; + private final JButton del; /** * Constructor. 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 8fbe9f1d2..d8f22a478 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 @@ -25,13 +25,15 @@ package com.iluwatar.intercepting.filter; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test. */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/interpreter/pom.xml b/interpreter/pom.xml index 118cfcdf6..d32c80839 100644 --- a/interpreter/pom.xml +++ b/interpreter/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT interpreter diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java index 24ef7914e..46b5c96cb 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java @@ -28,8 +28,8 @@ package com.iluwatar.interpreter; */ public class MinusExpression extends Expression { - private Expression leftExpression; - private Expression rightExpression; + private final Expression leftExpression; + private final Expression rightExpression; public MinusExpression(Expression leftExpression, Expression rightExpression) { this.leftExpression = leftExpression; diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java index 606937e0b..926d6c119 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java @@ -28,8 +28,8 @@ package com.iluwatar.interpreter; */ public class MultiplyExpression extends Expression { - private Expression leftExpression; - private Expression rightExpression; + private final Expression leftExpression; + private final Expression rightExpression; public MultiplyExpression(Expression leftExpression, Expression rightExpression) { this.leftExpression = leftExpression; diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java index 6b957f6aa..908eec8d1 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java @@ -28,7 +28,7 @@ package com.iluwatar.interpreter; */ public class NumberExpression extends Expression { - private int number; + private final int number; public NumberExpression(int number) { this.number = number; diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java index 1ce080259..38a8bb4af 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java @@ -28,8 +28,8 @@ package com.iluwatar.interpreter; */ public class PlusExpression extends Expression { - private Expression leftExpression; - private Expression rightExpression; + private final Expression leftExpression; + private final Expression rightExpression; public PlusExpression(Expression leftExpression, Expression rightExpression) { this.leftExpression = leftExpression; diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java index 505dc559c..f4fb45637 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.interpreter; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/iterator/README.md b/iterator/README.md index 7f06a64b9..71ad807cd 100644 --- a/iterator/README.md +++ b/iterator/README.md @@ -9,34 +9,39 @@ tags: --- ## Also known as + Cursor ## Intent -Provide a way to access the elements of an aggregate object -sequentially without exposing its underlying representation. +Provide a way to access the elements of an aggregate object sequentially without exposing its +underlying representation. ## Explanation Real world example -> Treasure chest contains a set of magical items. There multiple types of items such as rings, potions and weapons. The items can be browsed by type using an iterator the treasure chest provides. +> Treasure chest contains a set of magical items. There multiple types of items such as rings, +> potions and weapons. The items can be browsed by type using an iterator the treasure chest +> provides. In plain words -> Containers can provide a representation agnostic iterator interface to provide access to the elements. +> Containers can provide a representation agnostic iterator interface to provide access to the +> elements. Wikipedia says -> In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. +> In object-oriented programming, the iterator pattern is a design pattern in which an iterator is +> used to traverse a container and access the container's elements. **Programmatic Example** -The main class in our example is the treasure chest that contains items. +The main class in our example is the `TreasureChest` that contains items. ```java public class TreasureChest { - private List items; + private final List items; public TreasureChest() { items = List.of( @@ -60,11 +65,15 @@ public class TreasureChest { return new ArrayList<>(items); } } +``` +Here's the `Item` class: + +```java public class Item { private ItemType type; - private String name; + private final String name; public Item(ItemType type, String name) { this.setType(type); @@ -92,7 +101,7 @@ public enum ItemType { } ``` -The iterator interface is extremely simple. +The `Iterator` interface is extremely simple. ```java public interface Iterator { @@ -110,19 +119,26 @@ var itemIterator = TREASURE_CHEST.iterator(ItemType.RING); while (itemIterator.hasNext()) { LOGGER.info(itemIterator.next().toString()); } -// Ring of shadows -// Ring of armor +``` + +Program output: + +```java +Ring of shadows +Ring of armor ``` ## Class diagram + ![alt text](./etc/iterator_1.png "Iterator") ## 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 +* 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 diff --git a/iterator/pom.xml b/iterator/pom.xml index 514cedbea..bca091f11 100644 --- a/iterator/pom.xml +++ b/iterator/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.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 b81e765be..053b5c4fb 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/App.java +++ b/iterator/src/main/java/com/iluwatar/iterator/App.java @@ -62,7 +62,7 @@ public class App { LOGGER.info("------------------------"); LOGGER.info("BST Iterator: "); var root = buildIntegerBst(); - var bstIterator = new BstIterator(root); + var bstIterator = new BstIterator<>(root); while (bstIterator.hasNext()) { LOGGER.info("Next node: " + bstIterator.next().getVal()); } diff --git a/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java b/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java index b3e0dc3d6..9f584cddc 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java +++ b/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java @@ -36,7 +36,7 @@ import java.util.NoSuchElementException; */ public class BstIterator> implements Iterator> { - private ArrayDeque> pathStack; + private final ArrayDeque> pathStack; public BstIterator(TreeNode root) { pathStack = new ArrayDeque<>(); diff --git a/iterator/src/main/java/com/iluwatar/iterator/bst/README.md b/iterator/src/main/java/com/iluwatar/iterator/bst/README.md index 816d1a4fe..07bcbf84f 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/bst/README.md +++ b/iterator/src/main/java/com/iluwatar/iterator/bst/README.md @@ -1,8 +1,11 @@ # BSTIterator -An implementation of the Iterator design pattern, for the Binary Search Tree -data structure. A great explanation of BSTs can be found in this [video tutorial](https://www.youtube.com/watch?v=i_Q0v_Ct5lY). -### What it Does +An implementation of the Iterator design pattern, for the Binary Search Tree data structure. A great +explanation of BSTs can be found in this +[video tutorial](https://www.youtube.com/watch?v=i_Q0v_Ct5lY). + +### What It Does + This iterator assumes that the given binary search tree inserts nodes of smaller value to the left, and nodes of larger value to the right of current node. Accordingly, this iterator will return nodes according to "In Order" binary tree traversal. @@ -12,6 +15,7 @@ return values in order: 1, 3, 4, 6, 7, 8, 10, 13, 14. ![BST](../../../../../../../etc/bst.jpg "Binary Search Tree") ### How It's Done + **The trivial solution** to a binary search tree iterator would be to construct a List (or similar linear data structure) when you construct the BSTIterator. This would require traversing the entire BST, adding each node value to your list as you go. The downside to the trivial solution is twofold. @@ -80,7 +84,4 @@ In Big O terms, here are the costs for our improved solution, where h is the hei * Extra Space: O(h) As you can see, this solution more evenly distributes the work. It yields the same amortized -runtime for `next()`, reduces the run time of the constructor, and uses less extra space. - - - \ No newline at end of file +runtime for `next()`, reduces the run time of the constructor, and uses less extra space. diff --git a/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java b/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java index 87f16e96c..b0ec5f486 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java +++ b/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java @@ -31,7 +31,7 @@ package com.iluwatar.iterator.bst; */ public class TreeNode> { - private T val; + private final T val; private TreeNode left; private TreeNode right; diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/Item.java b/iterator/src/main/java/com/iluwatar/iterator/list/Item.java index 82e66eb30..00d5625a8 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/Item.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/Item.java @@ -29,7 +29,7 @@ package com.iluwatar.iterator.list; public class Item { private ItemType type; - private String name; + private final String name; public Item(ItemType type, String name) { this.setType(type); diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java index f390c760f..8eb4a8e18 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java @@ -32,7 +32,7 @@ import java.util.List; */ public class TreasureChest { - private List items; + private final List items; /** * Constructor. diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java index 90461c420..a309b4ece 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java @@ -30,9 +30,9 @@ import com.iluwatar.iterator.Iterator; */ public class TreasureChestItemIterator implements Iterator { - private TreasureChest chest; + private final TreasureChest chest; private int idx; - private ItemType type; + private final ItemType type; /** * Constructor. diff --git a/iterator/src/test/java/com/iluwatar/iterator/AppTest.java b/iterator/src/test/java/com/iluwatar/iterator/AppTest.java index ccb33307c..2e0d28b67 100644 --- a/iterator/src/test/java/com/iluwatar/iterator/AppTest.java +++ b/iterator/src/test/java/com/iluwatar/iterator/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.iterator; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application Test */ class AppTest { @Test - void testApp() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } \ No newline at end of file diff --git a/layers/README.md b/layers/README.md index c3c56ad00..d29ad40d0 100644 --- a/layers/README.md +++ b/layers/README.md @@ -10,26 +10,33 @@ tags: --- ## Intent -Layers is an architectural pattern where software responsibilities are - divided among the different layers of the application. + +Layers is an architectural pattern where software responsibilities are divided among the different +layers of the application. ## Explanation Real world example -> Consider a web site displaying decorated cakes for weddings and such. Instead of the web page directly reaching into the database, it relies on a service to deliver this information. The service then queries the data layer to assimilate the needed information. +> Consider a web site displaying decorated cakes for weddings and such. Instead of the web page +> directly reaching into the database, it relies on a service to deliver this information. The +> service then queries the data layer to assimilate the needed information. -In Plain Words +In plain words -> With Layers architectural pattern different concerns reside on separate layers. View layer is interested only in rendering, service layer assembles the requested data from various sources, and data layer gets the bits from the data storage. +> With Layers architectural pattern different concerns reside on separate layers. View layer is +> interested only in rendering, service layer assembles the requested data from various sources, and +> data layer gets the bits from the data storage. Wikipedia says -> In software engineering, multitier architecture (often referred to as n-tier architecture) or multilayered architecture is a client–server architecture in which presentation, application processing, and data management functions are physically separated. +> In software engineering, multitier architecture (often referred to as n-tier architecture) or +> multilayered architecture is a client–server architecture in which presentation, application +> processing, and data management functions are physically separated. **Programmatic Example** -On the data layer, we keep our cake building blocks. Cakes consist of layers and topping. +On the data layer, we keep our cake building blocks. `Cake` consist of layers and topping. ```java @Entity @@ -47,7 +54,7 @@ public class Cake { } ``` -The service layer offers CakeBakingService for easy access to different aspects of cakes. +The service layer offers `CakeBakingService` for easy access to different aspects of cakes. ```java public interface CakeBakingService { @@ -66,7 +73,7 @@ public interface CakeBakingService { } ``` -On the top we have our view responsible of rendering the cakes. +On the top we have our `View` responsible of rendering the cakes. ```java public interface View { @@ -79,7 +86,7 @@ public class CakeViewImpl implements View { private static final Logger LOGGER = LoggerFactory.getLogger(CakeViewImpl.class); - private CakeBakingService cakeBakingService; + private final CakeBakingService cakeBakingService; public CakeViewImpl(CakeBakingService cakeBakingService) { this.cakeBakingService = cakeBakingService; @@ -92,14 +99,16 @@ public class CakeViewImpl implements View { ``` ## Class diagram + ![alt text](./etc/layers.png "Layers") ## Applicability + Use the Layers architecture when -* you want clearly divide software responsibilities into different 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 +* You want clearly divide software responsibilities into different 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 diff --git a/layers/pom.xml b/layers/pom.xml index 2ebace18b..9c299d2ba 100644 --- a/layers/pom.xml +++ b/layers/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT com.iluwatar.layers layers diff --git a/layers/src/main/java/com/iluwatar/layers/app/App.java b/layers/src/main/java/com/iluwatar/layers/app/App.java index afeb5ba50..8574b8d02 100644 --- a/layers/src/main/java/com/iluwatar/layers/app/App.java +++ b/layers/src/main/java/com/iluwatar/layers/app/App.java @@ -80,7 +80,8 @@ import java.util.List; */ public class App { - private static CakeBakingService cakeBakingService = new CakeBakingServiceImpl(); + private static final CakeBakingService cakeBakingService = new CakeBakingServiceImpl(); + public static final String STRAWBERRY = "strawberry"; /** * Application entry point. @@ -103,10 +104,10 @@ public class App { private static void initializeData(CakeBakingService cakeBakingService) { cakeBakingService.saveNewLayer(new CakeLayerInfo("chocolate", 1200)); cakeBakingService.saveNewLayer(new CakeLayerInfo("banana", 900)); - cakeBakingService.saveNewLayer(new CakeLayerInfo("strawberry", 950)); + cakeBakingService.saveNewLayer(new CakeLayerInfo(STRAWBERRY, 950)); cakeBakingService.saveNewLayer(new CakeLayerInfo("lemon", 950)); cakeBakingService.saveNewLayer(new CakeLayerInfo("vanilla", 950)); - cakeBakingService.saveNewLayer(new CakeLayerInfo("strawberry", 950)); + cakeBakingService.saveNewLayer(new CakeLayerInfo(STRAWBERRY, 950)); cakeBakingService.saveNewTopping(new CakeToppingInfo("candies", 350)); cakeBakingService.saveNewTopping(new CakeToppingInfo("cherry", 350)); @@ -114,7 +115,7 @@ public class App { var cake1 = new CakeInfo(new CakeToppingInfo("candies", 0), List.of( new CakeLayerInfo("chocolate", 0), new CakeLayerInfo("banana", 0), - new CakeLayerInfo("strawberry", 0))); + new CakeLayerInfo(STRAWBERRY, 0))); try { cakeBakingService.bakeNewCake(cake1); } catch (CakeBakingException e) { @@ -123,7 +124,7 @@ public class App { var cake2 = new CakeInfo(new CakeToppingInfo("cherry", 0), List.of( new CakeLayerInfo("vanilla", 0), new CakeLayerInfo("lemon", 0), - new CakeLayerInfo("strawberry", 0))); + new CakeLayerInfo(STRAWBERRY, 0))); try { cakeBakingService.bakeNewCake(cake2); } catch (CakeBakingException e) { diff --git a/layers/src/main/java/com/iluwatar/layers/service/CakeBakingServiceImpl.java b/layers/src/main/java/com/iluwatar/layers/service/CakeBakingServiceImpl.java index 226b5bcea..4694082fa 100644 --- a/layers/src/main/java/com/iluwatar/layers/service/CakeBakingServiceImpl.java +++ b/layers/src/main/java/com/iluwatar/layers/service/CakeBakingServiceImpl.java @@ -35,9 +35,7 @@ import com.iluwatar.layers.entity.CakeTopping; import com.iluwatar.layers.exception.CakeBakingException; import java.util.ArrayList; import java.util.HashSet; -import java.util.Iterator; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.springframework.context.support.AbstractApplicationContext; @@ -52,7 +50,7 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class CakeBakingServiceImpl implements CakeBakingService { - private AbstractApplicationContext context; + private final AbstractApplicationContext context; public CakeBakingServiceImpl() { this.context = new ClassPathXmlApplicationContext("applicationContext.xml"); @@ -72,7 +70,7 @@ public class CakeBakingServiceImpl implements CakeBakingService { Set foundLayers = new HashSet<>(); for (var info : cakeInfo.cakeLayerInfos) { var found = allLayers.stream().filter(layer -> layer.getName().equals(info.name)).findFirst(); - if (!found.isPresent()) { + if (found.isEmpty()) { throw new CakeBakingException(String.format("Layer %s is not available", info.name)); } else { foundLayers.add(found.get()); @@ -114,9 +112,7 @@ public class CakeBakingServiceImpl implements CakeBakingService { private List getAvailableToppingEntities() { var bean = context.getBean(CakeToppingDao.class); List result = new ArrayList<>(); - var iterator = bean.findAll().iterator(); - while (iterator.hasNext()) { - var topping = iterator.next(); + for (CakeTopping topping : bean.findAll()) { if (topping.getCake() == null) { result.add(topping); } @@ -128,9 +124,7 @@ public class CakeBakingServiceImpl implements CakeBakingService { public List getAvailableToppings() { var bean = context.getBean(CakeToppingDao.class); List result = new ArrayList<>(); - var iterator = bean.findAll().iterator(); - while (iterator.hasNext()) { - var next = iterator.next(); + for (CakeTopping next : bean.findAll()) { if (next.getCake() == null) { result.add(new CakeToppingInfo(next.getId(), next.getName(), next.getCalories())); } @@ -141,9 +135,7 @@ public class CakeBakingServiceImpl implements CakeBakingService { private List getAvailableLayerEntities() { var bean = context.getBean(CakeLayerDao.class); List result = new ArrayList<>(); - var iterator = bean.findAll().iterator(); - while (iterator.hasNext()) { - var next = iterator.next(); + for (CakeLayer next : bean.findAll()) { if (next.getCake() == null) { result.add(next); } @@ -155,9 +147,7 @@ public class CakeBakingServiceImpl implements CakeBakingService { public List getAvailableLayers() { var bean = context.getBean(CakeLayerDao.class); List result = new ArrayList<>(); - var iterator = bean.findAll().iterator(); - while (iterator.hasNext()) { - var next = iterator.next(); + for (CakeLayer next : bean.findAll()) { if (next.getCake() == null) { result.add(new CakeLayerInfo(next.getId(), next.getName(), next.getCalories())); } @@ -169,9 +159,7 @@ public class CakeBakingServiceImpl implements CakeBakingService { public List getAllCakes() { var cakeBean = context.getBean(CakeDao.class); List result = new ArrayList<>(); - var iterator = cakeBean.findAll().iterator(); - while (iterator.hasNext()) { - var cake = iterator.next(); + for (Cake cake : cakeBean.findAll()) { var cakeToppingInfo = new CakeToppingInfo(cake.getTopping().getId(), cake.getTopping().getName(), cake .getTopping().getCalories()); diff --git a/layers/src/main/java/com/iluwatar/layers/view/CakeViewImpl.java b/layers/src/main/java/com/iluwatar/layers/view/CakeViewImpl.java index 5fcaac776..a5246e7db 100644 --- a/layers/src/main/java/com/iluwatar/layers/view/CakeViewImpl.java +++ b/layers/src/main/java/com/iluwatar/layers/view/CakeViewImpl.java @@ -34,7 +34,7 @@ public class CakeViewImpl implements View { private static final Logger LOGGER = LoggerFactory.getLogger(CakeViewImpl.class); - private CakeBakingService cakeBakingService; + private final CakeBakingService cakeBakingService; public CakeViewImpl(CakeBakingService cakeBakingService) { this.cakeBakingService = cakeBakingService; diff --git a/layers/src/test/java/com/iluwatar/layers/app/AppTest.java b/layers/src/test/java/com/iluwatar/layers/app/AppTest.java index 10d224a8e..105487683 100644 --- a/layers/src/test/java/com/iluwatar/layers/app/AppTest.java +++ b/layers/src/test/java/com/iluwatar/layers/app/AppTest.java @@ -23,19 +23,19 @@ package com.iluwatar.layers.app; -import com.iluwatar.layers.app.App; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * * Application test * */ -public class AppTest { +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/layers/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java b/layers/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java index b707731d2..3c13966de 100644 --- a/layers/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java +++ b/layers/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java @@ -90,7 +90,7 @@ public class CakeViewImplTest { private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); diff --git a/lazy-loading/pom.xml b/lazy-loading/pom.xml index a6a5d3a45..49421edc2 100644 --- a/lazy-loading/pom.xml +++ b/lazy-loading/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT lazy-loading 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 2854a7822..395dfb81c 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 @@ -55,7 +55,7 @@ public class Java8Holder { } } - if (!HeavyFactory.class.isInstance(heavy)) { + if (!(heavy instanceof HeavyFactory)) { heavy = new HeavyFactory(); } 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 eeccd10ce..74c324b24 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 @@ -25,16 +25,17 @@ package com.iluwatar.lazy.loading; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * * Application test * */ -public class AppTest { +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/leader-election/pom.xml b/leader-election/pom.xml index 8fc833f18..78ad13e4d 100644 --- a/leader-election/pom.xml +++ b/leader-election/pom.xml @@ -30,7 +30,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT leader-election diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java index f30610597..94db89ef7 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java @@ -36,6 +36,7 @@ public abstract class AbstractInstance implements Instance, Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractInstance.class); protected static final int HEARTBEAT_INTERVAL = 5000; + private static final String INSTANCE = "Instance "; protected MessageManager messageManager; protected Queue messageQueue; @@ -106,27 +107,27 @@ public abstract class AbstractInstance implements Instance, Runnable { private void processMessage(Message message) { switch (message.getType()) { case ELECTION: - LOGGER.info("Instance " + localId + " - Election Message handling..."); + LOGGER.info(INSTANCE + localId + " - Election Message handling..."); handleElectionMessage(message); break; case LEADER: - LOGGER.info("Instance " + localId + " - Leader Message handling..."); + LOGGER.info(INSTANCE + localId + " - Leader Message handling..."); handleLeaderMessage(message); break; case HEARTBEAT: - LOGGER.info("Instance " + localId + " - Heartbeat Message handling..."); + LOGGER.info(INSTANCE + localId + " - Heartbeat Message handling..."); handleHeartbeatMessage(message); break; case ELECTION_INVOKE: - LOGGER.info("Instance " + localId + " - Election Invoke Message handling..."); + LOGGER.info(INSTANCE + localId + " - Election Invoke Message handling..."); handleElectionInvokeMessage(); break; case LEADER_INVOKE: - LOGGER.info("Instance " + localId + " - Leader Invoke Message handling..."); + LOGGER.info(INSTANCE + localId + " - Leader Invoke Message handling..."); handleLeaderInvokeMessage(); break; case HEARTBEAT_INVOKE: - LOGGER.info("Instance " + localId + " - Heartbeat Invoke Message handling..."); + LOGGER.info(INSTANCE + localId + " - Heartbeat Invoke Message handling..."); handleHeartbeatInvokeMessage(); break; default: diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java index 92cb18ab4..3181791d4 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java @@ -41,6 +41,7 @@ import org.slf4j.LoggerFactory; public class BullyInstance extends AbstractInstance { private static final Logger LOGGER = LoggerFactory.getLogger(BullyInstance.class); + private static final String INSTANCE = "Instance "; /** * Constructor of BullyInstance. @@ -59,20 +60,20 @@ public class BullyInstance extends AbstractInstance { try { boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId); if (isLeaderAlive) { - LOGGER.info("Instance " + localId + "- Leader is alive."); + LOGGER.info(INSTANCE + localId + "- Leader is alive."); Thread.sleep(HEARTBEAT_INTERVAL); messageManager.sendHeartbeatInvokeMessage(localId); } else { - LOGGER.info("Instance " + localId + "- Leader is not alive. Start election."); + LOGGER.info(INSTANCE + localId + "- Leader is not alive. Start election."); boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); if (electionResult) { - LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification."); + LOGGER.info(INSTANCE + localId + "- Succeed in election. Start leader notification."); messageManager.sendLeaderMessage(localId, localId); } } } catch (InterruptedException e) { - LOGGER.info("Instance " + localId + "- Interrupted."); + LOGGER.info(INSTANCE + localId + "- Interrupted."); } } @@ -84,10 +85,10 @@ public class BullyInstance extends AbstractInstance { @Override protected void handleElectionInvokeMessage() { if (!isLeader()) { - LOGGER.info("Instance " + localId + "- Start election."); + LOGGER.info(INSTANCE + localId + "- Start election."); boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); if (electionResult) { - LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification."); + LOGGER.info(INSTANCE + localId + "- Succeed in election. Start leader notification."); leaderId = localId; messageManager.sendLeaderMessage(localId, localId); messageManager.sendHeartbeatInvokeMessage(localId); @@ -101,25 +102,25 @@ public class BullyInstance extends AbstractInstance { @Override protected void handleLeaderMessage(Message message) { leaderId = Integer.valueOf(message.getContent()); - LOGGER.info("Instance " + localId + " - Leader update done."); + LOGGER.info(INSTANCE + localId + " - Leader update done."); } private boolean isLeader() { return localId == leaderId; } - /** - * Not used in Bully instance. - */ @Override protected void handleLeaderInvokeMessage() { + // Not used in Bully Instance } @Override protected void handleHeartbeatMessage(Message message) { + // Not used in Bully Instance } @Override protected void handleElectionMessage(Message message) { + // Not used in Bully Instance } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index d4a3075d8..2d2d0ae1a 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -46,6 +46,7 @@ import org.slf4j.LoggerFactory; public class RingInstance extends AbstractInstance { private static final Logger LOGGER = LoggerFactory.getLogger(RingInstance.class); + private static final String INSTANCE = "Instance "; /** * Constructor of RingInstance. @@ -64,15 +65,15 @@ public class RingInstance extends AbstractInstance { try { var isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); if (isLeaderAlive) { - LOGGER.info("Instance " + localId + "- Leader is alive. Start next heartbeat in 5 second."); + LOGGER.info(INSTANCE + localId + "- Leader is alive. Start next heartbeat in 5 second."); Thread.sleep(HEARTBEAT_INTERVAL); messageManager.sendHeartbeatInvokeMessage(this.localId); } else { - LOGGER.info("Instance " + localId + "- Leader is not alive. Start election."); + LOGGER.info(INSTANCE + localId + "- Leader is not alive. Start election."); messageManager.sendElectionMessage(this.localId, String.valueOf(this.localId)); } } catch (InterruptedException e) { - LOGGER.info("Instance " + localId + "- Interrupted."); + LOGGER.info(INSTANCE + localId + "- Interrupted."); } } @@ -85,14 +86,14 @@ public class RingInstance extends AbstractInstance { @Override protected void handleElectionMessage(Message message) { var content = message.getContent(); - LOGGER.info("Instance " + localId + " - Election Message: " + content); + LOGGER.info(INSTANCE + localId + " - Election Message: " + content); var candidateList = Arrays.stream(content.trim().split(",")) .map(Integer::valueOf) .sorted() .collect(Collectors.toList()); if (candidateList.contains(localId)) { var newLeaderId = candidateList.get(0); - LOGGER.info("Instance " + localId + " - New leader should be " + newLeaderId + "."); + LOGGER.info(INSTANCE + localId + " - New leader should be " + newLeaderId + "."); messageManager.sendLeaderMessage(localId, newLeaderId); } else { content += "," + localId; @@ -108,11 +109,11 @@ public class RingInstance extends AbstractInstance { protected void handleLeaderMessage(Message message) { var newLeaderId = Integer.valueOf(message.getContent()); if (this.leaderId != newLeaderId) { - LOGGER.info("Instance " + localId + " - Update leaderID"); + LOGGER.info(INSTANCE + localId + " - Update leaderID"); this.leaderId = newLeaderId; messageManager.sendLeaderMessage(localId, newLeaderId); } else { - LOGGER.info("Instance " + localId + " - Leader update done. Start heartbeat."); + LOGGER.info(INSTANCE + localId + " - Leader update done. Start heartbeat."); messageManager.sendHeartbeatInvokeMessage(localId); } } @@ -122,14 +123,17 @@ public class RingInstance extends AbstractInstance { */ @Override protected void handleLeaderInvokeMessage() { + // Not used in Ring instance. } @Override protected void handleHeartbeatMessage(Message message) { + // Not used in Ring instance. } @Override protected void handleElectionInvokeMessage() { + // Not used in Ring instance. } } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java index 3ef36f758..19497a32c 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java @@ -25,15 +25,16 @@ package com.iluwatar.leaderelection.bully; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * BullyApp unit test. */ -public class BullyAppTest { +class BullyAppTest { @Test - public void test() { - String[] args = {}; - BullyApp.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> BullyApp.main(new String[]{})); } } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java index ec3851d9b..ce5c59aa7 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java @@ -25,15 +25,16 @@ package com.iluwatar.leaderelection.ring; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * RingApp unit test. */ -public class RingAppTest { +class RingAppTest { @Test - public void test() { - String[] args = {}; - RingApp.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> RingApp.main(new String[]{})); } } diff --git a/leader-followers/pom.xml b/leader-followers/pom.xml index 4efdbf3ac..42e552ac8 100644 --- a/leader-followers/pom.xml +++ b/leader-followers/pom.xml @@ -28,7 +28,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT leader-followers @@ -37,6 +37,11 @@ junit test + + org.junit.jupiter + junit-jupiter-engine + test + org.mockito mockito-core diff --git a/leader-followers/src/main/java/com.iluwatar.leaderfollowers/App.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java similarity index 100% rename from leader-followers/src/main/java/com.iluwatar.leaderfollowers/App.java rename to leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java diff --git a/leader-followers/src/main/java/com.iluwatar.leaderfollowers/Task.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Task.java similarity index 100% rename from leader-followers/src/main/java/com.iluwatar.leaderfollowers/Task.java rename to leader-followers/src/main/java/com/iluwatar/leaderfollowers/Task.java diff --git a/leader-followers/src/main/java/com.iluwatar.leaderfollowers/TaskHandler.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskHandler.java similarity index 100% rename from leader-followers/src/main/java/com.iluwatar.leaderfollowers/TaskHandler.java rename to leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskHandler.java diff --git a/leader-followers/src/main/java/com.iluwatar.leaderfollowers/TaskSet.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java similarity index 95% rename from leader-followers/src/main/java/com.iluwatar.leaderfollowers/TaskSet.java rename to leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java index 3138427a3..3461bc8c0 100644 --- a/leader-followers/src/main/java/com.iluwatar.leaderfollowers/TaskSet.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java @@ -31,7 +31,7 @@ import java.util.concurrent.BlockingQueue; */ public class TaskSet { - private BlockingQueue queue = new ArrayBlockingQueue<>(100); + private final BlockingQueue queue = new ArrayBlockingQueue<>(100); public void addTask(Task task) throws InterruptedException { queue.put(task); diff --git a/leader-followers/src/main/java/com.iluwatar.leaderfollowers/WorkCenter.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java similarity index 97% rename from leader-followers/src/main/java/com.iluwatar.leaderfollowers/WorkCenter.java rename to leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java index 7c63d95d2..935462037 100644 --- a/leader-followers/src/main/java/com.iluwatar.leaderfollowers/WorkCenter.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java @@ -34,7 +34,7 @@ import java.util.concurrent.CopyOnWriteArrayList; public class WorkCenter { private Worker leader; - private List workers = new CopyOnWriteArrayList<>(); + private final List workers = new CopyOnWriteArrayList<>(); /** * Create workers and set leader. diff --git a/leader-followers/src/main/java/com.iluwatar.leaderfollowers/Worker.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Worker.java similarity index 100% rename from leader-followers/src/main/java/com.iluwatar.leaderfollowers/Worker.java rename to leader-followers/src/main/java/com/iluwatar/leaderfollowers/Worker.java diff --git a/leader-followers/src/test/java/com.iluwatar.leaderfollowers/AppTest.java b/leader-followers/src/test/java/com/AppTest.java similarity index 83% rename from leader-followers/src/test/java/com.iluwatar.leaderfollowers/AppTest.java rename to leader-followers/src/test/java/com/AppTest.java index 00fbfe4d2..3d0588fef 100644 --- a/leader-followers/src/test/java/com.iluwatar.leaderfollowers/AppTest.java +++ b/leader-followers/src/test/java/com/AppTest.java @@ -21,10 +21,13 @@ * THE SOFTWARE. */ -package com.iluwatar.leaderfollowers; +package com; +import com.iluwatar.leaderfollowers.App; import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * * Application test @@ -33,9 +36,8 @@ import org.junit.Test; public class AppTest { @Test - public void test() throws InterruptedException { - String[] args = {}; - App.main(args); + public void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/leader-followers/src/test/java/com.iluwatar.leaderfollowers/TaskHandlerTest.java b/leader-followers/src/test/java/com/TaskHandlerTest.java similarity index 93% rename from leader-followers/src/test/java/com.iluwatar.leaderfollowers/TaskHandlerTest.java rename to leader-followers/src/test/java/com/TaskHandlerTest.java index 9fb39c922..7dc44c04f 100644 --- a/leader-followers/src/test/java/com.iluwatar.leaderfollowers/TaskHandlerTest.java +++ b/leader-followers/src/test/java/com/TaskHandlerTest.java @@ -21,8 +21,10 @@ * THE SOFTWARE. */ -package com.iluwatar.leaderfollowers; +package com; +import com.iluwatar.leaderfollowers.Task; +import com.iluwatar.leaderfollowers.TaskHandler; import org.junit.Assert; import org.junit.Test; diff --git a/leader-followers/src/test/java/com.iluwatar.leaderfollowers/TaskSetTest.java b/leader-followers/src/test/java/com/TaskSetTest.java similarity index 94% rename from leader-followers/src/test/java/com.iluwatar.leaderfollowers/TaskSetTest.java rename to leader-followers/src/test/java/com/TaskSetTest.java index 53cb31deb..e3691d220 100644 --- a/leader-followers/src/test/java/com.iluwatar.leaderfollowers/TaskSetTest.java +++ b/leader-followers/src/test/java/com/TaskSetTest.java @@ -21,8 +21,10 @@ * THE SOFTWARE. */ -package com.iluwatar.leaderfollowers; +package com; +import com.iluwatar.leaderfollowers.Task; +import com.iluwatar.leaderfollowers.TaskSet; import org.junit.Assert; import org.junit.Test; diff --git a/leader-followers/src/test/java/com.iluwatar.leaderfollowers/WorkCenterTest.java b/leader-followers/src/test/java/com/WorkCenterTest.java similarity index 93% rename from leader-followers/src/test/java/com.iluwatar.leaderfollowers/WorkCenterTest.java rename to leader-followers/src/test/java/com/WorkCenterTest.java index 045d8c3dc..41e9ff43f 100644 --- a/leader-followers/src/test/java/com.iluwatar.leaderfollowers/WorkCenterTest.java +++ b/leader-followers/src/test/java/com/WorkCenterTest.java @@ -21,8 +21,11 @@ * THE SOFTWARE. */ -package com.iluwatar.leaderfollowers; +package com; +import com.iluwatar.leaderfollowers.TaskHandler; +import com.iluwatar.leaderfollowers.TaskSet; +import com.iluwatar.leaderfollowers.WorkCenter; import org.junit.Assert; import org.junit.Test; diff --git a/marker/pom.xml b/marker/pom.xml index 5212832d8..64b7243f3 100644 --- a/marker/pom.xml +++ b/marker/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/marker/src/main/java/App.java b/marker/src/main/java/App.java index 384c999dc..0908503e5 100644 --- a/marker/src/main/java/App.java +++ b/marker/src/main/java/App.java @@ -46,17 +46,18 @@ public class App { * @param args command line args */ public static void main(String[] args) { + final var logger = LoggerFactory.getLogger(App.class); + var guard = new Guard(); + var thief = new Thief(); - final Logger logger = LoggerFactory.getLogger(App.class); - Guard guard = new Guard(); - Thief thief = new Thief(); - + //noinspection ConstantConditions if (guard instanceof Permission) { guard.enter(); } else { logger.info("You have no permission to enter, please leave this area"); } + //noinspection ConstantConditions if (thief instanceof Permission) { thief.steal(); } else { diff --git a/marker/src/main/java/Guard.java b/marker/src/main/java/Guard.java index 9a57e15fd..9e7b731b6 100644 --- a/marker/src/main/java/Guard.java +++ b/marker/src/main/java/Guard.java @@ -28,11 +28,9 @@ import org.slf4j.LoggerFactory; * Class defining Guard. */ public class Guard implements Permission { - private static final Logger LOGGER = LoggerFactory.getLogger(Guard.class); - protected static void enter() { - + protected void enter() { LOGGER.info("You can enter"); } } diff --git a/marker/src/main/java/Thief.java b/marker/src/main/java/Thief.java index 341eae377..0e4cf92e3 100644 --- a/marker/src/main/java/Thief.java +++ b/marker/src/main/java/Thief.java @@ -28,14 +28,13 @@ import org.slf4j.LoggerFactory; * Class defining Thief. */ public class Thief { - private static final Logger LOGGER = LoggerFactory.getLogger(Thief.class); - protected static void steal() { + protected void steal() { LOGGER.info("Steal valuable items"); } - protected static void doNothing() { + protected void doNothing() { LOGGER.info("Pretend nothing happened and just leave"); } } diff --git a/marker/src/test/java/AppTest.java b/marker/src/test/java/AppTest.java index 5d63db0ad..17a13c167 100644 --- a/marker/src/test/java/AppTest.java +++ b/marker/src/test/java/AppTest.java @@ -23,14 +23,15 @@ import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/marker/src/test/java/GuardTest.java b/marker/src/test/java/GuardTest.java index 615d4e129..ae92c27dc 100644 --- a/marker/src/test/java/GuardTest.java +++ b/marker/src/test/java/GuardTest.java @@ -33,7 +33,7 @@ public class GuardTest { @Test public void testGuard() { - Guard guard = new Guard(); + var guard = new Guard(); assertThat(guard, instanceOf(Permission.class)); } } \ No newline at end of file diff --git a/marker/src/test/java/ThiefTest.java b/marker/src/test/java/ThiefTest.java index 2732fc78a..dc081acf8 100644 --- a/marker/src/test/java/ThiefTest.java +++ b/marker/src/test/java/ThiefTest.java @@ -21,9 +21,11 @@ * THE SOFTWARE. */ -import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; +import org.junit.jupiter.api.Test; /** * Thief test @@ -31,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; public class ThiefTest { @Test public void testThief() { - Thief thief = new Thief(); - assertFalse(thief instanceof Permission); + var thief = new Thief(); + assertThat(thief, not(instanceOf(Permission.class))); } } \ No newline at end of file diff --git a/master-worker-pattern/pom.xml b/master-worker-pattern/pom.xml index 9924d6a5a..c6bc2facf 100644 --- a/master-worker-pattern/pom.xml +++ b/master-worker-pattern/pom.xml @@ -22,38 +22,39 @@ THE SOFTWARE. --> - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.23.0-SNAPSHOT - - master-worker-pattern - - - org.junit.jupiter - junit-jupiter-engine - test - + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.24.0-SNAPSHOT + + master-worker-pattern + + + org.junit.jupiter + junit-jupiter-engine + test + - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - - com.iluwatar.masterworker.App - - - - - - - - + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.masterworker.App + + + + + + + + diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/App.java b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/App.java index 547636066..592ba8c59 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/App.java +++ b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/App.java @@ -34,27 +34,25 @@ import org.slf4j.LoggerFactory; /** *

The Master-Worker pattern is used when the problem at hand can be solved by - * dividing into - * multiple parts which need to go through the same computation and may need to be aggregated to get - * final result. Parallel processing is performed using a system consisting of a master and some - * number of workers, where a master divides the work among the workers, gets the result back from - * them and assimilates all the results to give final result. The only communication is between the - * master and the worker - none of the workers communicate among one another and the user only - * communicates with the master to get required job done.

+ * dividing into multiple parts which need to go through the same computation and may need to be + * aggregated to get final result. Parallel processing is performed using a system consisting of a + * master and some number of workers, where a master divides the work among the workers, gets the + * result back from them and assimilates all the results to give final result. The only + * communication is between the master and the worker - none of the workers communicate among one + * another and the user only communicates with the master to get required job done.

*

In our example, we have generic abstract classes {@link MasterWorker}, {@link Master} and - * {@link Worker} which - * have to be extended by the classes which will perform the specific job at hand (in this case - * finding transpose of matrix, done by {@link ArrayTransposeMasterWorker}, {@link - * ArrayTransposeMaster} and {@link ArrayTransposeWorker}). The Master class divides the work into - * parts to be given to the workers, collects the results from the workers and aggregates it when - * all workers have responded before returning the solution. The Worker class extends the Thread - * class to enable parallel processing, and does the work once the data has been received from the - * Master. The MasterWorker contains a reference to the Master class, gets the input from the App - * and passes it on to the Master. These 3 classes define the system which computes the result. We - * also have 2 abstract classes {@link Input} and {@link Result}, which contain the input data and - * result data respectively. The Input class also has an abstract method divideData which defines - * how the data is to be divided into segments. These classes are extended by {@link ArrayInput} and - * {@link ArrayResult}.

+ * {@link Worker} which have to be extended by the classes which will perform the specific job at + * hand (in this case finding transpose of matrix, done by {@link ArrayTransposeMasterWorker}, + * {@link ArrayTransposeMaster} and {@link ArrayTransposeWorker}). The Master class divides the work + * into parts to be given to the workers, collects the results from the workers and aggregates it + * when all workers have responded before returning the solution. The Worker class extends the + * Thread class to enable parallel processing, and does the work once the data has been received + * from the Master. The MasterWorker contains a reference to the Master class, gets the input from + * the App and passes it on to the Master. These 3 classes define the system which computes the + * result. We also have 2 abstract classes {@link Input} and {@link Result}, which contain the input + * data and result data respectively. The Input class also has an abstract method divideData which + * defines how the data is to be divided into segments. These classes are extended by {@link + * ArrayInput} and {@link ArrayResult}.

*/ public class App { @@ -68,12 +66,12 @@ public class App { */ public static void main(String[] args) { - ArrayTransposeMasterWorker mw = new ArrayTransposeMasterWorker(); - int rows = 10; - int columns = 20; - int[][] inputMatrix = ArrayUtilityMethods.createRandomIntMatrix(rows, columns); - ArrayInput input = new ArrayInput(inputMatrix); - ArrayResult result = (ArrayResult) mw.getResult(input); + var mw = new ArrayTransposeMasterWorker(); + var rows = 10; + var columns = 20; + var inputMatrix = ArrayUtilityMethods.createRandomIntMatrix(rows, columns); + var input = new ArrayInput(inputMatrix); + var result = (ArrayResult) mw.getResult(input); if (result != null) { ArrayUtilityMethods.printMatrix(inputMatrix); ArrayUtilityMethods.printMatrix(result.data); diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayInput.java b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayInput.java index cd03a0a21..c8e68f958 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayInput.java +++ b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayInput.java @@ -25,6 +25,7 @@ package com.iluwatar.masterworker; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * Class ArrayInput extends abstract class {@link Input} and contains data of type int[][]. @@ -37,12 +38,12 @@ public class ArrayInput extends Input { } static int[] makeDivisions(int[][] data, int num) { - int initialDivision = data.length / num; //equally dividing - int[] divisions = new int[num]; + var initialDivision = data.length / num; //equally dividing + var divisions = new int[num]; Arrays.fill(divisions, initialDivision); if (initialDivision * num != data.length) { - int extra = data.length - initialDivision * num; - int l = 0; + var extra = data.length - initialDivision * num; + var l = 0; //equally dividing extra among all parts while (extra > 0) { divisions[l] = divisions[l] + 1; @@ -58,22 +59,20 @@ public class ArrayInput extends Input { } @Override - public ArrayList divideData(int num) { + public List> divideData(int num) { if (this.data == null) { return null; } else { - int[] divisions = makeDivisions(this.data, num); - ArrayList result = new ArrayList(num); - int rowsDone = 0; //number of rows divided so far - for (int i = 0; i < num; i++) { - int rows = divisions[i]; + var divisions = makeDivisions(this.data, num); + var result = new ArrayList>(num); + var rowsDone = 0; //number of rows divided so far + for (var i = 0; i < num; i++) { + var rows = divisions[i]; if (rows != 0) { - int[][] divided = new int[rows][this.data[0].length]; - for (int j = 0; j < rows; j++) { - divided[j] = this.data[rowsDone + j]; - } + var divided = new int[rows][this.data[0].length]; + System.arraycopy(this.data, rowsDone, divided, 0, rows); rowsDone += rows; - ArrayInput dividedInput = new ArrayInput(divided); + var dividedInput = new ArrayInput(divided); result.add(dividedInput); } else { break; //rest of divisions will also be 0 diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java index 525bed003..5e695e5da 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java +++ b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java @@ -47,8 +47,8 @@ public class ArrayUtilityMethods { if (a1.length != a2.length) { return false; } else { - boolean answer = false; - for (int i = 0; i < a1.length; i++) { + var answer = false; + for (var i = 0; i < a1.length; i++) { if (a1[i] == a2[i]) { answer = true; } else { @@ -69,8 +69,8 @@ public class ArrayUtilityMethods { if (m1.length != m2.length) { return false; } else { - boolean answer = false; - for (int i = 0; i < m1.length; i++) { + var answer = false; + for (var i = 0; i < m1.length; i++) { if (arraysSame(m1[i], m2[i])) { answer = true; } else { @@ -88,9 +88,9 @@ public class ArrayUtilityMethods { * @return it (int[][]). */ public static int[][] createRandomIntMatrix(int rows, int columns) { - int[][] matrix = new int[rows][columns]; - for (int i = 0; i < rows; i++) { - for (int j = 0; j < columns; j++) { + var matrix = new int[rows][columns]; + for (var i = 0; i < rows; i++) { + for (var j = 0; j < columns; j++) { //filling cells in matrix matrix[i][j] = RANDOM.nextInt(10); } @@ -104,9 +104,9 @@ public class ArrayUtilityMethods { public static void printMatrix(int[][] matrix) { //prints out int[][] - for (int i = 0; i < matrix.length; i++) { - for (int j = 0; j < matrix[0].length; j++) { - LOGGER.info(matrix[i][j] + " "); + for (var ints : matrix) { + for (var j = 0; j < matrix[0].length; j++) { + LOGGER.info(ints[j] + " "); } LOGGER.info(""); } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/Input.java b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/Input.java index 6a957ae80..8d832f6c7 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/Input.java +++ b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/Input.java @@ -23,7 +23,7 @@ package com.iluwatar.masterworker; -import java.util.ArrayList; +import java.util.List; /** * The abstract Input class, having 1 public field which contains input data, and abstract method @@ -40,5 +40,5 @@ public abstract class Input { this.data = data; } - public abstract ArrayList divideData(int num); + public abstract List> divideData(int num); } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java index 2b16cbf76..817fd65d3 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java +++ b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java @@ -40,7 +40,7 @@ public abstract class MasterWorker { abstract Master setMaster(int numOfWorkers); - public Result getResult(Input input) { + public Result getResult(Input input) { this.master.doWork(input); return this.master.getFinalResult(); } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java index ffa64572c..9bfbf200e 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java +++ b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java @@ -27,7 +27,8 @@ import com.iluwatar.masterworker.ArrayResult; import com.iluwatar.masterworker.system.systemworkers.ArrayTransposeWorker; import com.iluwatar.masterworker.system.systemworkers.Worker; import java.util.ArrayList; -import java.util.Enumeration; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Class ArrayTransposeMaster extends abstract class {@link Master} and contains definition of @@ -41,35 +42,33 @@ public class ArrayTransposeMaster extends Master { @Override ArrayList setWorkers(int num) { - ArrayList ws = new ArrayList(num); - for (int i = 0; i < num; i++) { - ws.add(new ArrayTransposeWorker(this, i + 1)); - //i+1 will be id - } - return ws; + //i+1 will be id + return IntStream.range(0, num) + .mapToObj(i -> new ArrayTransposeWorker(this, i + 1)) + .collect(Collectors.toCollection(() -> new ArrayList<>(num))); } @Override ArrayResult aggregateData() { // number of rows in final result is number of rows in any of obtained results from workers - int rows = ((ArrayResult) this.getAllResultData() - .get(this.getAllResultData().keys().nextElement())).data.length; - int columns = - 0; //number of columns is sum of number of columns in all results obtained from workers - for (Enumeration e = this.getAllResultData().keys(); e.hasMoreElements(); ) { - columns += ((ArrayResult) this.getAllResultData().get(e.nextElement())).data[0].length; + var allResultData = this.getAllResultData(); + var rows = ((ArrayResult) allResultData.elements().nextElement()).data.length; + var elements = allResultData.elements(); + var columns = 0; // columns = sum of number of columns in all results obtained from workers + while (elements.hasMoreElements()) { + columns += ((ArrayResult) elements.nextElement()).data[0].length; } - int[][] resultData = new int[rows][columns]; - int columnsDone = 0; //columns aggregated so far - for (int i = 0; i < this.getExpectedNumResults(); i++) { + var resultData = new int[rows][columns]; + var columnsDone = 0; //columns aggregated so far + var workers = this.getWorkers(); + for (var i = 0; i < this.getExpectedNumResults(); i++) { //result obtained from ith worker - int[][] work = - ((ArrayResult) this.getAllResultData().get(this.getWorkers().get(i).getWorkerId())).data; - for (int m = 0; m < work.length; m++) { + var worker = workers.get(i); + var workerId = worker.getWorkerId(); + var work = ((ArrayResult) allResultData.get(workerId)).data; + for (var m = 0; m < work.length; m++) { //m = row number, n = columns number - for (int n = 0; n < work[0].length; n++) { - resultData[m][columnsDone + n] = work[m][n]; - } + System.arraycopy(work[m], 0, resultData[m], columnsDone, work[0].length); } columnsDone += work[0].length; } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java index 2466df256..06ea3a8fe 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java +++ b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java @@ -26,8 +26,8 @@ package com.iluwatar.masterworker.system.systemmaster; import com.iluwatar.masterworker.Input; import com.iluwatar.masterworker.Result; import com.iluwatar.masterworker.system.systemworkers.Worker; -import java.util.ArrayList; import java.util.Hashtable; +import java.util.List; /** * The abstract Master class which contains private fields numOfWorkers (number of workers), workers @@ -38,24 +38,24 @@ import java.util.Hashtable; public abstract class Master { private final int numOfWorkers; - private final ArrayList workers; + private final List workers; + private final Hashtable> allResultData; private int expectedNumResults; - private Hashtable allResultData; - private Result finalResult; + private Result finalResult; Master(int numOfWorkers) { this.numOfWorkers = numOfWorkers; this.workers = setWorkers(numOfWorkers); this.expectedNumResults = 0; - this.allResultData = new Hashtable(numOfWorkers); + this.allResultData = new Hashtable<>(numOfWorkers); this.finalResult = null; } - public Result getFinalResult() { + public Result getFinalResult() { return this.finalResult; } - Hashtable getAllResultData() { + Hashtable> getAllResultData() { return this.allResultData; } @@ -63,34 +63,41 @@ public abstract class Master { return this.expectedNumResults; } - ArrayList getWorkers() { + List getWorkers() { return this.workers; } - abstract ArrayList setWorkers(int num); + abstract List setWorkers(int num); - public void doWork(Input input) { + public void doWork(Input input) { divideWork(input); } - private void divideWork(Input input) { - ArrayList dividedInput = input.divideData(numOfWorkers); + private void divideWork(Input input) { + var dividedInput = input.divideData(numOfWorkers); if (dividedInput != null) { this.expectedNumResults = dividedInput.size(); - for (int i = 0; i < this.expectedNumResults; i++) { + for (var i = 0; i < this.expectedNumResults; i++) { //ith division given to ith worker in this.workers this.workers.get(i).setReceivedData(this, dividedInput.get(i)); - this.workers.get(i).run(); + this.workers.get(i).start(); + } + for (var i = 0; i < this.expectedNumResults; i++) { + try { + this.workers.get(i).join(); + } catch (InterruptedException e) { + System.err.println("Error while executing thread"); + } } } } - public void receiveData(Result data, Worker w) { + public void receiveData(Result data, Worker w) { //check if can receive..if yes: collectResult(data, w.getWorkerId()); } - private void collectResult(Result data, int workerId) { + private void collectResult(Result data, int workerId) { this.allResultData.put(workerId, data); if (this.allResultData.size() == this.expectedNumResults) { //all data received @@ -98,5 +105,5 @@ public abstract class Master { } } - abstract Result aggregateData(); + abstract Result aggregateData(); } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java index 37d8ba005..3f2da0a0a 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java +++ b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java @@ -41,12 +41,12 @@ public class ArrayTransposeWorker extends Worker { @Override ArrayResult executeOperation() { //number of rows in result matrix is equal to number of columns in input matrix and vice versa - ArrayInput arrayInput = (ArrayInput) this.getReceivedData(); - final int rows = arrayInput.data[0].length; - final int cols = arrayInput.data.length; - int[][] resultData = new int[rows][cols]; - for (int i = 0; i < cols; i++) { - for (int j = 0; j < rows; j++) { + var arrayInput = (ArrayInput) this.getReceivedData(); + final var rows = arrayInput.data[0].length; + final var cols = arrayInput.data.length; + var resultData = new int[rows][cols]; + for (var i = 0; i < cols; i++) { + for (var j = 0; j < rows; j++) { //flipping element positions along diagonal resultData[j][i] = arrayInput.data[i][j]; } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java index bfe226ee0..526299645 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java +++ b/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java @@ -35,7 +35,7 @@ import com.iluwatar.masterworker.system.systemmaster.Master; public abstract class Worker extends Thread { private final Master master; private final int workerId; - private Input receivedData; + private Input receivedData; Worker(Master master, int id) { this.master = master; @@ -47,23 +47,23 @@ public abstract class Worker extends Thread { return this.workerId; } - Input getReceivedData() { + Input getReceivedData() { return this.receivedData; } - public void setReceivedData(Master m, Input i) { + public void setReceivedData(Master m, Input i) { //check if ready to receive..if yes: this.receivedData = i; } - abstract Result executeOperation(); + abstract Result executeOperation(); - private void sendToMaster(Result data) { + private void sendToMaster(Result data) { this.master.receiveData(data, this); } public void run() { //from Thread class - Result work = executeOperation(); + var work = executeOperation(); sendToMaster(work); } } diff --git a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java b/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java index b5820e2af..1d3c7f0bc 100644 --- a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java +++ b/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java @@ -23,38 +23,39 @@ package com.iluwatar.masterworker; -import static org.junit.jupiter.api.Assertions.*; -import java.util.ArrayList; +import static com.iluwatar.masterworker.ArrayUtilityMethods.matricesSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.util.Random; import org.junit.jupiter.api.Test; /** -* Testing divideData method in {@link ArrayInput} class. -*/ + * Testing divideData method in {@link ArrayInput} class. + */ class ArrayInputTest { @Test void divideDataTest() { - int rows = 10; - int columns = 10; - int[][] inputMatrix = new int[rows][columns]; - Random rand = new Random(); - for (int i = 0; i < rows; i++) { - for (int j = 0; j < columns; j++) { + var rows = 10; + var columns = 10; + var inputMatrix = new int[rows][columns]; + var rand = new Random(); + for (var i = 0; i < rows; i++) { + for (var j = 0; j < columns; j++) { inputMatrix[i][j] = rand.nextInt(10); } } - ArrayInput i = new ArrayInput(inputMatrix); - ArrayList table = i.divideData(4); - int[][] division1 = new int[][] {inputMatrix[0], inputMatrix[1], inputMatrix[2]}; - int[][] division2 = new int[][] {inputMatrix[3], inputMatrix[4], inputMatrix[5]}; - int[][] division3 = new int[][] {inputMatrix[6], inputMatrix[7]}; - int[][] division4 = new int[][] {inputMatrix[8], inputMatrix[9]}; - assertTrue(ArrayUtilityMethods.matricesSame((int[][]) table.get(0).data, division1) - && ArrayUtilityMethods.matricesSame((int[][]) table.get(1).data, division2) - && ArrayUtilityMethods.matricesSame((int[][]) table.get(2).data, division3) - && ArrayUtilityMethods.matricesSame((int[][]) table.get(3).data, division4)); + var i = new ArrayInput(inputMatrix); + var table = i.divideData(4); + var division1 = new int[][]{inputMatrix[0], inputMatrix[1], inputMatrix[2]}; + var division2 = new int[][]{inputMatrix[3], inputMatrix[4], inputMatrix[5]}; + var division3 = new int[][]{inputMatrix[6], inputMatrix[7]}; + var division4 = new int[][]{inputMatrix[8], inputMatrix[9]}; + assertTrue(matricesSame(table.get(0).data, division1) + && matricesSame(table.get(1).data, division2) + && matricesSame(table.get(2).data, division3) + && matricesSame(table.get(3).data, division4)); } } diff --git a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java b/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java index aae784b52..d25276572 100644 --- a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java +++ b/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java @@ -23,27 +23,27 @@ package com.iluwatar.masterworker; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; /** -* Testing utility methods in {@link ArrayUtilityMethods} class. -*/ + * Testing utility methods in {@link ArrayUtilityMethods} class. + */ class ArrayUtilityMethodsTest { @Test void arraysSameTest() { - int[] arr1 = new int[] {1,4,2,6}; - int[] arr2 = new int[] {1,4,2,6}; + var arr1 = new int[]{1, 4, 2, 6}; + var arr2 = new int[]{1, 4, 2, 6}; assertTrue(ArrayUtilityMethods.arraysSame(arr1, arr2)); } @Test void matricesSameTest() { - int[][] matrix1 = new int[][] {{1,4,2,6},{5,8,6,7}}; - int[][] matrix2 = new int[][] {{1,4,2,6},{5,8,6,7}}; + var matrix1 = new int[][]{{1, 4, 2, 6}, {5, 8, 6, 7}}; + var matrix2 = new int[][]{{1, 4, 2, 6}, {5, 8, 6, 7}}; assertTrue(ArrayUtilityMethods.matricesSame(matrix1, matrix2)); } diff --git a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java b/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java index b80d7881f..79838ed35 100644 --- a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java +++ b/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java @@ -23,25 +23,38 @@ package com.iluwatar.masterworker.system; -import static org.junit.jupiter.api.Assertions.*; -import org.junit.jupiter.api.Test; -import com.iluwatar.masterworker.ArrayUtilityMethods; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.iluwatar.masterworker.ArrayInput; import com.iluwatar.masterworker.ArrayResult; +import com.iluwatar.masterworker.ArrayUtilityMethods; +import org.junit.jupiter.api.Test; /** -* Testing getResult method in {@link ArrayTransposeMasterWorker} class. -*/ + * Testing getResult method in {@link ArrayTransposeMasterWorker} class. + */ class ArrayTransposeMasterWorkerTest { @Test void getResultTest() { - ArrayTransposeMasterWorker atmw = new ArrayTransposeMasterWorker(); - int[][] matrix = new int[][] {{1,2,3,4,5}, {1,2,3,4,5}, {1,2,3,4,5}, {1,2,3,4,5}, {1,2,3,4,5}}; - int[][] matrixTranspose = new int[][] {{1,1,1,1,1}, {2,2,2,2,2}, {3,3,3,3,3}, {4,4,4,4,4}, {5,5,5,5,5}}; - ArrayInput i = new ArrayInput(matrix); - ArrayResult r = (ArrayResult) atmw.getResult(i); + var atmw = new ArrayTransposeMasterWorker(); + var matrix = new int[][]{ + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5} + }; + var matrixTranspose = new int[][]{ + {1, 1, 1, 1, 1}, + {2, 2, 2, 2, 2}, + {3, 3, 3, 3, 3}, + {4, 4, 4, 4, 4}, + {5, 5, 5, 5, 5} + }; + var i = new ArrayInput(matrix); + var r = (ArrayResult) atmw.getResult(i); assertTrue(ArrayUtilityMethods.matricesSame(r.data, matrixTranspose)); - } + } } diff --git a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java b/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java index 3e5f581b9..c4b210643 100644 --- a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java +++ b/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java @@ -23,29 +23,29 @@ package com.iluwatar.masterworker.system.systemworkers; -import static org.junit.jupiter.api.Assertions.*; -import org.junit.jupiter.api.Test; -import com.iluwatar.masterworker.ArrayUtilityMethods; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.iluwatar.masterworker.ArrayInput; -import com.iluwatar.masterworker.ArrayResult; +import com.iluwatar.masterworker.ArrayUtilityMethods; import com.iluwatar.masterworker.system.systemmaster.ArrayTransposeMaster; +import org.junit.jupiter.api.Test; /** -* Testing executeOperation method in {@link ArrayTransposeWorker} class. -*/ + * Testing executeOperation method in {@link ArrayTransposeWorker} class. + */ class ArrayTransposeWorkerTest { @Test void executeOperationTest() { - ArrayTransposeMaster atm = new ArrayTransposeMaster(1); - ArrayTransposeWorker atw = new ArrayTransposeWorker(atm, 1); - int[][] matrix = new int[][] {{2,4}, {3,5}}; - int[][] matrixTranspose = new int[][] {{2,3}, {4,5}}; - ArrayInput i = new ArrayInput(matrix); + var atm = new ArrayTransposeMaster(1); + var atw = new ArrayTransposeWorker(atm, 1); + var matrix = new int[][]{{2, 4}, {3, 5}}; + var matrixTranspose = new int[][]{{2, 3}, {4, 5}}; + var i = new ArrayInput(matrix); atw.setReceivedData(atm, i); - ArrayResult r = atw.executeOperation(); + var r = atw.executeOperation(); assertTrue(ArrayUtilityMethods.matricesSame(r.data, matrixTranspose)); } - + } diff --git a/mediator/pom.xml b/mediator/pom.xml index 23d28726b..ae802a349 100644 --- a/mediator/pom.xml +++ b/mediator/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT mediator diff --git a/mediator/src/main/java/com/iluwatar/mediator/Action.java b/mediator/src/main/java/com/iluwatar/mediator/Action.java index 66e1f42c4..60ce3949a 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Action.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Action.java @@ -1,52 +1,51 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.mediator; - -/** - * Action enumeration. - */ -public enum Action { - - HUNT("hunted a rabbit", "arrives for dinner"), - TALE("tells a tale", "comes to listen"), - GOLD("found gold", "takes his share of the gold"), - ENEMY("spotted enemies", "runs for cover"), - NONE("", ""); - - private String title; - private String description; - - Action(String title, String description) { - this.title = title; - this.description = description; - } - - public String getDescription() { - return description; - } - - public String toString() { - return title; - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.mediator; + +/** + * Action enumeration. + */ +public enum Action { + HUNT("hunted a rabbit", "arrives for dinner"), + TALE("tells a tale", "comes to listen"), + GOLD("found gold", "takes his share of the gold"), + ENEMY("spotted enemies", "runs for cover"), + NONE("", ""); + + private final String title; + private final String description; + + Action(String title, String description) { + this.title = title; + this.description = description; + } + + public String getDescription() { + return description; + } + + public String toString() { + return title; + } +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/App.java b/mediator/src/main/java/com/iluwatar/mediator/App.java index 9dbedb4ab..0e9021c0d 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/App.java +++ b/mediator/src/main/java/com/iluwatar/mediator/App.java @@ -55,10 +55,10 @@ public class App { // create party and members Party party = new PartyImpl(); - Hobbit hobbit = new Hobbit(); - Wizard wizard = new Wizard(); - Rogue rogue = new Rogue(); - Hunter hunter = new Hunter(); + var hobbit = new Hobbit(); + var wizard = new Wizard(); + var rogue = new Rogue(); + var hunter = new Hunter(); // add party members party.addMember(hobbit); diff --git a/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java b/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java index 6384a2187..f842a0f39 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java +++ b/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java @@ -39,7 +39,7 @@ public class PartyImpl implements Party { @Override public void act(PartyMember actor, Action action) { - for (PartyMember member : members) { + for (var member : members) { 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 3a55d51d8..558ec4907 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/AppTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/AppTest.java @@ -25,16 +25,15 @@ package com.iluwatar.mediator; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** - * * Application test - * */ -public class AppTest { +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java b/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java index 5d2446545..d25562f84 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java @@ -43,10 +43,10 @@ public class PartyImplTest { */ @Test public void testPartyAction() { - final PartyMember partyMember1 = mock(PartyMember.class); - final PartyMember partyMember2 = mock(PartyMember.class); + final var partyMember1 = mock(PartyMember.class); + final var partyMember2 = mock(PartyMember.class); - final PartyImpl party = new PartyImpl(); + final var party = new PartyImpl(); party.addMember(partyMember1); party.addMember(partyMember2); @@ -58,7 +58,6 @@ public class PartyImplTest { 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 index 951f8e166..a0e722cfd 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java @@ -23,24 +23,24 @@ package com.iluwatar.mediator; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.AppenderBase; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.slf4j.LoggerFactory; - -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.function.Supplier; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.LoggerFactory; + /** * Date: 12/19/15 - 10:13 PM * @@ -48,12 +48,12 @@ import static org.mockito.Mockito.verify; */ public class PartyMemberTest { - static Collection[]> dataProvider() { - return List.of( - new Supplier[]{Hobbit::new}, - new Supplier[]{Hunter::new}, - new Supplier[]{Rogue::new}, - new Supplier[]{Wizard::new} + static Stream dataProvider() { + return Stream.of( + Arguments.of((Supplier) Hobbit::new), + Arguments.of((Supplier) Hunter::new), + Arguments.of((Supplier) Rogue::new), + Arguments.of((Supplier) Wizard::new) ); } @@ -75,9 +75,9 @@ public class PartyMemberTest { @ParameterizedTest @MethodSource("dataProvider") public void testPartyAction(Supplier memberSupplier) { - final PartyMember member = memberSupplier.get(); + final var member = memberSupplier.get(); - for (final Action action : Action.values()) { + for (final var action : Action.values()) { member.partyAction(action); assertEquals(member.toString() + " " + action.getDescription(), appender.getLastMessage()); } @@ -91,16 +91,16 @@ public class PartyMemberTest { @ParameterizedTest @MethodSource("dataProvider") public void testAct(Supplier memberSupplier) { - final PartyMember member = memberSupplier.get(); + final var member = memberSupplier.get(); member.act(Action.GOLD); assertEquals(0, appender.getLogSize()); - final Party party = mock(Party.class); + final var party = mock(Party.class); member.joinedParty(party); assertEquals(member.toString() + " joins the party", appender.getLastMessage()); - for (final Action action : Action.values()) { + for (final var action : Action.values()) { member.act(action); assertEquals(member.toString() + " " + action.toString(), appender.getLastMessage()); verify(party).act(member, action); @@ -114,16 +114,16 @@ public class PartyMemberTest { */ @ParameterizedTest @MethodSource("dataProvider") - public void testToString(Supplier memberSupplier) throws Exception { - final PartyMember member = memberSupplier.get(); - final Class memberClass = member.getClass(); + public void testToString(Supplier memberSupplier) { + final var member = memberSupplier.get(); + final var memberClass = member.getClass(); assertEquals(memberClass.getSimpleName(), member.toString()); } - private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private static class InMemoryAppender extends AppenderBase { + private final List log = new LinkedList<>(); - public InMemoryAppender(Class clazz) { + public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); start(); } diff --git a/memento/README.md b/memento/README.md index 8011dfc49..720d0f21d 100644 --- a/memento/README.md +++ b/memento/README.md @@ -9,24 +9,30 @@ tags: --- ## Also known as + Token ## Intent -Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored -to this state later. + +Without violating encapsulation, capture and externalize an object's internal state so that the +object can be restored to this state later. ## Explanation + Real world example -> We are working on astrology application where we need to analyze star properties over time. We are creating snapshots of star state using Memento pattern. +> We are working on astrology application where we need to analyze star properties over time. We are +> creating snapshots of star state using Memento pattern. In plain words -> Memento pattern captures object internal state making it easy to store and restore objects in any point of time. +> Memento pattern captures object internal state making it easy to store and restore objects in any +> point of time. Wikipedia says -> The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback). +> The memento pattern is a software design pattern that provides the ability to restore an object to +> its previous state (undo via rollback). **Programmatic Example** @@ -34,24 +40,17 @@ Let's first define the types of stars we are capable to handle. ```java public enum StarType { - - SUN("sun"), RED_GIANT("red giant"), WHITE_DWARF("white dwarf"), SUPERNOVA("supernova"), DEAD( - "dead star"), UNDEFINED(""); - - private String title; - - StarType(String title) { - this.title = title; - } - - @Override - public String toString() { - return title; - } + SUN("sun"), + RED_GIANT("red giant"), + WHITE_DWARF("white dwarf"), + SUPERNOVA("supernova"), + DEAD("dead star"); + ... } ``` -Next let's jump straight to the essentials. Here's the star class along with the mementos that we need manipulate. +Next, let's jump straight to the essentials. Here's the `Star` class along with the mementos that we +need manipulate. Especially pay attention to `getMemento` and `setMemento` methods. ```java public interface StarMemento { @@ -95,8 +94,7 @@ public class Star { } StarMemento getMemento() { - - StarMementoInternal state = new StarMementoInternal(); + var state = new StarMementoInternal(); state.setAgeYears(ageYears); state.setMassTons(massTons); state.setType(type); @@ -104,8 +102,7 @@ public class Star { } void setMemento(StarMemento memento) { - - StarMementoInternal state = (StarMementoInternal) memento; + var state = (StarMementoInternal) memento; this.type = state.getType(); this.ageYears = state.getAgeYears(); this.massTons = state.getMassTons(); @@ -122,29 +119,8 @@ public class Star { private int ageYears; private int massTons; - public StarType getType() { - return type; - } - - public void setType(StarType type) { - this.type = type; - } - - public int getAgeYears() { - return ageYears; - } - - public void setAgeYears(int ageYears) { - this.ageYears = ageYears; - } - - public int getMassTons() { - return massTons; - } - - public void setMassTons(int massTons) { - this.massTons = massTons; - } + // setters and getters -> + ... } } ``` @@ -152,8 +128,8 @@ public class Star { And finally here's how we use the mementos to store and restore star states. ```java - Stack states = new Stack<>(); - Star star = new Star(StarType.SUN, 10000000, 500000); + var states = new Stack<>(); + var star = new Star(StarType.SUN, 10000000, 500000); LOGGER.info(star.toString()); states.add(star.getMemento()); star.timePasses(); @@ -171,27 +147,33 @@ And finally here's how we use the mementos to store and restore star states. star.setMemento(states.pop()); LOGGER.info(star.toString()); } - - // sun age: 10000000 years mass: 500000 tons - // red giant age: 20000000 years mass: 4000000 tons - // white dwarf age: 40000000 years mass: 32000000 tons - // supernova age: 80000000 years mass: 256000000 tons - // dead star age: 160000000 years mass: 2048000000 tons - // supernova age: 80000000 years mass: 256000000 tons - // white dwarf age: 40000000 years mass: 32000000 tons - // red giant age: 20000000 years mass: 4000000 tons - // sun age: 10000000 years mass: 500000 tons ``` +Program output: + +``` +sun age: 10000000 years mass: 500000 tons +red giant age: 20000000 years mass: 4000000 tons +white dwarf age: 40000000 years mass: 32000000 tons +supernova age: 80000000 years mass: 256000000 tons +dead star age: 160000000 years mass: 2048000000 tons +supernova age: 80000000 years mass: 256000000 tons +white dwarf age: 40000000 years mass: 32000000 tons +red giant age: 20000000 years mass: 4000000 tons +sun age: 10000000 years mass: 500000 tons +``` ## Class diagram + ![alt text](./etc/memento.png "Memento") ## 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 +* 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 diff --git a/memento/pom.xml b/memento/pom.xml index 70121cea3..596883819 100644 --- a/memento/pom.xml +++ b/memento/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.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 af57d8d4a..77cc0f214 100644 --- a/memento/src/main/java/com/iluwatar/memento/App.java +++ b/memento/src/main/java/com/iluwatar/memento/App.java @@ -52,9 +52,9 @@ public class App { * Program entry point. */ public static void main(String[] args) { - Stack states = new Stack<>(); + var states = new Stack(); - Star star = new Star(StarType.SUN, 10000000, 500000); + var star = new Star(StarType.SUN, 10000000, 500000); LOGGER.info(star.toString()); states.add(star.getMemento()); star.timePasses(); diff --git a/memento/src/main/java/com/iluwatar/memento/Star.java b/memento/src/main/java/com/iluwatar/memento/Star.java index ebeea28f2..af1c98b04 100644 --- a/memento/src/main/java/com/iluwatar/memento/Star.java +++ b/memento/src/main/java/com/iluwatar/memento/Star.java @@ -70,22 +70,18 @@ public class Star { } StarMemento getMemento() { - - StarMementoInternal state = new StarMementoInternal(); + var state = new StarMementoInternal(); state.setAgeYears(ageYears); state.setMassTons(massTons); state.setType(type); return state; - } void setMemento(StarMemento memento) { - - StarMementoInternal state = (StarMementoInternal) memento; + var state = (StarMementoInternal) memento; this.type = state.getType(); this.ageYears = state.getAgeYears(); this.massTons = state.getMassTons(); - } @Override diff --git a/memento/src/main/java/com/iluwatar/memento/StarType.java b/memento/src/main/java/com/iluwatar/memento/StarType.java index 507cd506b..58fd935f2 100644 --- a/memento/src/main/java/com/iluwatar/memento/StarType.java +++ b/memento/src/main/java/com/iluwatar/memento/StarType.java @@ -27,11 +27,13 @@ package com.iluwatar.memento; * StarType enumeration. */ public enum StarType { + SUN("sun"), + RED_GIANT("red giant"), + WHITE_DWARF("white dwarf"), + SUPERNOVA("supernova"), + DEAD("dead star"); - SUN("sun"), RED_GIANT("red giant"), WHITE_DWARF("white dwarf"), SUPERNOVA("supernova"), DEAD( - "dead star"), UNDEFINED(""); - - private String title; + private final String title; StarType(String title) { this.title = title; diff --git a/memento/src/test/java/com/iluwatar/memento/AppTest.java b/memento/src/test/java/com/iluwatar/memento/AppTest.java index 074de2c41..2d8a518e2 100644 --- a/memento/src/test/java/com/iluwatar/memento/AppTest.java +++ b/memento/src/test/java/com/iluwatar/memento/AppTest.java @@ -25,16 +25,15 @@ package com.iluwatar.memento; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** - * * Application test - * */ -public class AppTest { +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/memento/src/test/java/com/iluwatar/memento/StarTest.java b/memento/src/test/java/com/iluwatar/memento/StarTest.java index 40adb99e1..aab59e9c3 100644 --- a/memento/src/test/java/com/iluwatar/memento/StarTest.java +++ b/memento/src/test/java/com/iluwatar/memento/StarTest.java @@ -23,10 +23,10 @@ package com.iluwatar.memento; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + /** * Date: 12/20/15 - 10:08 AM * @@ -39,7 +39,7 @@ public class StarTest { */ @Test public void testTimePasses() { - final Star star = new Star(StarType.SUN, 1, 2); + final var star = new Star(StarType.SUN, 1, 2); assertEquals("sun age: 1 years mass: 2 tons", star.toString()); star.timePasses(); @@ -66,16 +66,16 @@ public class StarTest { */ @Test public void testSetMemento() { - final Star star = new Star(StarType.SUN, 1, 2); - final StarMemento firstMemento = star.getMemento(); + final var star = new Star(StarType.SUN, 1, 2); + final var firstMemento = star.getMemento(); assertEquals("sun age: 1 years mass: 2 tons", star.toString()); star.timePasses(); - final StarMemento secondMemento = star.getMemento(); + final var secondMemento = star.getMemento(); assertEquals("red giant age: 2 years mass: 16 tons", star.toString()); star.timePasses(); - final StarMemento thirdMemento = star.getMemento(); + final var thirdMemento = star.getMemento(); assertEquals("white dwarf age: 4 years mass: 128 tons", star.toString()); star.timePasses(); diff --git a/model-view-controller/pom.xml b/model-view-controller/pom.xml index a8ef230e8..98c9dddfe 100644 --- a/model-view-controller/pom.xml +++ b/model-view-controller/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT model-view-controller diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/App.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/App.java index 4607f009d..cabc4d96f 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/App.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/App.java @@ -47,9 +47,9 @@ public class App { */ public static void main(String[] args) { // create model, view and controller - GiantModel giant = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); - GiantView view = new GiantView(); - GiantController controller = new GiantController(giant, view); + var giant = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + var view = new GiantView(); + var controller = new GiantController(giant, view); // initial display controller.updateView(); // controller receives some interactions that affect the giant diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java index b1663df1f..64bae6e51 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java @@ -27,10 +27,11 @@ package com.iluwatar.model.view.controller; * Fatigue enumeration. */ public enum Fatigue { + ALERT("alert"), + TIRED("tired"), + SLEEPING("sleeping"); - ALERT("alert"), TIRED("tired"), SLEEPING("sleeping"); - - private String title; + private final String title; Fatigue(String title) { this.title = title; diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java index e66608117..f96113574 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java @@ -28,14 +28,15 @@ package com.iluwatar.model.view.controller; */ public class GiantController { - private GiantModel giant; - private GiantView view; + private final GiantModel giant; + private final GiantView view; public GiantController(GiantModel giant, GiantView view) { this.giant = giant; this.view = view; } + @SuppressWarnings("UnusedReturnValue") public Health getHealth() { return giant.getHealth(); } @@ -44,6 +45,7 @@ public class GiantController { this.giant.setHealth(health); } + @SuppressWarnings("UnusedReturnValue") public Fatigue getFatigue() { return giant.getFatigue(); } @@ -52,6 +54,7 @@ public class GiantController { this.giant.setFatigue(fatigue); } + @SuppressWarnings("UnusedReturnValue") public Nourishment getNourishment() { return giant.getNourishment(); } diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java index 30b3b2b90..f15585cdd 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java @@ -27,10 +27,11 @@ package com.iluwatar.model.view.controller; * Health enumeration. */ public enum Health { + HEALTHY("healthy"), + WOUNDED("wounded"), + DEAD("dead"); - HEALTHY("healthy"), WOUNDED("wounded"), DEAD("dead"); - - private String title; + private final String title; Health(String title) { this.title = title; diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java index 3ced564cc..ba00c38c5 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java @@ -27,10 +27,11 @@ package com.iluwatar.model.view.controller; * Nourishment enumeration. */ public enum Nourishment { + SATURATED("saturated"), + HUNGRY("hungry"), + STARVING("starving"); - SATURATED("saturated"), HUNGRY("hungry"), STARVING("starving"); - - private String title; + private final String title; Nourishment(String title) { this.title = title; 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 e6d2d9a0b..a0fdbf3ba 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 @@ -25,16 +25,15 @@ package com.iluwatar.model.view.controller; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** - * * Application test - * */ -public class AppTest { +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } 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 index a2f42a80d..d106d0944 100644 --- 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 @@ -23,13 +23,13 @@ package com.iluwatar.model.view.controller; -import org.junit.jupiter.api.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; +import org.junit.jupiter.api.Test; + /** * Date: 12/20/15 - 2:19 PM * @@ -42,19 +42,20 @@ public class GiantControllerTest { */ @Test public void testSetHealth() { - final GiantModel model = mock(GiantModel.class); - final GiantView view = mock(GiantView.class); - final GiantController controller = new GiantController(model, view); + final var model = mock(GiantModel.class); + final var view = mock(GiantView.class); + final var controller = new GiantController(model, view); verifyZeroInteractions(model, view); - for (final Health health : Health.values()) { + for (final var health : Health.values()) { controller.setHealth(health); verify(model).setHealth(health); verifyZeroInteractions(view); } controller.getHealth(); + //noinspection ResultOfMethodCallIgnored verify(model).getHealth(); verifyNoMoreInteractions(model, view); @@ -65,19 +66,20 @@ public class GiantControllerTest { */ @Test public void testSetFatigue() { - final GiantModel model = mock(GiantModel.class); - final GiantView view = mock(GiantView.class); - final GiantController controller = new GiantController(model, view); + final var model = mock(GiantModel.class); + final var view = mock(GiantView.class); + final var controller = new GiantController(model, view); verifyZeroInteractions(model, view); - for (final Fatigue fatigue : Fatigue.values()) { + for (final var fatigue : Fatigue.values()) { controller.setFatigue(fatigue); verify(model).setFatigue(fatigue); verifyZeroInteractions(view); } controller.getFatigue(); + //noinspection ResultOfMethodCallIgnored verify(model).getFatigue(); verifyNoMoreInteractions(model, view); @@ -88,19 +90,20 @@ public class GiantControllerTest { */ @Test public void testSetNourishment() { - final GiantModel model = mock(GiantModel.class); - final GiantView view = mock(GiantView.class); - final GiantController controller = new GiantController(model, view); + final var model = mock(GiantModel.class); + final var view = mock(GiantView.class); + final var controller = new GiantController(model, view); verifyZeroInteractions(model, view); - for (final Nourishment nourishment : Nourishment.values()) { + for (final var nourishment : Nourishment.values()) { controller.setNourishment(nourishment); verify(model).setNourishment(nourishment); verifyZeroInteractions(view); } controller.getNourishment(); + //noinspection ResultOfMethodCallIgnored verify(model).getNourishment(); verifyNoMoreInteractions(model, view); @@ -108,9 +111,9 @@ public class GiantControllerTest { @Test public void testUpdateView() { - final GiantModel model = mock(GiantModel.class); - final GiantView view = mock(GiantView.class); - final GiantController controller = new GiantController(model, view); + final var model = mock(GiantModel.class); + final var view = mock(GiantView.class); + final var controller = new GiantController(model, view); verifyZeroInteractions(model, view); 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 index a566010cd..677ab436e 100644 --- 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 @@ -23,10 +23,10 @@ package com.iluwatar.model.view.controller; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + /** * Date: 12/20/15 - 2:10 PM * @@ -39,12 +39,13 @@ public class GiantModelTest { */ @Test public void testSetHealth() { - final GiantModel model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + final var model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); assertEquals(Health.HEALTHY, model.getHealth()); - for (final Health health : Health.values()) { + var messageFormat = "The giant looks %s, alert and saturated."; + for (final var health : Health.values()) { model.setHealth(health); assertEquals(health, model.getHealth()); - assertEquals("The giant looks " + health.toString() + ", alert and saturated.", model.toString()); + assertEquals(String.format(messageFormat, health), model.toString()); } } @@ -53,12 +54,13 @@ public class GiantModelTest { */ @Test public void testSetFatigue() { - final GiantModel model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + final var model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); assertEquals(Fatigue.ALERT, model.getFatigue()); - for (final Fatigue fatigue : Fatigue.values()) { + var messageFormat = "The giant looks healthy, %s and saturated."; + for (final var fatigue : Fatigue.values()) { model.setFatigue(fatigue); assertEquals(fatigue, model.getFatigue()); - assertEquals("The giant looks healthy, " + fatigue.toString() + " and saturated.", model.toString()); + assertEquals(String.format(messageFormat, fatigue), model.toString()); } } @@ -67,12 +69,13 @@ public class GiantModelTest { */ @Test public void testSetNourishment() { - final GiantModel model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + final var model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); assertEquals(Nourishment.SATURATED, model.getNourishment()); - for (final Nourishment nourishment : Nourishment.values()) { + var messageFormat = "The giant looks healthy, alert and %s."; + for (final var nourishment : Nourishment.values()) { model.setNourishment(nourishment); assertEquals(nourishment, model.getNourishment()); - assertEquals("The giant looks healthy, alert and " + nourishment.toString() + ".", model.toString()); + assertEquals(String.format(messageFormat, nourishment), 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 index a3e33f9dd..c6314c1dd 100644 --- 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 @@ -31,7 +31,6 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; import java.util.LinkedList; import java.util.List; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -62,9 +61,9 @@ public class GiantViewTest { */ @Test public void testDisplayGiant() { - final GiantView view = new GiantView(); + final var view = new GiantView(); - final GiantModel model = mock(GiantModel.class); + final var model = mock(GiantModel.class); view.displayGiant(model); assertEquals(model.toString(), appender.getLastMessage()); @@ -74,10 +73,10 @@ public class GiantViewTest { /** * Logging Appender Implementation */ - public class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + public static class InMemoryAppender extends AppenderBase { + private final List log = new LinkedList<>(); - public InMemoryAppender(Class clazz) { + public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); start(); } diff --git a/model-view-presenter/pom.xml b/model-view-presenter/pom.xml index 97b47f82c..ee309f292 100644 --- a/model-view-presenter/pom.xml +++ b/model-view-presenter/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT model-view-presenter model-view-presenter diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java index 43984e847..ac3b83927 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java @@ -44,9 +44,9 @@ public class App { * @param args command line args */ public static void main(String[] args) { - FileLoader loader = new FileLoader(); - FileSelectorJFrame frame = new FileSelectorJFrame(); - FileSelectorPresenter presenter = new FileSelectorPresenter(frame); + var loader = new FileLoader(); + var frame = new FileSelectorJFrame(); + var presenter = new FileSelectorPresenter(frame); presenter.setLoader(loader); presenter.start(); } 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 9c01b2044..7dd5dd215 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 @@ -27,6 +27,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.Serializable; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,18 +60,11 @@ public class FileLoader implements Serializable { * Loads the data of the file specified. */ public String loadData() { - String dataFileName = this.fileName; - try (BufferedReader br = new BufferedReader(new FileReader(new File(dataFileName)))) { - StringBuilder sb = new StringBuilder(); - String line; - - while ((line = br.readLine()) != null) { - sb.append(line).append('\n'); - } - + var dataFileName = this.fileName; + try (var br = new BufferedReader(new FileReader(new File(dataFileName)))) { + var result = br.lines().collect(Collectors.joining("\n")); this.loaded = true; - - return sb.toString(); + return result; } catch (Exception e) { LOGGER.error("File {} does not exist", dataFileName); } 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 77523ccaa..f59bcdf6f 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 @@ -23,9 +23,13 @@ package com.iluwatar.model.view.presenter; +import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED; +import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED; + import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; + import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; @@ -48,37 +52,22 @@ public class FileSelectorJFrame extends JFrame implements FileSelectorView, Acti /** * The "OK" button for loading the file. */ - private JButton ok; + private final JButton ok; /** * The cancel button. */ - private JButton cancel; - - /** - * The information label. - */ - private JLabel info; - - /** - * The contents label. - */ - private JLabel contents; + private final JButton cancel; /** * The text field for giving the name of the file that we want to open. */ - private JTextField input; + private final JTextField input; /** * A text area that will keep the contents of the file opened. */ - private JTextArea area; - - /** - * The panel that will hold our widgets. - */ - private JPanel panel; + private final JTextArea area; /** * The Presenter component that the frame will interact with. @@ -95,14 +84,14 @@ public class FileSelectorJFrame extends JFrame implements FileSelectorView, Acti */ public FileSelectorJFrame() { super("File Loader"); - this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + this.setDefaultCloseOperation(EXIT_ON_CLOSE); this.setLayout(null); this.setBounds(100, 100, 500, 200); /* * Add the panel. */ - this.panel = new JPanel(); + var panel = new JPanel(); panel.setLayout(null); this.add(panel); panel.setBounds(0, 0, 500, 200); @@ -111,32 +100,32 @@ public class FileSelectorJFrame extends JFrame implements FileSelectorView, Acti /* * Add the info label. */ - this.info = new JLabel("File Name :"); - this.panel.add(info); + var info = new JLabel("File Name :"); + panel.add(info); info.setBounds(30, 10, 100, 30); /* * Add the contents label. */ - this.contents = new JLabel("File contents :"); - this.panel.add(contents); - this.contents.setBounds(30, 100, 120, 30); + var contents = new JLabel("File contents :"); + panel.add(contents); + contents.setBounds(30, 100, 120, 30); /* * Add the text field. */ this.input = new JTextField(100); - this.panel.add(input); + panel.add(input); this.input.setBounds(150, 15, 200, 20); /* * Add the text area. */ this.area = new JTextArea(100, 100); - JScrollPane pane = new JScrollPane(area); - pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); - pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); - this.panel.add(pane); + var pane = new JScrollPane(area); + pane.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED); + pane.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED); + panel.add(pane); this.area.setEditable(false); pane.setBounds(150, 100, 250, 80); @@ -144,7 +133,7 @@ public class FileSelectorJFrame extends JFrame implements FileSelectorView, Acti * Add the OK button. */ this.ok = new JButton("OK"); - this.panel.add(ok); + panel.add(ok); this.ok.setBounds(250, 50, 100, 25); this.ok.addActionListener(this); @@ -152,7 +141,7 @@ public class FileSelectorJFrame extends JFrame implements FileSelectorView, Acti * Add the cancel button. */ this.cancel = new JButton("Cancel"); - this.panel.add(this.cancel); + panel.add(this.cancel); this.cancel.setBounds(380, 50, 100, 25); this.cancel.addActionListener(this); 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 35e1c0076..5cd6580d9 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 @@ -41,7 +41,7 @@ public class FileSelectorPresenter implements Serializable { /** * The View component that the presenter interacts with. */ - private FileSelectorView view; + private final FileSelectorView view; /** * The Model component that the presenter interacts with. @@ -91,7 +91,7 @@ public class FileSelectorPresenter implements Serializable { } if (loader.fileExists()) { - String data = loader.loadData(); + var data = loader.loadData(); view.displayData(data); } else { view.showMessage("The file specified does not exist."); 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 index 00e35ae1b..6529cd18d 100644 --- 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 @@ -25,17 +25,16 @@ package com.iluwatar.model.view.presenter; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** - * * Application test - * */ -public class AppTest { +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } 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 index a63ca5ae8..3787cd20b 100644 --- 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 @@ -23,10 +23,10 @@ package com.iluwatar.model.view.presenter; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.Test; + /** * Date: 12/21/15 - 12:12 PM * @@ -35,8 +35,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; public class FileLoaderTest { @Test - public void testLoadData() throws Exception { - final FileLoader fileLoader = new FileLoader(); + public void testLoadData() { + final var fileLoader = new FileLoader(); fileLoader.setFileName("non-existing-file"); assertNull(fileLoader.loadData()); } 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 fdc19398d..238d3a135 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 @@ -23,14 +23,14 @@ package com.iluwatar.model.view.presenter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + /** * This test case is responsible for testing our application by taking advantage of the * Model-View-Controller architectural pattern. @@ -79,7 +79,7 @@ public class FileSelectorPresenterTest { */ @Test public void updateFileNameToLoader() { - String expectedFile = "Stamatis"; + var expectedFile = "Stamatis"; stub.setFileName(expectedFile); presenter.start(); diff --git a/module/pom.xml b/module/pom.xml index 25ad707eb..2dc3fe340 100644 --- a/module/pom.xml +++ b/module/pom.xml @@ -23,38 +23,39 @@ THE SOFTWARE. --> - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.23.0-SNAPSHOT - - module - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - - com.iluwatar.module.App - - - - - - - - + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.24.0-SNAPSHOT + + module + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.module.App + + + + + + + + diff --git a/module/src/main/java/com/iluwatar/module/App.java b/module/src/main/java/com/iluwatar/module/App.java index d50693440..0f89d8f89 100644 --- a/module/src/main/java/com/iluwatar/module/App.java +++ b/module/src/main/java/com/iluwatar/module/App.java @@ -67,10 +67,8 @@ public class App { /** * Following method is main executor. - * - * @param args for providing default program arguments */ - public static void execute(final String... args) { + public static void execute() { /* Send logs on file system */ fileLoggerModule.printString(MESSAGE); @@ -90,7 +88,7 @@ public class App { */ public static void main(final String... args) throws FileNotFoundException { prepare(); - execute(args); + execute(); unprepare(); } } diff --git a/module/src/test/java/com/iluwatar/module/AppTest.java b/module/src/test/java/com/iluwatar/module/AppTest.java index 88fa4c68c..4bf72fcb0 100644 --- a/module/src/test/java/com/iluwatar/module/AppTest.java +++ b/module/src/test/java/com/iluwatar/module/AppTest.java @@ -23,18 +23,19 @@ package com.iluwatar.module; -import org.junit.jupiter.api.Test; - import java.io.FileNotFoundException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; /** * Tests that Module example runs without errors. */ -public final class AppTest { +final class AppTest { @Test - public void test() throws FileNotFoundException { - final String[] args = {}; - App.main(args); + void shouldExecuteWithoutException() { + assertDoesNotThrow((Executable) App::main); } } diff --git a/module/src/test/java/com/iluwatar/module/FileLoggerModuleTest.java b/module/src/test/java/com/iluwatar/module/FileLoggerModuleTest.java index 646bba642..9899d4c5c 100644 --- a/module/src/test/java/com/iluwatar/module/FileLoggerModuleTest.java +++ b/module/src/test/java/com/iluwatar/module/FileLoggerModuleTest.java @@ -23,17 +23,16 @@ package com.iluwatar.module; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The Module pattern can be considered a Creational pattern and a Structural pattern. It manages @@ -58,13 +57,13 @@ public final class FileLoggerModuleTest { /** * This test verify that 'MESSAGE' is perfectly printed in output file - * + * * @throws IOException if program is not able to find log files (output.txt and error.txt) */ @Test public void testFileMessage() throws IOException { - /* Get singletong instance of File Logger Module */ + /* Get singleton instance of File Logger Module */ final var fileLoggerModule = FileLoggerModule.getSingleton(); /* Prepare the essential sub modules, to perform the sequence of jobs */ @@ -82,13 +81,13 @@ public final class FileLoggerModuleTest { /** * This test verify that nothing is printed in output file - * + * * @throws IOException if program is not able to find log files (output.txt and error.txt) */ @Test public void testNoFileMessage() throws IOException { - /* Get singletong instance of File Logger Module */ + /* Get singleton instance of File Logger Module */ final var fileLoggerModule = FileLoggerModule.getSingleton(); /* Prepare the essential sub modules, to perform the sequence of jobs */ @@ -103,14 +102,14 @@ public final class FileLoggerModuleTest { /** * This test verify that 'ERROR' is perfectly printed in error file - * + * * @throws FileNotFoundException if program is not able to find log files (output.txt and - * error.txt) + * error.txt) */ @Test public void testFileErrorMessage() throws FileNotFoundException { - /* Get singletong instance of File Logger Module */ + /* Get singleton instance of File Logger Module */ final var fileLoggerModule = FileLoggerModule.getSingleton(); /* Prepare the essential sub modules, to perform the sequence of jobs */ @@ -122,20 +121,20 @@ public final class FileLoggerModuleTest { /* Test if 'Message' is printed in file */ assertEquals(ERROR, readFirstLine(ERROR_FILE)); - /* Unprepare to cleanup the modules */ + /* Un-prepare to cleanup the modules */ fileLoggerModule.unprepare(); } /** * This test verify that nothing is printed in error file - * + * * @throws FileNotFoundException if program is not able to find log files (output.txt and - * error.txt) + * error.txt) */ @Test public void testNoFileErrorMessage() throws FileNotFoundException { - /* Get singletong instance of File Logger Module */ + /* Get singleton instance of File Logger Module */ final var fileLoggerModule = FileLoggerModule.getSingleton(); /* Prepare the essential sub modules, to perform the sequence of jobs */ @@ -150,11 +149,11 @@ public final class FileLoggerModuleTest { /** * Utility method to read first line of a file - * + * * @param file as file name to be read * @return a string value as first line in file */ - private static final String readFirstLine(final String file) { + private static String readFirstLine(final String file) { String firstLine = null; try (var bufferedReader = new BufferedReader(new FileReader(file))) { diff --git a/monad/pom.xml b/monad/pom.xml index f553c3079..2c21a28b3 100644 --- a/monad/pom.xml +++ b/monad/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT monad diff --git a/monad/src/main/java/com/iluwatar/monad/App.java b/monad/src/main/java/com/iluwatar/monad/App.java index ccb42edd0..bb3315064 100644 --- a/monad/src/main/java/com/iluwatar/monad/App.java +++ b/monad/src/main/java/com/iluwatar/monad/App.java @@ -41,9 +41,8 @@ import org.slf4j.LoggerFactory; * instance of a plain object with {@link Validator#of(Object)} and validates it {@link * Validator#validate(Function, Predicate, String)} against given predicates. * - *

As a validation result {@link Validator#get()} it either returns valid object {@link - * Validator#t} or throws a list of exceptions {@link Validator#exceptions} collected during - * validation. + *

As a validation result {@link Validator#get()} either returns valid object + * or throws {@link IllegalStateException} with list of exceptions collected during validation. */ public class App { @@ -55,10 +54,10 @@ public class App { * @param args command line args */ public static void main(String[] args) { - User user = new User("user", 24, Sex.FEMALE, "foobar.com"); + var user = new User("user", 24, Sex.FEMALE, "foobar.com"); LOGGER.info(Validator.of(user).validate(User::getName, Objects::nonNull, "name is null") .validate(User::getName, name -> !name.isEmpty(), "name is empty") - .validate(User::getEmail, email -> !email.contains("@"), "email doesn't containt '@'") + .validate(User::getEmail, email -> !email.contains("@"), "email doesn't contains '@'") .validate(User::getAge, age -> age > 20 && age < 30, "age isn't between...").get() .toString()); } diff --git a/monad/src/main/java/com/iluwatar/monad/User.java b/monad/src/main/java/com/iluwatar/monad/User.java index 77766d1aa..8644c4c0a 100644 --- a/monad/src/main/java/com/iluwatar/monad/User.java +++ b/monad/src/main/java/com/iluwatar/monad/User.java @@ -1,66 +1,66 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.monad; - -/** - * User Definition. - */ -public class User { - - private String name; - private int age; - private Sex sex; - private String email; - - /** - * Constructor. - * - * @param name - name - * @param age - age - * @param sex - sex - * @param email - email address - */ - public User(String name, int age, Sex sex, String email) { - this.name = name; - this.age = age; - this.sex = sex; - this.email = email; - } - - public String getName() { - return name; - } - - public int getAge() { - return age; - } - - public Sex getSex() { - return sex; - } - - public String getEmail() { - return email; - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.monad; + +/** + * User Definition. + */ +public class User { + + private final String name; + private final int age; + private final Sex sex; + private final String email; + + /** + * Constructor. + * + * @param name - name + * @param age - age + * @param sex - sex + * @param email - email address + */ + public User(String name, int age, Sex sex, String email) { + this.name = name; + this.age = age; + this.sex = sex; + this.email = email; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public Sex getSex() { + return sex; + } + + public String getEmail() { + return email; + } +} diff --git a/monad/src/main/java/com/iluwatar/monad/Validator.java b/monad/src/main/java/com/iluwatar/monad/Validator.java index 2d1f1bdab..47acc8a42 100644 --- a/monad/src/main/java/com/iluwatar/monad/Validator.java +++ b/monad/src/main/java/com/iluwatar/monad/Validator.java @@ -85,18 +85,21 @@ public class Validator { } /** - * Extension for the {@link Validator#validate(Function, Predicate, String)} method, dedicated for - * objects, that need to be projected before requested validation. + * Extension for the {@link Validator#validate(Predicate, String)} method, dedicated for objects, + * that need to be projected before requested validation. * * @param projection function that gets an objects, and returns projection representing element to * be validated. - * @param validation see {@link Validator#validate(Function, Predicate, String)} - * @param message see {@link Validator#validate(Function, Predicate, String)} - * @param see {@link Validator#validate(Function, Predicate, String)} + * @param validation see {@link Validator#validate(Predicate, String)} + * @param message see {@link Validator#validate(Predicate, String)} + * @param see {@link Validator#validate(Predicate, String)} * @return this */ - public Validator validate(Function projection, Predicate validation, - String message) { + public Validator validate( + Function projection, + Predicate validation, + String message + ) { return validate(projection.andThen(validation::test)::apply, message); } @@ -110,7 +113,7 @@ public class Validator { if (exceptions.isEmpty()) { return obj; } - IllegalStateException e = new IllegalStateException(); + var e = new IllegalStateException(); exceptions.forEach(e::addSuppressed); throw e; } diff --git a/monad/src/test/java/com/iluwatar/monad/AppTest.java b/monad/src/test/java/com/iluwatar/monad/AppTest.java index f4d89a7cd..d56270173 100644 --- a/monad/src/test/java/com/iluwatar/monad/AppTest.java +++ b/monad/src/test/java/com/iluwatar/monad/AppTest.java @@ -32,8 +32,7 @@ public class AppTest { @Test public void testMain() { - String[] args = {}; - App.main(args); + App.main(new String[]{}); } } diff --git a/monad/src/test/java/com/iluwatar/monad/MonadTest.java b/monad/src/test/java/com/iluwatar/monad/MonadTest.java index d1bdd7487..afd5b50f8 100644 --- a/monad/src/test/java/com/iluwatar/monad/MonadTest.java +++ b/monad/src/test/java/com/iluwatar/monad/MonadTest.java @@ -23,13 +23,12 @@ package com.iluwatar.monad; -import org.junit.jupiter.api.Test; - -import java.util.Objects; - import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.Objects; +import org.junit.jupiter.api.Test; + /** * Test for Monad Pattern */ @@ -37,27 +36,33 @@ public class MonadTest { @Test public void testForInvalidName() { - User tom = new User(null, 21, Sex.MALE, "tom@foo.bar"); - assertThrows(IllegalStateException.class, () -> { - Validator.of(tom).validate(User::getName, Objects::nonNull, "name cannot be null").get(); - }); + var tom = new User(null, 21, Sex.MALE, "tom@foo.bar"); + assertThrows( + IllegalStateException.class, + () -> Validator.of(tom) + .validate(User::getName, Objects::nonNull, "name cannot be null") + .get() + ); } @Test public void testForInvalidAge() { - User john = new User("John", 17, Sex.MALE, "john@qwe.bar"); - assertThrows(IllegalStateException.class, () -> { - Validator.of(john).validate(User::getName, Objects::nonNull, "name cannot be null") - .validate(User::getAge, age -> age > 21, "user is underaged") - .get(); - }); + var john = new User("John", 17, Sex.MALE, "john@qwe.bar"); + assertThrows( + IllegalStateException.class, + () -> Validator.of(john) + .validate(User::getName, Objects::nonNull, "name cannot be null") + .validate(User::getAge, age -> age > 21, "user is underage") + .get() + ); } @Test public void testForValid() { - User sarah = new User("Sarah", 42, Sex.FEMALE, "sarah@det.org"); - User validated = Validator.of(sarah).validate(User::getName, Objects::nonNull, "name cannot be null") - .validate(User::getAge, age -> age > 21, "user is underaged") + var sarah = new User("Sarah", 42, Sex.FEMALE, "sarah@det.org"); + var validated = Validator.of(sarah) + .validate(User::getName, Objects::nonNull, "name cannot be null") + .validate(User::getAge, age -> age > 21, "user is underage") .validate(User::getSex, sex -> sex == Sex.FEMALE, "user is not female") .validate(User::getEmail, email -> email.contains("@"), "email does not contain @ sign") .get(); diff --git a/monostate/pom.xml b/monostate/pom.xml index 0e51fc700..02e271931 100644 --- a/monostate/pom.xml +++ b/monostate/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT monostate diff --git a/monostate/src/main/java/com/iluwatar/monostate/App.java b/monostate/src/main/java/com/iluwatar/monostate/App.java index 64cb38461..9f5b2c173 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/App.java +++ b/monostate/src/main/java/com/iluwatar/monostate/App.java @@ -30,7 +30,7 @@ package com.iluwatar.monostate; * *

In the following example, The {@link LoadBalancer} class represents the app's logic. It * contains a series of Servers, which can handle requests of type {@link Request}. Two instances of - * LoadBalacer are created. When a request is made to a server via the first LoadBalancer the state + * LoadBalancer are created. When a request is made to a server via the first LoadBalancer the state * change in the first load balancer affects the second. So if the first LoadBalancer selects the * Server 1, the second LoadBalancer on a new request will select the Second server. If a third * LoadBalancer is created and a new request is made to it, then it will select the third server as @@ -43,8 +43,8 @@ public class App { * @param args command line args */ public static void main(String[] args) { - LoadBalancer loadBalancer1 = new LoadBalancer(); - LoadBalancer loadBalancer2 = new LoadBalancer(); + var loadBalancer1 = new LoadBalancer(); + var loadBalancer2 = new LoadBalancer(); 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 8546ae177..7a784f514 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java +++ b/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java @@ -38,8 +38,8 @@ public class LoadBalancer { private static int lastServedId; static { - int id = 0; - for (int port : new int[]{8080, 8081, 8082, 8083, 8084}) { + var id = 0; + for (var port : new int[]{8080, 8081, 8082, 8083, 8084}) { SERVERS.add(new Server("localhost", port, ++id)); } } @@ -69,7 +69,7 @@ public class LoadBalancer { if (lastServedId >= SERVERS.size()) { lastServedId = 0; } - Server server = SERVERS.get(lastServedId++); + var server = SERVERS.get(lastServedId++); server.serve(request); } diff --git a/monostate/src/test/java/com/iluwatar/monostate/AppTest.java b/monostate/src/test/java/com/iluwatar/monostate/AppTest.java index c914f136e..d17a56bb9 100644 --- a/monostate/src/test/java/com/iluwatar/monostate/AppTest.java +++ b/monostate/src/test/java/com/iluwatar/monostate/AppTest.java @@ -32,8 +32,7 @@ public class AppTest { @Test public void testMain() { - String[] args = {}; - App.main(args); + App.main(new String[]{}); } } diff --git a/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java b/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java index 736bf6ea6..d62c029e2 100644 --- a/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java +++ b/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java @@ -44,8 +44,8 @@ public class LoadBalancerTest { @Test public void testSameStateAmongstAllInstances() { - final LoadBalancer firstBalancer = new LoadBalancer(); - final LoadBalancer secondBalancer = new LoadBalancer(); + final var firstBalancer = new LoadBalancer(); + final var secondBalancer = new LoadBalancer(); firstBalancer.addServer(new Server("localhost", 8085, 6)); // Both should have the same number of servers. assertEquals(firstBalancer.getNoOfServers(), secondBalancer.getNoOfServers()); @@ -55,18 +55,18 @@ public class LoadBalancerTest { @Test public void testServe() { - final Server server = mock(Server.class); + final var 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(); + final var loadBalancer = new LoadBalancer(); loadBalancer.addServer(server); verifyZeroInteractions(server); - final Request request = new Request("test"); - for (int i = 0; i < loadBalancer.getNoOfServers() * 2; i++) { + final var request = new Request("test"); + for (var i = 0; i < loadBalancer.getNoOfServers() * 2; i++) { loadBalancer.serverRequest(request); } diff --git a/multiton/README.md b/multiton/README.md index ec1429a8f..07a4bf895 100644 --- a/multiton/README.md +++ b/multiton/README.md @@ -9,16 +9,19 @@ tags: --- ## Also known as + Registry ## Intent + Ensure a class only has limited number of instances and provide a global point of access to them. ## Explanation Real world example -> The Nazgûl, also called ringwraiths or the Nine Riders, are Sauron's most terrible servants. By definition there's always nine of them. +> The Nazgûl, also called ringwraiths or the Nine Riders, are Sauron's most terrible servants. By +> definition there's always nine of them. In plain words @@ -26,23 +29,26 @@ In plain words Wikipedia says -> In software engineering, the multiton pattern is a design pattern which generalizes the singleton pattern. Whereas the singleton allows only one instance of a class to be created, the multiton pattern allows for the controlled creation of multiple instances, which it manages through the use of a map. +> In software engineering, the multiton pattern is a design pattern which generalizes the singleton +> pattern. Whereas the singleton allows only one instance of a class to be created, the multiton +> pattern allows for the controlled creation of multiple instances, which it manages through the use +> of a map. **Programmatic Example** -Nazgul is the multiton class. +`Nazgul` is the multiton class. ```java public enum NazgulName { - KHAMUL, MURAZOR, DWAR, JI_INDUR, AKHORAHIL, HOARMURATH, ADUNAPHEL, REN, UVATHA; + KHAMUL, MURAZOR, DWAR, JI_INDUR, AKHORAHIL, HOARMURATH, ADUNAPHEL, REN, UVATHA } public final class Nazgul { - private static Map nazguls; + private static final Map nazguls; - private NazgulName name; + private final NazgulName name; static { nazguls = new ConcurrentHashMap<>(); @@ -71,7 +77,7 @@ public final class Nazgul { } ``` -And here's how we access the Nazgul instances. +And here's how we access the `Nazgul` instances. ```java LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL)); @@ -83,22 +89,29 @@ And here's how we access the Nazgul instances. LOGGER.info("ADUNAPHEL={}", Nazgul.getInstance(NazgulName.ADUNAPHEL)); LOGGER.info("REN={}", Nazgul.getInstance(NazgulName.REN)); LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA)); - - // KHAMUL=com.iluwatar.multiton.Nazgul@2b214b94 - // MURAZOR=com.iluwatar.multiton.Nazgul@17814b1c - // DWAR=com.iluwatar.multiton.Nazgul@7ac9af2a - // JI_INDUR=com.iluwatar.multiton.Nazgul@7bb004b8 - // AKHORAHIL=com.iluwatar.multiton.Nazgul@78e89bfe - // HOARMURATH=com.iluwatar.multiton.Nazgul@652ce654 - // ADUNAPHEL=com.iluwatar.multiton.Nazgul@522ba524 - // REN=com.iluwatar.multiton.Nazgul@29c5ee1d - // UVATHA=com.iluwatar.multiton.Nazgul@15cea7b0 +``` + +Program output: + +``` +KHAMUL=com.iluwatar.multiton.Nazgul@2b214b94 +MURAZOR=com.iluwatar.multiton.Nazgul@17814b1c +DWAR=com.iluwatar.multiton.Nazgul@7ac9af2a +JI_INDUR=com.iluwatar.multiton.Nazgul@7bb004b8 +AKHORAHIL=com.iluwatar.multiton.Nazgul@78e89bfe +HOARMURATH=com.iluwatar.multiton.Nazgul@652ce654 +ADUNAPHEL=com.iluwatar.multiton.Nazgul@522ba524 +REN=com.iluwatar.multiton.Nazgul@29c5ee1d +UVATHA=com.iluwatar.multiton.Nazgul@15cea7b0 ``` ## Class diagram + ![alt text](./etc/multiton.png "Multiton") ## 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 +* 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 ef1e9c892..d1e4d50d5 100644 --- a/multiton/pom.xml +++ b/multiton/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT multiton diff --git a/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java b/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java index f55f85aca..e08107eeb 100644 --- a/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java +++ b/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java @@ -31,9 +31,9 @@ import java.util.concurrent.ConcurrentHashMap; */ public final class Nazgul { - private static Map nazguls; + private static final Map nazguls; - private NazgulName name; + private final NazgulName name; static { nazguls = new ConcurrentHashMap<>(); diff --git a/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java b/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java index 5b5c48d66..bb1454b9f 100644 --- a/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java +++ b/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java @@ -27,7 +27,13 @@ package com.iluwatar.multiton; * enum based multiton implementation. */ public enum NazgulEnum { - - KHAMUL, MURAZOR, DWAR, JI_INDUR, AKHORAHIL, HOARMURATH, ADUNAPHEL, REN, UVATHA; - + KHAMUL, + MURAZOR, + DWAR, + JI_INDUR, + AKHORAHIL, + HOARMURATH, + ADUNAPHEL, + REN, + UVATHA } diff --git a/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java b/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java index c7865dceb..cce19c6ff 100644 --- a/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java +++ b/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java @@ -27,7 +27,13 @@ package com.iluwatar.multiton; * Each Nazgul has different {@link NazgulName}. */ public enum NazgulName { - - KHAMUL, MURAZOR, DWAR, JI_INDUR, AKHORAHIL, HOARMURATH, ADUNAPHEL, REN, UVATHA; - + KHAMUL, + MURAZOR, + DWAR, + JI_INDUR, + AKHORAHIL, + HOARMURATH, + ADUNAPHEL, + REN, + UVATHA } diff --git a/multiton/src/test/java/com/iluwatar/multiton/AppTest.java b/multiton/src/test/java/com/iluwatar/multiton/AppTest.java index f577b7f07..0496ebdaf 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/AppTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/AppTest.java @@ -26,15 +26,12 @@ package com.iluwatar.multiton; import org.junit.jupiter.api.Test; /** - * * Application test - * */ public class AppTest { @Test public void test() { - String[] args = {}; - App.main(args); + App.main(new String[]{}); } } diff --git a/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java b/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java index 6668874f4..4d107a181 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java @@ -39,10 +39,10 @@ class NazgulEnumTest { */ @Test public void testTheSameObjectIsReturnedWithMultipleCalls() { - for (int i = 0; i < NazgulEnum.values().length; i++) { - NazgulEnum instance1 = NazgulEnum.values()[i]; - NazgulEnum instance2 = NazgulEnum.values()[i]; - NazgulEnum instance3 = NazgulEnum.values()[i]; + for (var i = 0; i < NazgulEnum.values().length; i++) { + var instance1 = NazgulEnum.values()[i]; + var instance2 = NazgulEnum.values()[i]; + var instance3 = NazgulEnum.values()[i]; assertSame(instance1, instance2); assertSame(instance1, instance3); assertSame(instance2, instance3); diff --git a/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java b/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java index 0429f8e29..f900659a8 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java @@ -41,8 +41,8 @@ public class NazgulTest { */ @Test public void testGetInstance() { - for (final NazgulName name : NazgulName.values()) { - final Nazgul nazgul = Nazgul.getInstance(name); + for (final var name : NazgulName.values()) { + final var nazgul = Nazgul.getInstance(name); assertNotNull(nazgul); assertSame(nazgul, Nazgul.getInstance(name)); assertEquals(name, nazgul.getName()); diff --git a/mute-idiom/pom.xml b/mute-idiom/pom.xml index a32f6a3ea..2bf95f070 100644 --- a/mute-idiom/pom.xml +++ b/mute-idiom/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT mute-idiom diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/App.java b/mute-idiom/src/main/java/com/iluwatar/mute/App.java index d4f140bf0..eca345014 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/App.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/App.java @@ -25,7 +25,7 @@ package com.iluwatar.mute; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.sql.SQLException; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,9 +52,8 @@ public class App { * Program entry point. * * @param args command line args. - * @throws Exception if any exception occurs */ - public static void main(String[] args) throws Exception { + public static void main(String[] args) { useOfLoggedMute(); @@ -68,17 +67,17 @@ public class App { * exception occurs. */ private static void useOfMute() { - ByteArrayOutputStream out = new ByteArrayOutputStream(); + var out = new ByteArrayOutputStream(); Mute.mute(() -> out.write("Hello".getBytes())); } - private static void useOfLoggedMute() throws SQLException { - Resource resource = null; + private static void useOfLoggedMute() { + Optional resource = Optional.empty(); try { - resource = acquireResource(); - utilizeResource(resource); + resource = Optional.of(acquireResource()); + utilizeResource(resource.get()); } finally { - closeResource(resource); + resource.ifPresent(App::closeResource); } } @@ -86,14 +85,14 @@ public class App { * All we can do while failed close of a resource is to log it. */ private static void closeResource(Resource resource) { - Mute.loggedMute(() -> resource.close()); + Mute.loggedMute(resource::close); } - private static void utilizeResource(Resource resource) throws SQLException { + private static void utilizeResource(Resource resource) { LOGGER.info("Utilizing acquired resource: {}", resource); } - private static Resource acquireResource() throws SQLException { + private static Resource acquireResource() { return new Resource() { @Override diff --git a/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java b/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java index 5ca525a9d..c5ce35582 100644 --- a/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java +++ b/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java @@ -25,14 +25,15 @@ package com.iluwatar.mute; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Mute idiom example runs without errors. - * */ -public class AppTest { +class AppTest { @Test - public void test() throws Exception { - App.main(null); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java b/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java index f2743113b..e92b26f9b 100644 --- a/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java +++ b/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java @@ -23,46 +23,41 @@ package com.iluwatar.mute; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /** * Test for the mute-idiom pattern */ -public class MuteTest { +class MuteTest { private static final Logger LOGGER = LoggerFactory.getLogger(MuteTest.class); private static final String MESSAGE = "should not occur"; @Test - public void muteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { - Mute.mute(this::methodNotThrowingAnyException); + void muteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { + assertDoesNotThrow(() -> Mute.mute(this::methodNotThrowingAnyException)); } @Test - public void muteShouldRethrowUnexpectedExceptionAsAssertionError() { - assertThrows(AssertionError.class, () -> { - Mute.mute(this::methodThrowingException); - }); + void muteShouldRethrowUnexpectedExceptionAsAssertionError() { + assertThrows(AssertionError.class, () -> Mute.mute(this::methodThrowingException)); } @Test - public void loggedMuteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { - Mute.loggedMute(this::methodNotThrowingAnyException); + void loggedMuteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { + assertDoesNotThrow(() -> Mute.mute(this::methodNotThrowingAnyException)); } @Test - public void loggedMuteShouldLogExceptionTraceBeforeSwallowingIt() { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); + void loggedMuteShouldLogExceptionTraceBeforeSwallowingIt() { + var stream = new ByteArrayOutputStream(); System.setErr(new PrintStream(stream)); Mute.loggedMute(this::methodThrowingException); diff --git a/mutex/pom.xml b/mutex/pom.xml index 9cdff25e4..84455abb1 100644 --- a/mutex/pom.xml +++ b/mutex/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT mutex diff --git a/mutex/src/main/java/com/iluwatar/mutex/App.java b/mutex/src/main/java/com/iluwatar/mutex/App.java index e4a952ef9..c50acc65a 100644 --- a/mutex/src/main/java/com/iluwatar/mutex/App.java +++ b/mutex/src/main/java/com/iluwatar/mutex/App.java @@ -38,10 +38,10 @@ public class App { * main method. */ public static void main(String[] args) { - Mutex mutex = new Mutex(); - Jar jar = new Jar(1000, mutex); - Thief peter = new Thief("Peter", jar); - Thief john = new Thief("John", jar); + var mutex = new Mutex(); + var jar = new Jar(1000, mutex); + var peter = new Thief("Peter", jar); + var john = new Thief("John", jar); peter.start(); john.start(); } diff --git a/mutex/src/main/java/com/iluwatar/mutex/Jar.java b/mutex/src/main/java/com/iluwatar/mutex/Jar.java index f68b266ad..4a0861e1a 100644 --- a/mutex/src/main/java/com/iluwatar/mutex/Jar.java +++ b/mutex/src/main/java/com/iluwatar/mutex/Jar.java @@ -48,7 +48,7 @@ public class Jar { * Method for a thief to take a bean. */ public boolean takeBean() { - boolean success = false; + var success = false; try { lock.acquire(); success = beans > 0; diff --git a/mutex/src/main/java/com/iluwatar/mutex/Thief.java b/mutex/src/main/java/com/iluwatar/mutex/Thief.java index 29caba540..a9a715970 100644 --- a/mutex/src/main/java/com/iluwatar/mutex/Thief.java +++ b/mutex/src/main/java/com/iluwatar/mutex/Thief.java @@ -54,7 +54,7 @@ public class Thief extends Thread { */ @Override public void run() { - int beans = 0; + var beans = 0; while (jar.takeBean()) { beans = beans + 1; diff --git a/mutex/src/test/java/com/iluwatar/mutex/AppTest.java b/mutex/src/test/java/com/iluwatar/mutex/AppTest.java index 1793bf90b..7866b22a8 100644 --- a/mutex/src/test/java/com/iluwatar/mutex/AppTest.java +++ b/mutex/src/test/java/com/iluwatar/mutex/AppTest.java @@ -25,15 +25,15 @@ package com.iluwatar.mutex; import org.junit.jupiter.api.Test; -import java.io.IOException; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; /** * Application Test Entrypoint */ -public class AppTest { +class AppTest { + @Test - public void test() throws IOException { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/mutex/src/test/java/com/iluwatar/mutex/JarTest.java b/mutex/src/test/java/com/iluwatar/mutex/JarTest.java index e0a316072..786f96e44 100644 --- a/mutex/src/test/java/com/iluwatar/mutex/JarTest.java +++ b/mutex/src/test/java/com/iluwatar/mutex/JarTest.java @@ -23,10 +23,11 @@ package com.iluwatar.mutex; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.stream.IntStream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; /** * Test case for taking beans from a Jar @@ -35,12 +36,10 @@ public class JarTest { @Test public void testTakeBeans() { - Mutex mutex = new Mutex(); - Jar jar = new Jar(10, mutex); - for (int i = 0; i < 10; i++) { - assertTrue(jar.takeBean()); - } + var mutex = new Mutex(); + var jar = new Jar(10, mutex); + IntStream.range(0, 10).mapToObj(i -> jar.takeBean()).forEach(Assertions::assertTrue); assertFalse(jar.takeBean()); } -} \ No newline at end of file +} diff --git a/mutex/src/test/java/com/iluwatar/mutex/MutexTest.java b/mutex/src/test/java/com/iluwatar/mutex/MutexTest.java index 2e3184c51..d6d0cc1d7 100644 --- a/mutex/src/test/java/com/iluwatar/mutex/MutexTest.java +++ b/mutex/src/test/java/com/iluwatar/mutex/MutexTest.java @@ -23,12 +23,12 @@ package com.iluwatar.mutex; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; + /** * Test case for acquiring and releasing a Mutex */ @@ -36,7 +36,7 @@ public class MutexTest { @Test public void acquireReleaseTest() { - Mutex mutex = new Mutex(); + var mutex = new Mutex(); assertNull(mutex.getOwner()); try { mutex.acquire(); diff --git a/naked-objects/dom/pom.xml b/naked-objects/dom/pom.xml index dffc1650c..1b1eb2266 100644 --- a/naked-objects/dom/pom.xml +++ b/naked-objects/dom/pom.xml @@ -30,7 +30,7 @@ com.iluwatar naked-objects - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT naked-objects-dom @@ -127,7 +127,7 @@ - + diff --git a/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageViewModel.layout.json b/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageViewModel.layout.json index 638473eee..fe39b5b42 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageViewModel.layout.json +++ b/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageViewModel.layout.json @@ -1,19 +1,3 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ { "columns": [ { diff --git a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.java b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.java index 809da6d31..43d96f280 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.java +++ b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.java @@ -50,9 +50,9 @@ import org.apache.isis.applib.util.ObjectContracts; strategy = javax.jdo.annotations.IdGeneratorStrategy.IDENTITY, column = "id") @javax.jdo.annotations.Version(strategy = VersionStrategy.VERSION_NUMBER, column = "version") @javax.jdo.annotations.Queries({ - @javax.jdo.annotations.Query(name = "find", language = "JDOQL", value = "SELECT " + @javax.jdo.annotations.Query(name = "find", value = "SELECT " + "FROM domainapp.dom.modules.simple.SimpleObject "), - @javax.jdo.annotations.Query(name = "findByName", language = "JDOQL", value = "SELECT " + @javax.jdo.annotations.Query(name = "findByName", value = "SELECT " + "FROM domainapp.dom.modules.simple.SimpleObject " + "WHERE name.indexOf(:name) >= 0 ")}) @javax.jdo.annotations.Unique(name = "SimpleObject_name_UNQ", members = {"name"}) @DomainObject diff --git a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.layout.json b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.layout.json index 78b2ac096..998c419f2 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.layout.json +++ b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.layout.json @@ -1,19 +1,3 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ { "columns": [ { diff --git a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java index 03ab30f75..5435325cf 100644 --- a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java +++ b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java @@ -37,12 +37,12 @@ public class SimpleObjectTest { SimpleObject simpleObject; @Before - public void setUp() throws Exception { + public void setUp() { simpleObject = new SimpleObject(); } @Test - public void testName() throws Exception { + public void testName() { // given String name = "Foobar"; assertNull(simpleObject.getName()); diff --git a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java index a95ad5aa3..5fbcfde2b 100644 --- a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java +++ b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java @@ -52,13 +52,13 @@ public class SimpleObjectsTest { SimpleObjects simpleObjects; @Before - public void setUp() throws Exception { + public void setUp() { simpleObjects = new SimpleObjects(); simpleObjects.container = mockContainer; } @Test - public void testCreate() throws Exception { + public void testCreate() { // given final SimpleObject simpleObject = new SimpleObject(); @@ -85,7 +85,7 @@ public class SimpleObjectsTest { } @Test - public void testListAll() throws Exception { + public void testListAll() { // given final List all = Lists.newArrayList(); diff --git a/naked-objects/fixture/pom.xml b/naked-objects/fixture/pom.xml index 3457ac0a4..a918e20de 100644 --- a/naked-objects/fixture/pom.xml +++ b/naked-objects/fixture/pom.xml @@ -30,7 +30,7 @@ com.iluwatar naked-objects - 1.23.0-SNAPSHOT + 1.24.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 dc19195ac..0df939678 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 @@ -67,8 +67,7 @@ public class SimpleObjectCreate extends FixtureScript { @Override protected void execute(final ExecutionContext ec) { - - String paramName = checkParam("name", ec, String.class); + var paramName = checkParam("name", ec, String.class); this.simpleObject = wrap(simpleObjects).create(paramName); 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 847f15d01..5dc9a4785 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 @@ -27,7 +27,6 @@ import com.google.common.collect.Lists; import domainapp.dom.modules.simple.SimpleObject; import domainapp.fixture.modules.simple.SimpleObjectCreate; import domainapp.fixture.modules.simple.SimpleObjectsTearDown; -import java.util.Collections; import java.util.List; import org.apache.isis.applib.fixturescripts.FixtureScript; @@ -37,8 +36,18 @@ import org.apache.isis.applib.fixturescripts.FixtureScript; */ public class RecreateSimpleObjects extends FixtureScript { - public final List names = Collections.unmodifiableList(List.of("Foo", "Bar", "Baz", - "Frodo", "Froyo", "Fizz", "Bip", "Bop", "Bang", "Boo")); + public final List names = List.of( + "Foo", + "Bar", + "Baz", + "Frodo", + "Froyo", + "Fizz", + "Bip", + "Bop", + "Bang", + "Boo" + ); // region > number (optional input) private Integer number; @@ -77,7 +86,7 @@ public class RecreateSimpleObjects extends FixtureScript { protected void execute(final ExecutionContext ec) { // defaults - final int paramNumber = defaultParam("number", ec, 3); + final var paramNumber = defaultParam("number", ec, 3); // validate if (paramNumber < 0 || paramNumber > names.size()) { @@ -90,8 +99,8 @@ public class RecreateSimpleObjects extends FixtureScript { // ec.executeChild(this, new SimpleObjectsTearDown()); - for (int i = 0; i < paramNumber; i++) { - final SimpleObjectCreate fs = new SimpleObjectCreate().setName(names.get(i)); + for (var i = 0; i < paramNumber; i++) { + final var fs = new SimpleObjectCreate().setName(names.get(i)); ec.executeChild(this, fs.getName(), fs); simpleObjects.add(fs.getSimpleObject()); } diff --git a/naked-objects/integtests/pom.xml b/naked-objects/integtests/pom.xml index f46541f48..b9482b292 100644 --- a/naked-objects/integtests/pom.xml +++ b/naked-objects/integtests/pom.xml @@ -30,7 +30,7 @@ com.iluwatar naked-objects - 1.23.0-SNAPSHOT + 1.24.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 f67c26876..ad186d706 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 @@ -40,7 +40,7 @@ public final class SimpleAppSystemInitializer { * Init test system */ public static void initIsft() { - IsisSystemForTest isft = IsisSystemForTest.getElseNull(); + var isft = IsisSystemForTest.getElseNull(); if (isft == null) { isft = new SimpleAppSystemBuilder().build().setUpSystem(); IsisSystemForTest.set(isft); @@ -58,8 +58,7 @@ public final class SimpleAppSystemInitializer { } private static IsisConfiguration testConfiguration() { - final IsisConfigurationForJdoIntegTests testConfiguration = - new IsisConfigurationForJdoIntegTests(); + final var testConfiguration = new IsisConfigurationForJdoIntegTests(); testConfiguration.addRegisterEntitiesPackagePrefix("domainapp.dom.modules"); return testConfiguration; diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java index 025c6724a..142b0e9fb 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java @@ -23,10 +23,9 @@ package domainapp.integtests.specglue; -import org.apache.isis.core.specsupport.specs.CukeGlueAbstract; - import cucumber.api.java.Before; import domainapp.fixture.scenarios.RecreateSimpleObjects; +import org.apache.isis.core.specsupport.specs.CukeGlueAbstract; /** * Test Execution to append a fixture of SimpleObjects @@ -34,7 +33,7 @@ import domainapp.fixture.scenarios.RecreateSimpleObjects; public class CatalogOfFixturesGlue extends CukeGlueAbstract { @Before(value = {"@integration", "@SimpleObjectsFixture"}, order = 20000) - public void integrationFixtures() throws Throwable { + public void integrationFixtures() { scenarioExecution().install(new RecreateSimpleObjects()); } } 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 7b508faf3..51253b667 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 @@ -28,9 +28,7 @@ import static org.junit.Assert.assertThat; import cucumber.api.java.en.Given; import cucumber.api.java.en.When; -import domainapp.dom.modules.simple.SimpleObject; import domainapp.dom.modules.simple.SimpleObjects; -import java.util.List; import java.util.UUID; import org.apache.isis.core.specsupport.specs.CukeGlueAbstract; @@ -40,9 +38,9 @@ import org.apache.isis.core.specsupport.specs.CukeGlueAbstract; public class SimpleObjectGlue extends CukeGlueAbstract { @Given("^there are.* (\\d+) simple objects$") - public void thereAreNumSimpleObjects(int n) throws Throwable { + public void thereAreNumSimpleObjects(int n) { try { - final List findAll = service(SimpleObjects.class).listAll(); + final var findAll = service(SimpleObjects.class).listAll(); assertThat(findAll.size(), is(n)); putVar("list", "all", findAll); @@ -52,7 +50,7 @@ public class SimpleObjectGlue extends CukeGlueAbstract { } @When("^I create a new simple object$") - public void createNewSimpleObject() throws Throwable { + public void createNewSimpleObject() { service(SimpleObjects.class).create(UUID.randomUUID().toString()); } 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 11ff6a47d..819220344 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 @@ -26,8 +26,10 @@ package domainapp.integtests.tests.modules.simple; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import domainapp.dom.modules.simple.SimpleObject; +import domainapp.fixture.scenarios.RecreateSimpleObjects; +import domainapp.integtests.tests.SimpleAppIntegTest; import javax.inject.Inject; - import org.apache.isis.applib.DomainObjectContainer; import org.apache.isis.applib.fixturescripts.FixtureScripts; import org.apache.isis.applib.services.wrapper.DisabledException; @@ -35,10 +37,6 @@ import org.apache.isis.applib.services.wrapper.InvalidException; import org.junit.Before; import org.junit.Test; -import domainapp.dom.modules.simple.SimpleObject; -import domainapp.fixture.scenarios.RecreateSimpleObjects; -import domainapp.integtests.tests.SimpleAppIntegTest; - /** * Test Fixtures with Simple Objects */ @@ -56,7 +54,7 @@ public class SimpleObjectIntegTest extends SimpleAppIntegTest { private static final String NEW_NAME = "new name"; @Before - public void setUp() throws Exception { + public void setUp() { // given fs = new RecreateSimpleObjects().setNumber(1); fixtureScripts.runFixtureScript(fs, null); @@ -68,15 +66,15 @@ public class SimpleObjectIntegTest extends SimpleAppIntegTest { } @Test - public void testNameAccessible() throws Exception { - // when - final String name = simpleObjectWrapped.getName(); + public void testNameAccessible() { + /* when */ + final var name = simpleObjectWrapped.getName(); // then assertEquals(fs.names.get(0), name); } @Test - public void testNameCannotBeUpdatedDirectly() throws Exception { + public void testNameCannotBeUpdatedDirectly() { // expect expectedExceptions.expect(DisabledException.class); @@ -86,7 +84,7 @@ public class SimpleObjectIntegTest extends SimpleAppIntegTest { } @Test - public void testUpdateName() throws Exception { + public void testUpdateName() { // when simpleObjectWrapped.updateName(NEW_NAME); @@ -96,7 +94,7 @@ public class SimpleObjectIntegTest extends SimpleAppIntegTest { } @Test - public void testUpdateNameFailsValidation() throws Exception { + public void testUpdateNameFailsValidation() { // expect expectedExceptions.expect(InvalidException.class); @@ -107,13 +105,13 @@ public class SimpleObjectIntegTest extends SimpleAppIntegTest { } @Test - public void testInterpolatesName() throws Exception { + public void testInterpolatesName() { // given - final String name = simpleObjectWrapped.getName(); + final var name = simpleObjectWrapped.getName(); // when - final String title = container.titleOf(simpleObjectWrapped); + final var title = container.titleOf(simpleObjectWrapped); // then assertEquals("Object: " + name, title); 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 c762dd88f..2699c5aad 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 @@ -25,11 +25,13 @@ package domainapp.integtests.tests.modules.simple; import static org.junit.Assert.assertEquals; +import com.google.common.base.Throwables; +import domainapp.dom.modules.simple.SimpleObjects; +import domainapp.fixture.modules.simple.SimpleObjectsTearDown; +import domainapp.fixture.scenarios.RecreateSimpleObjects; +import domainapp.integtests.tests.SimpleAppIntegTest; import java.sql.SQLIntegrityConstraintViolationException; -import java.util.List; - import javax.inject.Inject; - import org.apache.isis.applib.fixturescripts.FixtureScript; import org.apache.isis.applib.fixturescripts.FixtureScripts; import org.hamcrest.Description; @@ -37,14 +39,6 @@ import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.junit.Test; -import com.google.common.base.Throwables; - -import domainapp.dom.modules.simple.SimpleObject; -import domainapp.dom.modules.simple.SimpleObjects; -import domainapp.fixture.modules.simple.SimpleObjectsTearDown; -import domainapp.fixture.scenarios.RecreateSimpleObjects; -import domainapp.integtests.tests.SimpleAppIntegTest; - /** * Fixture Pattern Integration Test */ @@ -56,25 +50,25 @@ public class SimpleObjectsIntegTest extends SimpleAppIntegTest { SimpleObjects simpleObjects; @Test - public void testListAll() throws Exception { + public void testListAll() { // given - RecreateSimpleObjects fs = new RecreateSimpleObjects(); + var fs = new RecreateSimpleObjects(); fixtureScripts.runFixtureScript(fs, null); nextTransaction(); // when - final List all = wrap(simpleObjects).listAll(); + final var all = wrap(simpleObjects).listAll(); // then assertEquals(fs.getSimpleObjects().size(), all.size()); - SimpleObject simpleObject = wrap(all.get(0)); + var simpleObject = wrap(all.get(0)); assertEquals(fs.getSimpleObjects().get(0).getName(), simpleObject.getName()); } - + @Test - public void testListAllWhenNone() throws Exception { + public void testListAllWhenNone() { // given FixtureScript fs = new SimpleObjectsTearDown(); @@ -82,14 +76,14 @@ public class SimpleObjectsIntegTest extends SimpleAppIntegTest { nextTransaction(); // when - final List all = wrap(simpleObjects).listAll(); + final var all = wrap(simpleObjects).listAll(); // then assertEquals(0, all.size()); } - + @Test - public void testCreate() throws Exception { + public void testCreate() { // given FixtureScript fs = new SimpleObjectsTearDown(); @@ -100,12 +94,12 @@ public class SimpleObjectsIntegTest extends SimpleAppIntegTest { wrap(simpleObjects).create("Faz"); // then - final List all = wrap(simpleObjects).listAll(); + final var all = wrap(simpleObjects).listAll(); assertEquals(1, all.size()); } - + @Test - public void testCreateWhenAlreadyExists() throws Exception { + public void testCreateWhenAlreadyExists() { // given FixtureScript fs = new SimpleObjectsTearDown(); @@ -115,24 +109,22 @@ public class SimpleObjectsIntegTest extends SimpleAppIntegTest { nextTransaction(); // then - expectedExceptions.expectCause(causalChainContains(SQLIntegrityConstraintViolationException.class)); + expectedExceptions + .expectCause(causalChainContains(SQLIntegrityConstraintViolationException.class)); // when wrap(simpleObjects).create("Faz"); nextTransaction(); } - + + @SuppressWarnings("SameParameterValue") private static Matcher causalChainContains(final Class cls) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override + @SuppressWarnings("UnstableApiUsage") protected boolean matchesSafely(Throwable item) { - final List causalChain = Throwables.getCausalChain(item); - for (Throwable throwable : causalChain) { - if (cls.isAssignableFrom(throwable.getClass())) { - return true; - } - } - return false; + final var causalChain = Throwables.getCausalChain(item); + return causalChain.stream().map(Throwable::getClass).anyMatch(cls::isAssignableFrom); } @Override diff --git a/naked-objects/pom.xml b/naked-objects/pom.xml index 5b7a2e0c7..e8b9b79c5 100644 --- a/naked-objects/pom.xml +++ b/naked-objects/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT naked-objects pom @@ -333,17 +333,17 @@ ${project.groupId} naked-objects-dom - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT ${project.groupId} naked-objects-fixture - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT ${project.groupId} naked-objects-webapp - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT diff --git a/naked-objects/webapp/ide/eclipse/launch/.gitignore b/naked-objects/webapp/ide/eclipse/launch/.gitignore index 3d9734548..3cefd2567 100644 --- a/naked-objects/webapp/ide/eclipse/launch/.gitignore +++ b/naked-objects/webapp/ide/eclipse/launch/.gitignore @@ -2,7 +2,3 @@ /SimpleApp-PROTOTYPE-no-fixtures.launch /SimpleApp-PROTOTYPE-with-fixtures.launch /SimpleApp-SERVER-no-fixtures.launch -/SimpleApp-PROTOTYPE-jrebel.launch -/SimpleApp-PROTOTYPE-no-fixtures.launch -/SimpleApp-PROTOTYPE-with-fixtures.launch -/SimpleApp-SERVER-no-fixtures.launch diff --git a/naked-objects/webapp/pom.xml b/naked-objects/webapp/pom.xml index bdf638cba..3a817f9e2 100644 --- a/naked-objects/webapp/pom.xml +++ b/naked-objects/webapp/pom.xml @@ -30,7 +30,7 @@ com.iluwatar naked-objects - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT naked-objects-webapp @@ -129,7 +129,7 @@ - + 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 8425712dc..780e4027e 100644 --- a/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java +++ b/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java @@ -31,18 +31,15 @@ import com.google.inject.name.Names; import com.google.inject.util.Modules; import com.google.inject.util.Providers; import de.agilecoders.wicket.core.Bootstrap; -import de.agilecoders.wicket.core.settings.IBootstrapSettings; import de.agilecoders.wicket.themes.markup.html.bootswatch.BootswatchTheme; import de.agilecoders.wicket.themes.markup.html.bootswatch.BootswatchThemeProvider; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; -import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.isis.viewer.wicket.viewer.IsisWicketApplication; import org.apache.isis.viewer.wicket.viewer.integration.wicket.AuthenticatedWebSessionForIsis; import org.apache.wicket.Session; -import org.apache.wicket.request.IRequestParameters; import org.apache.wicket.request.Request; import org.apache.wicket.request.Response; import org.apache.wicket.request.http.WebRequest; @@ -85,7 +82,7 @@ public class SimpleApplication extends IsisWicketApplication { protected void init() { super.init(); - IBootstrapSettings settings = Bootstrap.getSettings(); + var settings = Bootstrap.getSettings(); settings.setThemeProvider(new BootswatchThemeProvider(BootswatchTheme.Flatly)); } @@ -96,13 +93,10 @@ public class SimpleApplication extends IsisWicketApplication { } // else demo mode - final AuthenticatedWebSessionForIsis s = - (AuthenticatedWebSessionForIsis) super.newSession(request, response); - IRequestParameters requestParameters = request.getRequestParameters(); - final org.apache.wicket.util.string.StringValue user = - requestParameters.getParameterValue("user"); - final org.apache.wicket.util.string.StringValue password = - requestParameters.getParameterValue("pass"); + final var s = (AuthenticatedWebSessionForIsis) super.newSession(request, response); + var requestParameters = request.getRequestParameters(); + final var user = requestParameters.getParameterValue("user"); + final var password = requestParameters.getParameterValue("pass"); s.signIn(user.toString(), password.toString()); return s; } @@ -115,7 +109,7 @@ public class SimpleApplication extends IsisWicketApplication { // else demo mode try { - String uname = servletRequest.getParameter("user"); + var uname = servletRequest.getParameter("user"); if (uname != null) { servletRequest.getSession().invalidate(); } @@ -127,7 +121,7 @@ public class SimpleApplication extends IsisWicketApplication { @Override protected Module newIsisWicketModule() { - final Module isisDefaults = super.newIsisWicketModule(); + final var isisDefaults = super.newIsisWicketModule(); final Module overrides = new AbstractModule() { @Override @@ -148,11 +142,11 @@ public class SimpleApplication extends IsisWicketApplication { return Modules.override(isisDefaults).with(overrides); } + @SuppressWarnings({"UnstableApiUsage", "SameParameterValue"}) private static String readLines(final Class contextClass, final String resourceName) { try { - List readLines = - Resources.readLines(Resources.getResource(contextClass, resourceName), - Charset.defaultCharset()); + var resource = Resources.getResource(contextClass, resourceName); + var readLines = Resources.readLines(resource, Charset.defaultCharset()); return Joiner.on("\n").join(readLines); } catch (IOException e) { return "This is a simple app"; diff --git a/naked-objects/webapp/src/main/webapp/about/index.html b/naked-objects/webapp/src/main/webapp/about/index.html index e929c5b6d..4579f3d0b 100644 --- a/naked-objects/webapp/src/main/webapp/about/index.html +++ b/naked-objects/webapp/src/main/webapp/about/index.html @@ -110,8 +110,8 @@ th, td {

provides access to a RESTful API conformant with the - Restful Objects spec. This is part of Apache Isis Core. The - implementation technology is JBoss RestEasy. + Restful Objects spec. This is part of Apache Isis Core. + The implementation technology is JBoss RestEasy.

diff --git a/null-object/README.md b/null-object/README.md index 0fce86f0e..0e95453cb 100644 --- a/null-object/README.md +++ b/null-object/README.md @@ -9,20 +9,20 @@ tags: --- ## 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 -object (for instance, a non-existent customer), one uses an object which -implements the expected interface, but whose method body is empty. The -advantage of this approach over a working default implementation is that a Null -Object is very predictable and has no side effects: it does nothing. + +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 +object (for instance, a non-existent customer), one uses an object which implements the expected +interface, but whose method body is empty. The advantage of this approach over a working default +implementation is that a Null Object is very predictable and has no side effects: it does nothing. ## Explanation Real world example -> We are building a binary tree from nodes. There are ordinary nodes and "empty" nodes. Traversing the tree normally should not cause errors, so we use null object pattern where necessary. +> We are building a binary tree from nodes. There are ordinary nodes and "empty" nodes. Traversing +> the tree normally should not cause errors, so we use null object pattern where necessary. In plain words @@ -30,11 +30,13 @@ In plain words Wikipedia says -> In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral ("null") behavior. The null object design pattern describes the uses of such objects and their behavior (or lack thereof). +> In object-oriented computer programming, a null object is an object with no referenced value or +> with defined neutral ("null") behavior. The null object design pattern describes the uses of such +> objects and their behavior (or lack thereof). **Programmatic Example** -Here's the definitions for node interface and its implementations. +Here's the definition of `Node` interface. ```java public interface Node { @@ -49,7 +51,12 @@ public interface Node { void walk(); } +``` +We have two implementations of `Node`. The normal implementation `NodeImpl` and `NullNode` for +empty nodes. + +```java public class NodeImpl implements Node { private static final Logger LOGGER = LoggerFactory.getLogger(NodeImpl.class); @@ -101,7 +108,7 @@ public class NodeImpl implements Node { public final class NullNode implements Node { - private static NullNode instance = new NullNode(); + private static final NullNode instance = new NullNode(); private NullNode() { } @@ -135,30 +142,40 @@ public final class NullNode implements Node { // Do nothing } } - ``` Then we can construct and traverse the binary tree without errors as follows. ```java - Node root = - new NodeImpl("1", new NodeImpl("11", new NodeImpl("111", NullNode.getInstance(), - NullNode.getInstance()), NullNode.getInstance()), new NodeImpl("12", - NullNode.getInstance(), new NodeImpl("122", NullNode.getInstance(), - NullNode.getInstance()))); + var root = new NodeImpl("1", + new NodeImpl("11", + new NodeImpl("111", NullNode.getInstance(), NullNode.getInstance()), + NullNode.getInstance() + ), + new NodeImpl("12", + NullNode.getInstance(), + new NodeImpl("122", NullNode.getInstance(), NullNode.getInstance()) + ) + ); root.walk(); - - // 1 - // 11 - // 111 - // 12 - // 122 +``` + +Program output: + +``` +1 +11 +111 +12 +122 ``` ## Class diagram + ![alt text](./etc/null-object.png "Null Object") ## 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 7b88fca79..4317ee36a 100644 --- a/null-object/pom.xml +++ b/null-object/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT null-object diff --git a/null-object/src/main/java/com/iluwatar/nullobject/App.java b/null-object/src/main/java/com/iluwatar/nullobject/App.java index 2826bafd0..cd35a3042 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/App.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/App.java @@ -37,12 +37,16 @@ public class App { * @param args command line args */ public static void main(String[] args) { - - Node root = - new NodeImpl("1", new NodeImpl("11", new NodeImpl("111", NullNode.getInstance(), - NullNode.getInstance()), NullNode.getInstance()), new NodeImpl("12", - NullNode.getInstance(), new NodeImpl("122", NullNode.getInstance(), - NullNode.getInstance()))); + var root = new NodeImpl("1", + new NodeImpl("11", + new NodeImpl("111", NullNode.getInstance(), NullNode.getInstance()), + NullNode.getInstance() + ), + new NodeImpl("12", + NullNode.getInstance(), + new NodeImpl("122", NullNode.getInstance(), NullNode.getInstance()) + ) + ); root.walk(); } diff --git a/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java b/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java index 9b28c249b..472a1a2fd 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java @@ -30,7 +30,7 @@ package com.iluwatar.nullobject; */ public final class NullNode implements Node { - private static NullNode instance = new NullNode(); + private static final NullNode instance = new NullNode(); private NullNode() { } 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 97d6b5eef..27724a724 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java @@ -25,16 +25,15 @@ package com.iluwatar.nullobject; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** - * * Application test - * */ -public class AppTest { +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java b/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java index b4d9f72d0..5862d1e1a 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java @@ -35,30 +35,29 @@ import static org.junit.jupiter.api.Assertions.assertSame; * * @author Jeroen Meulemeester */ -public class NullNodeTest { +class NullNodeTest { /** * Verify if {@link NullNode#getInstance()} actually returns the same object instance */ @Test - public void testGetInstance() { - final NullNode instance = NullNode.getInstance(); + void testGetInstance() { + final var instance = NullNode.getInstance(); assertNotNull(instance); assertSame(instance, NullNode.getInstance()); } @Test - public void testFields() { - final NullNode node = NullNode.getInstance(); + void testFields() { + final var node = NullNode.getInstance(); assertEquals(0, node.getTreeSize()); assertNull(node.getName()); assertNull(node.getLeft()); assertNull(node.getRight()); } - @Test - public void testWalk() { - NullNode.getInstance().walk(); - } + /** + * Removed unnecessary test method for {@link NullNode#walk()} as the method doesn't have an implementation. + */ } diff --git a/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java b/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java index 4ff30f524..9a2b485d0 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java @@ -23,22 +23,21 @@ package com.iluwatar.nullobject; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.AppenderBase; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.slf4j.LoggerFactory; - -import java.util.LinkedList; -import java.util.List; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import java.util.LinkedList; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + /** * Date: 12/26/15 - 11:44 PM * @@ -75,12 +74,12 @@ public class TreeTest { 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); + final var level1B = new NodeImpl("level1_b", NullNode.getInstance(), NullNode.getInstance()); + final var level2B = new NodeImpl("level2_b", NullNode.getInstance(), NullNode.getInstance()); + final var level3A = new NodeImpl("level3_a", NullNode.getInstance(), NullNode.getInstance()); + final var level3B = new NodeImpl("level3_b", NullNode.getInstance(), NullNode.getInstance()); + final var level2A = new NodeImpl("level2_a", level3A, level3B); + final var level1A = new NodeImpl("level1_a", level2A, level2B); TREE_ROOT = new NodeImpl("root", level1A, level1B); } @@ -112,17 +111,17 @@ public class TreeTest { @Test public void testGetLeft() { - final Node level1 = TREE_ROOT.getLeft(); + final var level1 = TREE_ROOT.getLeft(); assertNotNull(level1); assertEquals("level1_a", level1.getName()); assertEquals(5, level1.getTreeSize()); - final Node level2 = level1.getLeft(); + final var level2 = level1.getLeft(); assertNotNull(level2); assertEquals("level2_a", level2.getName()); assertEquals(3, level2.getTreeSize()); - final Node level3 = level2.getLeft(); + final var level3 = level2.getLeft(); assertNotNull(level3); assertEquals("level3_a", level3.getName()); assertEquals(1, level3.getTreeSize()); @@ -132,7 +131,7 @@ public class TreeTest { @Test public void testGetRight() { - final Node level1 = TREE_ROOT.getRight(); + final var level1 = TREE_ROOT.getRight(); assertNotNull(level1); assertEquals("level1_b", level1.getName()); assertEquals(1, level1.getTreeSize()); @@ -140,8 +139,8 @@ public class TreeTest { assertSame(NullNode.getInstance(), level1.getLeft()); } - private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private static class InMemoryAppender extends AppenderBase { + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); @@ -154,7 +153,7 @@ public class TreeTest { } public boolean logContains(String message) { - return log.stream().anyMatch(event -> event.getMessage().equals(message)); + return log.stream().map(ILoggingEvent::getMessage).anyMatch(message::equals); } public int getLogSize() { diff --git a/object-mother/pom.xml b/object-mother/pom.xml index 5ac3ce410..f9ca156ad 100644 --- a/object-mother/pom.xml +++ b/object-mother/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT object-mother diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java b/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java index 4c704f6b1..308760ba9 100644 --- a/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java +++ b/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java @@ -66,9 +66,6 @@ public class Queen implements Royalty { * @return A value which describes if the flirt was successful or not. */ public boolean getFlirted(King king) { - if (this.isFlirty && king.isHappy && !king.isDrunk) { - return true; - } - return false; + return this.isFlirty && king.isHappy && !king.isDrunk; } } diff --git a/object-pool/README.md b/object-pool/README.md index a8a20638c..2f49d6f47 100644 --- a/object-pool/README.md +++ b/object-pool/README.md @@ -9,17 +9,23 @@ tags: - Performance --- +## Also known as + +Resource Pool + ## 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. + +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. ## Explanation Real world example -> In our war game we need to use oliphaunts, massive and mythic beasts, but the problem is that they are extremely expensive to create. The solution is to create a pool of them, track which ones are in-use, and instead of disposing them re-use the instances. +> In our war game we need to use oliphaunts, massive and mythic beasts, but the problem is that they +> are extremely expensive to create. The solution is to create a pool of them, track which ones are +> in-use, and instead of disposing them re-use the instances. In plain words @@ -27,16 +33,17 @@ In plain words Wikipedia says -> The object pool pattern is a software creational design pattern that uses a set of initialized objects kept ready to use – a "pool" – rather than allocating and destroying them on demand. +> The object pool pattern is a software creational design pattern that uses a set of initialized +> objects kept ready to use – a "pool" – rather than allocating and destroying them on demand. **Programmatic Example** -Here's the basic Oliphaunt class. These are very expensive to create. +Here's the basic `Oliphaunt` class. These giants are very expensive to create. ```java public class Oliphaunt { - private static AtomicInteger counter = new AtomicInteger(0); + private static final AtomicInteger counter = new AtomicInteger(0); private final int id; @@ -60,13 +67,13 @@ public class Oliphaunt { } ``` -Next we present the Object Pool and more specifically Oliphaunt Pool. +Next we present the `ObjectPool` and more specifically `OliphauntPool`. ```java public abstract class ObjectPool { - private Set available = new HashSet<>(); - private Set inUse = new HashSet<>(); + private final Set available = new HashSet<>(); + private final Set inUse = new HashSet<>(); protected abstract T create(); @@ -100,7 +107,7 @@ public class OliphauntPool extends ObjectPool { } ``` -And finally here's how we utilize the pool. +Finally, here's how we utilize the pool. ```java var pool = new OliphauntPool(); @@ -113,11 +120,30 @@ And finally here's how we utilize the pool. var oliphaunt5 = pool.checkOut(); ``` +Program output: + +``` +Pool available=0 inUse=0 +Checked out Oliphaunt id=1 +Pool available=0 inUse=1 +Checked out Oliphaunt id=2 +Checked out Oliphaunt id=3 +Pool available=0 inUse=3 +Checking in Oliphaunt id=1 +Checking in Oliphaunt id=2 +Pool available=2 inUse=1 +Checked out Oliphaunt id=2 +Checked out Oliphaunt id=1 +Pool available=0 inUse=3 +``` + ## Class diagram + ![alt text](./etc/object-pool.png "Object Pool") ## 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) +* 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 2adad8942..0587c3968 100644 --- a/object-pool/pom.xml +++ b/object-pool/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT object-pool diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/App.java b/object-pool/src/main/java/com/iluwatar/object/pool/App.java index cbfc7dea5..48957ce24 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/App.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/App.java @@ -57,12 +57,14 @@ public class App { var pool = new OliphauntPool(); LOGGER.info(pool.toString()); var oliphaunt1 = pool.checkOut(); - LOGGER.info("Checked out {}", oliphaunt1); + String checkedOut = "Checked out {}"; + + LOGGER.info(checkedOut, oliphaunt1); LOGGER.info(pool.toString()); var oliphaunt2 = pool.checkOut(); - LOGGER.info("Checked out {}", oliphaunt2); + LOGGER.info(checkedOut, oliphaunt2); var oliphaunt3 = pool.checkOut(); - LOGGER.info("Checked out {}", oliphaunt3); + LOGGER.info(checkedOut, oliphaunt3); LOGGER.info(pool.toString()); LOGGER.info("Checking in {}", oliphaunt1); pool.checkIn(oliphaunt1); @@ -70,9 +72,9 @@ public class App { pool.checkIn(oliphaunt2); LOGGER.info(pool.toString()); var oliphaunt4 = pool.checkOut(); - LOGGER.info("Checked out {}", oliphaunt4); + LOGGER.info(checkedOut, oliphaunt4); var oliphaunt5 = pool.checkOut(); - LOGGER.info("Checked out {}", oliphaunt5); + LOGGER.info(checkedOut, oliphaunt5); LOGGER.info(pool.toString()); } } 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 b8ce3cc05..43ac5d873 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 @@ -33,8 +33,8 @@ import java.util.Set; */ public abstract class ObjectPool { - private Set available = new HashSet<>(); - private Set inUse = new HashSet<>(); + private final Set available = new HashSet<>(); + private final Set inUse = new HashSet<>(); protected abstract T 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 42db07158..09dedbab0 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 @@ -30,7 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ public class Oliphaunt { - private static AtomicInteger counter = new AtomicInteger(0); + private static final AtomicInteger counter = new AtomicInteger(0); private final int id; 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 7113f9304..f36563a8c 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 @@ -30,11 +30,10 @@ import org.junit.jupiter.api.Test; * Application test * */ -public class AppTest { +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + App.main(new String[]{}); } } diff --git a/observer/README.md b/observer/README.md index edc72ae24..cbde540ab 100644 --- a/observer/README.md +++ b/observer/README.md @@ -10,29 +10,35 @@ tags: --- ## Also known as + Dependents, Publish-Subscribe ## Intent -Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified -and updated automatically. + +Define a one-to-many dependency between objects so that when one object changes state, all its +dependents are notified and updated automatically. ## Explanation Real world example -> In a land far away lives the races of hobbits and orcs. Both of them are mostly outdoors so they closely follow the changes in weather. One could say that they are constantly observing the weather. +> In a land far away lives the races of hobbits and orcs. Both of them are mostly outdoors so they +> closely follow the changes in weather. One could say that they are constantly observing the +> weather. In plain words -> Register as an observer to receive state changes in the object. +> Register as an observer to receive state changes in the object. Wikipedia says -> The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. +> The observer pattern is a software design pattern in which an object, called the subject, +> maintains a list of its dependents, called observers, and notifies them automatically of any state +> changes, usually by calling one of their methods. **Programmatic Example** -Let's first introduce the weather observer interface and our races, orcs and hobbits. +Let's first introduce the `WeatherObserver` interface and our races, `Orcs` and `Hobbits`. ```java public interface WeatherObserver { @@ -46,22 +52,7 @@ public class Orcs implements WeatherObserver { @Override public void update(WeatherType currentWeather) { - switch (currentWeather) { - case COLD: - LOGGER.info("The orcs are freezing cold."); - break; - case RAINY: - LOGGER.info("The orcs are dripping wet."); - break; - case SUNNY: - LOGGER.info("The sun hurts the orcs' eyes."); - break; - case WINDY: - LOGGER.info("The orc smell almost vanishes in the wind."); - break; - default: - break; - } + LOGGER.info("The orcs are facing " + currentWeather.getDescription() + " weather now"); } } @@ -72,26 +63,13 @@ public class Hobbits implements WeatherObserver { @Override public void update(WeatherType currentWeather) { switch (currentWeather) { - case COLD: - LOGGER.info("The hobbits are shivering in the cold weather."); - break; - case RAINY: - LOGGER.info("The hobbits look for cover from the rain."); - break; - case SUNNY: - LOGGER.info("The happy hobbits bade in the warm sun."); - break; - case WINDY: - LOGGER.info("The hobbits hold their hats tightly in the windy weather."); - break; - default: - break; + LOGGER.info("The hobbits are facing " + currentWeather.getDescription() + " weather now"); } } } ``` -Then here's the weather that is constantly changing. +Then here's the `Weather` that is constantly changing. ```java public class Weather { @@ -99,7 +77,7 @@ public class Weather { private static final Logger LOGGER = LoggerFactory.getLogger(Weather.class); private WeatherType currentWeather; - private List observers; + private final List observers; public Weather() { observers = new ArrayList<>(); @@ -138,38 +116,47 @@ Here's the full example in action. var weather = new Weather(); weather.addObserver(new Orcs()); weather.addObserver(new Hobbits()); + weather.timePasses(); + weather.timePasses(); + weather.timePasses(); + weather.timePasses(); +``` - weather.timePasses(); - // The weather changed to rainy. - // The orcs are dripping wet. - // The hobbits look for cover from the rain. - weather.timePasses(); - // The weather changed to windy. - // The orc smell almost vanishes in the wind. - // The hobbits hold their hats tightly in the windy weather. - weather.timePasses(); - // The weather changed to cold. - // The orcs are freezing cold. - // The hobbits are shivering in the cold weather. - weather.timePasses(); - // The weather changed to sunny. - // The sun hurts the orcs' eyes. - // The happy hobbits bade in the warm sun. +Program output: + +``` +The weather changed to rainy. +The orcs are facing rainy weather now +The hobbits are facing rainy weather now +The weather changed to windy. +The orcs are facing windy weather now +The hobbits are facing windy weather now +The weather changed to cold. +The orcs are facing cold weather now +The hobbits are facing cold weather now +The weather changed to sunny. +The orcs are facing sunny weather now +The hobbits are facing sunny weather now ``` ## Class diagram + ![alt text](./etc/observer.png "Observer") ## 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 +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 -* Changing in one object leads to a change in other objects +* Changing in one object leads to a change in other objects. ## Real world examples diff --git a/observer/etc/observer.urm.puml b/observer/etc/observer.urm.puml index bea9aab53..497ef5fde 100644 --- a/observer/etc/observer.urm.puml +++ b/observer/etc/observer.urm.puml @@ -33,7 +33,9 @@ package com.iluwatar.observer { + RAINY {static} + SUNNY {static} + WINDY {static} + + description String + toString() : String + + getDescription() : String + valueOf(name : String) : WeatherType {static} + values() : WeatherType[] {static} } @@ -71,10 +73,10 @@ package com.iluwatar.observer.generic { Weather --> "-currentWeather" WeatherType GWeather --> "-currentWeather" WeatherType Weather --> "-observers" WeatherObserver -Hobbits ..|> WeatherObserver -Orcs ..|> WeatherObserver -GHobbits ..|> Race -GOrcs ..|> Race -GWeather --|> Observable -Race --|> Observer -@enduml \ No newline at end of file +Hobbits ..|> WeatherObserver +Orcs ..|> WeatherObserver +GHobbits ..|> Race +GOrcs ..|> Race +GWeather --|> Observable +Race --|> Observer +@enduml diff --git a/observer/etc/observer_with_generics.png b/observer/etc/observer_with_generics.png new file mode 100644 index 000000000..06ff0d9cc Binary files /dev/null and b/observer/etc/observer_with_generics.png differ diff --git a/observer/pom.xml b/observer/pom.xml index 1e48268d8..c558992ac 100644 --- a/observer/pom.xml +++ b/observer/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT observer diff --git a/observer/src/main/java/com/iluwatar/observer/Hobbits.java b/observer/src/main/java/com/iluwatar/observer/Hobbits.java index 646ceebfd..5894c93a6 100644 --- a/observer/src/main/java/com/iluwatar/observer/Hobbits.java +++ b/observer/src/main/java/com/iluwatar/observer/Hobbits.java @@ -35,21 +35,6 @@ public class Hobbits implements WeatherObserver { @Override public void update(WeatherType currentWeather) { - switch (currentWeather) { - case COLD: - LOGGER.info("The hobbits are shivering in the cold weather."); - break; - case RAINY: - LOGGER.info("The hobbits look for cover from the rain."); - break; - case SUNNY: - LOGGER.info("The happy hobbits bade in the warm sun."); - break; - case WINDY: - LOGGER.info("The hobbits hold their hats tightly in the windy weather."); - break; - default: - break; - } + LOGGER.info("The hobbits are facing " + currentWeather.getDescription() + " weather now"); } } diff --git a/observer/src/main/java/com/iluwatar/observer/Orcs.java b/observer/src/main/java/com/iluwatar/observer/Orcs.java index a28ffbc5b..1a955aafd 100644 --- a/observer/src/main/java/com/iluwatar/observer/Orcs.java +++ b/observer/src/main/java/com/iluwatar/observer/Orcs.java @@ -35,21 +35,6 @@ public class Orcs implements WeatherObserver { @Override public void update(WeatherType currentWeather) { - switch (currentWeather) { - case COLD: - LOGGER.info("The orcs are freezing cold."); - break; - case RAINY: - LOGGER.info("The orcs are dripping wet."); - break; - case SUNNY: - LOGGER.info("The sun hurts the orcs' eyes."); - break; - case WINDY: - LOGGER.info("The orc smell almost vanishes in the wind."); - break; - default: - break; - } + LOGGER.info("The orcs are facing " + currentWeather.getDescription() + " weather now"); } } diff --git a/observer/src/main/java/com/iluwatar/observer/Weather.java b/observer/src/main/java/com/iluwatar/observer/Weather.java index 778858107..a0d80d6bc 100644 --- a/observer/src/main/java/com/iluwatar/observer/Weather.java +++ b/observer/src/main/java/com/iluwatar/observer/Weather.java @@ -37,7 +37,7 @@ public class Weather { private static final Logger LOGGER = LoggerFactory.getLogger(Weather.class); private WeatherType currentWeather; - private List observers; + private final List observers; public Weather() { observers = new ArrayList<>(); diff --git a/observer/src/main/java/com/iluwatar/observer/WeatherType.java b/observer/src/main/java/com/iluwatar/observer/WeatherType.java index 75ee17d60..e11317c21 100644 --- a/observer/src/main/java/com/iluwatar/observer/WeatherType.java +++ b/observer/src/main/java/com/iluwatar/observer/WeatherType.java @@ -28,7 +28,20 @@ package com.iluwatar.observer; */ public enum WeatherType { - SUNNY, RAINY, WINDY, COLD; + SUNNY("Sunny"), + RAINY("Rainy"), + WINDY("Windy"), + COLD("Cold"); + + private final String description; + + WeatherType(String description) { + this.description = description; + } + + public String getDescription() { + return this.description; + } @Override public String toString() { diff --git a/observer/src/main/java/com/iluwatar/observer/generic/GHobbits.java b/observer/src/main/java/com/iluwatar/observer/generic/GHobbits.java index 7a555d850..90fd4e300 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/GHobbits.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/GHobbits.java @@ -36,21 +36,6 @@ public class GHobbits implements Race { @Override public void update(GWeather weather, WeatherType weatherType) { - switch (weatherType) { - case COLD: - LOGGER.info("The hobbits are shivering in the cold weather."); - break; - case RAINY: - LOGGER.info("The hobbits look for cover from the rain."); - break; - case SUNNY: - LOGGER.info("The happy hobbits bade in the warm sun."); - break; - case WINDY: - LOGGER.info("The hobbits hold their hats tightly in the windy weather."); - break; - default: - break; - } + LOGGER.info("The hobbits are facing " + weatherType.getDescription() + " weather now"); } } diff --git a/observer/src/main/java/com/iluwatar/observer/generic/GOrcs.java b/observer/src/main/java/com/iluwatar/observer/generic/GOrcs.java index d9adbf116..bc49c4e30 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/GOrcs.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/GOrcs.java @@ -36,21 +36,6 @@ public class GOrcs implements Race { @Override public void update(GWeather weather, WeatherType weatherType) { - switch (weatherType) { - case COLD: - LOGGER.info("The orcs are freezing cold."); - break; - case RAINY: - LOGGER.info("The orcs are dripping wet."); - break; - case SUNNY: - LOGGER.info("The sun hurts the orcs' eyes."); - break; - case WINDY: - LOGGER.info("The orc smell almost vanishes in the wind."); - break; - default: - break; - } + LOGGER.info("The orcs are facing " + weatherType.getDescription() + " weather now"); } } 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 fbb078941..d90219c92 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/Observable.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/Observable.java @@ -35,7 +35,7 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public abstract class Observable, O extends Observer, A> { - protected List observers; + protected final List observers; public Observable() { this.observers = new CopyOnWriteArrayList<>(); diff --git a/observer/src/test/java/com/iluwatar/observer/AppTest.java b/observer/src/test/java/com/iluwatar/observer/AppTest.java index b557fdf7e..6b1f426d6 100644 --- a/observer/src/test/java/com/iluwatar/observer/AppTest.java +++ b/observer/src/test/java/com/iluwatar/observer/AppTest.java @@ -25,16 +25,17 @@ package com.iluwatar.observer; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * * Application test * */ -public class AppTest { +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java b/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java index 66ec45fdb..345b8e331 100644 --- a/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java @@ -36,10 +36,10 @@ public class HobbitsTest extends WeatherObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The happy hobbits bade in the warm sun."}, - new Object[]{WeatherType.RAINY, "The hobbits look for cover from the rain."}, - new Object[]{WeatherType.WINDY, "The hobbits hold their hats tightly in the windy weather."}, - new Object[]{WeatherType.COLD, "The hobbits are shivering in the cold weather."}); + new Object[]{WeatherType.SUNNY, "The hobbits are facing Sunny weather now"}, + new Object[]{WeatherType.RAINY, "The hobbits are facing Rainy weather now"}, + new Object[]{WeatherType.WINDY, "The hobbits are facing Windy weather now"}, + new Object[]{WeatherType.COLD, "The hobbits are facing Cold weather now"}); } /** diff --git a/observer/src/test/java/com/iluwatar/observer/OrcsTest.java b/observer/src/test/java/com/iluwatar/observer/OrcsTest.java index ff615df3c..65beeaf0e 100644 --- a/observer/src/test/java/com/iluwatar/observer/OrcsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/OrcsTest.java @@ -36,10 +36,10 @@ public class OrcsTest extends WeatherObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The sun hurts the orcs' eyes."}, - new Object[]{WeatherType.RAINY, "The orcs are dripping wet."}, - new Object[]{WeatherType.WINDY, "The orc smell almost vanishes in the wind."}, - new Object[]{WeatherType.COLD, "The orcs are freezing cold."}); + new Object[]{WeatherType.SUNNY, "The orcs are facing Sunny weather now"}, + new Object[]{WeatherType.RAINY, "The orcs are facing Rainy weather now"}, + new Object[]{WeatherType.WINDY, "The orcs are facing Windy weather now"}, + new Object[]{WeatherType.COLD, "The orcs are facing Cold weather now"}); } /** diff --git a/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java b/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java index dd0e6d6bf..756d72239 100644 --- a/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java @@ -38,10 +38,10 @@ public class GHobbitsTest extends ObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The happy hobbits bade in the warm sun."}, - new Object[]{WeatherType.RAINY, "The hobbits look for cover from the rain."}, - new Object[]{WeatherType.WINDY, "The hobbits hold their hats tightly in the windy weather."}, - new Object[]{WeatherType.COLD, "The hobbits are shivering in the cold weather."} + new Object[]{WeatherType.SUNNY, "The hobbits are facing Sunny weather now"}, + new Object[]{WeatherType.RAINY, "The hobbits are facing Rainy weather now"}, + new Object[]{WeatherType.WINDY, "The hobbits are facing Windy weather now"}, + new Object[]{WeatherType.COLD, "The hobbits are facing Cold weather now"} ); } diff --git a/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java b/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java index 396de4456..523678288 100644 --- a/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java @@ -38,10 +38,10 @@ public class OrcsTest extends ObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The sun hurts the orcs' eyes."}, - new Object[]{WeatherType.RAINY, "The orcs are dripping wet."}, - new Object[]{WeatherType.WINDY, "The orc smell almost vanishes in the wind."}, - new Object[]{WeatherType.COLD, "The orcs are freezing cold."} + new Object[]{WeatherType.SUNNY, "The orcs are facing Sunny weather now"}, + new Object[]{WeatherType.RAINY, "The orcs are facing Rainy weather now"}, + new Object[]{WeatherType.WINDY, "The orcs are facing Windy weather now"}, + new Object[]{WeatherType.COLD, "The orcs are facing Cold weather now"} ); } diff --git a/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java b/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java index b3d2bf1bc..132216d19 100644 --- a/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java +++ b/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java @@ -35,7 +35,7 @@ import java.util.List; * InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); diff --git a/page-object/pom.xml b/page-object/pom.xml index 4bf31d72e..a91843121 100644 --- a/page-object/pom.xml +++ b/page-object/pom.xml @@ -46,7 +46,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT page-object pom diff --git a/page-object/sample-application/pom.xml b/page-object/sample-application/pom.xml index 5ded184cc..6748a4063 100644 --- a/page-object/sample-application/pom.xml +++ b/page-object/sample-application/pom.xml @@ -29,7 +29,7 @@ page-object com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT sample-application
diff --git a/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java index 779458e05..22bc8a5fb 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java @@ -36,7 +36,7 @@ import org.junit.jupiter.api.Test; */ public class AlbumListPageTest { - private AlbumListPage albumListPage = new AlbumListPage(new WebClient()); + private final AlbumListPage albumListPage = new AlbumListPage(new WebClient()); @BeforeEach public void setUp() { diff --git a/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java index 601093343..68c836bd3 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java @@ -36,7 +36,7 @@ import org.junit.jupiter.api.Test; */ public class AlbumPageTest { - private AlbumPage albumPage = new AlbumPage(new WebClient()); + private final AlbumPage albumPage = new AlbumPage(new WebClient()); @BeforeEach public void setUp() { diff --git a/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java index 022f736ca..460bdcf96 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java @@ -36,7 +36,7 @@ import org.junit.jupiter.api.Test; */ public class LoginPageTest { - private LoginPage loginPage = new LoginPage(new WebClient()); + private final LoginPage loginPage = new LoginPage(new WebClient()); @BeforeEach public void setUp() { diff --git a/page-object/test-automation/pom.xml b/page-object/test-automation/pom.xml index 68025f4b8..5f64807de 100644 --- a/page-object/test-automation/pom.xml +++ b/page-object/test-automation/pom.xml @@ -29,7 +29,7 @@ page-object com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT test-automation diff --git a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java index d1b450a24..1acdd5ba5 100644 --- a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java +++ b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test; */ public class AlbumListPageTest { - private AlbumListPage albumListPage = new AlbumListPage(new WebClient()); + private final AlbumListPage albumListPage = new AlbumListPage(new WebClient()); @BeforeEach public void setUp() { diff --git a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java index 8e694a592..ecde999c3 100644 --- a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java +++ b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test; */ public class AlbumPageTest { - private AlbumPage albumPage = new AlbumPage(new WebClient()); + private final AlbumPage albumPage = new AlbumPage(new WebClient()); @BeforeEach public void setUp() { diff --git a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java index 89668882d..429b7fcc5 100644 --- a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java +++ b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test; */ public class LoginPageTest { - private LoginPage loginPage = new LoginPage(new WebClient()); + private final LoginPage loginPage = new LoginPage(new WebClient()); @BeforeEach public void setUp() { diff --git a/partial-response/pom.xml b/partial-response/pom.xml index 83d81bf82..fbece028e 100644 --- a/partial-response/pom.xml +++ b/partial-response/pom.xml @@ -29,24 +29,28 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 partial-response - - junit - junit - org.junit.vintage junit-vintage-engine + 5.4.0 test org.mockito - mockito-core + mockito-junit-jupiter + 3.5.0 + test + + + org.junit.jupiter + junit-jupiter-engine + test diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java b/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java index a61a3c429..11a4f23ca 100644 --- a/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java @@ -30,8 +30,8 @@ import java.util.Map; * has all video details. */ public class VideoResource { - private FieldJsonMapper fieldJsonMapper; - private Map videos; + private final FieldJsonMapper fieldJsonMapper; + private final Map videos; /** * Constructor. diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java index d50084a36..c423355f5 100644 --- a/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java @@ -23,16 +23,17 @@ package com.iluwatar.partialresponse; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; /** * Application test */ -public class AppTest { +class AppTest { @Test - public void main() throws Exception { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + Assertions.assertDoesNotThrow(() -> App.main(new String[]{})); } } \ No newline at end of file diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java index 57741c1d7..6bf5bf190 100644 --- a/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java @@ -23,24 +23,23 @@ package com.iluwatar.partialresponse; -import static org.junit.Assert.assertEquals; - -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; /** * tests {@link FieldJsonMapper}. */ -public class FieldJsonMapperTest { - private FieldJsonMapper mapper; +class FieldJsonMapperTest { + private static FieldJsonMapper mapper; - @Before - public void setUp() { + @BeforeAll + static void setUp() { mapper = new FieldJsonMapper(); } @Test - public void shouldReturnJsonForSpecifiedFieldsInVideo() throws Exception { + void shouldReturnJsonForSpecifiedFieldsInVideo() throws Exception { var fields = new String[]{"id", "title", "length"}; var video = new Video( 2, "Godzilla Resurgence", 120, @@ -50,6 +49,6 @@ public class FieldJsonMapperTest { var jsonFieldResponse = mapper.toJson(video, fields); var expectedDetails = "{\"id\": 2,\"title\": \"Godzilla Resurgence\",\"length\": 120}"; - assertEquals(expectedDetails, jsonFieldResponse); + Assertions.assertEquals(expectedDetails, jsonFieldResponse); } } \ No newline at end of file diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java index 252499803..dbd8e78dd 100644 --- a/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java @@ -23,30 +23,29 @@ package com.iluwatar.partialresponse; -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.Map; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; /** * tests {@link VideoResource}. */ -@RunWith(MockitoJUnitRunner.class) -public class VideoResourceTest { +@ExtendWith(MockitoExtension.class) +class VideoResourceTest { @Mock - private FieldJsonMapper fieldJsonMapper; + private static FieldJsonMapper fieldJsonMapper; - private VideoResource resource; + private static VideoResource resource; - @Before - public void setUp() { + @BeforeAll + static void setUp() { var videos = Map.of( 1, new Video(1, "Avatar", 178, "epic science fiction film", "James Cameron", "English"), @@ -58,23 +57,23 @@ public class VideoResourceTest { } @Test - public void shouldGiveVideoDetailsById() throws Exception { + void shouldGiveVideoDetailsById() throws Exception { var actualDetails = resource.getDetails(1); var expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178,\"description\": " + "\"epic science fiction film\",\"director\": \"James Cameron\",\"language\": \"English\",}"; - assertEquals(expectedDetails, actualDetails); + Assertions.assertEquals(expectedDetails, actualDetails); } @Test - public void shouldGiveSpecifiedFieldsInformationOfVideo() throws Exception { + void shouldGiveSpecifiedFieldsInformationOfVideo() throws Exception { var fields = new String[]{"id", "title", "length"}; var expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178}"; - when(fieldJsonMapper.toJson(any(Video.class), eq(fields))).thenReturn(expectedDetails); + Mockito.when(fieldJsonMapper.toJson(Matchers.any(Video.class), Matchers.eq(fields))).thenReturn(expectedDetails); var actualFieldsDetails = resource.getDetails(2, fields); - assertEquals(expectedDetails, actualFieldsDetails); + Assertions.assertEquals(expectedDetails, actualFieldsDetails); } } \ No newline at end of file diff --git a/pipeline/README.md b/pipeline/README.md index fd03cd7b9..d7f0f749b 100644 --- a/pipeline/README.md +++ b/pipeline/README.md @@ -9,19 +9,22 @@ tags: --- ## Intent -Allows processing of data in a series of stages by giving in an initial input and passing the processed output to be -used by the next stages. + +Allows processing of data in a series of stages by giving in an initial input and passing the +processed output to be used by the next stages. ## Explanation -The Pipeline pattern uses ordered stages to process a sequence of input values. Each implemented task is represented by -a stage of the pipeline. You can think of pipelines as similar to assembly lines in a factory, where each item in the -assembly line is constructed in stages. The partially assembled item is passed from one assembly stage to another. The -outputs of the assembly line occur in the same order as that of the inputs. +The Pipeline pattern uses ordered stages to process a sequence of input values. Each implemented +task is represented by a stage of the pipeline. You can think of pipelines as similar to assembly +lines in a factory, where each item in the assembly line is constructed in stages. The partially +assembled item is passed from one assembly stage to another. The outputs of the assembly line occur +in the same order as that of the inputs. Real world example -> Suppose we wanted to pass through a string to a series of filtering stages and convert it as a char array on the last stage. +> Suppose we wanted to pass through a string to a series of filtering stages and convert it as a +> char array on the last stage. In plain words @@ -29,7 +32,9 @@ In plain words Wikipedia says -> In software engineering, a pipeline consists of a chain of processing elements (processes, threads, coroutines, functions, etc.), arranged so that the output of each element is the input of the next; the name is by analogy to a physical pipeline. +> In software engineering, a pipeline consists of a chain of processing elements (processes, +> threads, coroutines, functions, etc.), arranged so that the output of each element is the input +> of the next; the name is by analogy to a physical pipeline. **Programmatic Example** @@ -88,14 +93,17 @@ And here's the `Pipeline` in action processing the string. ``` ## Class diagram + ![alt text](./etc/pipeline.urm.png "Pipeline pattern class diagram") ## Applicability + Use the Pipeline pattern when you want to -* Execute individual stages that yields a final value -* Add readability to complex sequence of operations by providing a fluent builder as an interface -* Improve testability of code since stages will most likely be doing a single thing, complying to the [Single Responsibility Principle (SRP)](https://java-design-patterns.com/principles/#single-responsibility-principle) +* Execute individual stages that yields a final value. +* Add readability to complex sequence of operations by providing a fluent builder as an interface. +* Improve testability of code since stages will most likely be doing a single thing, complying to +the [Single Responsibility Principle (SRP)](https://java-design-patterns.com/principles/#single-responsibility-principle) ## Known uses diff --git a/pipeline/pom.xml b/pipeline/pom.xml index 8c511cd8a..fca6180bc 100644 --- a/pipeline/pom.xml +++ b/pipeline/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT pipeline diff --git a/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java b/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java index 8ea6a4c60..1316491ad 100644 --- a/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java +++ b/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.pipeline; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application Test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/poison-pill/README.md b/poison-pill/README.md index 823bb7df8..05cd8c2a0 100644 --- a/poison-pill/README.md +++ b/poison-pill/README.md @@ -10,14 +10,17 @@ tags: --- ## Intent -Poison Pill is known predefined data item that allows to provide graceful shutdown for separate distributed consumption -process. + +Poison Pill is known predefined data item that allows to provide graceful shutdown for separate +distributed consumption process. ## Explanation Real world example -> Let's think about a message queue with one producer and one consumer. The producer keeps pushing new messages in the queue and the consumer keeps reading them. Finally when it's time to gracefully shut down the producer sends the poison pill message. +> Let's think about a message queue with one producer and one consumer. The producer keeps pushing +> new messages in the queue and the consumer keeps reading them. Finally when it's time to +> gracefully shut down the producer sends the poison pill message. In plain words @@ -25,43 +28,13 @@ In plain words **Programmatic Example** -Let's define the message structure first. +Let's define the message structure first. There's interface `Message` and implementation +`SimpleMessage`. ```java public interface Message { - Message POISON_PILL = new Message() { - - @Override - public void addHeader(Headers header, String value) { - throw poison(); - } - - @Override - public String getHeader(Headers header) { - throw poison(); - } - - @Override - public Map getHeaders() { - throw poison(); - } - - @Override - public void setBody(String body) { - throw poison(); - } - - @Override - public String getBody() { - throw poison(); - } - - private RuntimeException poison() { - return new UnsupportedOperationException("Poison"); - } - - }; + ... enum Headers { DATE, SENDER @@ -80,7 +53,7 @@ public interface Message { public class SimpleMessage implements Message { - private Map headers = new HashMap<>(); + private final Map headers = new HashMap<>(); private String body; @Override @@ -110,7 +83,9 @@ public class SimpleMessage implements Message { } ``` -Next we define the types related to the message queue. +To pass messages we are using message queues. Here we define the types related to the message queue: +`MqPublishPoint`, `MqSubscribePoint` and `MessageQueue`. `SimpleMessageQueue` implements all these +interfaces. ```java public interface MqPublishPoint { @@ -146,29 +121,15 @@ public class SimpleMessageQueue implements MessageQueue { } ``` -Now we need to create the message producer and consumer. +Next we need message `Producer` and `Consumer`. Internally they use the message queues from above. +It's important to notice that when `Producer` stops, it sends out the poison pill to inform +`Consumer` that the messaging has finished. ```java public class Producer { + + ... - private static final Logger LOGGER = LoggerFactory.getLogger(Producer.class); - - private final MqPublishPoint queue; - private final String name; - private boolean isStopped; - - /** - * 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( @@ -187,9 +148,6 @@ public class Producer { } } - /** - * Stop system by sending poison pill. - */ public void stop() { isStopped = true; try { @@ -203,19 +161,8 @@ public class Producer { public class Consumer { - private static final Logger LOGGER = LoggerFactory.getLogger(Consumer.class); + ... - private final MqSubscribePoint queue; - private final String name; - - public Consumer(String name, MqSubscribePoint queue) { - this.name = name; - this.queue = queue; - } - - /** - * Consume message. - */ public void consume() { while (true) { try { @@ -255,13 +202,24 @@ Finally we are ready to present the whole example in action. }).start(); ``` +Program output: + +``` +Message [hand shake] from [PRODUCER_1] received by [CONSUMER_1] +Message [some very important information] from [PRODUCER_1] received by [CONSUMER_1] +Message [bye!] from [PRODUCER_1] received by [CONSUMER_1] +Consumer CONSUMER_1 receive request to terminate. +``` + ## Class diagram + ![alt text](./etc/poison-pill.png "Poison Pill") ## Applicability -Use the Poison Pill idiom when -* Need to send signal from one thread/process to another to terminate +Use the Poison Pill idiom when: + +* There's a need to send signal from one thread/process to another to terminate. ## Real world examples diff --git a/poison-pill/pom.xml b/poison-pill/pom.xml index 4989581d7..c4780fe1b 100644 --- a/poison-pill/pom.xml +++ b/poison-pill/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT poison-pill diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java index 8a7af515f..70d116c9f 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java @@ -32,7 +32,7 @@ import java.util.Map; */ public class SimpleMessage implements Message { - private Map headers = new HashMap<>(); + private final Map headers = new HashMap<>(); private String body; @Override 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 c23fe5563..50d7a90c9 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 @@ -25,13 +25,15 @@ package com.iluwatar.poison.pill; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } 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 index 100565fbc..8365fca17 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java @@ -92,7 +92,7 @@ public class ConsumerTest { } private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); diff --git a/pom.xml b/pom.xml index f23369107..e7fd2e4b3 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT pom 2014-2019 @@ -39,7 +39,7 @@ 1.4 2.24.0 19.0 - 1.10.19 + 3.5.6 2.22 4.0 3.12.1 @@ -52,13 +52,14 @@ 2.3.1 2.3.2 1.3.2 - 1.19.0 - 1.4.8 + 1.1.0 + 2.0.0 + 3.5.0 https://sonarcloud.io iluwatar iluwatar_java-design-patterns - ${artifactId} + ${project.artifactId} Java Design Patterns @@ -88,6 +89,7 @@ state strategy template-method + version-number visitor double-checked-locking servant @@ -192,6 +194,10 @@ leader-followers strangler arrange-act-assert + transaction-script + filterer + factory + separated-interface @@ -203,6 +209,18 @@ + + net.bytebuddy + byte-buddy + 1.9.7 + test + + + net.bytebuddy + byte-buddy-agent + 1.9.7 + test + org.hibernate hibernate-core @@ -335,8 +353,8 @@ com.github.stefanbirkner - system-rules - ${system-rules.version} + system-lambda + ${system-lambda.version} test @@ -375,10 +393,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M3 - - -Xmx1024M ${argLine} - + 3.0.0-M5 org.springframework.boot @@ -474,7 +489,7 @@ true - ${projectRoot}${file.separator}license-plugin-header-style.xml + license-plugin-header-style.xml SLASHSTAR_CUSTOM_STYLE @@ -540,14 +555,4 @@ - - - - org.apache.maven.plugins - maven-jxr-plugin - 3.0.0 - - - - - \ No newline at end of file + diff --git a/priority-queue/README.md b/priority-queue/README.md index 924d7169f..8481915be 100644 --- a/priority-queue/README.md +++ b/priority-queue/README.md @@ -10,19 +10,203 @@ tags: --- ## Intent -Prioritize requests sent to services so that requests with a higher priority are received and processed more quickly than those of a lower priority. This pattern is useful in applications that offer different service level guarantees to individual clients. + +Prioritize requests sent to services so that requests with a higher priority are received and +processed more quickly than those of a lower priority. This pattern is useful in applications that +offer different service level guarantees to individual clients. ## Explanation -Applications may delegate specific tasks to other services; for example, to perform background processing or to integrate with other applications or services. In the cloud, a message queue is typically used to delegate tasks to background processing. In many cases the order in which requests are received by a service is not important. However, in some cases it may be necessary to prioritize specific requests. These requests should be processed earlier than others of a lower priority that may have been sent previously by the application. + +Applications may delegate specific tasks to other services; for example, to perform background +processing or to integrate with other applications or services. In the cloud, a message queue is +typically used to delegate tasks to background processing. In many cases the order in which requests +are received by a service is not important. However, in some cases it may be necessary to prioritize +specific requests. These requests should be processed earlier than others of a lower priority that +may have been sent previously by the application. + +Real world example + +> Imagine a video processing service with free and premium customers. The requests coming from the +> paying premium customers should be prioritized over the others. + +In plain words + +> Priority Queue enables processing of high priority messages first, regardless of queue size or +> message age. + +Wikipedia says + +> In computer science, a priority queue is an abstract data type similar to regular queue or stack +> data structure in which each element additionally has a "priority" associated with it. In a +> priority queue, an element with high priority is served before an element with low priority. + +**Programmatic Example** + +Looking at the video processing example from above, let's first see the `Message` structure. + +```java +public class Message implements Comparable { + + private final String message; + private final int priority; // define message priority in queue + + public Message(String message, int priority) { + this.message = message; + this.priority = priority; + } + + @Override + public int compareTo(Message o) { + return priority - o.priority; + } + ... +} +``` + +Here's `PriorityMessageQueue` that handles storing the messages and serving them in priority +order. + +```java +public class PriorityMessageQueue { + + ... + + public T remove() { + if (isEmpty()) { + return null; + } + + final var root = queue[0]; + queue[0] = queue[size - 1]; + size--; + maxHeapifyDown(); + return root; + } + + public void add(T t) { + ensureCapacity(); + queue[size] = t; + size++; + maxHeapifyUp(); + } + + ... +} +``` + +`QueueManager` has a `PriorityMessageQueue` and makes it easy to `publishMessage` and +`receiveMessage`. + +```java +public class QueueManager { + + private final PriorityMessageQueue messagePriorityMessageQueue; + + public QueueManager(int initialCapacity) { + messagePriorityMessageQueue = new PriorityMessageQueue<>(new Message[initialCapacity]); + } + + public void publishMessage(Message message) { + messagePriorityMessageQueue.add(message); + } + + public Message receiveMessage() { + if (messagePriorityMessageQueue.isEmpty()) { + return null; + } + return messagePriorityMessageQueue.remove(); + } +} +``` + +`Worker` constantly polls `QueueManager` for highest priority message and processes it. + +```java +public class Worker { + + private static final Logger LOGGER = LoggerFactory.getLogger(Worker.class); + + private final QueueManager queueManager; + + public Worker(QueueManager queueManager) { + this.queueManager = queueManager; + } + + public void run() throws Exception { + while (true) { + var message = queueManager.receiveMessage(); + if (message == null) { + LOGGER.info("No Message ... waiting"); + Thread.sleep(200); + } else { + processMessage(message); + } + } + } + + private void processMessage(Message message) { + LOGGER.info(message.toString()); + } +} +``` + +Here's the full example how we create an instance of `QueueManager` and process messages using +`Worker`. + +```java + var queueManager = new QueueManager(100); + + for (var i = 0; i < 100; i++) { + queueManager.publishMessage(new Message("Low Message Priority", 0)); + } + + for (var i = 0; i < 100; i++) { + queueManager.publishMessage(new Message("High Message Priority", 1)); + } + + var worker = new Worker(queueManager); + worker.run(); +``` + +Program output: + +``` +Message{message='High Message Priority', priority=1} +Message{message='High Message Priority', priority=1} +Message{message='High Message Priority', priority=1} +Message{message='High Message Priority', priority=1} +Message{message='High Message Priority', priority=1} +Message{message='High Message Priority', priority=1} +Message{message='High Message Priority', priority=1} +Message{message='High Message Priority', priority=1} +Message{message='High Message Priority', priority=1} +Message{message='High Message Priority', priority=1} +Message{message='Low Message Priority', priority=0} +Message{message='Low Message Priority', priority=0} +Message{message='Low Message Priority', priority=0} +Message{message='Low Message Priority', priority=0} +Message{message='Low Message Priority', priority=0} +Message{message='Low Message Priority', priority=0} +Message{message='Low Message Priority', priority=0} +Message{message='Low Message Priority', priority=0} +Message{message='Low Message Priority', priority=0} +Message{message='Low Message Priority', priority=0} +No Message ... waiting +No Message ... waiting +No Message ... waiting +``` + ## Class diagram + ![alt text](./etc/priority-queue.urm.png "Priority Queue pattern class diagram") ## Applicability -Use the Priority Queue pattern when + +Use the Priority Queue pattern when: * The system must handle multiple tasks that might have different priorities. -* Different users or tenants should be served with different priority.. +* Different users or tenants should be served with different priority. ## Credits diff --git a/priority-queue/pom.xml b/priority-queue/pom.xml index 7f435f489..ff04a5359 100644 --- a/priority-queue/pom.xml +++ b/priority-queue/pom.xml @@ -31,7 +31,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/Application.java b/priority-queue/src/main/java/com/iluwatar/priority/queue/Application.java index 433a72d74..cdef90ab8 100644 --- a/priority-queue/src/main/java/com/iluwatar/priority/queue/Application.java +++ b/priority-queue/src/main/java/com/iluwatar/priority/queue/Application.java @@ -37,20 +37,19 @@ public class Application { */ public static void main(String[] args) throws Exception { - var queueManager = new QueueManager(100); + var queueManager = new QueueManager(10); // push some message to queue // Low Priority message - for (var i = 0; i < 100; i++) { + for (var i = 0; i < 10; i++) { queueManager.publishMessage(new Message("Low Message Priority", 0)); } // High Priority message - for (var i = 0; i < 100; i++) { + for (var i = 0; i < 10; i++) { queueManager.publishMessage(new Message("High Message Priority", 1)); } - // run worker var worker = new Worker(queueManager); worker.run(); diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/QueueManager.java b/priority-queue/src/main/java/com/iluwatar/priority/queue/QueueManager.java index 0b4be910f..3a0761f4a 100644 --- a/priority-queue/src/main/java/com/iluwatar/priority/queue/QueueManager.java +++ b/priority-queue/src/main/java/com/iluwatar/priority/queue/QueueManager.java @@ -45,7 +45,7 @@ public class QueueManager { /** - * recive message from queue. + * Receive message from queue. */ public Message receiveMessage() { if (messagePriorityMessageQueue.isEmpty()) { diff --git a/private-class-data/README.md b/private-class-data/README.md index 20e343285..5d0e6eb51 100644 --- a/private-class-data/README.md +++ b/private-class-data/README.md @@ -9,14 +9,127 @@ tags: --- ## 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. + +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. + +## Explanation + +Real world example + +> Imagine you are cooking a stew for your family for dinner. You want to prevent your family members +> from consuming the stew by tasting it while you are cooking, otherwise there will be no more stew +> for dinner later. + +In plain words + +> Private class data pattern prevents manipulation of data that is meant to be immutable by +> separating the data from the methods that use it into a class that maintains the data state. + +Wikipedia says + +> Private class data is a design pattern in computer programming used to encapsulate class +> attributes and their manipulation. + +**Programmatic Example** + +Taking our stew example from above. First we have a `Stew` class where its data is not protected by +private class data, making the stew's ingredient mutable to class methods. + +```java +public class Stew { + private static final Logger LOGGER = LoggerFactory.getLogger(Stew.class); + private int numPotatoes; + private int numCarrots; + private int numMeat; + private int numPeppers; + public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { + this.numPotatoes = numPotatoes; + this.numCarrots = numCarrots; + this.numMeat = numMeat; + this.numPeppers = numPeppers; + } + public void mix() { + LOGGER.info("Mixing the stew we find: {} potatoes, {} carrots, {} meat and {} peppers", + numPotatoes, numCarrots, numMeat, numPeppers); + } + public void taste() { + LOGGER.info("Tasting the stew"); + if (numPotatoes > 0) { + numPotatoes--; + } + if (numCarrots > 0) { + numCarrots--; + } + if (numMeat > 0) { + numMeat--; + } + if (numPeppers > 0) { + numPeppers--; + } + } +} +``` + +Now, we have `ImmutableStew` class, where its data is protected by `StewData` class. Now, the +methods in are unable to manipulate the data of the `ImmutableStew` class. + +```java +public class StewData { + private final int numPotatoes; + private final int numCarrots; + private final int numMeat; + private final int numPeppers; + public StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { + this.numPotatoes = numPotatoes; + this.numCarrots = numCarrots; + this.numMeat = numMeat; + this.numPeppers = numPeppers; + } + public int getNumPotatoes() { + return numPotatoes; + } + public int getNumCarrots() { + return numCarrots; + } + public int getNumMeat() { + return numMeat; + } + public int getNumPeppers() { + return numPeppers; + } +} +public class ImmutableStew { + private static final Logger LOGGER = LoggerFactory.getLogger(ImmutableStew.class); + private final StewData data; + public ImmutableStew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { + data = new StewData(numPotatoes, numCarrots, numMeat, numPeppers); + } + public void mix() { + LOGGER + .info("Mixing the immutable stew we find: {} potatoes, {} carrots, {} meat and {} peppers", + data.getNumPotatoes(), data.getNumCarrots(), data.getNumMeat(), data.getNumPeppers()); + } +} +``` + +Let's try creating an instance of each class and call their methods: + +```java +var stew = new Stew(1, 2, 3, 4); +stew.mix(); // Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers +stew.taste(); // Tasting the stew +stew.mix(); // Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers +var immutableStew = new ImmutableStew(2, 4, 3, 6); +immutableStew.mix(); // Mixing the immutable stew we find: 2 potatoes, 4 carrots, 3 meat and 6 peppers +``` ## Class diagram + ![alt text](./etc/private-class-data.png "Private Class Data") ## Applicability + Use the Private Class Data pattern when -* You want to prevent write access to class data members +* 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 cb81ca3fc..01d0149ea 100644 --- a/private-class-data/pom.xml +++ b/private-class-data/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT private-class-data 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 695424695..d312cd34a 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 @@ -1,50 +1,50 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.privateclassdata; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Immutable stew class, protected with Private Class Data pattern. - */ -public class ImmutableStew { - - private static final Logger LOGGER = LoggerFactory.getLogger(ImmutableStew.class); - - private StewData data; - - public ImmutableStew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { - data = new StewData(numPotatoes, numCarrots, numMeat, numPeppers); - } - - /** - * Mix the stew. - */ - public void mix() { - LOGGER - .info("Mixing the immutable stew we find: {} potatoes, {} carrots, {} meat and {} peppers", - data.getNumPotatoes(), data.getNumCarrots(), data.getNumMeat(), data.getNumPeppers()); - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.privateclassdata; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Immutable stew class, protected with Private Class Data pattern. + */ +public class ImmutableStew { + + private static final Logger LOGGER = LoggerFactory.getLogger(ImmutableStew.class); + + private final StewData data; + + public ImmutableStew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { + data = new StewData(numPotatoes, numCarrots, numMeat, numPeppers); + } + + /** + * Mix the stew. + */ + public void mix() { + LOGGER + .info("Mixing the immutable stew we find: {} potatoes, {} carrots, {} meat and {} peppers", + data.getNumPotatoes(), data.getNumCarrots(), data.getNumMeat(), data.getNumPeppers()); + } +} 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 bcdaba3e9..1b0fd269b 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 @@ -1,61 +1,61 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.privateclassdata; - -/** - * Stew ingredients. - */ -public class StewData { - - private int numPotatoes; - private int numCarrots; - private int numMeat; - private int numPeppers; - - /** - * Constructor. - */ - public StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { - this.numPotatoes = numPotatoes; - this.numCarrots = numCarrots; - this.numMeat = numMeat; - this.numPeppers = numPeppers; - } - - public int getNumPotatoes() { - return numPotatoes; - } - - public int getNumCarrots() { - return numCarrots; - } - - public int getNumMeat() { - return numMeat; - } - - public int getNumPeppers() { - return numPeppers; - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.privateclassdata; + +/** + * Stew ingredients. + */ +public class StewData { + + private final int numPotatoes; + private final int numCarrots; + private final int numMeat; + private final int numPeppers; + + /** + * Constructor. + */ + public StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { + this.numPotatoes = numPotatoes; + this.numCarrots = numCarrots; + this.numMeat = numMeat; + this.numPeppers = numPeppers; + } + + public int getNumPotatoes() { + return numPotatoes; + } + + public int getNumCarrots() { + return numCarrots; + } + + public int getNumMeat() { + return numMeat; + } + + public int getNumPeppers() { + return numPeppers; + } +} 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 166ddbdee..d551614cf 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 @@ -25,13 +25,15 @@ package com.iluwatar.privateclassdata; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java index 6fbe638ae..bbcbc8021 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java @@ -34,7 +34,7 @@ import org.slf4j.LoggerFactory; * InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); diff --git a/producer-consumer/pom.xml b/producer-consumer/pom.xml index ab1872c51..4884ef728 100644 --- a/producer-consumer/pom.xml +++ b/producer-consumer/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT producer-consumer diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java index 6991ec4d1..89f692282 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java @@ -28,9 +28,9 @@ package com.iluwatar.producer.consumer; */ public class Item { - private String producer; + private final String producer; - private int id; + private final int id; public Item(String producer, int id) { this.id = id; diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java index 674fb069a..118e3265d 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java @@ -31,7 +31,7 @@ import java.util.concurrent.LinkedBlockingQueue; */ public class ItemQueue { - private BlockingQueue queue; + private final BlockingQueue queue; public ItemQueue() { 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 bf4b6cc66..0a436bd02 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 @@ -25,14 +25,16 @@ package com.iluwatar.producer.consumer; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/promise/README.md b/promise/README.md index 993d29a9f..73dce13ec 100644 --- a/promise/README.md +++ b/promise/README.md @@ -14,23 +14,25 @@ CompletableFuture ## Intent -A Promise represents a proxy for a value not necessarily known when the promise is created. It allows you to associate -dependent promises to an asynchronous action's eventual success value or failure reason. Promises are a way to write -async code that still appears as though it is executing in a synchronous way. +A Promise represents a proxy for a value not necessarily known when the promise is created. It +allows you to associate dependent promises to an asynchronous action's eventual success value or +failure reason. Promises are a way to write async code that still appears as though it is executing +in a synchronous way. ## Explanation -The Promise object is used for asynchronous computations. A Promise represents an operation that hasn't completed yet, -but is expected in the future. +The Promise object is used for asynchronous computations. A Promise represents an operation that +hasn't completed yet, but is expected in the future. Promises provide a few advantages over callback objects: - * Functional composition and error handling - * Prevents callback hell and provides callback aggregation + * Functional composition and error handling. + * Prevents callback hell and provides callback aggregation. Real world example -> We are developing a software solution that downloads files and calculates the number of lines and character -frequencies in those files. Promise is an ideal solution to make the code concise and easy to understand. +> We are developing a software solution that downloads files and calculates the number of lines and +> character frequencies in those files. Promise is an ideal solution to make the code concise and +> easy to understand. In plain words @@ -38,14 +40,15 @@ In plain words Wikipedia says -> In computer science, future, promise, delay, and deferred refer to constructs used for synchronizing program -execution in some concurrent programming languages. They describe an object that acts as a proxy for a result that is -initially unknown, usually because the computation of its value is not yet complete. +> In computer science, future, promise, delay, and deferred refer to constructs used for +> synchronizing program execution in some concurrent programming languages. They describe an object +> that acts as a proxy for a result that is initially unknown, usually because the computation of +> its value is not yet complete. **Programmatic Example** -In the example a file is downloaded and its line count is calculated. The calculated line count is then consumed and -printed on console. +In the example a file is downloaded and its line count is calculated. The calculated line count is +then consumed and printed on console. Let's first introduce a support class we need for implementation. Here's `PromiseSupport`. @@ -195,7 +198,7 @@ public class Promise extends PromiseSupport { public Promise thenApply(Function func) { Promise dest = new Promise<>(); - fulfillmentAction = new TransformAction(this, dest, func); + fulfillmentAction = new TransformAction<>(this, dest, func); return dest; } @@ -246,8 +249,8 @@ public class Promise extends PromiseSupport { } ``` -Now we can show the full example in action. Here's how to download and count the number of lines in a file using -`Promise`. +Now we can show the full example in action. Here's how to download and count the number of lines in +a file using `Promise`. ```java countLines().thenAccept( @@ -280,8 +283,8 @@ Now we can show the full example in action. Here's how to download and count the ## Applicability -Promise pattern is applicable in concurrent programming when some work needs to be done asynchronously -and: +Promise pattern is applicable in concurrent programming when some work needs to be done +asynchronously and: * Code maintainability and readability suffers due to callback hell. * You need to compose promises and need better error handling for asynchronous tasks. diff --git a/promise/pom.xml b/promise/pom.xml index 4a9d76df1..6ef67edec 100644 --- a/promise/pom.xml +++ b/promise/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT promise diff --git a/promise/src/main/java/com/iluwatar/promise/App.java b/promise/src/main/java/com/iluwatar/promise/App.java index fb2f14bd5..6c0921936 100644 --- a/promise/src/main/java/com/iluwatar/promise/App.java +++ b/promise/src/main/java/com/iluwatar/promise/App.java @@ -81,7 +81,7 @@ public class App { * @throws InterruptedException if main thread is interrupted. * @throws ExecutionException if an execution error occurs. */ - public static void main(String[] args) throws InterruptedException, ExecutionException { + public static void main(String[] args) throws InterruptedException { var app = new App(); try { app.promiseUsage(); diff --git a/promise/src/main/java/com/iluwatar/promise/Promise.java b/promise/src/main/java/com/iluwatar/promise/Promise.java index 9eecf00eb..5b79ebcd6 100644 --- a/promise/src/main/java/com/iluwatar/promise/Promise.java +++ b/promise/src/main/java/com/iluwatar/promise/Promise.java @@ -47,6 +47,7 @@ public class Promise extends PromiseSupport { * Creates a promise that will be fulfilled in future. */ public Promise() { + // Empty constructor } /** @@ -140,7 +141,7 @@ public class Promise extends PromiseSupport { */ public Promise thenApply(Function func) { Promise dest = new Promise<>(); - fulfillmentAction = new TransformAction(this, dest, func); + fulfillmentAction = new TransformAction<>(this, dest, func); return dest; } diff --git a/promise/src/main/java/com/iluwatar/promise/PromiseSupport.java b/promise/src/main/java/com/iluwatar/promise/PromiseSupport.java index 7a12f0a0f..01e9926a0 100644 --- a/promise/src/main/java/com/iluwatar/promise/PromiseSupport.java +++ b/promise/src/main/java/com/iluwatar/promise/PromiseSupport.java @@ -26,7 +26,6 @@ package com.iluwatar.promise; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,4 +113,4 @@ class PromiseSupport implements Future { } throw new ExecutionException(exception); } -} \ No newline at end of file +} diff --git a/promise/src/test/java/com/iluwatar/promise/AppTest.java b/promise/src/test/java/com/iluwatar/promise/AppTest.java index 63ac06aec..bf2f264c0 100644 --- a/promise/src/test/java/com/iluwatar/promise/AppTest.java +++ b/promise/src/test/java/com/iluwatar/promise/AppTest.java @@ -26,13 +26,15 @@ package com.iluwatar.promise; import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test. */ -public class AppTest { +class AppTest { @Test - public void testApp() throws InterruptedException, ExecutionException { - App.main(null); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(null)); } } diff --git a/promise/src/test/java/com/iluwatar/promise/PromiseTest.java b/promise/src/test/java/com/iluwatar/promise/PromiseTest.java index a72faf01e..3b6d43be2 100644 --- a/promise/src/test/java/com/iluwatar/promise/PromiseTest.java +++ b/promise/src/test/java/com/iluwatar/promise/PromiseTest.java @@ -67,7 +67,7 @@ public class PromiseTest { @Test public void promiseIsFulfilledWithAnExceptionIfTaskThrowsAnException() - throws InterruptedException, TimeoutException { + throws InterruptedException { testWaitingForeverForPromiseToBeFulfilled(); testWaitingSomeTimeForPromiseToBeFulfilled(); } diff --git a/property/pom.xml b/property/pom.xml index d271af036..8746a60ac 100644 --- a/property/pom.xml +++ b/property/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.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 afe9fe16c..3d3813e5f 100644 --- a/property/src/main/java/com/iluwatar/property/Character.java +++ b/property/src/main/java/com/iluwatar/property/Character.java @@ -61,10 +61,12 @@ public class Character implements Prototype { @Override public void set(Stats stat, Integer val) { + // Does Nothing } @Override public void remove(Stats stat) { + // Does Nothing. } }; } diff --git a/property/src/test/java/com/iluwatar/property/AppTest.java b/property/src/test/java/com/iluwatar/property/AppTest.java index 89c0dcbf6..38c5cb4d5 100644 --- a/property/src/test/java/com/iluwatar/property/AppTest.java +++ b/property/src/test/java/com/iluwatar/property/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.property; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/prototype/README.md b/prototype/README.md index 472e8330c..9128ccb2c 100644 --- a/prototype/README.md +++ b/prototype/README.md @@ -10,16 +10,19 @@ tags: --- ## Intent -Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype. + +Specify the kinds of objects to create using a prototypical instance, and create new objects by +copying this prototype. ## Explanation -First it should be noted that Prototype pattern is not used to gain performance benefits. It's only used for creating -new objects from prototype instance. +First it should be noted that Prototype pattern is not used to gain performance benefits. It's only +used for creating new objects from prototype instance. Real world example -> Remember Dolly? The sheep that was cloned! Lets not get into the details but the key point here is that it is all about cloning. +> Remember Dolly? The sheep that was cloned! Lets not get into the details but the key point here is +> that it is all about cloning. In plain words @@ -27,9 +30,12 @@ In plain words Wikipedia says -> The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects. +> The prototype pattern is a creational design pattern in software development. It is used when the +> type of objects to create is determined by a prototypical instance, which is cloned to produce new +> objects. -In short, it allows you to create a copy of an existing object and modify it to your needs, instead of going through the trouble of creating an object from scratch and setting it up. +In short, it allows you to create a copy of an existing object and modify it to your needs, instead +of going through the trouble of creating an object from scratch and setting it up. **Programmatic Example** @@ -52,7 +58,7 @@ class Sheep implements Cloneable { } ``` -Then it can be cloned like below +Then it can be cloned like below: ```java var original = new Sheep("Jolly"); @@ -65,15 +71,20 @@ System.out.println(cloned.getName()); // Dolly ``` ## Class diagram + ![alt text](./etc/prototype.urm.png "Prototype pattern class diagram") ## 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 -* To avoid building a class hierarchy of factories that parallels the class hierarchy of products -* 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 -* When object creation is expensive compared to cloning +Use the Prototype pattern when a system should be independent of how its products are created, +composed, represented and + +* When the classes to instantiate are specified at run-time, for example, by dynamic loading. +* To avoid building a class hierarchy of factories that parallels the class hierarchy of products. +* 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. +* When object creation is expensive compared to cloning. ## Real world examples diff --git a/prototype/pom.xml b/prototype/pom.xml index e68b11892..220ed4521 100644 --- a/prototype/pom.xml +++ b/prototype/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT prototype diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java b/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java index 1401460d6..8e2ed9474 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java @@ -28,7 +28,7 @@ package com.iluwatar.prototype; */ public class ElfBeast extends Beast { - private String helpType; + private final String helpType; public ElfBeast(String helpType) { this.helpType = helpType; diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java b/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java index 4a7eea98f..42a54ca97 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java @@ -28,7 +28,7 @@ package com.iluwatar.prototype; */ public class ElfMage extends Mage { - private String helpType; + private final String helpType; public ElfMage(String helpType) { this.helpType = helpType; diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java b/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java index 101cd5942..fb426a444 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java @@ -28,7 +28,7 @@ package com.iluwatar.prototype; */ public class ElfWarlord extends Warlord { - private String helpType; + private final String helpType; public ElfWarlord(String helpType) { this.helpType = helpType; diff --git a/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java b/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java index eb84b2982..14516f3b4 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java +++ b/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java @@ -1,65 +1,65 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.prototype; - -/** - * Concrete factory class. - */ -public class HeroFactoryImpl implements HeroFactory { - - private Mage mage; - 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() { - return mage.copy(); - } - - /** - * Create warlord. - */ - public Warlord createWarlord() { - return warlord.copy(); - } - - /** - * Create beast. - */ - public Beast createBeast() { - return beast.copy(); - } - -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.prototype; + +/** + * Concrete factory class. + */ +public class HeroFactoryImpl implements HeroFactory { + + private final Mage mage; + private final Warlord warlord; + private final 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() { + return mage.copy(); + } + + /** + * Create warlord. + */ + public Warlord createWarlord() { + return warlord.copy(); + } + + /** + * Create beast. + */ + public Beast createBeast() { + return beast.copy(); + } + +} diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java b/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java index cf3dc18d8..91339887c 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java @@ -28,7 +28,7 @@ package com.iluwatar.prototype; */ public class OrcBeast extends Beast { - private String weapon; + private final String weapon; public OrcBeast(String weapon) { this.weapon = weapon; diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java b/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java index cb8239c3f..439e7f368 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java @@ -28,7 +28,7 @@ package com.iluwatar.prototype; */ public class OrcMage extends Mage { - private String weapon; + private final String weapon; public OrcMage(String weapon) { this.weapon = weapon; diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java b/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java index 39facc41e..a2ae31b4d 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java @@ -28,7 +28,7 @@ package com.iluwatar.prototype; */ public class OrcWarlord extends Warlord { - private String weapon; + private final String weapon; public OrcWarlord(String weapon) { this.weapon = weapon; diff --git a/prototype/src/test/java/com/iluwatar/prototype/AppTest.java b/prototype/src/test/java/com/iluwatar/prototype/AppTest.java index 489b6365c..d82568ddf 100644 --- a/prototype/src/test/java/com/iluwatar/prototype/AppTest.java +++ b/prototype/src/test/java/com/iluwatar/prototype/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.prototype; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/proxy/README.md b/proxy/README.md index b89d2a624..edf223b23 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -10,16 +10,20 @@ tags: --- ## Also known as + Surrogate ## Intent -Provide a surrogate or placeholder for another object to control -access to it. + +Provide a surrogate or placeholder for another object to control access to it. ## Explanation + Real world example -> Imagine a tower where the local wizards go to study their spells. The ivory tower can only be accessed through a proxy which ensures that only the first three wizards can enter. Here the proxy represents the functionality of the tower and adds access control to it. +> Imagine a tower where the local wizards go to study their spells. The ivory tower can only be +> accessed through a proxy which ensures that only the first three wizards can enter. Here the proxy +> represents the functionality of the tower and adds access control to it. In plain words @@ -27,11 +31,17 @@ In plain words Wikipedia says -> A proxy, in its most general form, is a class functioning as an interface to something else. A proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. In the proxy extra functionality can be provided, for example caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked. +> A proxy, in its most general form, is a class functioning as an interface to something else. +> A proxy is a wrapper or agent object that is being called by the client to access the real serving +> object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can +> provide additional logic. In the proxy extra functionality can be provided, for example caching +> when operations on the real object are resource intensive, or checking preconditions before +> operations on the real object are invoked. **Programmatic Example** -Taking our wizard tower example from above. Firstly we have the wizard tower interface and the ivory tower class +Taking our wizard tower example from above. Firstly we have the `WizardTower` interface and the +`IvoryTower` class. ```java public interface WizardTower { @@ -50,7 +60,7 @@ public class IvoryTower implements WizardTower { } ``` -Then a simple wizard class +Then a simple `Wizard` class. ```java public class Wizard { @@ -68,7 +78,7 @@ public class Wizard { } ``` -Then we have the proxy to add access control to wizard tower +Then we have the `WizardTowerProxy` to add access control to `WizardTower`. ```java public class WizardTowerProxy implements WizardTower { @@ -97,28 +107,41 @@ public class WizardTowerProxy implements WizardTower { } ``` -And here is tower entering scenario +And here is the tower entering scenario. ```java var proxy = new WizardTowerProxy(new IvoryTower()); -proxy.enter(new Wizard("Red wizard")); // Red wizard enters the tower. -proxy.enter(new Wizard("White wizard")); // White wizard enters the tower. -proxy.enter(new Wizard("Black wizard")); // Black wizard enters the tower. -proxy.enter(new Wizard("Green wizard")); // Green wizard is not allowed to enter! -proxy.enter(new Wizard("Brown wizard")); // Brown wizard is not allowed to enter! +proxy.enter(new Wizard("Red wizard")); +proxy.enter(new Wizard("White wizard")); +proxy.enter(new Wizard("Black wizard")); +proxy.enter(new Wizard("Green wizard")); +proxy.enter(new Wizard("Brown wizard")); +``` + +Program output: + +``` +Red wizard enters the tower. +White wizard enters the tower. +Black wizard enters the tower. +Green wizard is not allowed to enter! +Brown wizard is not allowed to enter! ``` ## Class diagram + ![alt text](./etc/proxy.urm.png "Proxy pattern class diagram") ## 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 + +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. * Remote proxy provides a local representative for an object in a different address space. * Virtual proxy creates expensive objects on demand. -* Protection proxy controls access to the original object. Protection proxies are useful when objects should have different access rights. +* Protection proxy controls access to the original object. Protection proxies are useful when +objects should have different access rights. ## Typical Use Case @@ -132,11 +155,16 @@ are several common situations in which the Proxy pattern is applicable * [Controlling Access With Proxy Pattern](http://java-design-patterns.com/blog/controlling-access-with-proxy-pattern/) -## Real world examples +## Known uses * [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/) -* Mocking frameworks Mockito, Powermock, EasyMock +* Mocking frameworks [Mockito](https://site.mockito.org/), +[Powermock](https://powermock.github.io/), [EasyMock](https://easymock.org/) + +## Related patterns + +* [Ambassador](https://java-design-patterns.com/patterns/ambassador/) ## Credits diff --git a/proxy/pom.xml b/proxy/pom.xml index f54c77dcf..0e7707bf8 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT proxy diff --git a/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java b/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java index 658b19949..2523af8b8 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java +++ b/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java @@ -1,40 +1,40 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.proxy; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The object to be proxyed. - */ -public class IvoryTower implements WizardTower { - - private static final Logger LOGGER = LoggerFactory.getLogger(IvoryTower.class); - - public void enter(Wizard wizard) { - LOGGER.info("{} enters the tower.", wizard); - } - -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.proxy; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The object to be proxied. + */ +public class IvoryTower implements WizardTower { + + private static final Logger LOGGER = LoggerFactory.getLogger(IvoryTower.class); + + public void enter(Wizard wizard) { + LOGGER.info("{} enters the tower.", wizard); + } + +} diff --git a/proxy/src/test/java/com/iluwatar/proxy/AppTest.java b/proxy/src/test/java/com/iluwatar/proxy/AppTest.java index 2fdd5253e..dde0bbe57 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/AppTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.proxy; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java b/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java index 2187c3300..173825288 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java +++ b/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java @@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory; * InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); diff --git a/queue-load-leveling/pom.xml b/queue-load-leveling/pom.xml index ee6e6c623..817e6e3cc 100644 --- a/queue-load-leveling/pom.xml +++ b/queue-load-leveling/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT queue-load-leveling diff --git a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java index 55c9528e6..2adc15011 100644 --- a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java +++ b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java @@ -25,12 +25,15 @@ package com.iluwatar.queue.load.leveling; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application Test */ -public class AppTest { +class AppTest { + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } \ No newline at end of file diff --git a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java index 1e81c5d11..3d12e6abf 100644 --- a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java +++ b/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java @@ -25,14 +25,17 @@ package com.iluwatar.queue.load.leveling; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + /** * Test case for submitting Message to Blocking Queue by TaskGenerator and retrieve the message by * ServiceExecutor. */ -public class TaskGenSrvExeTest { +class TaskGenSrvExeTest { @Test - public void taskGeneratorTest() { + void taskGeneratorTest() { var msgQueue = new MessageQueue(); // Create a task generator thread with 1 job to submit. @@ -40,10 +43,14 @@ public class TaskGenSrvExeTest { var taskGenThr = new Thread(taskRunnable); taskGenThr.start(); + assertNotNull(taskGenThr); + // Create a service executor thread. var srvRunnable = new ServiceExecutor(msgQueue); var srvExeThr = new Thread(srvRunnable); srvExeThr.start(); + + assertNotNull(srvExeThr); } } diff --git a/reactor/pom.xml b/reactor/pom.xml index b95b0b6e2..c25ed45ae 100644 --- a/reactor/pom.xml +++ b/reactor/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.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 3bd8176a6..f656eacf6 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/App.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/App.java @@ -89,8 +89,8 @@ import java.util.List; public class App { private NioReactor reactor; - private List channels = new ArrayList<>(); - private Dispatcher dispatcher; + private final List channels = new ArrayList<>(); + private final Dispatcher dispatcher; /** * Creates an instance of App which will use provided dispatcher for dispatching events on diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java b/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java index 13657cdb2..aba99d65c 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java @@ -134,7 +134,7 @@ public class NioDatagramChannel extends AbstractNioChannel { */ public static class DatagramPacket { private SocketAddress sender; - private ByteBuffer data; + private final ByteBuffer data; private SocketAddress receiver; /** 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 1a0b17386..77e39a88d 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java @@ -228,8 +228,8 @@ public class NioReactor { * A command that changes the interested operations of the key provided. */ class ChangeKeyOpsCommand implements Runnable { - private SelectionKey key; - private int interestedOps; + private final SelectionKey key; + private final int interestedOps; public ChangeKeyOpsCommand(SelectionKey key, int interestedOps) { this.key = key; diff --git a/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java b/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java index 76ddfbdba..1d7faab7e 100644 --- a/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java +++ b/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java @@ -30,6 +30,9 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + /** * This class tests the Distributed Logging service by starting a Reactor and then sending it * concurrent logging requests using multiple clients. @@ -50,9 +53,13 @@ public class ReactorTest { var app = new App(new ThreadPoolDispatcher(2)); app.start(); + assertNotNull(app); + var client = new AppClient(); client.start(); + assertNotNull(client); + // allow clients to send requests. Artificial delay. try { Thread.sleep(2000); @@ -78,9 +85,13 @@ public class ReactorTest { var app = new App(new SameThreadDispatcher()); app.start(); + assertNotNull(app); + var client = new AppClient(); client.start(); + assertNotNull(client); + // allow clients to send requests. Artificial delay. try { Thread.sleep(2000); diff --git a/reader-writer-lock/pom.xml b/reader-writer-lock/pom.xml index 92f53df66..37f70b435 100644 --- a/reader-writer-lock/pom.xml +++ b/reader-writer-lock/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT reader-writer-lock 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 index 6d705de2f..c54e62e58 100644 --- 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 @@ -34,11 +34,11 @@ public class Reader implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(Reader.class); - private Lock readLock; + private final Lock readLock; - private String name; + private final String name; - private long readingTime; + private final long readingTime; /** * Create new Reader. 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 index 99c9b056b..932428b4f 100644 --- 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 @@ -59,8 +59,8 @@ public class ReaderWriterLock implements ReadWriteLock { */ private final Set globalMutex = new HashSet<>(); - private ReadLock readerLock = new ReadLock(); - private WriteLock writerLock = new WriteLock(); + private final ReadLock readerLock = new ReadLock(); + private final WriteLock writerLock = new WriteLock(); @Override public Lock readLock() { 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 index 7a971b28b..fbc8321f2 100644 --- 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 @@ -34,11 +34,11 @@ public class Writer implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(Writer.class); - private Lock writeLock; + private final Lock writeLock; - private String name; + private final String name; - private long writingTime; + private final long writingTime; /** * Create new Writer who writes for 250ms. 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 index a0175e259..63f4c764d 100644 --- 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 @@ -25,14 +25,16 @@ package com.iluwatar.reader.writer.lock; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java index c7e8bc02a..01a63d6c8 100644 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java @@ -34,7 +34,7 @@ import org.slf4j.LoggerFactory; * InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); diff --git a/repository/README.md b/repository/README.md index 09a9a2bba..ab13934bd 100644 --- a/repository/README.md +++ b/repository/README.md @@ -9,26 +9,33 @@ tags: --- ## 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 querying is utilized. + +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 +querying is utilized. ## Explanation + Real world example -> Let's say we need a persistent data store for persons. Adding new persons and searching for them according to different criteria must be easy. +> Let's say we need a persistent data store for persons. Adding new persons and searching for them +> according to different criteria must be easy. In plain words -> Repository architectural pattern creates a uniform layer of data repositories that can be used for CRUD operations. +> Repository architectural pattern creates a uniform layer of data repositories that can be used for +> CRUD operations. [Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design) says -> Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer. +> Repositories are classes or components that encapsulate the logic required to access data sources. +> They centralize common data access functionality, providing better maintainability and decoupling +> the infrastructure or technology used to access databases from the domain model layer. **Programmatic Example** -Let's first look at the person class that we need to persist. +Let's first look at the person entity that we need to persist. ```java @Entity @@ -39,107 +46,23 @@ public class Person { private Long id; private String name; private String surname; - private int age; public Person() { } - /** - * Constructor. - */ public Person(String name, String surname, int age) { this.name = name; this.surname = surname; this.age = age; } - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getSurname() { - return surname; - } - - public void setSurname(String surname) { - this.surname = surname; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - @Override - public String toString() { - return "Person [id=" + id + ", name=" + name + ", surname=" + surname + ", age=" + age + "]"; - } - - @Override - public int hashCode() { - final var prime = 31; - var result = 1; - result = prime * result + age; - result = prime * result + (id == null ? 0 : id.hashCode()); - result = prime * result + (name == null ? 0 : name.hashCode()); - result = prime * result + (surname == null ? 0 : surname.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - var other = (Person) obj; - if (age != other.age) { - return false; - } - if (id == null) { - if (other.id != null) { - return false; - } - } else if (!id.equals(other.id)) { - return false; - } - if (name == null) { - if (other.name != null) { - return false; - } - } else if (!name.equals(other.name)) { - return false; - } - if (surname == null) { - return other.surname == null; - } - return surname.equals(other.surname); - } + // getters and setters -> + ... } ``` -We are using Spring Data to create the repository so it becomes really simple. +We are using Spring Data to create the `PersonRepository` so it becomes really simple. ```java @Repository @@ -150,16 +73,16 @@ public interface PersonRepository } ``` -Additionally we define a helper class for specification queries. +Additionally we define a helper class `PersonSpecifications` for specification queries. ```java public class PersonSpecifications { public static class AgeBetweenSpec implements Specification { - private int from; + private final int from; - private int to; + private final int to; public AgeBetweenSpec(int from, int to) { this.from = from; @@ -189,7 +112,7 @@ public class PersonSpecifications { } ``` -And here's the repository in action. +And here's the repository example in action. ```java var peter = new Person("Peter", "Sagan", 17); @@ -226,30 +149,36 @@ And here's the repository in action. persons.stream().map(Person::toString).forEach(LOGGER::info); repository.deleteAll(); - - // Count Person records: 4 - // Person [id=1, name=Peter, surname=Sagan, age=17] - // Person [id=2, name=Nasta, surname=Kuzminova, age=25] - // Person [id=3, name=John, surname=lawrence, age=35] - // Person [id=4, name=Terry, surname=Law, age=36] - // Find by id 2: Person [id=2, name=Barbora, surname=Spotakova, age=25] - // Count Person records: 3 - // Find by John is Person [id=3, name=John, surname=lawrence, age=35] - // Find Person with age between 20,40: - // Person [id=3, name=John, surname=lawrence, age=35] - // Person [id=4, name=Terry, surname=Law, age=36] +``` + +Program output: + +``` +Count Person records: 4 +Person [id=1, name=Peter, surname=Sagan, age=17] +Person [id=2, name=Nasta, surname=Kuzminova, age=25] +Person [id=3, name=John, surname=lawrence, age=35] +Person [id=4, name=Terry, surname=Law, age=36] +Find by id 2: Person [id=2, name=Barbora, surname=Spotakova, age=25] +Count Person records: 3 +Find by John is Person [id=3, name=John, surname=lawrence, age=35] +Find Person with age between 20,40: +Person [id=3, name=John, surname=lawrence, age=35] +Person [id=4, name=Terry, surname=Law, age=36] ``` ## Class diagram + ![alt text](./etc/repository.png "Repository") ## 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 +* 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 diff --git a/repository/pom.xml b/repository/pom.xml index 0b98cdb41..694629efa 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT repository diff --git a/repository/src/main/java/com/iluwatar/repository/AppConfig.java b/repository/src/main/java/com/iluwatar/repository/AppConfig.java index 50a23026f..81d158d23 100644 --- a/repository/src/main/java/com/iluwatar/repository/AppConfig.java +++ b/repository/src/main/java/com/iluwatar/repository/AppConfig.java @@ -23,7 +23,6 @@ package com.iluwatar.repository; -import java.sql.SQLException; import java.util.List; import java.util.Properties; import javax.sql.DataSource; @@ -89,7 +88,7 @@ public class AppConfig { * Get transaction manager. */ @Bean - public JpaTransactionManager transactionManager() throws SQLException { + public JpaTransactionManager transactionManager() { var transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); return transactionManager; diff --git a/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java index f91c0a6e1..823fa2d3d 100644 --- a/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java +++ b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java @@ -39,9 +39,9 @@ public class PersonSpecifications { */ public static class AgeBetweenSpec implements Specification { - private int from; + private final int from; - private int to; + private final int to; public AgeBetweenSpec(int from, int to) { this.from = from; @@ -60,7 +60,7 @@ public class PersonSpecifications { */ public static class NameEqualSpec implements Specification { - public String name; + public final String name; public NameEqualSpec(String name) { this.name = name; diff --git a/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java index 6b47cbe9a..9e2e1f4e1 100644 --- a/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java @@ -48,12 +48,12 @@ public class AnnotationBasedRepositoryTest { @Resource private PersonRepository repository; - private Person peter = new Person("Peter", "Sagan", 17); - private Person nasta = new Person("Nasta", "Kuzminova", 25); - private Person john = new Person("John", "lawrence", 35); - private Person terry = new Person("Terry", "Law", 36); + private final Person peter = new Person("Peter", "Sagan", 17); + private final Person nasta = new Person("Nasta", "Kuzminova", 25); + private final Person john = new Person("John", "lawrence", 35); + private final Person terry = new Person("Terry", "Law", 36); - private List persons = List.of(peter, nasta, john, terry); + private final List persons = List.of(peter, nasta, john, terry); /** * Prepare data for test diff --git a/repository/src/test/java/com/iluwatar/repository/AppTest.java b/repository/src/test/java/com/iluwatar/repository/AppTest.java index b12f03d8c..d44af23db 100644 --- a/repository/src/test/java/com/iluwatar/repository/AppTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AppTest.java @@ -25,12 +25,15 @@ package com.iluwatar.repository; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that Repository example runs without errors. */ -public class AppTest { +class AppTest { + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java index ad9587aca..77e2b3e35 100644 --- a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java +++ b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java @@ -48,12 +48,12 @@ public class RepositoryTest { @Resource private PersonRepository repository; - private Person peter = new Person("Peter", "Sagan", 17); - private Person nasta = new Person("Nasta", "Kuzminova", 25); - private Person john = new Person("John", "lawrence", 35); - private Person terry = new Person("Terry", "Law", 36); + private final Person peter = new Person("Peter", "Sagan", 17); + private final Person nasta = new Person("Nasta", "Kuzminova", 25); + private final Person john = new Person("John", "lawrence", 35); + private final Person terry = new Person("Terry", "Law", 36); - private List persons = List.of(peter, nasta, john, terry); + private final List persons = List.of(peter, nasta, john, terry); /** * Prepare data for test diff --git a/resource-acquisition-is-initialization/pom.xml b/resource-acquisition-is-initialization/pom.xml index ef8e19f48..34428033b 100644 --- a/resource-acquisition-is-initialization/pom.xml +++ b/resource-acquisition-is-initialization/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT resource-acquisition-is-initialization 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 dedeee7e0..057a85500 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 @@ -25,13 +25,15 @@ package com.iluwatar.resource.acquisition.is.initialization; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() throws Exception { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } 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 index 7bba17553..53caabea7 100644 --- 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 @@ -68,7 +68,7 @@ public class ClosableTest { * Logging Appender Implementation */ public class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); diff --git a/retry/README.md b/retry/README.md index 056674a18..8cf6bb94f 100644 --- a/retry/README.md +++ b/retry/README.md @@ -10,39 +10,40 @@ tags: --- ## Intent -Transparently retry certain operations that involve communication with external resources, particularly over the -network, isolating calling code from the retry implementation details. + +Transparently retry certain operations that involve communication with external resources, +particularly over the network, isolating calling code from the retry implementation details. ## Explanation -Retry pattern consists retrying operations on remote resources over the -network a set number of times. It closely depends on both business and technical -requirements: how much time will the business allow the end user to wait while -the operation finishes? What are the performance characteristics of the -remote resource during peak loads as well as our application as more threads -are waiting for the remote resource's availability? Among the errors returned -by the remote service, which can be safely ignored in order to retry? Is the -operation [idempotent](https://en.wikipedia.org/wiki/Idempotence)? -Another concern is the impact on the calling code by implementing the retry -mechanism. The retry mechanics should ideally be completely transparent to the -calling code (service interface remains unaltered). There are two general -approaches to this problem: from an enterprise architecture standpoint -(strategic), and a shared library standpoint (tactical). +Retry pattern consists retrying operations on remote resources over the network a set number of +times. It closely depends on both business and technical requirements: How much time will the +business allow the end user to wait while the operation finishes? What are the performance +characteristics of the remote resource during peak loads as well as our application as more threads +are waiting for the remote resource's availability? Among the errors returned by the remote service, +which can be safely ignored in order to retry? Is the operation +[idempotent](https://en.wikipedia.org/wiki/Idempotence)? -From a strategic point of view, this would be solved by having requests -be redirected to a separate intermediary system, traditionally an -[ESB](https://en.wikipedia.org/wiki/Enterprise_service_bus), but more recently -a [Service Mesh](https://medium.com/microservices-in-practice/service-mesh-for-microservices-2953109a3c9a). +Another concern is the impact on the calling code by implementing the retry mechanism. The retry +mechanics should ideally be completely transparent to the calling code (service interface remains +unaltered). There are two general approaches to this problem: From an enterprise architecture +standpoint (strategic), and a shared library standpoint (tactical). -From a tactical point of view, this would be solved by reusing shared libraries -like [Hystrix](https://github.com/Netflix/Hystrix) (please note that *Hystrix* is a complete implementation of -the [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) pattern, of which the Retry pattern -can be considered a subset of.). This is the type of solution showcased in the simple example that accompanies this -*README*. +From a strategic point of view, this would be solved by having requests redirected to a separate +intermediary system, traditionally an [ESB](https://en.wikipedia.org/wiki/Enterprise_service_bus), +but more recently a [Service Mesh](https://medium.com/microservices-in-practice/service-mesh-for-microservices-2953109a3c9a). + +From a tactical point of view, this would be solved by reusing shared libraries like +[Hystrix](https://github.com/Netflix/Hystrix) (please note that Hystrix is a complete implementation +of the [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) pattern, of +which the Retry pattern can be considered a subset of). This is the type of solution showcased in +the simple example that accompanies this `README.md`. Real world example -> Our application uses a service providing customer information. Once in a while the service seems to be flaky and can return errors or sometimes it just times out. To circumvent these problems we apply the retry pattern. +> Our application uses a service providing customer information. Once in a while the service seems +> to be flaky and can return errors or sometimes it just times out. To circumvent these problems we +> apply the retry pattern. In plain words @@ -50,11 +51,14 @@ In plain words [Microsoft documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/retry) says -> Enable an application to handle transient failures when it tries to connect to a service or network resource, by transparently retrying a failed operation. This can improve the stability of the application. +> Enable an application to handle transient failures when it tries to connect to a service or +> network resource, by transparently retrying a failed operation. This can improve the stability of +> the application. **Programmatic Example** -In our hypothetical application, we have a generic interface for all operations on remote interfaces. +In our hypothetical application, we have a generic interface for all operations on remote +interfaces. ```java public interface BusinessOperation { @@ -73,16 +77,14 @@ public final class FindCustomer implements BusinessOperation { } ``` -Our `FindCustomer` implementation can be configured to throw -`BusinessException`s before returning the customer's ID, thereby simulating a -'flaky' service that intermittently fails. Some exceptions, like the -`CustomerNotFoundException`, are deemed to be recoverable after some -hypothetical analysis because the root cause of the error stems from "some -database locking issue". However, the `DatabaseNotAvailableException` is -considered to be a definite showstopper - the application should not attempt -to recover from this error. +Our `FindCustomer` implementation can be configured to throw `BusinessException`s before returning +the customer's ID, thereby simulating a flaky service that intermittently fails. Some exceptions, +like the `CustomerNotFoundException`, are deemed to be recoverable after some hypothetical analysis +because the root cause of the error stems from "some database locking issue". However, the +`DatabaseNotAvailableException` is considered to be a definite showstopper - the application should +not attempt to recover from this error. -We can model a 'recoverable' scenario by instantiating `FindCustomer` like this: +We can model a recoverable scenario by instantiating `FindCustomer` like this: ```java final var op = new FindCustomer( @@ -93,15 +95,12 @@ final var op = new FindCustomer( ); ``` -In this configuration, `FindCustomer` will throw `CustomerNotFoundException` -three times, after which it will consistently return the customer's ID -(`12345`). +In this configuration, `FindCustomer` will throw `CustomerNotFoundException` three times, after +which it will consistently return the customer's ID (`12345`). -In our hypothetical scenario, our analysts indicate that this operation -typically fails 2-4 times for a given input during peak hours, and that each -worker thread in the database subsystem typically needs 50ms to -"recover from an error". Applying these policies would yield something like -this: +In our hypothetical scenario, our analysts indicate that this operation typically fails 2-4 times +for a given input during peak hours, and that each worker thread in the database subsystem typically +needs 50ms to "recover from an error". Applying these policies would yield something like this: ```java final var op = new Retry<>( @@ -117,26 +116,27 @@ final var op = new Retry<>( ); ``` -Executing `op` *once* would automatically trigger at most 5 retry attempts, -with a 100 millisecond delay between attempts, ignoring any -`CustomerNotFoundException` thrown while trying. In this particular scenario, -due to the configuration for `FindCustomer`, there will be 1 initial attempt +Executing `op` once would automatically trigger at most 5 retry attempts, with a 100 millisecond +delay between attempts, ignoring any `CustomerNotFoundException` thrown while trying. In this +particular scenario, due to the configuration for `FindCustomer`, there will be 1 initial attempt and 3 additional retries before finally returning the desired result `12345`. -If our `FindCustomer` operation were instead to throw a fatal -`DatabaseNotFoundException`, which we were instructed not to ignore, but -more importantly we did *not* instruct our `Retry` to ignore, then the operation -would have failed immediately upon receiving the error, not matter how many -attempts were left. +If our `FindCustomer` operation were instead to throw a fatal `DatabaseNotFoundException`, which we +were instructed not to ignore, but more importantly we did not instruct our `Retry` to ignore, then +the operation would have failed immediately upon receiving the error, not matter how many attempts +were left. ## Class diagram + ![alt text](./etc/retry.png "Retry") ## Applicability -Whenever an application needs to communicate with an external resource, particularly in a cloud environment, and if -the business requirements allow it. + +Whenever an application needs to communicate with an external resource, particularly in a cloud +environment, and if the business requirements allow it. ## Consequences + **Pros:** * Resiliency diff --git a/retry/pom.xml b/retry/pom.xml index e6c2701e0..2f0b15bd2 100644 --- a/retry/pom.xml +++ b/retry/pom.xml @@ -28,7 +28,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT retry jar diff --git a/retry/src/main/java/com/iluwatar/retry/App.java b/retry/src/main/java/com/iluwatar/retry/App.java index 06f43c29f..7d6e6faec 100644 --- a/retry/src/main/java/com/iluwatar/retry/App.java +++ b/retry/src/main/java/com/iluwatar/retry/App.java @@ -59,6 +59,7 @@ import org.slf4j.LoggerFactory; */ public final class App { private static final Logger LOG = LoggerFactory.getLogger(App.class); + public static final String NOT_FOUND = "not found"; private static BusinessOperation op; /** @@ -81,7 +82,7 @@ public final class App { } private static void errorNoRetry() throws Exception { - op = new FindCustomer("123", new CustomerNotFoundException("not found")); + op = new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)); try { op.perform(); } catch (CustomerNotFoundException e) { @@ -91,7 +92,7 @@ public final class App { private static void errorWithRetry() throws Exception { final var retry = new Retry<>( - new FindCustomer("123", new CustomerNotFoundException("not found")), + new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), 3, //3 attempts 100, //100 ms delay between attempts e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) @@ -106,7 +107,7 @@ public final class App { private static void errorWithRetryExponentialBackoff() throws Exception { final var retry = new RetryExponentialBackoff<>( - new FindCustomer("123", new CustomerNotFoundException("not found")), + new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), 6, //6 attempts 30000, //30 s max delay between attempts e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) diff --git a/role-object/pom.xml b/role-object/pom.xml index ccb8219eb..07becfc9b 100644 --- a/role-object/pom.xml +++ b/role-object/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT role-object @@ -39,6 +39,11 @@ junit test + + org.junit.jupiter + junit-jupiter-engine + test + diff --git a/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java b/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java index 966d0e3f0..1c4cf0383 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java @@ -36,7 +36,7 @@ import java.util.Optional; */ public class CustomerCore extends Customer { - private Map roles; + private final Map roles; public CustomerCore() { roles = new HashMap<>(); diff --git a/role-object/src/main/java/com/iluwatar/roleobject/Role.java b/role-object/src/main/java/com/iluwatar/roleobject/Role.java index cbc6cc79b..a776178fb 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/Role.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/Role.java @@ -34,7 +34,7 @@ public enum Role { Borrower(BorrowerRole.class), Investor(InvestorRole.class); - private Class typeCst; + private final Class typeCst; Role(Class typeCst) { this.typeCst = typeCst; diff --git a/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java b/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java index eb8d57f4f..01535f477 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java @@ -24,10 +24,12 @@ package com.iluwatar.roleobject; import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + public class ApplicationRoleObjectTest { @Test - public void mainTest() { - ApplicationRoleObject.main(new String[]{}); + public void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> ApplicationRoleObject.main(new String[]{})); } } \ No newline at end of file diff --git a/saga/pom.xml b/saga/pom.xml index 08a5cdc77..a3ae60ed8 100644 --- a/saga/pom.xml +++ b/saga/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT saga @@ -40,6 +40,11 @@ junit test + + org.junit.jupiter + junit-jupiter-engine + test + diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java b/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java index 818b59a14..506587c76 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java @@ -33,7 +33,7 @@ import java.util.List; */ public class Saga { - private List chapters; + private final List chapters; private int pos; private boolean forward; private boolean finished; @@ -153,7 +153,7 @@ public class Saga { * outcoming parameter). */ public static class Chapter { - private String name; + private final String name; private ChapterResult result; private Object inValue; diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java index a616ff4a5..c6bc7bc80 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java @@ -32,7 +32,7 @@ import java.util.Optional; * The class representing a service discovery pattern. */ public class ServiceDiscoveryService { - private Map services; + private final Map services; /** * find any service. diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java index ef34ddb98..b04d22849 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java @@ -29,8 +29,8 @@ package com.iluwatar.saga.orchestration; * @param incoming value */ public class ChapterResult { - private K value; - private State state; + private final K value; + private final State state; public K getValue() { return value; diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java index aff3593f1..1b68d6cf7 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java @@ -32,7 +32,7 @@ import java.util.List; */ public class Saga { - private List chapters; + private final List chapters; private Saga() { diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java index dbc6e7eb5..f88efae52 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java @@ -31,7 +31,7 @@ import java.util.Optional; * The class representing a service discovery pattern. */ public class ServiceDiscoveryService { - private Map> services; + private final Map> services; public Optional find(String service) { return Optional.ofNullable(services.getOrDefault(service, null)); diff --git a/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java b/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java index d7a2a34b2..5d73a9fa3 100644 --- a/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java +++ b/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java @@ -25,13 +25,15 @@ package com.iluwatar.saga.choreography; import com.iluwatar.saga.orchestration.SagaApplication; import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /*** * empty test */ public class SagaApplicationTest { @Test - public void mainTest() { - SagaApplication.main(new String[]{}); + public void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> SagaApplication.main(new String[]{})); } } \ No newline at end of file diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java index 423b8e12e..f80a46fdc 100644 --- a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java @@ -34,7 +34,7 @@ import org.junit.Test; */ public class SagaOrchestratorInternallyTest { - private List records = new ArrayList<>(); + private final List records = new ArrayList<>(); @Test public void executeTest() { diff --git a/semaphore/pom.xml b/semaphore/pom.xml index b6375366b..64fd44db6 100644 --- a/semaphore/pom.xml +++ b/semaphore/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT semaphore diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.java b/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.java index d94764dbe..1f4026b92 100644 --- a/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.java +++ b/semaphore/src/main/java/com/iluwatar/semaphore/Fruit.java @@ -35,7 +35,7 @@ public class Fruit { ORANGE, APPLE, LEMON } - private FruitType type; + private final FruitType type; public Fruit(FruitType type) { this.type = type; diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.java b/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.java index 6b43c8100..5c2901efe 100644 --- a/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.java +++ b/semaphore/src/main/java/com/iluwatar/semaphore/FruitBowl.java @@ -31,7 +31,7 @@ import java.util.List; */ public class FruitBowl { - private List fruit = new ArrayList<>(); + private final List fruit = new ArrayList<>(); /** * Returns the amount of fruits left in bowl. diff --git a/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java b/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java index a360f955c..c74145610 100644 --- a/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java +++ b/semaphore/src/main/java/com/iluwatar/semaphore/FruitShop.java @@ -31,7 +31,7 @@ public class FruitShop { /** * The FruitBowl instances stored in the class. */ - private FruitBowl[] bowls = { + private final FruitBowl[] bowls = { new FruitBowl(), new FruitBowl(), new FruitBowl() @@ -40,7 +40,7 @@ public class FruitShop { /** * Access flags for each of the FruitBowl instances. */ - private boolean[] available = { + private final boolean[] available = { true, true, true @@ -49,7 +49,7 @@ public class FruitShop { /** * The Semaphore that controls access to the class resources. */ - private Semaphore semaphore; + private final Semaphore semaphore; /** * FruitShop constructor. diff --git a/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java b/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java index f450c0593..302796238 100644 --- a/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java +++ b/semaphore/src/test/java/com/iluwatar/semaphore/AppTest.java @@ -25,12 +25,15 @@ package com.iluwatar.semaphore; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application Test Entrypoint */ -public class AppTest { +class AppTest { + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/separated-interface/README.md b/separated-interface/README.md new file mode 100644 index 000000000..35c9e5fe0 --- /dev/null +++ b/separated-interface/README.md @@ -0,0 +1,138 @@ +--- +layout: pattern +title: Separated Interface +folder: separated-interface +permalink: /patterns/separated-interface/ +categories: Structural +tags: + - Decoupling +--- + + +## Intent + +Separate the interface definition and implementation in different packages. This allows the client +to be completely unaware of the implementation. + +## Explanation + +Real world example + +> An Invoice generator may be created with ability to use different Tax calculators that may be +> added in the invoice depending upon type of purchase, region etc. + +In plain words + +> Separated interface pattern encourages to keep the implementations of an interface decoupled from +> the client and its definition, so the client is not dependent on the implementation. + +A client code may abstract some specific functionality to an interface, and define the definition of +the interface as an SPI ([Service Programming Interface](https://en.wikipedia.org/wiki/Service_provider_interface) +is an API intended and open to be implemented or extended by a third party). Another package may +implement this interface definition with a concrete logic, which will be injected into the client +code at runtime (with a third class, injecting the implementation in the client) or at compile time +(using Plugin pattern with some configurable file). + +**Programmatic Example** + +**Client** + +`InvoiceGenerator` class accepts the cost of the product and calculates the total +amount payable inclusive of tax. + +```java +public class InvoiceGenerator { + + private final TaxCalculator taxCalculator; + + private final double amount; + + public InvoiceGenerator(double amount, TaxCalculator taxCalculator) { + this.amount = amount; + this.taxCalculator = taxCalculator; + } + + public double getAmountWithTax() { + return amount + taxCalculator.calculate(amount); + } + +} +``` + +The tax calculation logic is delegated to the `TaxCalculator` interface. + +```java +public interface TaxCalculator { + + double calculate(double amount); + +} +``` + +**Implementation package** + +In another package (which the client is completely unaware of) there exist multiple implementations +of the `TaxCalculator` interface. `ForeignTaxCalculator` is one of them which levies 60% tax +for international products. + +```java +public class ForeignTaxCalculator implements TaxCalculator { + + public static final double TAX_PERCENTAGE = 60; + + @Override + public double calculate(double amount) { + return amount * TAX_PERCENTAGE / 100.0; + } + +} +``` + +Another is `DomesticTaxCalculator` which levies 20% tax for international products. + +```java +public class DomesticTaxCalculator implements TaxCalculator { + + public static final double TAX_PERCENTAGE = 20; + + @Override + public double calculate(double amount) { + return amount * TAX_PERCENTAGE / 100.0; + } + +} +``` + +These both implementations are instantiated and injected in the client class by the ```App.java``` +class. + +```java + var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, new ForeignTaxCalculator()); + + LOGGER.info("Foreign Tax applied: {}", "" + internationalProductInvoice.getAmountWithTax()); + + var domesticProductInvoice = new InvoiceGenerator(PRODUCT_COST, new DomesticTaxCalculator()); + + LOGGER.info("Domestic Tax applied: {}", "" + domesticProductInvoice.getAmountWithTax()); +``` + +## Class diagram + +![alt text](./etc/class_diagram.png "Separated Interface") + +## Applicability + +Use the Separated interface pattern when + +* You are developing a framework package, and your framework needs to call some application code through interfaces. +* You have separate packages implementing the functionalities which may be plugged in your client code at runtime or compile-time. +* Your code resides in a layer that is not allowed to call the interface implementation layer by rule. For example, a domain layer needs to call a data mapper. + +## Tutorial + +* [Separated Interface Tutorial](https://www.youtube.com/watch?v=d3k-hOA7k2Y) + +## Credits + +* [Martin Fowler](https://www.martinfowler.com/eaaCatalog/separatedInterface.html) +* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=e08dfb7f2cf6153542ef1b5a00b10abc) diff --git a/separated-interface/etc/class_diagram.png b/separated-interface/etc/class_diagram.png new file mode 100644 index 000000000..10d509801 Binary files /dev/null and b/separated-interface/etc/class_diagram.png differ diff --git a/separated-interface/etc/separated-interface.urm.puml b/separated-interface/etc/separated-interface.urm.puml new file mode 100644 index 000000000..eab2ad60c --- /dev/null +++ b/separated-interface/etc/separated-interface.urm.puml @@ -0,0 +1,36 @@ +@startuml +package com.iluwatar.separatedinterface { + class App { + - LOGGER : Logger {static} + + PRODUCT_COST : double {static} + + App() + + main(args : String[]) {static} + } +} +package com.iluwatar.separatedinterface.taxes { + class DomesticTaxCalculator { + + TAX_PERCENTAGE : double {static} + + DomesticTaxCalculator() + + calculate(amount : double) : double + } + class ForeignTaxCalculator { + + TAX_PERCENTAGE : double {static} + + ForeignTaxCalculator() + + calculate(amount : double) : double + } +} +package com.iluwatar.separatedinterface.invoice { + class InvoiceGenerator { + - amount : double + - taxCalculator : TaxCalculator + + InvoiceGenerator(amount : double, taxCalculator : TaxCalculator) + + getAmountWithTax() : double + } + interface TaxCalculator { + + calculate(double) : double {abstract} + } +} +InvoiceGenerator --> "-taxCalculator" TaxCalculator +DomesticTaxCalculator ..|> TaxCalculator +ForeignTaxCalculator ..|> TaxCalculator +@enduml \ No newline at end of file diff --git a/separated-interface/pom.xml b/separated-interface/pom.xml new file mode 100644 index 000000000..da7584a93 --- /dev/null +++ b/separated-interface/pom.xml @@ -0,0 +1,71 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.24.0-SNAPSHOT + + separated-interface + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.mockito + mockito-core + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.separatedinterface.App + + + + + + + + + diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java new file mode 100644 index 000000000..1951e738b --- /dev/null +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.separatedinterface; + +import com.iluwatar.separatedinterface.invoice.InvoiceGenerator; +import com.iluwatar.separatedinterface.taxes.DomesticTaxCalculator; +import com.iluwatar.separatedinterface.taxes.ForeignTaxCalculator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

The Separated Interface pattern encourages to separate the interface definition and + * implementation in different packages. This allows the client to be completely unaware of the + * implementation.

+ * + *

In this class the {@link InvoiceGenerator} class is injected with different instances of + * {@link com.iluwatar.separatedinterface.invoice.TaxCalculator} implementations located in separate + * packages, to receive different responses for both of the implementations.

+ */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public static final double PRODUCT_COST = 50.0; + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + //Create the invoice generator with product cost as 50 and foreign product tax + var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, + new ForeignTaxCalculator()); + LOGGER.info("Foreign Tax applied: {}", "" + internationalProductInvoice.getAmountWithTax()); + + //Create the invoice generator with product cost as 50 and domestic product tax + var domesticProductInvoice = new InvoiceGenerator(PRODUCT_COST, new DomesticTaxCalculator()); + LOGGER.info("Domestic Tax applied: {}", "" + domesticProductInvoice.getAmountWithTax()); + } +} diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java new file mode 100644 index 000000000..c17ed5ac5 --- /dev/null +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.separatedinterface.invoice; + +/** + * InvoiceGenerator class generates an invoice, accepting the product cost and calculating the total + * price payable inclusive tax (calculated by {@link TaxCalculator}). + */ +public class InvoiceGenerator { + + /** + * The TaxCalculator interface to calculate the payable tax. + */ + private final TaxCalculator taxCalculator; + + /** + * The base product amount without tax. + */ + private final double amount; + + public InvoiceGenerator(double amount, TaxCalculator taxCalculator) { + this.amount = amount; + this.taxCalculator = taxCalculator; + } + + public double getAmountWithTax() { + return amount + taxCalculator.calculate(amount); + } + +} \ No newline at end of file diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/module-info.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java similarity index 90% rename from abstract-factory/src/main/java/com/iluwatar/abstractfactory/module-info.java rename to separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java index f075aadc0..aa4b81a84 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/module-info.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java @@ -21,6 +21,10 @@ * THE SOFTWARE. */ -module com.iluwatar.abstractfactory { - requires org.slf4j; -} \ No newline at end of file +package com.iluwatar.separatedinterface.invoice; + +public interface TaxCalculator { + + double calculate(double amount); + +} diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java new file mode 100644 index 000000000..788afb90a --- /dev/null +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java @@ -0,0 +1,40 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.separatedinterface.taxes; + +import com.iluwatar.separatedinterface.invoice.TaxCalculator; + +/** + * TaxCalculator for Domestic goods with 20% tax. + */ +public class DomesticTaxCalculator implements TaxCalculator { + + public static final double TAX_PERCENTAGE = 20; + + @Override + public double calculate(double amount) { + return amount * TAX_PERCENTAGE / 100.0; + } + +} diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java new file mode 100644 index 000000000..c2b12e2f5 --- /dev/null +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java @@ -0,0 +1,40 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.separatedinterface.taxes; + +import com.iluwatar.separatedinterface.invoice.TaxCalculator; + +/** + * TaxCalculator for foreign goods with 60% tax. + */ +public class ForeignTaxCalculator implements TaxCalculator { + + public static final double TAX_PERCENTAGE = 60; + + @Override + public double calculate(double amount) { + return amount * TAX_PERCENTAGE / 100.0; + } + +} diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DelayedServiceTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java similarity index 77% rename from circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DelayedServiceTest.java rename to separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java index af747f794..4114f9bb7 100644 --- a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DelayedServiceTest.java +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java @@ -21,21 +21,21 @@ * THE SOFTWARE. */ -package com.iluwatar.circuitbreaker; - -import static org.junit.jupiter.api.Assertions.assertEquals; +package com.iluwatar.separatedinterface; import org.junit.jupiter.api.Test; -/** - * Monitoring Service test - */ -public class DelayedServiceTest { +import com.iluwatar.separatedinterface.App; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Application test. + */ +class AppTest { - //Improves code coverage @Test - public void testDefaultConstructor() { - var obj = new DelayedService(); - assertEquals(obj.response(System.nanoTime()), "Delayed service is down"); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/command/src/main/java/com/iluwatar/command/ShrinkSpell.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java similarity index 63% rename from command/src/main/java/com/iluwatar/command/ShrinkSpell.java rename to separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java index 87497bb7b..ac75d3b70 100644 --- a/command/src/main/java/com/iluwatar/command/ShrinkSpell.java +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java @@ -21,39 +21,28 @@ * THE SOFTWARE. */ -package com.iluwatar.command; +package com.iluwatar.separatedinterface.invoice; -/** - * ShrinkSpell is a concrete command. - */ -public class ShrinkSpell extends Command { +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; - private Size oldSize; - private Target target; +import static org.mockito.Mockito.*; - @Override - public void execute(Target target) { - oldSize = target.getSize(); - target.setSize(Size.SMALL); - this.target = target; +public class InvoiceGeneratorTest { + + private InvoiceGenerator target; + + @Test + public void testGenerateTax() { + var productCost = 50.0; + var tax = 10.0; + TaxCalculator taxCalculatorMock = mock(TaxCalculator.class); + doReturn(tax).when(taxCalculatorMock).calculate(productCost); + + target = new InvoiceGenerator(productCost, taxCalculatorMock); + + Assertions.assertEquals(target.getAmountWithTax(), productCost + tax); + verify(taxCalculatorMock, times(1)).calculate(productCost); } - @Override - public void undo() { - if (oldSize != null && target != null) { - var temp = target.getSize(); - target.setSize(oldSize); - oldSize = temp; - } - } - - @Override - public void redo() { - undo(); - } - - @Override - public String toString() { - return "Shrink spell"; - } } diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java new file mode 100644 index 000000000..dac3ec2d6 --- /dev/null +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.separatedinterface.taxes; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class DomesticTaxCalculatorTest { + + private DomesticTaxCalculator target; + + @Test + public void testTaxCalculation(){ + target = new DomesticTaxCalculator(); + + var tax=target.calculate(100.0); + Assertions.assertEquals(tax,20.0); + } + +} diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java new file mode 100644 index 000000000..22526b6df --- /dev/null +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.separatedinterface.taxes; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ForeignTaxCalculatorTest { + + private ForeignTaxCalculator target; + + @Test + public void testTaxCalculation(){ + target = new ForeignTaxCalculator(); + + var tax=target.calculate(100.0); + Assertions.assertEquals(tax,60.0); + } + +} diff --git a/servant/pom.xml b/servant/pom.xml index 395060d50..c7b282c09 100644 --- a/servant/pom.xml +++ b/servant/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT servant diff --git a/servant/src/main/java/com/iluwatar/servant/App.java b/servant/src/main/java/com/iluwatar/servant/App.java index b68cb9aee..9c4591b05 100644 --- a/servant/src/main/java/com/iluwatar/servant/App.java +++ b/servant/src/main/java/com/iluwatar/servant/App.java @@ -39,8 +39,8 @@ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); - private static Servant jenkins = new Servant("Jenkins"); - private static Servant travis = new Servant("Travis"); + private static final Servant jenkins = new Servant("Jenkins"); + private static final Servant travis = new Servant("Travis"); /** * Program entry point. diff --git a/servant/src/test/java/com/iluwatar/servant/AppTest.java b/servant/src/test/java/com/iluwatar/servant/AppTest.java index ab1e99e55..d05c22eeb 100644 --- a/servant/src/test/java/com/iluwatar/servant/AppTest.java +++ b/servant/src/test/java/com/iluwatar/servant/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.servant; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/serverless/pom.xml b/serverless/pom.xml index 1fb55ec47..2880764ec 100644 --- a/serverless/pom.xml +++ b/serverless/pom.xml @@ -31,7 +31,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java b/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java index a13893f70..abe7c388d 100644 --- a/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java @@ -40,7 +40,7 @@ import java.util.Map; public abstract class AbstractDynamoDbHandler { private DynamoDBMapper dynamoDbMapper; - private ObjectMapper objectMapper; + private final ObjectMapper objectMapper; public AbstractDynamoDbHandler() { this.initAmazonDynamoDb(); diff --git a/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java b/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java index ef3909adc..a8c729163 100644 --- a/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java +++ b/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java @@ -52,7 +52,7 @@ public class SavePersonApiHandlerTest { @Mock private DynamoDBMapper dynamoDbMapper; - private ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper = new ObjectMapper(); @Before public void setUp() { diff --git a/service-layer/README.md b/service-layer/README.md index 910eaeaea..5e8e49ea6 100644 --- a/service-layer/README.md +++ b/service-layer/README.md @@ -155,9 +155,9 @@ public interface MagicService { public class MagicServiceImpl implements MagicService { - private WizardDao wizardDao; - private SpellbookDao spellbookDao; - private SpellDao spellDao; + private final WizardDao wizardDao; + private final SpellbookDao spellbookDao; + private final SpellDao spellDao; public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { this.wizardDao = wizardDao; diff --git a/service-layer/pom.xml b/service-layer/pom.xml index 881ec8ba6..071cf0f49 100644 --- a/service-layer/pom.xml +++ b/service-layer/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT service-layer 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 ea660f01e..e360e5c94 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 @@ -55,6 +55,7 @@ import org.slf4j.LoggerFactory; public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + public static final String BOOK_OF_IDORES = "Book of Idores"; /** * Program entry point. @@ -135,7 +136,7 @@ public class App { spellbook4.addSpell(spell11); spellbook4.addSpell(spell12); spellbookDao.merge(spellbook4); - var spellbook5 = new Spellbook("Book of Idores"); + var spellbook5 = new Spellbook(BOOK_OF_IDORES); spellbookDao.persist(spellbook5); spellbook5.addSpell(spell13); spellbookDao.merge(spellbook5); @@ -164,7 +165,7 @@ public class App { wizardDao.merge(wizard2); var wizard3 = new Wizard("Xuban Munoa"); wizardDao.persist(wizard3); - wizard3.addSpellbook(spellbookDao.findByName("Book of Idores")); + wizard3.addSpellbook(spellbookDao.findByName(BOOK_OF_IDORES)); wizard3.addSpellbook(spellbookDao.findByName("Book of Opaen")); wizardDao.merge(wizard3); var wizard4 = new Wizard("Blasius Dehooge"); @@ -188,7 +189,7 @@ public class App { LOGGER.info("Enumerating all spells"); service.findAllSpells().stream().map(Spell::getName).forEach(LOGGER::info); LOGGER.info("Find wizards with spellbook 'Book of Idores'"); - var wizardsWithSpellbook = service.findWizardsWithSpellbook("Book of Idores"); + var wizardsWithSpellbook = service.findWizardsWithSpellbook(BOOK_OF_IDORES); wizardsWithSpellbook.forEach(w -> LOGGER.info("{} has 'Book of Idores'", w.getName())); LOGGER.info("Find wizards with spell 'Fireball'"); var wizardsWithSpell = service.findWizardsWithSpell("Fireball"); 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 962487bd9..cdcf926d0 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 @@ -37,9 +37,9 @@ import java.util.List; */ public class MagicServiceImpl implements MagicService { - private WizardDao wizardDao; - private SpellbookDao spellbookDao; - private SpellDao spellDao; + private final WizardDao wizardDao; + private final SpellbookDao spellbookDao; + private final SpellDao spellDao; /** * Constructor. 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 26aa2b168..5cc1ccfff 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 @@ -27,18 +27,20 @@ import com.iluwatar.servicelayer.hibernate.HibernateUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } @AfterEach - public void tearDown() { + void tearDown() { HibernateUtil.dropSession(); } diff --git a/service-locator/pom.xml b/service-locator/pom.xml index 1d8e9fcd8..2b0037eb8 100644 --- a/service-locator/pom.xml +++ b/service-locator/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT service-locator diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/App.java b/service-locator/src/main/java/com/iluwatar/servicelocator/App.java index efc25bd89..85419b603 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/App.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/App.java @@ -37,19 +37,22 @@ package com.iluwatar.servicelocator; */ public class App { + public static final String JNDI_SERVICE_A = "jndi/serviceA"; + public static final String JNDI_SERVICE_B = "jndi/serviceB"; + /** * Program entry point. * * @param args command line args */ public static void main(String[] args) { - var service = ServiceLocator.getService("jndi/serviceA"); + var service = ServiceLocator.getService(JNDI_SERVICE_A); service.execute(); - service = ServiceLocator.getService("jndi/serviceB"); + service = ServiceLocator.getService(JNDI_SERVICE_B); service.execute(); - service = ServiceLocator.getService("jndi/serviceA"); + service = ServiceLocator.getService(JNDI_SERVICE_A); service.execute(); - service = ServiceLocator.getService("jndi/serviceA"); + service = ServiceLocator.getService(JNDI_SERVICE_A); service.execute(); } } 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 60f0f7c03..fad1dafe7 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java @@ -31,7 +31,7 @@ package com.iluwatar.servicelocator; */ public final class ServiceLocator { - private static ServiceCache serviceCache = new ServiceCache(); + private static final ServiceCache serviceCache = new ServiceCache(); private ServiceLocator() { } @@ -50,7 +50,7 @@ public final class ServiceLocator { return serviceObj; } else { /* - * If we are unable to retrive anything from cache, then lookup the service and add it in the + * If we are unable to retrieve anything from cache, then lookup the service and add it in the * cache map */ var ctx = new InitContext(); 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 cb4c98c07..75d6f8b05 100644 --- a/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java +++ b/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.servicelocator; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/sharding/pom.xml b/sharding/pom.xml index a641ce375..5f6c40ee5 100644 --- a/sharding/pom.xml +++ b/sharding/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 @@ -40,6 +40,11 @@ junit junit + + org.junit.jupiter + junit-jupiter-engine + test + diff --git a/sharding/src/main/java/com/iluwatar/sharding/App.java b/sharding/src/main/java/com/iluwatar/sharding/App.java index fe3cb7923..109f88cf8 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/App.java +++ b/sharding/src/main/java/com/iluwatar/sharding/App.java @@ -36,10 +36,10 @@ public class App { */ public static void main(String[] args) { - var data1 = new Data(1, "data1", Data.DataType.type1); - var data2 = new Data(2, "data2", Data.DataType.type2); - var data3 = new Data(3, "data3", Data.DataType.type3); - var data4 = new Data(4, "data4", Data.DataType.type1); + var data1 = new Data(1, "data1", Data.DataType.TYPE_1); + var data2 = new Data(2, "data2", Data.DataType.TYPE_2); + var data3 = new Data(3, "data3", Data.DataType.TYPE_3); + var data4 = new Data(4, "data4", Data.DataType.TYPE_1); var shard1 = new Shard(1); var shard2 = new Shard(2); diff --git a/sharding/src/main/java/com/iluwatar/sharding/Data.java b/sharding/src/main/java/com/iluwatar/sharding/Data.java index 92e84e93a..abe49f15d 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/Data.java +++ b/sharding/src/main/java/com/iluwatar/sharding/Data.java @@ -71,7 +71,7 @@ public class Data { } enum DataType { - type1, type2, type3 + TYPE_1, TYPE_2, TYPE_3 } @Override diff --git a/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java index 4948c2a19..ce239c156 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java +++ b/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java @@ -39,7 +39,7 @@ public class LookupShardManager extends ShardManager { private static final Logger LOGGER = LoggerFactory.getLogger(LookupShardManager.class); - private Map lookupMap = new HashMap<>(); + private final Map lookupMap = new HashMap<>(); @Override public int storeData(Data data) { diff --git a/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java index bdb862571..d06cd51da 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java +++ b/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java @@ -47,11 +47,11 @@ public class RangeShardManager extends ShardManager { protected int allocateShard(Data data) { var type = data.getType(); switch (type) { - case type1: + case TYPE_1: return 1; - case type2: + case TYPE_2: return 2; - case type3: + case TYPE_3: return 3; default: return -1; diff --git a/sharding/src/main/java/com/iluwatar/sharding/Shard.java b/sharding/src/main/java/com/iluwatar/sharding/Shard.java index eb0814258..56037bc3a 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/Shard.java +++ b/sharding/src/main/java/com/iluwatar/sharding/Shard.java @@ -33,7 +33,7 @@ public class Shard { private final int id; - private Map dataStore; + private final Map dataStore; public Shard(final int id) { this.id = id; diff --git a/sharding/src/test/java/com/iluwatar/sharding/AppTest.java b/sharding/src/test/java/com/iluwatar/sharding/AppTest.java index 40e6391bf..f23b71d1e 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/AppTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/AppTest.java @@ -25,14 +25,16 @@ package com.iluwatar.sharding; import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Unit tests for App class. */ public class AppTest { @Test - public void testMain() { - App.main(new String[]{}); + public void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java index 6418cf0c4..e19de75d2 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java @@ -56,7 +56,7 @@ public class HashShardManagerTest { @Test public void testStoreData() { - var data = new Data(1, "test", Data.DataType.type1); + var data = new Data(1, "test", Data.DataType.TYPE_1); hashShardManager.storeData(data); Assert.assertEquals(data, hashShardManager.getShardById(1).getDataById(1)); } diff --git a/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java index 70650bb50..c6ce5851d 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java @@ -52,7 +52,7 @@ public class LookupShardManagerTest { @Test public void testStoreData() { try { - var data = new Data(1, "test", Data.DataType.type1); + var data = new Data(1, "test", Data.DataType.TYPE_1); lookupShardManager.storeData(data); var field = LookupShardManager.class.getDeclaredField("lookupMap"); field.setAccessible(true); diff --git a/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java index 997687dfc..9ea4a2d86 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java @@ -50,7 +50,7 @@ public class RangeShardManagerTest { @Test public void testStoreData() { - var data = new Data(1, "test", Data.DataType.type1); + var data = new Data(1, "test", Data.DataType.TYPE_1); rangeShardManager.storeData(data); Assert.assertEquals(data, rangeShardManager.getShardById(1).getDataById(1)); } diff --git a/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java b/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java index a747933af..a12ab3a97 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java @@ -42,7 +42,7 @@ public class ShardTest { @Before public void setup() { - data = new Data(1, "test", Data.DataType.type1); + data = new Data(1, "test", Data.DataType.TYPE_1); shard = new Shard(1); } diff --git a/singleton/README.md b/singleton/README.md index 83f7fb355..915b3c810 100644 --- a/singleton/README.md +++ b/singleton/README.md @@ -9,14 +9,16 @@ tags: --- ## Intent -Ensure a class only has one instance, and provide a global point of -access to it. + +Ensure a class only has one instance, and provide a global point of access to it. ## Explanation + Real world example -> There can only be one ivory tower where the wizards study their magic. The same enchanted ivory tower is always used by the wizards. Ivory tower here is singleton. +> There can only be one ivory tower where the wizards study their magic. The same enchanted ivory +> tower is always used by the wizards. Ivory tower here is singleton. In plain words @@ -24,7 +26,9 @@ In plain words Wikipedia says -> In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. +> In software engineering, the singleton pattern is a software design pattern that restricts the +> instantiation of a class to one object. This is useful when exactly one object is needed to +> coordinate actions across the system. **Programmatic Example** @@ -34,11 +38,11 @@ Joshua Bloch, Effective Java 2nd Edition p.18 ```java public enum EnumIvoryTower { - INSTANCE; + INSTANCE } ``` -Then in order to use +Then in order to use: ```java var enumIvoryTower1 = EnumIvoryTower.INSTANCE; @@ -47,9 +51,11 @@ assertEquals(enumIvoryTower1, enumIvoryTower2); // true ``` ## Class diagram + ![alt text](./etc/singleton.urm.png "Singleton pattern class diagram") ## 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 diff --git a/singleton/pom.xml b/singleton/pom.xml index b09602d0e..80a34ea86 100644 --- a/singleton/pom.xml +++ b/singleton/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT singleton diff --git a/singleton/src/main/java/com/iluwatar/singleton/App.java b/singleton/src/main/java/com/iluwatar/singleton/App.java index 319e077ef..2cea2edd3 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/App.java +++ b/singleton/src/main/java/com/iluwatar/singleton/App.java @@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory; *

One of the risks of this pattern is that bugs resulting from setting a singleton up in a * distributed environment can be tricky to debug, since it will work fine if you debug with a * single classloader. Additionally, these problems can crop up a while after the implementation of - * a singleton, since they may start out synchronous and only become async with time, so you it may + * a singleton, since they may start out synchronous and only become async with time, so it may * not be clear why you are seeing certain changes in behaviour.

* *

There are many ways to implement the Singleton. The first one is the eagerly initialized diff --git a/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java b/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java index 205a7bd80..42779ff71 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java +++ b/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java @@ -45,7 +45,7 @@ public final class InitializingOnDemandHolderIdiom { } /** - * Sigleton instance. + * Singleton instance. * * @return Singleton instance */ diff --git a/singleton/src/test/java/com/iluwatar/singleton/AppTest.java b/singleton/src/test/java/com/iluwatar/singleton/AppTest.java index b7c55ddea..5d3f247de 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/AppTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.singleton; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test. */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/spatial-partition/README.md b/spatial-partition/README.md index 29d54533d..0114fadba 100644 --- a/spatial-partition/README.md +++ b/spatial-partition/README.md @@ -10,12 +10,17 @@ tags: --- ## Intent -As explained in the book [Game Programming Patterns](http://gameprogrammingpatterns.com/spatial-partition.html) by Bob Nystrom, spatial partition pattern helps to -> efficiently locate objects by storing them in a data structure organized by their positions. +As explained in the book [Game Programming Patterns](http://gameprogrammingpatterns.com/spatial-partition.html) +by Bob Nystrom, spatial partition pattern helps to efficiently locate objects by storing them in a +data structure organized by their positions. ## Explanation -Say, you are building a war game with hundreds, or maybe even thousands of players, who are clashing on the battle field. Each player's position is getting updated every frame. The simple way to handle all interactions taking place on the field is to check each player's position against every other player's position: + +Say, you are building a war game with hundreds, or maybe even thousands of players, who are clashing +on the battle field. Each player's position is getting updated every frame. The simple way to handle +all interactions taking place on the field is to check each player's position against every other +player's position: ```java public void handleMeLee(Unit units[], int numUnits) { @@ -32,21 +37,33 @@ public void handleMeLee(Unit units[], int numUnits) { } ``` -This will include a lot of unnecessary checks between players which are too far apart to have any influence on each other. The nested loops gives this operation an O(n^2) complexity, which has to be performed every frame since many of the objects on the field may be moving each frame. -The idea behind the Spatial Partition design pattern is to enable quick location of objects using a data structure that is organised by their positions, so when performing an operation like the one above, every object's position need not be checked against all other objects' positions. The data structure can be used to store moving and static objects, though in order to keep track of the moving objects, their positions will have to be reset each time they move. This would mean having to create a new instance of the data structure each time an object moves, which would use up additional memory. The common data structures used for this design pattern are: +This will include a lot of unnecessary checks between players which are too far apart to have any +influence on each other. The nested loops gives this operation an O(n^2) complexity, which has to be +performed every frame since many of the objects on the field may be moving each frame. The idea +behind the Spatial Partition design pattern is to enable quick location of objects using a data +structure that is organised by their positions, so when performing an operation like the one above, +every object's position need not be checked against all other objects' positions. The data structure +can be used to store moving and static objects, though in order to keep track of the moving objects, +their positions will have to be reset each time they move. This would mean having to create a new +instance of the data structure each time an object moves, which would use up additional memory. The +common data structures used for this design pattern are: * Grid * Quad tree -* k-d tree +* K-d tree * BSP * Boundary volume hierarchy -In our implementation, we use the Quadtree data structure which will reduce the time complexity of finding the objects within a certain range from O(n^2) to O(nlogn), decreasing the computations required significantly in case of large number of objects. +In our implementation, we use the Quadtree data structure which will reduce the time complexity of +finding the objects within a certain range from O(n^2) to O(nlogn), decreasing the computations +required significantly in case of large number of objects. ## Class diagram + ![alt text](./etc/spatial-partition.urm.png "Spatial Partition pattern class diagram") ## Applicability + This pattern can be used: * When you need to keep track of a large number of objects' positions, which are getting updated every frame. diff --git a/spatial-partition/pom.xml b/spatial-partition/pom.xml index 7312427d0..fe749b22b 100644 --- a/spatial-partition/pom.xml +++ b/spatial-partition/pom.xml @@ -46,7 +46,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT spatial-partition diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java index 8a0e2383c..083d6a019 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java @@ -59,6 +59,7 @@ import org.slf4j.LoggerFactory; public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + private static final String BUBBLE = "Bubble "; static void noSpatialPartition(int numOfMovements, Hashtable bubbles) { //all bubbles have to be checked for collision for all bubbles @@ -76,7 +77,7 @@ public class App { numOfMovements--; } //bubbles not popped - bubbles.keySet().stream().map(key -> "Bubble " + key + " not popped").forEach(LOGGER::info); + bubbles.keySet().stream().map(key -> BUBBLE + key + " not popped").forEach(LOGGER::info); } static void withSpatialPartition( @@ -99,7 +100,7 @@ public class App { numOfMovements--; } //bubbles not popped - bubbles.keySet().stream().map(key -> "Bubble " + key + " not popped").forEach(LOGGER::info); + bubbles.keySet().stream().map(key -> BUBBLE + key + " not popped").forEach(LOGGER::info); } /** @@ -116,7 +117,7 @@ public class App { var b = new Bubble(rand.nextInt(300), rand.nextInt(300), i, rand.nextInt(2) + 1); bubbles1.put(i, b); bubbles2.put(i, b); - LOGGER.info("Bubble " + i + " with radius " + b.radius + LOGGER.info(BUBBLE + i + " with radius " + b.radius + " added at (" + b.coordinateX + "," + b.coordinateY + ")"); } diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java index b3f60632f..c543da12c 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java @@ -24,7 +24,6 @@ package com.iluwatar.spatialpartition; import java.util.ArrayList; -import java.util.Collection; import java.util.Hashtable; /** @@ -34,8 +33,8 @@ import java.util.Hashtable; public class SpatialPartitionBubbles extends SpatialPartitionGeneric { - final Hashtable bubbles; - final QuadTree quadTree; + private final Hashtable bubbles; + private final QuadTree quadTree; SpatialPartitionBubbles(Hashtable bubbles, QuadTree quadTree) { this.bubbles = bubbles; diff --git a/specification/README.md b/specification/README.md index 6e52bd2e7..5648a73b0 100644 --- a/specification/README.md +++ b/specification/README.md @@ -9,36 +9,46 @@ tags: --- ## Also known as + Filter, Criteria ## 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. + +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. ## Explanation Real world example -> There is a pool of different creatures and we often need to select some subset of them. -> We can write our search specification such as "creatures that can fly", "creatures heavier than 500 kilograms", or as a combination of other search specifications, and then give it to the party that will perform the filtering. +> There is a pool of different creatures and we often need to select some subset of them. We can +> write our search specification such as "creatures that can fly", "creatures heavier than 500 +> kilograms", or as a combination of other search specifications, and then give it to the party that +> will perform the filtering. In Plain Words -> Specification pattern allows us to separate the search criteria from the object that performs the search. +> Specification pattern allows us to separate the search criteria from the object that performs the +> search. Wikipedia says -> In computer programming, the specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic. +> In computer programming, the specification pattern is a particular software design pattern, +> whereby business rules can be recombined by chaining the business rules together using boolean +> logic. **Programmatic Example** -If we look at our creature pool example from above, we have a set of creatures with certain properties. -Those properties can be part of a pre-defined, limited set (represented here by the enums Size, Movement and Color); but they can also be continuous values (e.g. the mass of a Creature). -In this case, it is more appropriate to use what we call "parameterized specification", where the property value can be given as an argument when the Creature is instantiated, allowing for more flexibility. -A third option is to combine pre-defined and/or parameterized properties using boolean logic, allowing for near-endless selection possibilities (this is called "composite specification", see below). -The pros and cons of each approach are detailed in the table at the end of this document. +If we look at our creature pool example from above, we have a set of creatures with certain +properties. Those properties can be part of a pre-defined, limited set (represented here by the +enums Size, Movement and Color); but they can also be continuous values (e.g. the mass of a +Creature). In this case, it is more appropriate to use what we call "parameterized specification", +where the property value can be given as an argument when the Creature is instantiated, allowing for +more flexibility. A third option is to combine pre-defined and/or parameterized properties using +boolean logic, allowing for near-endless selection possibilities (this is called "composite +specification", see below). The pros and cons of each approach are detailed in the table at the end +of this document. ```java public interface Creature { @@ -50,7 +60,7 @@ public interface Creature { } ``` -And ``Dragon`` implementation looks like this. +And `Dragon` implementation looks like this. ```java public class Dragon extends AbstractCreature { @@ -61,7 +71,8 @@ public class Dragon extends AbstractCreature { } ``` -Now that we want to select some subset of them, we use selectors. To select creatures that fly, we should use ``MovementSelector``. +Now that we want to select some subset of them, we use selectors. To select creatures that fly, we +should use `MovementSelector`. ```java public class MovementSelector extends AbstractSelector { @@ -79,7 +90,8 @@ public class MovementSelector extends AbstractSelector { } ``` -On the other hand, when selecting creatures heavier than a chosen amount, we use ``MassGreaterThanSelector``. +On the other hand, when selecting creatures heavier than a chosen amount, we use +`MassGreaterThanSelector`. ```java public class MassGreaterThanSelector extends AbstractSelector { @@ -111,7 +123,8 @@ But we could also use our parameterized selector like this: .collect(Collectors.toList()); ``` -Our third option is to combine multiple selectors together. Performing a search for special creatures (defined as red, flying, and not small) could be done as follows: +Our third option is to combine multiple selectors together. Performing a search for special +creatures (defined as red, flying, and not small) could be done as follows: ```java var specialCreaturesSelector = @@ -123,8 +136,9 @@ Our third option is to combine multiple selectors together. Performing a search **More on Composite Specification** -In Composite Specification, we will create custom instances of ``AbstractSelector`` by combining other selectors (called "leaves") using the three basic logical operators. -These are implemented in ``ConjunctionSelector``, ``DisjunctionSelector`` and ``NegationSelector``. +In Composite Specification, we will create custom instances of `AbstractSelector` by combining +other selectors (called "leaves") using the three basic logical operators. These are implemented in +`ConjunctionSelector`, `DisjunctionSelector` and `NegationSelector`. ```java public abstract class AbstractSelector implements Predicate { @@ -146,7 +160,7 @@ public abstract class AbstractSelector implements Predicate { ```java public class ConjunctionSelector extends AbstractSelector { - private List> leafComponents; + private final List> leafComponents; @SafeVarargs ConjunctionSelector(AbstractSelector... selectors) { @@ -163,12 +177,14 @@ public class ConjunctionSelector extends AbstractSelector { } ``` -All that is left to do is now to create leaf selectors (be it hard-coded or parameterized ones) that are as generic as possible, -and we will be able to instantiate the ``AbstractSelector`` class by combining any amount of selectors, as exemplified above. -We should be careful though, as it is easy to make a mistake when combining many logical operators; in particular, we should pay attention to the priority of the operations.\ -In general, Composite Specification is a great way to write more reusable code, as there is no need to create a Selector class for each filtering operation. -Instead, we just create an instance of ``AbstractSelector`` "on the spot", using tour generic "leaf" selectors and some basic boolean logic. - +All that is left to do is now to create leaf selectors (be it hard-coded or parameterized ones) that +are as generic as possible, and we will be able to instantiate the ``AbstractSelector`` class by +combining any amount of selectors, as exemplified above. We should be careful though, as it is easy +to make a mistake when combining many logical operators; in particular, we should pay attention to +the priority of the operations. In general, Composite Specification is a great way to write more +reusable code, as there is no need to create a Selector class for each filtering operation. Instead, +we just create an instance of ``AbstractSelector`` "on the spot", using tour generic "leaf" +selectors and some basic boolean logic. **Comparison of the different approaches** @@ -181,9 +197,11 @@ Instead, we just create an instance of ``AbstractSelector`` "on the spot", using | | | + Supports logical operations | - You still need to create the base classes used as leaves | ## Class diagram + ![alt text](./etc/specification.png "Specification") ## 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. diff --git a/specification/pom.xml b/specification/pom.xml index 9214e984e..1494ca305 100644 --- a/specification/pom.xml +++ b/specification/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT specification 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 6b359d8ac..214ae4562 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java @@ -33,11 +33,11 @@ import com.iluwatar.specification.property.Size; */ public abstract class AbstractCreature implements Creature { - private String name; - private Size size; - private Movement movement; - private Color color; - private Mass mass; + private final String name; + private final Size size; + private final Movement movement; + private final Color color; + private final Mass mass; /** * Constructor. diff --git a/specification/src/main/java/com/iluwatar/specification/property/Color.java b/specification/src/main/java/com/iluwatar/specification/property/Color.java index 6e96b5813..b3128054e 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Color.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Color.java @@ -30,7 +30,7 @@ public enum Color { DARK("dark"), LIGHT("light"), GREEN("green"), RED("red"); - private String title; + private final String title; Color(String title) { this.title = title; diff --git a/specification/src/main/java/com/iluwatar/specification/property/Mass.java b/specification/src/main/java/com/iluwatar/specification/property/Mass.java index b2d6ddc66..e0e90aa06 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Mass.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Mass.java @@ -28,8 +28,8 @@ package com.iluwatar.specification.property; */ public class Mass { - private double value; - private String title; + private final double value; + private final String title; public Mass(double value) { this.value = value; diff --git a/specification/src/main/java/com/iluwatar/specification/property/Movement.java b/specification/src/main/java/com/iluwatar/specification/property/Movement.java index f76b0584f..fcdcfae70 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Movement.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Movement.java @@ -30,7 +30,7 @@ public enum Movement { WALKING("walking"), SWIMMING("swimming"), FLYING("flying"); - private String title; + private final String title; Movement(String title) { this.title = title; diff --git a/specification/src/main/java/com/iluwatar/specification/property/Size.java b/specification/src/main/java/com/iluwatar/specification/property/Size.java index 27bc48024..c5ad5525c 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Size.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Size.java @@ -30,7 +30,7 @@ public enum Size { SMALL("small"), NORMAL("normal"), LARGE("large"); - private String title; + private final String title; Size(String title) { this.title = title; diff --git a/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java index bd29aa260..661c8bceb 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java @@ -30,7 +30,7 @@ import java.util.List; */ public class ConjunctionSelector extends AbstractSelector { - private List> leafComponents; + private final List> leafComponents; @SafeVarargs ConjunctionSelector(AbstractSelector... selectors) { diff --git a/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java index 1fb38a43d..32dcf7e73 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java @@ -30,7 +30,7 @@ import java.util.List; */ public class DisjunctionSelector extends AbstractSelector { - private List> leafComponents; + private final List> leafComponents; @SafeVarargs DisjunctionSelector(AbstractSelector... selectors) { diff --git a/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java index ad3063000..30302baa2 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java @@ -30,7 +30,7 @@ package com.iluwatar.specification.selector; */ public class NegationSelector extends AbstractSelector { - private AbstractSelector component; + private final AbstractSelector component; NegationSelector(AbstractSelector selector) { this.component = selector; 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 bc6f2226f..4b29e11e8 100644 --- a/specification/src/test/java/com/iluwatar/specification/app/AppTest.java +++ b/specification/src/test/java/com/iluwatar/specification/app/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.specification.app; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/state/README.md b/state/README.md index 7be4d3351..7ee201be2 100644 --- a/state/README.md +++ b/state/README.md @@ -9,15 +9,21 @@ tags: --- ## Also known as + Objects for States ## Intent -Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. + +Allow an object to alter its behavior when its internal state changes. The object will appear to +change its class. ## Explanation + Real world example -> When observing a mammoth in its natural habitat it seems to change its behavior based on the situation. It may first appear calm but over time when it detects a threat it gets angry and dangerous to its surroundings. +> When observing a mammoth in its natural habitat it seems to change its behavior based on the +> situation. It may first appear calm but over time when it detects a threat it gets angry and +> dangerous to its surroundings. In plain words @@ -25,7 +31,10 @@ In plain words Wikipedia says -> The state pattern is a behavioral software design pattern that allows an object to alter its behavior when its internal state changes. This pattern is close to the concept of finite-state machines. The state pattern can be interpreted as a strategy pattern, which is able to switch a strategy through invocations of methods defined in the pattern's interface. +> The state pattern is a behavioral software design pattern that allows an object to alter its +> behavior when its internal state changes. This pattern is close to the concept of finite-state +> machines. The state pattern can be interpreted as a strategy pattern, which is able to switch a +> strategy through invocations of methods defined in the pattern's interface. **Programmatic Example** @@ -43,7 +52,7 @@ public class PeacefulState implements State { private static final Logger LOGGER = LoggerFactory.getLogger(PeacefulState.class); - private Mammoth mammoth; + private final Mammoth mammoth; public PeacefulState(Mammoth mammoth) { this.mammoth = mammoth; @@ -64,7 +73,7 @@ public class AngryState implements State { private static final Logger LOGGER = LoggerFactory.getLogger(AngryState.class); - private Mammoth mammoth; + private final Mammoth mammoth; public AngryState(Mammoth mammoth) { this.mammoth = mammoth; @@ -126,17 +135,23 @@ And here is the full example how the mammoth behaves over time. mammoth.observe(); mammoth.timePasses(); mammoth.observe(); - - // The mammoth gets angry! - // The mammoth is furious! - // The mammoth calms down. - // The mammoth is calm and peaceful. +``` + +Program output: + +```java + The mammoth gets angry! + The mammoth is furious! + The mammoth calms down. + The mammoth is calm and peaceful. ``` ## Class diagram -![alt text](./etc/state_1.png "State") + +![alt text](./etc/state_urm.png "State") ## 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 diff --git a/state/etc/state.png b/state/etc/state.png deleted file mode 100644 index fb1648238..000000000 Binary files a/state/etc/state.png and /dev/null differ diff --git a/state/etc/state.ucls b/state/etc/state.ucls deleted file mode 100644 index e0be8d712..000000000 --- a/state/etc/state.ucls +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/state/etc/state_1.png b/state/etc/state_1.png deleted file mode 100644 index 112bb9aff..000000000 Binary files a/state/etc/state_1.png and /dev/null differ diff --git a/state/etc/state_urm.png b/state/etc/state_urm.png new file mode 100644 index 000000000..c2cf9f562 Binary files /dev/null and b/state/etc/state_urm.png differ diff --git a/state/pom.xml b/state/pom.xml index b1ff3f5f3..a434dabe4 100644 --- a/state/pom.xml +++ b/state/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT state diff --git a/state/src/main/java/com/iluwatar/state/AngryState.java b/state/src/main/java/com/iluwatar/state/AngryState.java index 8dc296c53..e105262b8 100644 --- a/state/src/main/java/com/iluwatar/state/AngryState.java +++ b/state/src/main/java/com/iluwatar/state/AngryState.java @@ -1,52 +1,52 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.state; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Angry state. - */ -public class AngryState implements State { - - private static final Logger LOGGER = LoggerFactory.getLogger(AngryState.class); - - private Mammoth mammoth; - - public AngryState(Mammoth mammoth) { - this.mammoth = mammoth; - } - - @Override - public void observe() { - LOGGER.info("{} is furious!", mammoth); - } - - @Override - public void onEnterState() { - LOGGER.info("{} gets angry!", mammoth); - } - -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.state; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Angry state. + */ +public class AngryState implements State { + + private static final Logger LOGGER = LoggerFactory.getLogger(AngryState.class); + + private final Mammoth mammoth; + + public AngryState(Mammoth mammoth) { + this.mammoth = mammoth; + } + + @Override + public void observe() { + LOGGER.info("{} is furious!", mammoth); + } + + @Override + public void onEnterState() { + LOGGER.info("{} gets angry!", mammoth); + } + +} diff --git a/state/src/main/java/com/iluwatar/state/PeacefulState.java b/state/src/main/java/com/iluwatar/state/PeacefulState.java index ed83a0738..adf8be209 100644 --- a/state/src/main/java/com/iluwatar/state/PeacefulState.java +++ b/state/src/main/java/com/iluwatar/state/PeacefulState.java @@ -1,52 +1,52 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.state; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Peaceful state. - */ -public class PeacefulState implements State { - - private static final Logger LOGGER = LoggerFactory.getLogger(PeacefulState.class); - - private Mammoth mammoth; - - public PeacefulState(Mammoth mammoth) { - this.mammoth = mammoth; - } - - @Override - public void observe() { - LOGGER.info("{} is calm and peaceful.", mammoth); - } - - @Override - public void onEnterState() { - LOGGER.info("{} calms down.", mammoth); - } - -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.state; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Peaceful state. + */ +public class PeacefulState implements State { + + private static final Logger LOGGER = LoggerFactory.getLogger(PeacefulState.class); + + private final Mammoth mammoth; + + public PeacefulState(Mammoth mammoth) { + this.mammoth = mammoth; + } + + @Override + public void observe() { + LOGGER.info("{} is calm and peaceful.", mammoth); + } + + @Override + public void onEnterState() { + LOGGER.info("{} calms down.", mammoth); + } + +} diff --git a/state/src/test/java/com/iluwatar/state/AppTest.java b/state/src/test/java/com/iluwatar/state/AppTest.java index 9f7b65ff2..90beddb33 100644 --- a/state/src/test/java/com/iluwatar/state/AppTest.java +++ b/state/src/test/java/com/iluwatar/state/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.state; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/state/src/test/java/com/iluwatar/state/MammothTest.java b/state/src/test/java/com/iluwatar/state/MammothTest.java index 15624c7ab..4cc6e0adb 100644 --- a/state/src/test/java/com/iluwatar/state/MammothTest.java +++ b/state/src/test/java/com/iluwatar/state/MammothTest.java @@ -96,7 +96,7 @@ public class MammothTest { } private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); diff --git a/step-builder/pom.xml b/step-builder/pom.xml index 3cea3b158..de5763695 100644 --- a/step-builder/pom.xml +++ b/step-builder/pom.xml @@ -30,7 +30,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT step-builder diff --git a/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java b/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java index a0c7f84e6..5be38e471 100644 --- a/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java +++ b/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java @@ -105,7 +105,7 @@ public final class CharacterStepBuilder { private String wizardClass; private String weapon; private String spell; - private List abilities = new ArrayList<>(); + private final List abilities = new ArrayList<>(); @Override public ClassStep name(String name) { diff --git a/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java b/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java index 1f3fc6238..20119b41b 100644 --- a/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java +++ b/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.stepbuilder; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/strangler/pom.xml b/strangler/pom.xml index 4a1fb42ba..7cb2ff263 100644 --- a/strangler/pom.xml +++ b/strangler/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/strangler/src/main/java/com/iluwatar/strangler/NewSource.java b/strangler/src/main/java/com/iluwatar/strangler/NewSource.java index f53a31bd8..8cc1822fe 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/NewSource.java +++ b/strangler/src/main/java/com/iluwatar/strangler/NewSource.java @@ -34,9 +34,10 @@ import org.slf4j.LoggerFactory; public class NewSource { private static final Logger LOGGER = LoggerFactory.getLogger(NewSource.class); private static final String VERSION = "2.0"; + public static final String SOURCE_MODULE = "Source module {}"; public int accumulateSum(int... nums) { - LOGGER.info("Source module {}", VERSION); + LOGGER.info(SOURCE_MODULE, VERSION); return Arrays.stream(nums).reduce(0, Integer::sum); } @@ -45,12 +46,12 @@ public class NewSource { * Replace old one in {@link OldSource} */ public int accumulateMul(int... nums) { - LOGGER.info("Source module {}", VERSION); + LOGGER.info(SOURCE_MODULE, VERSION); return Arrays.stream(nums).reduce(1, (a, b) -> a * b); } public boolean ifNonZero(int... nums) { - LOGGER.info("Source module {}", VERSION); + LOGGER.info(SOURCE_MODULE, VERSION); return Arrays.stream(nums).allMatch(num -> num != 0); } } diff --git a/strangler/src/test/java/com/iluwatar/strangler/AppTest.java b/strangler/src/test/java/com/iluwatar/strangler/AppTest.java index a9e878a94..7e45d63ec 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/AppTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/AppTest.java @@ -25,12 +25,15 @@ package com.iluwatar.strangler; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { + @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/strategy/README.md b/strategy/README.md index 21ac1c94b..b2330ceb8 100644 --- a/strategy/README.md +++ b/strategy/README.md @@ -9,17 +9,20 @@ tags: --- ## Also known as + Policy ## Intent -Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary -independently from clients that use it. + +Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets +the algorithm vary independently from clients that use it. ## Explanation Real world example -> Slaying dragons is a dangerous profession. With experience it becomes easier. Veteran dragonslayers have developed different fighting strategies against different types of dragons. +> Slaying dragons is a dangerous job. With experience it becomes easier. Veteran +> dragonslayers have developed different fighting strategies against different types of dragons. In plain words @@ -27,7 +30,8 @@ In plain words Wikipedia says -> In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. +> In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral +> software design pattern that enables selecting an algorithm at runtime. **Programmatic Example** @@ -71,7 +75,8 @@ public class SpellStrategy implements DragonSlayingStrategy { } ``` -And here is the mighty dragonslayer who is able to pick his fighting strategy based on the opponent. +And here is the mighty dragonslayer, who is able to pick his fighting strategy based on the +opponent. ```java public class DragonSlayer { @@ -92,7 +97,7 @@ public class DragonSlayer { } ``` -Finally here's dragonslayer in action. +Finally here's the dragonslayer in action. ```java LOGGER.info("Green dragon spotted ahead!"); @@ -104,19 +109,25 @@ Finally here's dragonslayer in action. LOGGER.info("Black dragon lands before you."); dragonSlayer.changeStrategy(new SpellStrategy()); dragonSlayer.goToBattle(); - - // Green dragon spotted ahead! - // With your Excalibur you sever the dragon's head! - // Red dragon emerges. - // You shoot the dragon with the magical crossbow and it falls dead on the ground! - // Black dragon lands before you. - // You cast the spell of disintegration and the dragon vaporizes in a pile of dust! +``` + +Program output: + +``` + Green dragon spotted ahead! + With your Excalibur you sever the dragon's head! + Red dragon emerges. + You shoot the dragon with the magical crossbow and it falls dead on the ground! + Black dragon lands before you. + You cast the spell of disintegration and the dragon vaporizes in a pile of dust! ``` ## Class diagram -![alt text](./etc/strategy_1.png "Strategy") + +![alt text](./etc/strategy_urm.png "Strategy") ## 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 diff --git a/strategy/etc/strategy.png b/strategy/etc/strategy.png deleted file mode 100644 index ae7442150..000000000 Binary files a/strategy/etc/strategy.png and /dev/null differ diff --git a/strategy/etc/strategy.ucls b/strategy/etc/strategy.ucls deleted file mode 100644 index 9eba02417..000000000 --- a/strategy/etc/strategy.ucls +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/strategy/etc/strategy_1.png b/strategy/etc/strategy_1.png deleted file mode 100644 index c341e58c6..000000000 Binary files a/strategy/etc/strategy_1.png and /dev/null differ diff --git a/strategy/etc/strategy_urm.png b/strategy/etc/strategy_urm.png new file mode 100644 index 000000000..8e15c3c36 Binary files /dev/null and b/strategy/etc/strategy_urm.png differ diff --git a/strategy/pom.xml b/strategy/pom.xml index cd1395c7a..0f3b0e830 100644 --- a/strategy/pom.xml +++ b/strategy/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT strategy diff --git a/strategy/src/test/java/com/iluwatar/strategy/AppTest.java b/strategy/src/test/java/com/iluwatar/strategy/AppTest.java index f1137c4c5..174334f51 100644 --- a/strategy/src/test/java/com/iluwatar/strategy/AppTest.java +++ b/strategy/src/test/java/com/iluwatar/strategy/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.strategy; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test. */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java index cca82cefc..0b5a2d615 100644 --- a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java +++ b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java @@ -91,7 +91,7 @@ public class DragonSlayingStrategyTest { } private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); diff --git a/subclass-sandbox/pom.xml b/subclass-sandbox/pom.xml index 264343b00..780547e95 100644 --- a/subclass-sandbox/pom.xml +++ b/subclass-sandbox/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 @@ -42,7 +42,12 @@ com.github.stefanbirkner - system-rules + system-lambda + + + org.junit.jupiter + junit-jupiter-engine + test diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java index 83ba5315f..8d4865f6b 100644 --- a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.subclasssandbox; import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * App unit tests. */ public class AppTest { @Test - public void testMain() { - App.main(new String[]{}); + public void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java index 97e2ac67d..3b379946c 100644 --- a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java @@ -23,55 +23,48 @@ package com.iluwatar.subclasssandbox; +import com.github.stefanbirkner.systemlambda.Statement; import org.junit.Assert; -import org.junit.Rule; import org.junit.Test; -import org.junit.contrib.java.lang.system.SystemOutRule; + +import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOutNormalized; /** * GroundDive unit tests. */ public class GroundDiveTest { - @Rule - public SystemOutRule log = new SystemOutRule().enableLog(); - @Test - public void testMove() { - log.clearLog(); + public void testMove() throws Exception { var groundDive = new GroundDive(); groundDive.move(1.0, 1.0, 1.0); - var outputLog = getLogContent(log.getLog()); + var outputLog = getLogContent(() -> groundDive.move(1.0, 1.0, 1.0)); var expectedLog = "Move to ( 1.0, 1.0, 1.0 )"; Assert.assertEquals(outputLog, expectedLog); } @Test - public void testPlaySound() { - log.clearLog(); + public void testPlaySound() throws Exception { var groundDive = new GroundDive(); - groundDive.playSound("SOUND_NAME", 1); - var outputLog = getLogContent(log.getLog()); + var outputLog = getLogContent(() -> groundDive.playSound("SOUND_NAME", 1)); var expectedLog = "Play SOUND_NAME with volumn 1"; Assert.assertEquals(outputLog, expectedLog); } @Test - public void testSpawnParticles() { - log.clearLog(); + public void testSpawnParticles() throws Exception { var groundDive = new GroundDive(); - groundDive.spawnParticles("PARTICLE_TYPE", 100); - final var outputLog = getLogContent(log.getLog()); + final var outputLog = getLogContent( + () -> groundDive.spawnParticles("PARTICLE_TYPE", 100)); final var expectedLog = "Spawn 100 particle with type PARTICLE_TYPE"; Assert.assertEquals(outputLog, expectedLog); } @Test - public void testActivate() { - log.clearLog(); + public void testActivate() throws Exception { var groundDive = new GroundDive(); - groundDive.activate(); - var logs = log.getLog().split("\n"); + var logs = tapSystemOutNormalized(groundDive::activate) + .split("\n"); final var expectedSize = 3; final var log1 = logs[0].split("-")[1].trim() + " -" + logs[0].split("-")[2].trim(); final var expectedLog1 = "Move to ( 0.0, 0.0, -20.0 )"; @@ -85,6 +78,11 @@ public class GroundDiveTest { Assert.assertEquals(log3, expectedLog3); } + private String getLogContent(Statement statement) throws Exception { + var log = tapSystemOutNormalized(statement); + return getLogContent(log); + } + private String getLogContent(String log) { return log.split("-")[1].trim(); } diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java index e192737f6..d285e6c7d 100644 --- a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java @@ -23,55 +23,47 @@ package com.iluwatar.subclasssandbox; +import com.github.stefanbirkner.systemlambda.Statement; import org.junit.Assert; -import org.junit.Rule; import org.junit.Test; -import org.junit.contrib.java.lang.system.SystemOutRule; + +import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOutNormalized; /** * SkyLaunch unit tests. */ public class SkyLaunchTest { - @Rule - public SystemOutRule log = new SystemOutRule().enableLog(); - @Test - public void testMove() { - log.clearLog(); + public void testMove() throws Exception { var skyLaunch = new SkyLaunch(); - skyLaunch.move(1.0, 1.0, 1.0); - var outputLog = getLogContent(log.getLog()); + var outputLog = getLogContent(() -> skyLaunch.move(1.0, 1.0, 1.0)); var expectedLog = "Move to ( 1.0, 1.0, 1.0 )"; Assert.assertEquals(outputLog, expectedLog); } @Test - public void testPlaySound() { - log.clearLog(); + public void testPlaySound() throws Exception { var skyLaunch = new SkyLaunch(); - skyLaunch.playSound("SOUND_NAME", 1); - var outputLog = getLogContent(log.getLog()); + var outputLog = getLogContent(() -> skyLaunch.playSound("SOUND_NAME", 1)); var expectedLog = "Play SOUND_NAME with volumn 1"; Assert.assertEquals(outputLog, expectedLog); } @Test - public void testSpawnParticles() { - log.clearLog(); + public void testSpawnParticles() throws Exception { var skyLaunch = new SkyLaunch(); - skyLaunch.spawnParticles("PARTICLE_TYPE", 100); - var outputLog = getLogContent(log.getLog()); + var outputLog = getLogContent( + () -> skyLaunch.spawnParticles("PARTICLE_TYPE", 100)); var expectedLog = "Spawn 100 particle with type PARTICLE_TYPE"; Assert.assertEquals(outputLog, expectedLog); } @Test - public void testActivate() { - log.clearLog(); + public void testActivate() throws Exception { var skyLaunch = new SkyLaunch(); - skyLaunch.activate(); - var logs = log.getLog().split("\n"); + var logs = tapSystemOutNormalized(skyLaunch::activate) + .split("\n"); final var expectedSize = 3; final var log1 = getLogContent(logs[0]); final var expectedLog1 = "Move to ( 0.0, 0.0, 20.0 )"; @@ -85,6 +77,11 @@ public class SkyLaunchTest { Assert.assertEquals(log3, expectedLog3); } + private String getLogContent(Statement statement) throws Exception { + var log = tapSystemOutNormalized(statement); + return getLogContent(log); + } + private String getLogContent(String log) { return log.split("-")[1].trim(); } diff --git a/template-method/README.md b/template-method/README.md index 695644488..23dd4d242 100644 --- a/template-method/README.md +++ b/template-method/README.md @@ -9,21 +9,30 @@ tags: --- ## 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. + +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. ## Explanation + Real world example -> The general steps in stealing an item are the same. First you pick the target, next you confuse him somehow and finally you steal the item. However there are many ways to implement these steps. +> The general steps in stealing an item are the same. First you pick the target, next you confuse +> him somehow and finally you steal the item. However there are many ways to implement these steps. In plain words -> Template Method pattern outlines the general steps in the parent class and lets the concrete child implementations define the details. +> Template Method pattern outlines the general steps in the parent class and lets the concrete child +> implementations define the details. Wikipedia says -> In object-oriented programming, the template method is one of the behavioral design patterns identified by Gamma et al. in the book Design Patterns. The template method is a method in a superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of a number of high-level steps. These steps are themselves implemented by additional helper methods in the same class as the template method. +> In object-oriented programming, the template method is one of the behavioral design patterns +> identified by Gamma et al. in the book Design Patterns. The template method is a method in a +> superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of +> a number of high-level steps. These steps are themselves implemented by additional helper methods +> in the same class as the template method. **Programmatic Example** @@ -120,9 +129,11 @@ And finally we show how the halfling thief utilizes the different stealing metho ``` ## Class diagram -![alt text](./etc/template-method_1.png "Template Method") + +![alt text](./etc/template_method_urm.png "Template Method") ## 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 diff --git a/template-method/etc/template-method.png b/template-method/etc/template-method.png deleted file mode 100644 index e19781339..000000000 Binary files a/template-method/etc/template-method.png and /dev/null differ diff --git a/template-method/etc/template-method.ucls b/template-method/etc/template-method.ucls deleted file mode 100644 index ec0ce620a..000000000 --- a/template-method/etc/template-method.ucls +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/template-method/etc/template-method_1.png b/template-method/etc/template-method_1.png deleted file mode 100644 index f7cd1a077..000000000 Binary files a/template-method/etc/template-method_1.png and /dev/null differ diff --git a/template-method/etc/template_method_urm.png b/template-method/etc/template_method_urm.png new file mode 100644 index 000000000..b7babccff Binary files /dev/null and b/template-method/etc/template_method_urm.png differ diff --git a/template-method/pom.xml b/template-method/pom.xml index c449ef04f..cbf2ac156 100644 --- a/template-method/pom.xml +++ b/template-method/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT template-method 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 e70e8a78f..32f09242e 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.templatemethod; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java index ba6030da4..95326eeec 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java @@ -146,7 +146,7 @@ public abstract class StealingMethodTest { } private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); diff --git a/thread-pool/README.md b/thread-pool/README.md index 62a2a3339..de6403fe8 100644 --- a/thread-pool/README.md +++ b/thread-pool/README.md @@ -9,16 +9,19 @@ tags: --- ## 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 -and eliminating the latency of creating new threads. + +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 and eliminating the latency of creating new threads. ## Explanation + Real world example -> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we establish a thread pool. +> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes +> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we +> establish a thread pool. In plain words @@ -26,11 +29,18 @@ In plain words Wikipedia says -> In computer programming, a thread pool is a software design pattern for achieving concurrency of execution in a computer program. Often also called a replicated workers or worker-crew model, a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program. By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks. The number of available threads is tuned to the computing resources available to the program, such as a parallel task queue after completion of execution. +> In computer programming, a thread pool is a software design pattern for achieving concurrency of +> execution in a computer program. Often also called a replicated workers or worker-crew model, +> a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent +> execution by the supervising program. By maintaining a pool of threads, the model increases +> performance and avoids latency in execution due to frequent creation and destruction of threads +> for short-lived tasks. The number of available threads is tuned to the computing resources +> available to the program, such as a parallel task queue after completion of execution. **Programmatic Example** -Let's first look at our task hierarchy. We have a base class and then concrete CoffeeMakingTask and PotatoPeelingTask. +Let's first look at our task hierarchy. We have a base class and then concrete `CoffeeMakingTask` +and `PotatoPeelingTask`. ```java public abstract class Task { @@ -88,8 +98,8 @@ public class PotatoPeelingTask extends Task { } ``` -Next we present a runnable Worker class that the thread pool will utilize to handle all the potato peeling and coffee -making. +Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato +peeling and coffee making. ```java public class Worker implements Runnable { @@ -156,9 +166,11 @@ Now we are ready to show the full example in action. ``` ## Class diagram -![alt text](./etc/thread-pool.png "Thread Pool") + +![alt text](./etc/thread_pool_urm.png "Thread Pool") ## 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/etc/thread-pool.png b/thread-pool/etc/thread-pool.png deleted file mode 100644 index 9e88a9196..000000000 Binary files a/thread-pool/etc/thread-pool.png and /dev/null differ diff --git a/thread-pool/etc/thread-pool.ucls b/thread-pool/etc/thread-pool.ucls deleted file mode 100644 index 9a4be91b6..000000000 --- a/thread-pool/etc/thread-pool.ucls +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/thread-pool/etc/thread_pool_urm.png b/thread-pool/etc/thread_pool_urm.png new file mode 100644 index 000000000..3d433824f Binary files /dev/null and b/thread-pool/etc/thread_pool_urm.png differ diff --git a/thread-pool/pom.xml b/thread-pool/pom.xml index 0ea0b1266..3727bd240 100644 --- a/thread-pool/pom.xml +++ b/thread-pool/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT thread-pool 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 ca6dc30e4..4c1cf90d6 100644 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java @@ -25,15 +25,17 @@ package com.iluwatar.threadpool; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test * * @author ilkka */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/throttling/README.md b/throttling/README.md index 48e1b1c78..4a77638e1 100644 --- a/throttling/README.md +++ b/throttling/README.md @@ -10,12 +10,15 @@ tags: --- ## Intent + Ensure that a given client is not able to access service resources more than the assigned limit. ## Explanation + Real world example -> A large multinational corporation offers API to its customers. The API is rate-limited and each customer can only make certain amount of calls per second. +> A large multinational corporation offers API to its customers. The API is rate-limited and each +> customer can only make certain amount of calls per second. In plain words @@ -23,7 +26,9 @@ In plain words [Microsoft documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling) says -> Control the consumption of resources used by an instance of an application, an individual tenant, or an entire service. This can allow the system to continue to function and meet service level agreements, even when an increase in demand places an extreme load on resources. +> Control the consumption of resources used by an instance of an application, an individual tenant, +> or an entire service. This can allow the system to continue to function and meet service level +> agreements, even when an increase in demand places an extreme load on resources. **Programmatic Example** @@ -32,8 +37,8 @@ Tenant class presents the clients of the API. CallsCount tracks the number of AP ```java public class Tenant { - private String name; - private int allowedCallsPerSecond; + private final String name; + private final int allowedCallsPerSecond; public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) { if (allowedCallsPerSecond < 0) { @@ -56,7 +61,7 @@ public class Tenant { public final class CallsCount { private static final Logger LOGGER = LoggerFactory.getLogger(CallsCount.class); - private Map tenantCallsCount = new ConcurrentHashMap<>(); + private final Map tenantCallsCount = new ConcurrentHashMap<>(); public void addTenant(String tenantName) { tenantCallsCount.putIfAbsent(tenantName, new AtomicLong(0)); @@ -77,7 +82,8 @@ public final class CallsCount { } ``` -Next we introduce the service that the tenants are calling. To track the call count we use the throttler timer. +Next we introduce the service that the tenants are calling. To track the call count we use the +throttler timer. ```java public interface Throttler { @@ -134,7 +140,8 @@ class B2BService { } ``` -Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per second and Nike to 6. +Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per +second and Nike to 6. ```java public static void main(String[] args) { @@ -171,9 +178,11 @@ Now we are ready to see the full example in action. Tenant Adidas is rate-limite ## Class diagram -![alt text](./etc/throttling-pattern.png "Throttling pattern class diagram") + +![alt text](./etc/throttling_urm.png "Throttling pattern class diagram") ## Applicability + The Throttling pattern should be used: * When a service access needs to be restricted to not have high impacts on the performance of the service. diff --git a/throttling/etc/throttling-pattern.png b/throttling/etc/throttling-pattern.png deleted file mode 100644 index 59e590aaf..000000000 Binary files a/throttling/etc/throttling-pattern.png and /dev/null differ diff --git a/throttling/etc/throttling-pattern.ucls b/throttling/etc/throttling-pattern.ucls deleted file mode 100644 index 8d4d8a5a2..000000000 --- a/throttling/etc/throttling-pattern.ucls +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/throttling/etc/throttling_urm.png b/throttling/etc/throttling_urm.png new file mode 100644 index 000000000..a9824e24b Binary files /dev/null and b/throttling/etc/throttling_urm.png differ diff --git a/throttling/pom.xml b/throttling/pom.xml index 6ae062c5e..ba2ca9c47 100644 --- a/throttling/pom.xml +++ b/throttling/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 diff --git a/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java index 8f8036286..115d6781e 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java +++ b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java @@ -24,7 +24,6 @@ package com.iluwatar.throttling; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; @@ -38,7 +37,7 @@ import org.slf4j.LoggerFactory; public final class CallsCount { private static final Logger LOGGER = LoggerFactory.getLogger(CallsCount.class); - private Map tenantCallsCount = new ConcurrentHashMap<>(); + private final Map tenantCallsCount = new ConcurrentHashMap<>(); /** * Add a new tenant to the map. diff --git a/throttling/src/main/java/com/iluwatar/throttling/Tenant.java b/throttling/src/main/java/com/iluwatar/throttling/Tenant.java index d94344428..5fe2c72db 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/Tenant.java +++ b/throttling/src/main/java/com/iluwatar/throttling/Tenant.java @@ -30,8 +30,8 @@ import java.security.InvalidParameterException; */ public class Tenant { - private String name; - private int allowedCallsPerSecond; + private final String name; + private final int allowedCallsPerSecond; /** * Constructor. diff --git a/throttling/src/test/java/com/iluwatar/throttling/AppTest.java b/throttling/src/test/java/com/iluwatar/throttling/AppTest.java index daf8c2f21..dcabb02b3 100644 --- a/throttling/src/test/java/com/iluwatar/throttling/AppTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.throttling; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java b/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java index 6a328c3f0..786325237 100644 --- a/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/B2BServiceTest.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test; */ public class B2BServiceTest { - private CallsCount callsCount = new CallsCount(); + private final CallsCount callsCount = new CallsCount(); @Test public void dummyCustomerApiTest() { diff --git a/tls/pom.xml b/tls/pom.xml index 7100ae295..54e911e40 100644 --- a/tls/pom.xml +++ b/tls/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT tls diff --git a/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java b/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java index c4e885896..4e5c14e0b 100644 --- a/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java +++ b/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java @@ -1,93 +1,93 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.tls; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.concurrent.Callable; -import java.util.stream.IntStream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * DateFormatCallable converts string dates to a date format using SimpleDateFormat. The date format - * and the date value will be passed to the Callable by the constructor. The constructor creates a - * instance of SimpleDateFormat and stores it in a ThreadLocal class variable. For the complete - * description of the example see {@link App}. - * - *

You can comment out the code marked with //TLTL and comment in the code marked //NTLNTL. Then - * you can see what will happen if you do not use the ThreadLocal. For details see the description - * of {@link App} - * - * @author Thomas Bauer, 2017 - */ -public class DateFormatCallable implements Callable { - - private static final Logger LOGGER = LoggerFactory.getLogger(DateFormatCallable.class); - // class variables (members) - private ThreadLocal df; //TLTL - // private DateFormat df; //NTLNTL - - private String dateValue; // for dateValue Thread Local not needed - - - /** - * The date format and the date value are passed to the constructor. - * - * @param inDateFormat string date format string, e.g. "dd/MM/yyyy" - * @param inDateValue string date value, e.g. "21/06/2016" - */ - public DateFormatCallable(String inDateFormat, String inDateValue) { - final var idf = inDateFormat; //TLTL - this.df = ThreadLocal.withInitial(() -> { //TLTL - return new SimpleDateFormat(idf); //TLTL - }); //TLTL - // this.df = new SimpleDateFormat(inDateFormat); //NTLNTL - this.dateValue = inDateValue; - } - - @Override - public Result call() { - LOGGER.info(Thread.currentThread() + " started executing..."); - var result = new Result(); - - // Convert date value to date 5 times - IntStream.rangeClosed(1, 5).forEach(i -> { - try { - // this is the statement where it is important to have the - // instance of SimpleDateFormat locally - // Create the date value and store it in dateList - result.getDateList().add(this.df.get().parse(this.dateValue)); //TLTL - // result.getDateList().add(this.df.parse(this.dateValue)); //NTLNTL - } catch (Exception e) { - // write the Exception to a list and continue work - result.getExceptionList().add(e.getClass() + ": " + e.getMessage()); - } - }); - - LOGGER.info(Thread.currentThread() + " finished processing part of the thread"); - - return result; - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.tls; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.concurrent.Callable; +import java.util.stream.IntStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * DateFormatCallable converts string dates to a date format using SimpleDateFormat. The date format + * and the date value will be passed to the Callable by the constructor. The constructor creates a + * instance of SimpleDateFormat and stores it in a ThreadLocal class variable. For the complete + * description of the example see {@link App}. + * + *

You can comment out the code marked with //TLTL and comment in the code marked //NTLNTL. Then + * you can see what will happen if you do not use the ThreadLocal. For details see the description + * of {@link App} + * + * @author Thomas Bauer, 2017 + */ +public class DateFormatCallable implements Callable { + + private static final Logger LOGGER = LoggerFactory.getLogger(DateFormatCallable.class); + // class variables (members) + private final ThreadLocal df; //TLTL + // private DateFormat df; //NTLNTL + + private final String dateValue; // for dateValue Thread Local not needed + + + /** + * The date format and the date value are passed to the constructor. + * + * @param inDateFormat string date format string, e.g. "dd/MM/yyyy" + * @param inDateValue string date value, e.g. "21/06/2016" + */ + public DateFormatCallable(String inDateFormat, String inDateValue) { + final var idf = inDateFormat; //TLTL + this.df = ThreadLocal.withInitial(() -> { //TLTL + return new SimpleDateFormat(idf); //TLTL + }); //TLTL + // this.df = new SimpleDateFormat(inDateFormat); //NTLNTL + this.dateValue = inDateValue; + } + + @Override + public Result call() { + LOGGER.info(Thread.currentThread() + " started executing..."); + var result = new Result(); + + // Convert date value to date 5 times + IntStream.rangeClosed(1, 5).forEach(i -> { + try { + // this is the statement where it is important to have the + // instance of SimpleDateFormat locally + // Create the date value and store it in dateList + result.getDateList().add(this.df.get().parse(this.dateValue)); //TLTL + // result.getDateList().add(this.df.parse(this.dateValue)); //NTLNTL + } catch (Exception e) { + // write the Exception to a list and continue work + result.getExceptionList().add(e.getClass() + ": " + e.getMessage()); + } + }); + + LOGGER.info(Thread.currentThread() + " finished processing part of the thread"); + + return result; + } +} diff --git a/tls/src/main/java/com/iluwatar/tls/Result.java b/tls/src/main/java/com/iluwatar/tls/Result.java index c98a07b91..38dc197cf 100644 --- a/tls/src/main/java/com/iluwatar/tls/Result.java +++ b/tls/src/main/java/com/iluwatar/tls/Result.java @@ -1,65 +1,65 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/* - * Fiducia IT AG, All rights reserved. Use is subject to license terms. - */ - -package com.iluwatar.tls; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * Result object that will be returned by the Callable {@link DateFormatCallable} used in {@link - * App}. - * - * @author Thomas Bauer, 2017 - */ -public class Result { - // A list to collect the date values created in one thread - private List dateList = new ArrayList<>(); - - // A list to collect Exceptions thrown in one threads (should be none in - // this example) - private List exceptionList = new ArrayList<>(); - - /** - * Get list of date values collected within a thread execution. - * - * @return List of date values collected within an thread execution - */ - public List getDateList() { - return dateList; - } - - /** - * Get list of exceptions thrown within a thread execution. - * - * @return List of exceptions thrown within an thread execution - */ - public List getExceptionList() { - return exceptionList; - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Fiducia IT AG, All rights reserved. Use is subject to license terms. + */ + +package com.iluwatar.tls; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Result object that will be returned by the Callable {@link DateFormatCallable} used in {@link + * App}. + * + * @author Thomas Bauer, 2017 + */ +public class Result { + // A list to collect the date values created in one thread + private final List dateList = new ArrayList<>(); + + // A list to collect Exceptions thrown in one threads (should be none in + // this example) + private final List exceptionList = new ArrayList<>(); + + /** + * Get list of date values collected within a thread execution. + * + * @return List of date values collected within an thread execution + */ + public List getDateList() { + return dateList; + } + + /** + * Get list of exceptions thrown within a thread execution. + * + * @return List of exceptions thrown within an thread execution + */ + public List getExceptionList() { + return exceptionList; + } +} diff --git a/tls/src/test/java/com/iluwatar/tls/AppTest.java b/tls/src/test/java/com/iluwatar/tls/AppTest.java index bae673b97..ba3774b3e 100644 --- a/tls/src/test/java/com/iluwatar/tls/AppTest.java +++ b/tls/src/test/java/com/iluwatar/tls/AppTest.java @@ -25,14 +25,16 @@ package com.iluwatar.tls; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Tests that thread local storage example runs without errors. * * @author Thomas Bauer, January 2017 */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java index 48e5854a3..5338829d0 100644 --- a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java @@ -66,18 +66,18 @@ public class DateFormatCallableTest { /** * Expected number of date values in the date value list created by the run of DateFormatRunnalbe */ - private int expectedCounterDateValues = 5; + private final int expectedCounterDateValues = 5; /** * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. */ - private int expectedCounterExceptions = 0; + private final int expectedCounterExceptions = 0; /** * Expected content of the list containing the date values created by the run of * DateFormatRunnalbe */ - private List expectedDateValues = + private final List expectedDateValues = List.of("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); /** diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java index 8b02faf0b..7b3d6b4ad 100644 --- a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java @@ -54,18 +54,18 @@ public class DateFormatCallableTestIncorrectDateFormat { /** * Expected number of date values in the date value list created by the run of DateFormatRunnalbe */ - private int expectedCounterDateValues = 0; + private final int expectedCounterDateValues = 0; /** * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. */ - private int expectedCounterExceptions = 5; + private final int expectedCounterExceptions = 5; /** * Expected content of the list containing the exceptions created by the run of * DateFormatRunnalbe */ - private List expectedExceptions = List.of( + private final List expectedExceptions = List.of( "class java.text.ParseException: Unparseable date: \"15.12.2015\"", "class java.text.ParseException: Unparseable date: \"15.12.2015\"", "class java.text.ParseException: Unparseable date: \"15.12.2015\"", diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java index c0e8e1844..b3328d4c5 100644 --- a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java @@ -55,7 +55,7 @@ public class DateFormatCallableTestMultiThread { * Result object given back by DateFormatCallable, one for each thread -- Array with converted * date values -- Array with thrown exceptions */ - private static Result[] result = new Result[4]; + private static final Result[] result = new Result[4]; /** * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() @@ -66,22 +66,22 @@ public class DateFormatCallableTestMultiThread { /* nothing needed here */ } - private static List[] createdDateValues = new StringArrayList[4]; + private static final List[] createdDateValues = new StringArrayList[4]; /** * Expected number of date values in the date value list created by each thread */ - private int expectedCounterDateValues = 5; + private final int expectedCounterDateValues = 5; /** * Expected number of exceptions in the exception list created by each thread */ - private int expectedCounterExceptions = 0; + private final int expectedCounterExceptions = 0; /** * Expected content of the list containing the date values created by each thread */ - private List expectedDateValues = + private final List expectedDateValues = List.of("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); /** diff --git a/tolerant-reader/README.md b/tolerant-reader/README.md index a62e5f4cd..30517b7b8 100644 --- a/tolerant-reader/README.md +++ b/tolerant-reader/README.md @@ -9,14 +9,18 @@ tags: --- ## 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. + +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. ## Explanation + Real world example -> We are persisting rainbowfish objects to file and later on they need to be restored. What makes it problematic is that rainbowfish data structure is versioned and evolves over time. New version of rainbowfish needs to be able to restore old versions as well. +> We are persisting rainbowfish objects to file and later on they need to be restored. What makes it +> problematic is that rainbowfish data structure is versioned and evolves over time. New version of +> rainbowfish needs to be able to restore old versions as well. In plain words @@ -24,21 +28,21 @@ In plain words [Robustness Principle](https://java-design-patterns.com/principles/#robustness-principle) says -> Be conservative in what you do, be liberal in what you accept from others +> Be conservative in what you do, be liberal in what you accept from others. **Programmatic Example** -Here's the versioned rainbowfish. Notice how the second version introduces additional properties. +Here's the versioned `RainbowFish`. Notice how the second version introduces additional properties. ```java public class RainbowFish implements Serializable { private static final long serialVersionUID = 1L; - private String name; - private int age; - private int lengthMeters; - private int weightTons; + private final String name; + private final int age; + private final int lengthMeters; + private final int weightTons; /** * Constructor. @@ -104,7 +108,8 @@ public class RainbowFishV2 extends RainbowFish { } ``` -Next we introduce the rainbowfish serializer. This is the class that implements the Tolerant Reader pattern. +Next we introduce the `RainbowFishSerializer`. This is the class that implements the Tolerant Reader +pattern. ```java public final class RainbowFishSerializer { @@ -185,18 +190,23 @@ And finally here's the full example in action. LOGGER.info("deserializedFishV2 name={} age={} length={} weight={}", deserializedFishV2.getName(), deserializedFishV2.getAge(), deserializedFishV2.getLengthMeters(), deserializedFishV2.getWeightTons()); - - // fishV1 name=Zed age=10 length=11 weight=12 - // deserializedFishV1 name=Zed age=10 length=11 weight=12 - // fishV2 name=Scar age=5 length=12 weight=15 sleeping=true hungry=true angry=true - // deserializedFishV2 name=Scar age=5 length=12 weight=15 ``` +Program output: + +``` +fishV1 name=Zed age=10 length=11 weight=12 +deserializedFishV1 name=Zed age=10 length=11 weight=12 +fishV2 name=Scar age=5 length=12 weight=15 sleeping=true hungry=true angry=true +deserializedFishV2 name=Scar age=5 length=12 weight=15 +``` ## Class diagram -![alt text](./etc/tolerant-reader.png "Tolerant Reader") + +![alt text](./etc/tolerant_reader_urm.png "Tolerant Reader") ## Applicability + Use the Tolerant Reader pattern when * The communication schema can evolve and change and yet the receiving side should not break diff --git a/tolerant-reader/etc/tolerant-reader.png b/tolerant-reader/etc/tolerant-reader.png deleted file mode 100644 index 45f0302a2..000000000 Binary files a/tolerant-reader/etc/tolerant-reader.png and /dev/null differ diff --git a/tolerant-reader/etc/tolerant-reader.ucls b/tolerant-reader/etc/tolerant-reader.ucls deleted file mode 100644 index 92e069f8e..000000000 --- a/tolerant-reader/etc/tolerant-reader.ucls +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tolerant-reader/etc/tolerant_reader_urm.png b/tolerant-reader/etc/tolerant_reader_urm.png new file mode 100644 index 000000000..9f5499172 Binary files /dev/null and b/tolerant-reader/etc/tolerant_reader_urm.png differ diff --git a/tolerant-reader/pom.xml b/tolerant-reader/pom.xml index 2966cca19..6cc0ac283 100644 --- a/tolerant-reader/pom.xml +++ b/tolerant-reader/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT tolerant-reader 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 775fc98f7..7529435fe 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java @@ -32,10 +32,10 @@ public class RainbowFish implements Serializable { private static final long serialVersionUID = 1L; - private String name; - private int age; - private int lengthMeters; - private int weightTons; + private final String name; + private final int age; + private final int lengthMeters; + private final int weightTons; /** * Constructor. 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 f6a9e6a0e..98ad31fe5 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java @@ -38,6 +38,9 @@ import java.util.Map; */ public final class RainbowFishSerializer { + public static final String LENGTH_METERS = "lengthMeters"; + public static final String WEIGHT_TONS = "weightTons"; + private RainbowFishSerializer() { } @@ -48,8 +51,8 @@ public final class RainbowFishSerializer { var map = Map.of( "name", rainbowFish.getName(), "age", String.format("%d", rainbowFish.getAge()), - "lengthMeters", String.format("%d", rainbowFish.getLengthMeters()), - "weightTons", String.format("%d", rainbowFish.getWeightTons()) + LENGTH_METERS, String.format("%d", rainbowFish.getLengthMeters()), + WEIGHT_TONS, String.format("%d", rainbowFish.getWeightTons()) ); try (var fileOut = new FileOutputStream(filename); @@ -65,8 +68,8 @@ public final class RainbowFishSerializer { var map = Map.of( "name", rainbowFish.getName(), "age", String.format("%d", rainbowFish.getAge()), - "lengthMeters", String.format("%d", rainbowFish.getLengthMeters()), - "weightTons", String.format("%d", rainbowFish.getWeightTons()), + "lengthMeters", String.format("%d", rainbowFish.getLengthMeters()), + WEIGHT_TONS, String.format("%d", rainbowFish.getWeightTons()), "angry", Boolean.toString(rainbowFish.getAngry()), "hungry", Boolean.toString(rainbowFish.getHungry()), "sleeping", Boolean.toString(rainbowFish.getSleeping()) @@ -93,7 +96,7 @@ public final class RainbowFishSerializer { map.get("name"), Integer.parseInt(map.get("age")), Integer.parseInt(map.get("lengthMeters")), - Integer.parseInt(map.get("weightTons")) + Integer.parseInt(map.get(WEIGHT_TONS)) ); } } 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 d28e118d2..4b9432408 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java @@ -29,19 +29,21 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() throws ClassNotFoundException, IOException { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } @BeforeEach @AfterEach - public void cleanup() { + void cleanup() { var file1 = new File("fish1.out"); file1.delete(); var file2 = new File("fish2.out"); diff --git a/trampoline/README.md b/trampoline/README.md index 8831f41e1..d2ff7386b 100644 --- a/trampoline/README.md +++ b/trampoline/README.md @@ -10,22 +10,23 @@ tags: ## Intent -Trampoline pattern is used for implementing algorithms recursively in Java without blowing the stack and to interleave -the execution of functions without hard coding them together. +Trampoline pattern is used for implementing algorithms recursively in Java without blowing the stack +and to interleave the execution of functions without hard coding them together. ## Explanation Recursion is a frequently adopted technique for solving algorithmic problems in a divide and conquer -style. For example calculating fibonacci accumulating sum and factorials. In these kinds of problems recursion is -more straightforward than their loop counterpart. Furthermore recursion may need less code and looks more concise. -There is a saying that every recursion problem can be solved using a loop with the cost of writing code that is more -difficult to understand. +style. For example calculating fibonacci accumulating sum and factorials. In these kinds of problems +recursion is more straightforward than their loop counterpart. Furthermore recursion may need less +code and looks more concise. There is a saying that every recursion problem can be solved using +a loop with the cost of writing code that is more difficult to understand. -However recursion type solutions have one big caveat. For each recursive call it typically needs an intermediate value -stored and there is a limited amount of stack memory available. Running out of stack memory creates a stack overflow -error and halts the program execution. +However recursion type solutions have one big caveat. For each recursive call it typically needs +an intermediate value stored and there is a limited amount of stack memory available. Running out of +stack memory creates a stack overflow error and halts the program execution. -Trampoline pattern is a trick that allows us define recursive algorithms in Java without blowing the stack. +Trampoline pattern is a trick that allows us define recursive algorithms in Java without blowing the +stack. Real world example @@ -37,14 +38,18 @@ In plain words Wikipedia says -> In Java, trampoline refers to using reflection to avoid using inner classes, for example in event listeners. The time overhead of a reflection call is traded for the space overhead of an inner class. Trampolines in Java usually involve the creation of a GenericListener to pass events to an outer class. +> In Java, trampoline refers to using reflection to avoid using inner classes, for example in event +> listeners. The time overhead of a reflection call is traded for the space overhead of an inner +> class. Trampolines in Java usually involve the creation of a GenericListener to pass events to +> an outer class. **Programmatic Example** Here's the `Trampoline` implementation in Java. -When `get` is called on the returned Trampoline, internally it will iterate calling `jump` on the returned `Trampoline` -as long as the concrete instance returned is `Trampoline`, stopping once the returned instance is `done`. +When `get` is called on the returned Trampoline, internally it will iterate calling `jump` on the +returned `Trampoline` as long as the concrete instance returned is `Trampoline`, stopping once the +returned instance is `done`. ```java public interface Trampoline { @@ -110,15 +115,21 @@ Using the `Trampoline` to get Fibonacci values. log.info("start pattern"); var result = loop(10, 1).result(); log.info("result {}", result); - - // start pattern - // result 3628800 +``` + +Program output: + +``` +start pattern +result 3628800 ``` ## Class diagram + ![alt text](./etc/trampoline.urm.png "Trampoline pattern class diagram") ## Applicability + Use the Trampoline pattern when * For implementing tail recursive function. This pattern allows to switch on a stackless operation. diff --git a/trampoline/pom.xml b/trampoline/pom.xml index 13988ca92..d78236473 100644 --- a/trampoline/pom.xml +++ b/trampoline/pom.xml @@ -30,16 +30,11 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT trampoline - - junit - junit - test - org.junit.jupiter diff --git a/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java b/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java index 7b5a564cf..40117a67a 100644 --- a/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java +++ b/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java @@ -98,12 +98,12 @@ public interface Trampoline { return trampoline(this); } - T trampoline(final Trampoline trampoline) { + private T trampoline(final Trampoline trampoline) { return Stream.iterate(trampoline, Trampoline::jump) .filter(Trampoline::complete) .findFirst() .map(Trampoline::result) - .orElseThrow(); + .get(); } }; } diff --git a/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java b/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java index c5d6571cd..664a95634 100644 --- a/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java +++ b/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java @@ -23,10 +23,9 @@ package com.iluwatar.trampoline; -import static org.junit.Assert.assertEquals; - -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; /** * Test for trampoline pattern. @@ -37,7 +36,7 @@ public class TrampolineAppTest { @Test public void testTrampolineWithFactorialFunction() { long result = TrampolineApp.loop(10, 1).result(); - assertEquals("Be equal", 3628800, result); + assertEquals(3_628_800, result); } } \ No newline at end of file diff --git a/transaction-script/.gitignore b/transaction-script/.gitignore new file mode 100644 index 000000000..431845ed0 --- /dev/null +++ b/transaction-script/.gitignore @@ -0,0 +1,2 @@ +/target/ +.idea/ diff --git a/transaction-script/README.md b/transaction-script/README.md new file mode 100644 index 000000000..3ae3cf4c1 --- /dev/null +++ b/transaction-script/README.md @@ -0,0 +1,119 @@ +--- +layout: pattern +title: Transaction Script +folder: transaction-script +permalink: /patterns/transaction-script/ +categories: Behavioral +tags: + - Data access +--- + +## Intent + +Transaction Script organizes business logic by procedures where each procedure handles a single +request from the presentation. + +## Explanation + +Real world example + +> You need to create a hotel room booking system. Since the requirements are quite simple we intend +> to use the Transaction Script pattern here. + +In plain words + +> Transaction Script organizes business logic into transactions that the system needs to carry out. + +Programmatic example + +The `Hotel` class takes care of booking and cancelling room reservations. + +```java +public class Hotel { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + private final HotelDaoImpl hotelDao; + + public Hotel(HotelDaoImpl hotelDao) { + this.hotelDao = hotelDao; + } + + public void bookRoom(int roomNumber) throws Exception { + + Optional room = hotelDao.getById(roomNumber); + + if (room.isEmpty()) { + throw new Exception("Room number: " + roomNumber + " does not exist"); + } else { + if (room.get().isBooked()) { + throw new Exception("Room already booked!"); + } else { + Room updateRoomBooking = room.get(); + updateRoomBooking.setBooked(true); + hotelDao.update(updateRoomBooking); + } + } + } + + public void cancelRoomBooking(int roomNumber) throws Exception { + + Optional room = hotelDao.getById(roomNumber); + + if (room.isEmpty()) { + throw new Exception("Room number: " + roomNumber + " does not exist"); + } else { + if (room.get().isBooked()) { + Room updateRoomBooking = room.get(); + updateRoomBooking.setBooked(false); + int refundAmount = updateRoomBooking.getPrice(); + hotelDao.update(updateRoomBooking); + + LOGGER.info("Booking cancelled for room number: " + roomNumber); + LOGGER.info(refundAmount + " is refunded"); + } else { + throw new Exception("No booking for the room exists"); + } + } + } +} +``` + +The `Hotel` class has two methods, one for booking and cancelling a room respectively. Each one of +them handles a single transaction in the system, making `Hotel` implement the Transaction Script +pattern. + +The `bookRoom` method consolidates all the needed steps like checking if the room is already booked +or not, if not booked then books the room and updates the database by using the DAO. + +The `cancelRoom` method consolidates steps like checking if the room is booked or not, +if booked then calculates the refund amount and updates the database using the DAO. + +## Class diagram + +![alt text](./etc/transaction-script.png "Transaction script model") + +## Applicability + +Use the Transaction Script pattern when the application has only a small amount of logic and that +logic won't be extended in the future. + +## Consequences + +* As the business logic gets more complicated, +it gets progressively harder to keep the transaction script +in a well-designed state. +* Code duplication between transaction scripts can occur. +* Normally not easy to refactor transactions script to other domain logic +patterns. + +## Related patterns + +* Domain Model +* Table Module +* Service Layer + +## Credits + +* [Transaction Script Pattern](https://dzone.com/articles/transaction-script-pattern#:~:text=Transaction%20Script%20(TS)%20is%20the,need%20big%20architecture%20behind%20them.) +* [Transaction Script](https://www.informit.com/articles/article.aspx?p=1398617) +* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=18acc13ba60d66690009505577c45c04) diff --git a/transaction-script/etc/transaction-script.png b/transaction-script/etc/transaction-script.png new file mode 100644 index 000000000..6d0cffb6a Binary files /dev/null and b/transaction-script/etc/transaction-script.png differ diff --git a/transaction-script/etc/transaction-script.urm.puml b/transaction-script/etc/transaction-script.urm.puml new file mode 100644 index 000000000..e8d172377 --- /dev/null +++ b/transaction-script/etc/transaction-script.urm.puml @@ -0,0 +1,65 @@ +@startuml +package com.ashishtrivedi16.transaction-script { + class App { + - H2_DB_URL : String {static} + - LOGGER : Logger {static} + - addRooms(hotelDaoImpl : HotelDaoImpl) {static} + - createDataSource() : DataSource {static} + - createSchema(dataSource : DataSource) {static} + - deleteSchema(dataSource : DataSource) {static} + - getRoomsStatus(hotelDaoImpl : HotelDaoImpl) {static} + - generateSampleRooms() : List {static} + + main(args : String[]) {static} + } + class Room { + - id: Int + - roomType: String + - price: Int + - booked: Boolean + + Customer(id : int, roomType : String, price: Int, booked: Boolean) + + getId() : int + + getRoomType() : String + + getPrice() : Int + + isBooked() : Boolean + + setId(id : int) + + setRoomType(roomType : String) + + setPrice(price : Int) + + setBooked(booked : boolean) + + equals(that : Object) : boolean + + hashCode() : int + + toString() : String + } + interface HotelDao { + + add(Room) : boolean {abstract} + + delete(Room) : boolean {abstract} + + getAll() : Stream {abstract} + + getById(int) : Optional {abstract} + + update(Room) : boolean {abstract} + } + class RoomSchemaSql { + + CREATE_SCHEMA_SQL : String {static} + + DELETE_SCHEMA_SQL : String {static} + - RoomSchemaSql() + } + class HotelDaoImpl { + - dataSource : DataSource + + HotelDaoImpl(dataSource : DataSource) + + add(room : Room) : boolean + - createRoom(resultSet : ResultSet) : Room + + delete(room : Room) : boolean + + getAll() : Stream + + getById(id : int) : Optional + - getConnection() : Connection + - mutedClose(connection : Connection, statement : PreparedStatement, resultSet : ResultSet) + + update(room : Room) : boolean + } + class Hotel { + - LOGGER : Logger {static} + - hotelDao: HotelDaoImpl + + Hotel(hotelDao: HotelDaoImpl) + + bookRoom(roomNumber: Int) + + cancelRoomBooking(roomNumber: Int) + } +} +HotelDaoImpl ..|> HotelDao +@enduml diff --git a/transaction-script/pom.xml b/transaction-script/pom.xml new file mode 100644 index 000000000..7a6f40219 --- /dev/null +++ b/transaction-script/pom.xml @@ -0,0 +1,73 @@ + + + + + java-design-patterns + com.iluwatar + 1.24.0-SNAPSHOT + + 4.0.0 + + transaction-script + + + + com.h2database + h2 + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.transactionscript.App + + + + + + + + + + diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java new file mode 100644 index 000000000..59ea5363d --- /dev/null +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java @@ -0,0 +1,145 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.transactionscript; + +import java.util.List; +import javax.sql.DataSource; +import org.h2.jdbcx.JdbcDataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Transaction Script (TS) is one of the simplest domain logic pattern. + * It needs less work to implement than other domain logic patterns and therefore + * it’s perfect fit for smaller applications that don't need big architecture behind them. + * + *

In this example we will use the TS pattern to implement booking and cancellation + * methods for a Hotel management App. The main method will initialise an instance of + * {@link Hotel} and add rooms to it. After that it will book and cancel a couple of rooms + * and that will be printed by the logger.

+ * + *

The thing we have to note here is that all the operations related to booking or cancelling + * a room like checking the database if the room exists, checking the booking status or the + * room, calculating refund price are all clubbed inside a single transaction script method.

+ */ +public class App { + + private static final String H2_DB_URL = "jdbc:h2:~/test"; + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Program entry point. + * Initialises an instance of Hotel and adds rooms to it. + * Carries out booking and cancel booking transactions. + * @param args command line arguments + * @throws Exception if any error occurs + */ + public static void main(String[] args) throws Exception { + + final var dataSource = createDataSource(); + deleteSchema(dataSource); + createSchema(dataSource); + final var dao = new HotelDaoImpl(dataSource); + + // Add rooms + addRooms(dao); + + // Print room booking status + getRoomStatus(dao); + + var hotel = new Hotel(dao); + + // Book rooms + hotel.bookRoom(1); + hotel.bookRoom(2); + hotel.bookRoom(3); + hotel.bookRoom(4); + hotel.bookRoom(5); + hotel.bookRoom(6); + + // Cancel booking for a few rooms + hotel.cancelRoomBooking(1); + hotel.cancelRoomBooking(3); + hotel.cancelRoomBooking(5); + + getRoomStatus(dao); + + deleteSchema(dataSource); + + } + + private static void getRoomStatus(HotelDaoImpl dao) throws Exception { + try (var customerStream = dao.getAll()) { + customerStream.forEach((customer) -> LOGGER.info(customer.toString())); + } + } + + private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); + } + } + + private static void createSchema(DataSource dataSource) throws Exception { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL); + } catch (Exception e) { + throw new Exception(e.getMessage(), e); + } + } + + /** + * Get database. + * + * @return h2 datasource + */ + private static DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setUrl(H2_DB_URL); + return dataSource; + } + + private static void addRooms(HotelDaoImpl hotelDao) throws Exception { + for (var room : generateSampleRooms()) { + hotelDao.add(room); + } + } + + /** + * Generate rooms. + * + * @return list of rooms + */ + private static List generateSampleRooms() { + final var room1 = new Room(1, "Single", 50, false); + final var room2 = new Room(2, "Double", 80, false); + final var room3 = new Room(3, "Queen", 120, false); + final var room4 = new Room(4, "King", 150, false); + final var room5 = new Room(5, "Single", 50, false); + final var room6 = new Room(6, "Double", 80, false); + return List.of(room1, room2, room3, room4, room5, room6); + } +} diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java new file mode 100644 index 000000000..c24ea4f68 --- /dev/null +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java @@ -0,0 +1,87 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.transactionscript; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Hotel { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + private final HotelDaoImpl hotelDao; + + public Hotel(HotelDaoImpl hotelDao) { + this.hotelDao = hotelDao; + } + + /** + * Book a room. + * + * @param roomNumber room to book + * @throws Exception if any error + */ + public void bookRoom(int roomNumber) throws Exception { + + var room = hotelDao.getById(roomNumber); + + if (room.isEmpty()) { + throw new Exception("Room number: " + roomNumber + " does not exist"); + } else { + if (room.get().isBooked()) { + throw new Exception("Room already booked!"); + } else { + var updateRoomBooking = room.get(); + updateRoomBooking.setBooked(true); + hotelDao.update(updateRoomBooking); + } + } + } + + /** + * Cancel a room booking. + * + * @param roomNumber room to cancel booking + * @throws Exception if any error + */ + public void cancelRoomBooking(int roomNumber) throws Exception { + + var room = hotelDao.getById(roomNumber); + + if (room.isEmpty()) { + throw new Exception("Room number: " + roomNumber + " does not exist"); + } else { + if (room.get().isBooked()) { + var updateRoomBooking = room.get(); + updateRoomBooking.setBooked(false); + int refundAmount = updateRoomBooking.getPrice(); + hotelDao.update(updateRoomBooking); + + LOGGER.info("Booking cancelled for room number: " + roomNumber); + LOGGER.info(refundAmount + " is refunded"); + } else { + throw new Exception("No booking for the room exists"); + } + } + } +} diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java new file mode 100644 index 000000000..b06211905 --- /dev/null +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java @@ -0,0 +1,40 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.transactionscript; + +import java.util.Optional; +import java.util.stream.Stream; + +public interface HotelDao { + + Stream getAll() throws Exception; + + Optional getById(int id) throws Exception; + + Boolean add(Room room) throws Exception; + + Boolean update(Room room) throws Exception; + + Boolean delete(Room room) throws Exception; +} diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java new file mode 100644 index 000000000..240f96892 --- /dev/null +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java @@ -0,0 +1,172 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.transactionscript; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Optional; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.sql.DataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HotelDaoImpl implements HotelDao { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + private final DataSource dataSource; + + public HotelDaoImpl(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public Stream getAll() throws Exception { + try { + var connection = getConnection(); + var statement = connection.prepareStatement("SELECT * FROM ROOMS"); + var resultSet = statement.executeQuery(); // NOSONAR + return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, + Spliterator.ORDERED) { + + @Override + public boolean tryAdvance(Consumer action) { + try { + if (!resultSet.next()) { + return false; + } + action.accept(createRoom(resultSet)); + return true; + } catch (Exception e) { + throw new RuntimeException(e); // NOSONAR + } + } + }, false).onClose(() -> { + try { + mutedClose(connection, statement, resultSet); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + }); + } catch (Exception e) { + throw new Exception(e.getMessage(), e); + } + } + + @Override + public Optional getById(int id) throws Exception { + ResultSet resultSet = null; + + try (var connection = getConnection(); + var statement = connection.prepareStatement("SELECT * FROM ROOMS WHERE ID = ?")) { + + statement.setInt(1, id); + resultSet = statement.executeQuery(); + if (resultSet.next()) { + return Optional.of(createRoom(resultSet)); + } else { + return Optional.empty(); + } + } catch (Exception e) { + throw new Exception(e.getMessage(), e); + } finally { + if (resultSet != null) { + resultSet.close(); + } + } + } + + @Override + public Boolean add(Room room) throws Exception { + if (getById(room.getId()).isPresent()) { + return false; + } + + try (var connection = getConnection(); + var statement = connection.prepareStatement("INSERT INTO ROOMS VALUES (?,?,?,?)")) { + statement.setInt(1, room.getId()); + statement.setString(2, room.getRoomType()); + statement.setInt(3, room.getPrice()); + statement.setBoolean(4, room.isBooked()); + statement.execute(); + return true; + } catch (Exception e) { + throw new Exception(e.getMessage(), e); + } + } + + @Override + public Boolean update(Room room) throws Exception { + try (var connection = getConnection(); + var statement = + connection + .prepareStatement("UPDATE ROOMS SET ROOM_TYPE = ?, PRICE = ?, BOOKED = ?" + + " WHERE ID = ?")) { + statement.setString(1, room.getRoomType()); + statement.setInt(2, room.getPrice()); + statement.setBoolean(3, room.isBooked()); + statement.setInt(4, room.getId()); + return statement.executeUpdate() > 0; + } catch (Exception e) { + throw new Exception(e.getMessage(), e); + } + } + + @Override + public Boolean delete(Room room) throws Exception { + try (var connection = getConnection(); + var statement = connection.prepareStatement("DELETE FROM ROOMS WHERE ID = ?")) { + statement.setInt(1, room.getId()); + return statement.executeUpdate() > 0; + } catch (Exception e) { + throw new Exception(e.getMessage(), e); + } + } + + private Connection getConnection() throws Exception { + return dataSource.getConnection(); + } + + private void mutedClose(Connection connection, PreparedStatement statement, ResultSet resultSet) + throws Exception { + try { + resultSet.close(); + statement.close(); + connection.close(); + } catch (Exception e) { + throw new Exception(e.getMessage(), e); + } + } + + private Room createRoom(ResultSet resultSet) throws Exception { + return new Room(resultSet.getInt("ID"), + resultSet.getString("ROOM_TYPE"), + resultSet.getInt("PRICE"), + resultSet.getBoolean("BOOKED")); + } +} diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java new file mode 100644 index 000000000..fedf4ed21 --- /dev/null +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java @@ -0,0 +1,123 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.transactionscript; + +/** + * A room POJO that represents the data that will be read from the data source. + */ +public class Room { + + private int id; + private String roomType; + private int price; + private boolean booked; + + /** + * Create an instance of room. + * @param id room id + * @param roomType room type + * @param price room price + * @param booked room booking status + */ + public Room(int id, String roomType, int price, boolean booked) { + this.id = id; + this.roomType = roomType; + this.price = price; + this.booked = booked; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getRoomType() { + return roomType; + } + + public void setRoomType(String roomType) { + this.roomType = roomType; + } + + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } + + public boolean isBooked() { + return booked; + } + + public void setBooked(boolean booked) { + this.booked = booked; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Room room = (Room) o; + + if (id != room.id) { + return false; + } + if (price != room.price) { + return false; + } + if (booked != room.booked) { + return false; + } + return roomType.equals(room.roomType); + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + roomType.hashCode(); + result = 31 * result + price; + result = 31 * result + (booked ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "Room{" + + "id=" + id + + ", roomType=" + roomType + + ", price=" + price + + ", booked=" + booked + + '}'; + } +} diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java new file mode 100644 index 000000000..4d919137a --- /dev/null +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.transactionscript; + +/** + * Customer Schema SQL Class. + */ +public final class RoomSchemaSql { + + public static final String CREATE_SCHEMA_SQL = + "CREATE TABLE ROOMS (ID NUMBER, ROOM_TYPE VARCHAR(100), PRICE INT(100), BOOKED VARCHAR(100))"; + public static final String DELETE_SCHEMA_SQL = "DROP TABLE ROOMS IF EXISTS"; + + private RoomSchemaSql() { + } + +} diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java new file mode 100644 index 000000000..eb819cabe --- /dev/null +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java @@ -0,0 +1,39 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.transactionscript; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Tests that Transaction script example runs without errors. + */ +class AppTest { + + @Test + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } +} diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java new file mode 100644 index 000000000..6dbbc8ff4 --- /dev/null +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java @@ -0,0 +1,273 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.transactionscript; + +import org.h2.jdbcx.JdbcDataSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.mockito.Mockito.*; + +/** + * Tests {@link HotelDaoImpl}. + */ +public class HotelDaoImplTest { + + private static final String DB_URL = "jdbc:h2:~/test"; + private HotelDaoImpl dao; + private Room existingRoom = new Room(1, "Single", 50, false); + + /** + * Creates rooms schema. + * + * @throws SQLException if there is any error while creating schema. + */ + @BeforeEach + public void createSchema() throws SQLException { + try (var connection = DriverManager.getConnection(DB_URL); + var statement = connection.createStatement()) { + statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); + statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL); + } + } + + /** + * Represents the scenario where DB connectivity is present. + */ + @Nested + public class ConnectionSuccess { + + /** + * Setup for connection success scenario. + * + * @throws Exception if any error occurs. + */ + @BeforeEach + public void setUp() throws Exception { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + dao = new HotelDaoImpl(dataSource); + var result = dao.add(existingRoom); + Assertions.assertTrue(result); + } + + /** + * Represents the scenario when DAO operations are being performed on a non existing room. + */ + @Nested + public class NonExistingRoom { + + @Test + public void addingShouldResultInSuccess() throws Exception { + try (var allRooms = dao.getAll()) { + assumeTrue(allRooms.count() == 1); + } + + final var nonExistingRoom = new Room(2, "Double", 80, false); + var result = dao.add(nonExistingRoom); + Assertions.assertTrue(result); + + assertRoomCountIs(2); + assertEquals(nonExistingRoom, dao.getById(nonExistingRoom.getId()).get()); + } + + @Test + public void deletionShouldBeFailureAndNotAffectExistingRooms() throws Exception { + final var nonExistingRoom = new Room(2, "Double", 80, false); + var result = dao.delete(nonExistingRoom); + + Assertions.assertFalse(result); + assertRoomCountIs(1); + } + + @Test + public void updationShouldBeFailureAndNotAffectExistingRooms() throws Exception { + final var nonExistingId = getNonExistingRoomId(); + final var newRoomType = "Double"; + final var newPrice = 80; + final var room = new Room(nonExistingId, newRoomType, newPrice, false); + var result = dao.update(room); + + Assertions.assertFalse(result); + assertFalse(dao.getById(nonExistingId).isPresent()); + } + + @Test + public void retrieveShouldReturnNoRoom() throws Exception { + assertFalse(dao.getById(getNonExistingRoomId()).isPresent()); + } + } + + /** + * Represents a scenario where DAO operations are being performed on an already existing + * room. + */ + @Nested + public class ExistingRoom { + + @Test + public void addingShouldResultInFailureAndNotAffectExistingRooms() throws Exception { + var existingRoom = new Room(1, "Single", 50, false); + var result = dao.add(existingRoom); + + Assertions.assertFalse(result); + assertRoomCountIs(1); + assertEquals(existingRoom, dao.getById(existingRoom.getId()).get()); + } + + @Test + public void deletionShouldBeSuccessAndRoomShouldBeNonAccessible() throws Exception { + var result = dao.delete(existingRoom); + + Assertions.assertTrue(result); + assertRoomCountIs(0); + assertFalse(dao.getById(existingRoom.getId()).isPresent()); + } + + @Test + public void updationShouldBeSuccessAndAccessingTheSameRoomShouldReturnUpdatedInformation() throws + Exception { + final var newRoomType = "Double"; + final var newPrice = 80; + final var newBookingStatus = false; + final var Room = new Room(existingRoom.getId(), newRoomType, newPrice, newBookingStatus); + var result = dao.update(Room); + + Assertions.assertTrue(result); + + final var room = dao.getById(existingRoom.getId()).get(); + assertEquals(newRoomType, room.getRoomType()); + assertEquals(newPrice, room.getPrice()); + assertEquals(newBookingStatus, room.isBooked()); + } + } + } + + /** + * Represents a scenario where DB connectivity is not present due to network issue, or DB service + * unavailable. + */ + @Nested + public class ConnectivityIssue { + + private static final String EXCEPTION_CAUSE = "Connection not available"; + + /** + * setup a connection failure scenario. + * + * @throws SQLException if any error occurs. + */ + @BeforeEach + public void setUp() throws SQLException { + dao = new HotelDaoImpl(mockedDatasource()); + } + + private DataSource mockedDatasource() throws SQLException { + var mockedDataSource = mock(DataSource.class); + var mockedConnection = mock(Connection.class); + var exception = new SQLException(EXCEPTION_CAUSE); + doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString()); + doReturn(mockedConnection).when(mockedDataSource).getConnection(); + return mockedDataSource; + } + + @Test + public void addingARoomFailsWithExceptionAsFeedbackToClient() { + assertThrows(Exception.class, () -> { + dao.add(new Room(2, "Double", 80, false)); + }); + } + + @Test + public void deletingARoomFailsWithExceptionAsFeedbackToTheClient() { + assertThrows(Exception.class, () -> { + dao.delete(existingRoom); + }); + } + + @Test + public void updatingARoomFailsWithFeedbackToTheClient() { + final var newRoomType = "Double"; + final var newPrice = 80; + final var newBookingStatus = false; + assertThrows(Exception.class, () -> { + dao.update(new Room(existingRoom.getId(), newRoomType, newPrice, newBookingStatus)); + }); + } + + @Test + public void retrievingARoomByIdFailsWithExceptionAsFeedbackToClient() { + assertThrows(Exception.class, () -> { + dao.getById(existingRoom.getId()); + }); + } + + @Test + public void retrievingAllRoomsFailsWithExceptionAsFeedbackToClient() { + assertThrows(Exception.class, () -> { + dao.getAll(); + }); + } + + } + + /** + * Delete room schema for fresh setup per test. + * + * @throws SQLException if any error occurs. + */ + @AfterEach + public void deleteSchema() throws SQLException { + try (var connection = DriverManager.getConnection(DB_URL); + var statement = connection.createStatement()) { + statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); + } + } + + private void assertRoomCountIs(int count) throws Exception { + try (var allRooms = dao.getAll()) { + assertEquals(count, allRooms.count()); + } + } + + /** + * An arbitrary number which does not correspond to an active Room id. + * + * @return an int of a room id which doesn't exist + */ + private int getNonExistingRoomId() { + return 999; + } +} diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java new file mode 100644 index 000000000..ab0265244 --- /dev/null +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java @@ -0,0 +1,151 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.transactionscript; + +import org.h2.jdbcx.JdbcDataSource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.sql.DataSource; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests {@link Hotel} + */ +public class HotelTest { + + private static final String H2_DB_URL = "jdbc:h2:~/test"; + + private Hotel hotel; + private HotelDaoImpl dao; + + @BeforeEach + public void setUp() throws Exception { + final var dataSource = createDataSource(); + deleteSchema(dataSource); + createSchema(dataSource); + dao = new HotelDaoImpl(dataSource); + addRooms(dao); + hotel = new Hotel(dao); + + } + + @Test + public void bookingRoomShouldChangeBookedStatusToTrue() throws Exception { + hotel.bookRoom(1); + assertTrue(dao.getById(1).get().isBooked()); + } + + @Test() + public void bookingRoomWithInvalidIdShouldRaiseException() { + assertThrows(Exception.class, () -> { + hotel.bookRoom(getNonExistingRoomId()); + }); + } + + @Test() + public void bookingRoomAgainShouldRaiseException() { + assertThrows(Exception.class, () -> { + hotel.bookRoom(1); + hotel.bookRoom(1); + }); + } + + @Test + public void NotBookingRoomShouldNotChangeBookedStatus() throws Exception { + assertFalse(dao.getById(1).get().isBooked()); + } + + @Test + public void cancelRoomBookingShouldChangeBookedStatus() throws Exception { + hotel.bookRoom(1); + assertTrue(dao.getById(1).get().isBooked()); + hotel.cancelRoomBooking(1); + assertFalse(dao.getById(1).get().isBooked()); + } + + @Test + public void cancelRoomBookingWithInvalidIdShouldRaiseException() { + assertThrows(Exception.class, () -> { + hotel.cancelRoomBooking(getNonExistingRoomId()); + }); + } + + @Test + public void cancelRoomBookingForUnbookedRoomShouldRaiseException() { + assertThrows(Exception.class, () -> { + hotel.cancelRoomBooking(1); + }); + } + + + private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); + } + } + + private static void createSchema(DataSource dataSource) throws Exception { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL); + } catch (Exception e) { + throw new Exception(e.getMessage(), e); + } + } + + public static DataSource createDataSource() { + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setUrl(H2_DB_URL); + return dataSource; + } + + private static void addRooms(HotelDaoImpl hotelDao) throws Exception { + for (var room : generateSampleRooms()) { + hotelDao.add(room); + } + } + + public static List generateSampleRooms() { + final var room1 = new Room(1, "Single", 50, false); + final var room2 = new Room(2, "Double", 80, false); + final var room3 = new Room(3, "Queen", 120, false); + final var room4 = new Room(4, "King", 150, false); + final var room5 = new Room(5, "Single", 50, false); + final var room6 = new Room(6, "Double", 80, false); + return List.of(room1, room2, room3, room4, room5, room6); + } + + /** + * An arbitrary number which does not correspond to an active Room id. + * + * @return an int of a room id which doesn't exist + */ + private int getNonExistingRoomId() { + return 999; + } +} diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java new file mode 100644 index 000000000..6755c2e5a --- /dev/null +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java @@ -0,0 +1,95 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.transactionscript; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +/** + * Tests {@link Room}. + */ +public class RoomTest { + + private Room room; + private static final int ID = 1; + private static final String ROOMTYPE = "Single"; + private static final int PRICE = 50; + private static final boolean BOOKED = false; + + @BeforeEach + public void setUp() { + room = new Room(ID, ROOMTYPE, PRICE, BOOKED); + } + + @Test + public void getAndSetId() { + final var newId = 2; + room.setId(newId); + assertEquals(newId, room.getId()); + } + + @Test + public void getAndSetRoomType() { + final var newRoomType = "Double"; + room.setRoomType(newRoomType); + assertEquals(newRoomType, room.getRoomType()); + } + + @Test + public void getAndSetLastName() { + final var newPrice = 60; + room.setPrice(newPrice); + assertEquals(newPrice, room.getPrice()); + } + + @Test + public void notEqualWithDifferentId() { + final var newId = 2; + final var otherRoom = new Room(newId, ROOMTYPE, PRICE, BOOKED); + assertNotEquals(room, otherRoom); + assertNotEquals(room.hashCode(), otherRoom.hashCode()); + } + + @Test + public void equalsWithSameObjectValues() { + final var otherRoom = new Room(ID, ROOMTYPE, PRICE, BOOKED); + assertEquals(room, otherRoom); + assertEquals(room.hashCode(), otherRoom.hashCode()); + } + + @Test + public void equalsWithSameObjects() { + assertEquals(room, room); + assertEquals(room.hashCode(), room.hashCode()); + } + + @Test + public void testToString() { + assertEquals(String.format("Room{id=%s, roomType=%s, price=%s, booked=%s}", + room.getId(), room.getRoomType(), room.getPrice(), room.isBooked()), room.toString()); + } +} diff --git a/twin/pom.xml b/twin/pom.xml index cb60511c9..b879ac593 100644 --- a/twin/pom.xml +++ b/twin/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT twin diff --git a/twin/src/test/java/com/iluwatar/twin/AppTest.java b/twin/src/test/java/com/iluwatar/twin/AppTest.java index 50b787eeb..38a3208ad 100644 --- a/twin/src/test/java/com/iluwatar/twin/AppTest.java +++ b/twin/src/test/java/com/iluwatar/twin/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.twin; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() throws Exception { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/twin/src/test/java/com/iluwatar/twin/BallItemTest.java b/twin/src/test/java/com/iluwatar/twin/BallItemTest.java index 568c1d7b0..18aba2bed 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallItemTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallItemTest.java @@ -108,7 +108,7 @@ public class BallItemTest { * Logging Appender Implementation */ public class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); diff --git a/typeobjectpattern/pom.xml b/typeobjectpattern/pom.xml index c8f0005af..03af8e78d 100644 --- a/typeobjectpattern/pom.xml +++ b/typeobjectpattern/pom.xml @@ -27,7 +27,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT typeobjectpattern diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java index e70acbf9e..80054a5fb 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java @@ -56,7 +56,7 @@ public class App { * * @param args command line args */ - public static void main(String[] args) throws FileNotFoundException, IOException, ParseException { + public static void main(String[] args) throws IOException, ParseException { var givenTime = 50; //50ms var toWin = 500; //points var pointsWon = 0; diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java index ec41dc6cd..7183c99df 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java @@ -30,15 +30,15 @@ package com.iluwatar.typeobject; public class Candy { enum Type { - crushableCandy, - rewardFruit + CRUSHABLE_CANDY, + REWARD_FRUIT } String name; Candy parent; String parentName; private int points; - private Type type; + private final Type type; Candy(String name, String parentName, Type type, int points) { this.name = name; diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java index 04e281a99..ab175e660 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java @@ -104,13 +104,13 @@ public class CandyGame { boolean continueRound() { for (var i = 0; i < this.cells.length; i++) { - if (this.cells[cells.length - 1][i].candy.getType().equals(Type.rewardFruit)) { + if (this.cells[cells.length - 1][i].candy.getType().equals(Type.REWARD_FRUIT)) { return true; } } for (var i = 0; i < this.cells.length; i++) { for (var j = 0; j < this.cells.length; j++) { - if (!this.cells[i][j].candy.getType().equals(Type.rewardFruit)) { + if (!this.cells[i][j].candy.getType().equals(Type.REWARD_FRUIT)) { var adj = adjacentCells(i, j); for (Cell cell : adj) { if (this.cells[i][j].candy.name.equals(cell.candy.name)) { @@ -136,7 +136,7 @@ public class CandyGame { for (var i = 0; i < this.cells.length; i++) { var points = 0; var j = this.cells.length - 1; - while (this.cells[j][i].candy.getType().equals(Type.rewardFruit)) { + while (this.cells[j][i].candy.getType().equals(Type.REWARD_FRUIT)) { points = this.cells[j][i].candy.getPoints(); this.cells[j][i].crush(pool, this.cells); handleChange(points); diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Cell.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Cell.java index e4d9d497f..76629b9dd 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Cell.java +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Cell.java @@ -74,8 +74,8 @@ public class Cell { } int interact(Cell c, CellPool pool, Cell[][] cellMatrix) { - if (this.candy.getType().equals(Type.rewardFruit) || c.candy.getType() - .equals(Type.rewardFruit)) { + if (this.candy.getType().equals(Type.REWARD_FRUIT) || c.candy.getType() + .equals(Type.REWARD_FRUIT)) { return 0; } else { if (this.candy.name.equals(c.candy.name)) { diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CellPool.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CellPool.java index 553458a6d..923a5c403 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CellPool.java +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CellPool.java @@ -24,7 +24,7 @@ package com.iluwatar.typeobject; import com.iluwatar.typeobject.Candy.Type; -import java.io.FileNotFoundException; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -39,6 +39,8 @@ import org.json.simple.parser.ParseException; public class CellPool { private static final Random RANDOM = new Random(); + public static final String FRUIT = "fruit"; + public static final String CANDY = "candy"; List pool; int pointer; Candy[] randomCode; @@ -51,11 +53,11 @@ public class CellPool { e.printStackTrace(); //manually initialising this.randomCode this.randomCode = new Candy[5]; - randomCode[0] = new Candy("cherry", "fruit", Type.rewardFruit, 20); - randomCode[1] = new Candy("mango", "fruit", Type.rewardFruit, 20); - randomCode[2] = new Candy("purple popsicle", "candy", Type.crushableCandy, 10); - randomCode[3] = new Candy("green jellybean", "candy", Type.crushableCandy, 10); - randomCode[4] = new Candy("orange gum", "candy", Type.crushableCandy, 10); + randomCode[0] = new Candy("cherry", FRUIT, Type.REWARD_FRUIT, 20); + randomCode[1] = new Candy("mango", FRUIT, Type.REWARD_FRUIT, 20); + randomCode[2] = new Candy("purple popsicle", CANDY, Type.CRUSHABLE_CANDY, 10); + randomCode[3] = new Candy("green jellybean", CANDY, Type.CRUSHABLE_CANDY, 10); + randomCode[4] = new Candy("orange gum", CANDY, Type.CRUSHABLE_CANDY, 10); } for (int i = 0; i < num; i++) { var c = new Cell(); @@ -77,14 +79,14 @@ public class CellPool { pointer++; } - Candy[] assignRandomCandytypes() throws FileNotFoundException, IOException, ParseException { + Candy[] assignRandomCandytypes() throws IOException, ParseException { var jp = new JsonParser(); jp.parse(); var randomCode = new Candy[jp.candies.size() - 2]; //exclude generic types 'fruit' and 'candy' var i = 0; for (var e = jp.candies.keys(); e.hasMoreElements(); ) { var s = e.nextElement(); - if (!s.equals("fruit") && !s.equals("candy")) { + if (!s.equals(FRUIT) && !s.equals(CANDY)) { //not generic randomCode[i] = jp.candies.get(s); i++; diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/JsonParser.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/JsonParser.java index 01e709c8f..150c648d3 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/JsonParser.java +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/JsonParser.java @@ -29,7 +29,7 @@ import java.io.FileReader; import java.io.IOException; import java.util.Hashtable; import java.util.List; -import java.util.stream.Collectors; + import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; @@ -58,9 +58,9 @@ public class JsonParser { var name = (String) candy.get("name"); var parentName = (String) candy.get("parent"); var t = (String) candy.get("type"); - var type = Type.crushableCandy; + var type = Type.CRUSHABLE_CANDY; if (t.equals("rewardFruit")) { - type = Type.rewardFruit; + type = Type.REWARD_FRUIT; } var points = Integer.parseInt((String) candy.get("points")); var c = new Candy(name, parentName, type, points); diff --git a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CandyGameTest.java b/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CandyGameTest.java index 8175d1dd0..69bcc67c5 100644 --- a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CandyGameTest.java +++ b/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CandyGameTest.java @@ -46,9 +46,9 @@ class CandyGameTest { @Test void continueRoundTest() { var matrix = new Cell[2][2]; - var c1 = new Candy("green jelly", "jelly", Type.crushableCandy, 5); - var c2 = new Candy("purple jelly", "jelly", Type.crushableCandy, 5); - var c3 = new Candy("green apple", "apple", Type.rewardFruit, 10); + var c1 = new Candy("green jelly", "jelly", Type.CRUSHABLE_CANDY, 5); + var c2 = new Candy("purple jelly", "jelly", Type.CRUSHABLE_CANDY, 5); + var c3 = new Candy("green apple", "apple", Type.REWARD_FRUIT, 10); matrix[0][0] = new Cell(c1, 0, 0); matrix[0][1] = new Cell(c2, 1, 0); matrix[1][0] = new Cell(c3, 0, 1); diff --git a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellTest.java b/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellTest.java index 18bca62bb..6d910e47c 100644 --- a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellTest.java +++ b/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellTest.java @@ -36,8 +36,8 @@ class CellTest { @Test void interactTest() { - var c1 = new Candy("green jelly", "jelly", Type.crushableCandy, 5); - var c2 = new Candy("green apple", "apple", Type.rewardFruit, 10); + var c1 = new Candy("green jelly", "jelly", Type.CRUSHABLE_CANDY, 5); + var c2 = new Candy("green apple", "apple", Type.REWARD_FRUIT, 10); var matrix = new Cell[4][4]; matrix[0][0] = new Cell(c1, 0, 0); matrix[0][1] = new Cell(c1, 1, 0); @@ -51,8 +51,8 @@ class CellTest { @Test void crushTest() { - var c1 = new Candy("green jelly", "jelly", Type.crushableCandy, 5); - var c2 = new Candy("purple candy", "candy", Type.crushableCandy, 5); + var c1 = new Candy("green jelly", "jelly", Type.CRUSHABLE_CANDY, 5); + var c2 = new Candy("purple candy", "candy", Type.CRUSHABLE_CANDY, 5); var matrix = new Cell[4][4]; matrix[0][0] = new Cell(c1, 0, 0); matrix[1][0] = new Cell(c2, 0, 1); diff --git a/unit-of-work/README.md b/unit-of-work/README.md index 1f6c7c5b2..01aa8e5d7 100644 --- a/unit-of-work/README.md +++ b/unit-of-work/README.md @@ -11,21 +11,27 @@ tags: --- ## Intent -When a business transaction is completed, all the the updates are sent as one big unit of work to be persisted -in one go to minimize database round-trips. + +When a business transaction is completed, all the the updates are sent as one big unit of work to be +persisted in one go to minimize database round-trips. ## Explanation + Real world example -> We have a database containing student information. Administrators all over the country are constantly updating this information and it causes high load on the database server. To make the load more manageable we apply to Unit of Work pattern to send many small updates in batches. +> We have a database containing student information. Administrators all over the country are +> constantly updating this information and it causes high load on the database server. To make the +> load more manageable we apply to Unit of Work pattern to send many small updates in batches. In plain words -> Unit of Work merges many small database updates in single batch to optimize the number of round-trips. +> Unit of Work merges many small database updates in single batch to optimize the number of +> round-trips. [MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says -> Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. +> Maintains a list of objects affected by a business transaction and coordinates the writing out of +> changes and the resolution of concurrency problems. **Programmatic Example** @@ -57,8 +63,9 @@ public class Student { } ``` -The essence of the implementation is the `StudentRepository` implementing the Unit of Work pattern. It maintains a map -of database operations (`context`) that need to be done and when `commit` is called it applies them in single batch. +The essence of the implementation is the `StudentRepository` implementing the Unit of Work pattern. +It maintains a map of database operations (`context`) that need to be done and when `commit` is +called it applies them in single batch. ```java public interface IUnitOfWork { @@ -79,8 +86,8 @@ public interface IUnitOfWork { public class StudentRepository implements IUnitOfWork { private static final Logger LOGGER = LoggerFactory.getLogger(StudentRepository.class); - private Map> context; - private StudentDatabase studentDatabase; + private final Map> context; + private final StudentDatabase studentDatabase; public StudentRepository(Map> context, StudentDatabase studentDatabase) { this.context = context; @@ -160,7 +167,7 @@ public class StudentRepository implements IUnitOfWork { } ``` -Finally here's how we use the `StudentRepository` and `commit` the transaction. +Finally, here's how we use the `StudentRepository` and `commit` the transaction. ```java studentRepository.registerNew(ram); @@ -170,9 +177,11 @@ Finally here's how we use the `StudentRepository` and `commit` the transaction. ``` ## Class diagram + ![alt text](etc/unit-of-work.urm.png "unit-of-work") ## Applicability + Use the Unit Of Work pattern when * To optimize the time taken for database transactions. diff --git a/unit-of-work/pom.xml b/unit-of-work/pom.xml index cd6ce06f1..1d57e138f 100644 --- a/unit-of-work/pom.xml +++ b/unit-of-work/pom.xml @@ -29,7 +29,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 @@ -44,6 +44,12 @@ junit-vintage-engine test + + org.junit.jupiter + junit-jupiter-engine + 5.0.0 + test + org.mockito mockito-core diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java index a9e41dd7c..6caf1de49 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java @@ -35,6 +35,7 @@ public class App { * * @param args no argument sent */ + public static void main(String[] args) { var ram = new Student(1, "Ram", "Street 9, Cupertino"); var shyam = new Student(2, "Shyam", "Z bridge, Pune"); diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/IUnitOfWork.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/IUnitOfWork.java index c44a696e3..e5e24817f 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/IUnitOfWork.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/IUnitOfWork.java @@ -29,9 +29,6 @@ package com.iluwatar.unitofwork; * @param Any generic entity */ public interface IUnitOfWork { - String INSERT = "INSERT"; - String DELETE = "DELETE"; - String MODIFY = "MODIFY"; /** * Any register new operation occurring on UnitOfWork is only going to be performed on commit. diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java index ee5fc613d..70433ed98 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java @@ -35,8 +35,8 @@ import org.slf4j.LoggerFactory; public class StudentRepository implements IUnitOfWork { private static final Logger LOGGER = LoggerFactory.getLogger(StudentRepository.class); - private Map> context; - private StudentDatabase studentDatabase; + private final Map> context; + private final StudentDatabase studentDatabase; /** * Constructor. @@ -52,20 +52,20 @@ public class StudentRepository implements IUnitOfWork { @Override public void registerNew(Student student) { LOGGER.info("Registering {} for insert in context.", student.getName()); - register(student, IUnitOfWork.INSERT); + register(student, UnitActions.INSERT.getActionValue()); } @Override public void registerModified(Student student) { LOGGER.info("Registering {} for modify in context.", student.getName()); - register(student, IUnitOfWork.MODIFY); + register(student, UnitActions.MODIFY.getActionValue()); } @Override public void registerDeleted(Student student) { LOGGER.info("Registering {} for delete in context.", student.getName()); - register(student, IUnitOfWork.DELETE); + register(student, UnitActions.DELETE.getActionValue()); } private void register(Student student, String operation) { @@ -86,21 +86,21 @@ public class StudentRepository implements IUnitOfWork { return; } LOGGER.info("Commit started"); - if (context.containsKey(IUnitOfWork.INSERT)) { + if (context.containsKey(UnitActions.INSERT.getActionValue())) { commitInsert(); } - if (context.containsKey(IUnitOfWork.MODIFY)) { + if (context.containsKey(UnitActions.MODIFY.getActionValue())) { commitModify(); } - if (context.containsKey(IUnitOfWork.DELETE)) { + if (context.containsKey(UnitActions.DELETE.getActionValue())) { commitDelete(); } LOGGER.info("Commit finished."); } private void commitInsert() { - var studentsToBeInserted = context.get(IUnitOfWork.INSERT); + var studentsToBeInserted = context.get(UnitActions.INSERT.getActionValue()); for (var student : studentsToBeInserted) { LOGGER.info("Saving {} to database.", student.getName()); studentDatabase.insert(student); @@ -108,7 +108,7 @@ public class StudentRepository implements IUnitOfWork { } private void commitModify() { - var modifiedStudents = context.get(IUnitOfWork.MODIFY); + var modifiedStudents = context.get(UnitActions.MODIFY.getActionValue()); for (var student : modifiedStudents) { LOGGER.info("Modifying {} to database.", student.getName()); studentDatabase.modify(student); @@ -116,7 +116,7 @@ public class StudentRepository implements IUnitOfWork { } private void commitDelete() { - var deletedStudents = context.get(IUnitOfWork.DELETE); + var deletedStudents = context.get(UnitActions.DELETE.getActionValue()); for (var student : deletedStudents) { LOGGER.info("Deleting {} to database.", student.getName()); studentDatabase.delete(student); diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java new file mode 100644 index 000000000..36e738626 --- /dev/null +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java @@ -0,0 +1,40 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.unitofwork; + +public enum UnitActions { + INSERT("INSERT"), + DELETE("DELETE"), + MODIFY("MODIFY"); + + private final String actionValue; + + UnitActions(String actionValue) { + this.actionValue = actionValue; + } + + public String getActionValue() { + return actionValue; + } +} diff --git a/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java b/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java index ecbe5fc53..630bb1392 100644 --- a/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java +++ b/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java @@ -25,12 +25,15 @@ package com.iluwatar.unitofwork; import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * AppTest */ public class AppTest { + @Test - public void test() { - App.main(new String[]{}); + public void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/unit-of-work/src/test/java/com/iluwatar/unitofwork/StudentRepositoryTest.java b/unit-of-work/src/test/java/com/iluwatar/unitofwork/StudentRepositoryTest.java index cda2f6fca..6cf1e6e16 100644 --- a/unit-of-work/src/test/java/com/iluwatar/unitofwork/StudentRepositoryTest.java +++ b/unit-of-work/src/test/java/com/iluwatar/unitofwork/StudentRepositoryTest.java @@ -63,7 +63,7 @@ public class StudentRepositoryTest { studentRepository.registerNew(student1); studentRepository.registerNew(student2); - assertEquals(2, context.get(IUnitOfWork.INSERT).size()); + assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size()); verifyNoMoreInteractions(studentDatabase); } @@ -72,7 +72,7 @@ public class StudentRepositoryTest { studentRepository.registerDeleted(student1); studentRepository.registerDeleted(student2); - assertEquals(2, context.get(IUnitOfWork.DELETE).size()); + assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size()); verifyNoMoreInteractions(studentDatabase); } @@ -81,15 +81,15 @@ public class StudentRepositoryTest { studentRepository.registerModified(student1); studentRepository.registerModified(student2); - assertEquals(2, context.get(IUnitOfWork.MODIFY).size()); + assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size()); verifyNoMoreInteractions(studentDatabase); } @Test public void shouldSaveAllLocalChangesToDb() { - context.put(IUnitOfWork.INSERT, List.of(student1)); - context.put(IUnitOfWork.MODIFY, List.of(student1)); - context.put(IUnitOfWork.DELETE, List.of(student1)); + context.put(UnitActions.INSERT.getActionValue(), List.of(student1)); + context.put(UnitActions.MODIFY.getActionValue(), List.of(student1)); + context.put(UnitActions.DELETE.getActionValue(), List.of(student1)); studentRepository.commit(); @@ -118,8 +118,8 @@ public class StudentRepositoryTest { @Test public void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() { - context.put(IUnitOfWork.MODIFY, List.of(student1)); - context.put(IUnitOfWork.DELETE, List.of(student1)); + context.put(UnitActions.MODIFY.getActionValue(), List.of(student1)); + context.put(UnitActions.DELETE.getActionValue(), List.of(student1)); studentRepository.commit(); @@ -128,8 +128,8 @@ public class StudentRepositoryTest { @Test public void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() { - context.put(IUnitOfWork.INSERT, List.of(student1)); - context.put(IUnitOfWork.DELETE, List.of(student1)); + context.put(UnitActions.INSERT.getActionValue(), List.of(student1)); + context.put(UnitActions.DELETE.getActionValue(), List.of(student1)); studentRepository.commit(); @@ -138,8 +138,8 @@ public class StudentRepositoryTest { @Test public void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() { - context.put(IUnitOfWork.INSERT, List.of(student1)); - context.put(IUnitOfWork.MODIFY, List.of(student1)); + context.put(UnitActions.INSERT.getActionValue(), List.of(student1)); + context.put(UnitActions.MODIFY.getActionValue(), List.of(student1)); studentRepository.commit(); diff --git a/update-method/pom.xml b/update-method/pom.xml index a89364328..3a60d433b 100644 --- a/update-method/pom.xml +++ b/update-method/pom.xml @@ -28,7 +28,7 @@ java-design-patterns com.iluwatar - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT 4.0.0 @@ -38,6 +38,11 @@ junit junit + + org.junit.jupiter + junit-jupiter-engine + test + diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/World.java b/update-method/src/main/java/com/iluwatar/updatemethod/World.java index 8cabead56..37211abb1 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/World.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/World.java @@ -87,7 +87,9 @@ public class World { * Render the next frame. Here we do nothing since it is not related to the * pattern. */ - private void render() {} + private void render() { + // Does Nothing + } /** * Run game loop. diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java index 75c30470d..4849c4e62 100644 --- a/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java +++ b/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java @@ -25,11 +25,12 @@ package com.iluwatar.updatemethod; import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + public class AppTest { @Test - public void testMain() { - String[] args = {}; - App.main(args); + public void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/value-object/pom.xml b/value-object/pom.xml index aa4101b5c..dbbbccd01 100644 --- a/value-object/pom.xml +++ b/value-object/pom.xml @@ -30,7 +30,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT value-object diff --git a/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java b/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java index 740be76b1..453718875 100644 --- a/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java +++ b/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java @@ -103,10 +103,7 @@ public class HeroStat { if (luck != other.luck) { return false; } - if (strength != other.strength) { - return false; - } - return true; + return strength == other.strength; } // The clone() method should not be public. Just don't override it. diff --git a/value-object/src/test/java/com/iluwatar/value/object/AppTest.java b/value-object/src/test/java/com/iluwatar/value/object/AppTest.java index c0e680ec4..39eaef28c 100644 --- a/value-object/src/test/java/com/iluwatar/value/object/AppTest.java +++ b/value-object/src/test/java/com/iluwatar/value/object/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.value.object; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/version-number/README.md b/version-number/README.md new file mode 100644 index 000000000..cc0cf7601 --- /dev/null +++ b/version-number/README.md @@ -0,0 +1,169 @@ +--- +layout: pattern +title: Version Number +folder: versionnumber +permalink: /patterns/versionnumber/ +description: Entity versioning with version number + +categories: + - Concurrency + +tags: + - Data access + - Microservices +--- + +## Name / classification + +Version Number. + +## Also known as + +Entity Versioning, Optimistic Locking. + +## Intent + +Resolve concurrency conflicts when multiple clients are trying to update same entity simultaneously. + +## Explanation + +Real world example + +> Alice and Bob are working on the book, which stored in the database. Our heroes are making +> changes simultaneously, and we need some mechanism to prevent them from overwriting each other. + +In plain words + +> Version Number pattern grants protection against concurrent updates to same entity. + +Wikipedia says + +> Optimistic concurrency control assumes that multiple transactions can frequently complete +> without interfering with each other. While running, transactions use data resources without +> acquiring locks on those resources. Before committing, each transaction verifies that no other +> transaction has modified the data it has read. If the check reveals conflicting modifications, +> the committing transaction rolls back and can be restarted. + +**Programmatic Example** + +We have a `Book` entity, which is versioned, and has a copy-constructor: + +```java +public class Book { + private long id; + private String title = ""; + private String author = ""; + + private long version = 0; // version number + + public Book(Book book) { + this.id = book.id; + this.title = book.title; + this.author = book.author; + this.version = book.version; + } + + // getters and setters are omitted here +} +``` + +We also have `BookRepository`, which implements concurrency control: + +```java +public class BookRepository { + private final Map collection = new HashMap<>(); + + public void update(Book book) throws BookNotFoundException, VersionMismatchException { + if (!collection.containsKey(book.getId())) { + throw new BookNotFoundException("Not found book with id: " + book.getId()); + } + + var latestBook = collection.get(book.getId()); + if (book.getVersion() != latestBook.getVersion()) { + throw new VersionMismatchException( + "Tried to update stale version " + book.getVersion() + + " while actual version is " + latestBook.getVersion() + ); + } + + // update version, including client representation - modify by reference here + book.setVersion(book.getVersion() + 1); + + // save book copy to repository + collection.put(book.getId(), new Book(book)); + } + + public Book get(long bookId) throws BookNotFoundException { + if (!collection.containsKey(bookId)) { + throw new BookNotFoundException("Not found book with id: " + bookId); + } + + // return copy of the book + return new Book(collection.get(bookId)); + } +} +``` + +Here's the concurrency control in action: + +```java +var bookId = 1; +// Alice and Bob took the book concurrently +final var aliceBook = bookRepository.get(bookId); +final var bobBook = bookRepository.get(bookId); + +aliceBook.setTitle("Kama Sutra"); // Alice has updated book title +bookRepository.update(aliceBook); // and successfully saved book in database +LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion()); + +// now Bob has the stale version of the book with empty title and version = 0 +// while actual book in database has filled title and version = 1 +bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author +try { + LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion()); + bookRepository.update(bobBook); // Bob tries to save his book to database +} catch (VersionMismatchException e) { + // Bob update fails, and book in repository remained untouchable + LOGGER.info("Exception: {}", e.getMessage()); + // Now Bob should reread actual book from repository, do his changes again and save again +} +``` + +Program output: + +```java +Alice updates the book with new version 1 +Bob tries to update the book with his version 0 +Exception: Tried to update stale version 0 while actual version is 1 +``` + +## Class diagram + +![alt text](./etc/version-number.urm.png "Version Number pattern class diagram") + +## Applicability + +Use Version Number for: + +* resolving concurrent write-access to the data +* strong data consistency + +## Tutorials +* [Version Number Pattern Tutorial](http://www.java2s.com/Tutorial/Java/0355__JPA/VersioningEntity.htm) + +## Known uses + * [Hibernate](https://vladmihalcea.com/jpa-entity-version-property-hibernate/) + * [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning) + * [Apache Solr](https://lucene.apache.org/solr/guide/6_6/updating-parts-of-documents.html) + +## Consequences +Version Number pattern allows to implement a concurrency control, which is usually done +via Optimistic Offline Lock pattern. + +## Related patterns +* [Optimistic Offline Lock](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html) + +## Credits +* [Optimistic Locking in JPA](https://www.baeldung.com/jpa-optimistic-locking) +* [JPA entity versioning](https://www.byteslounge.com/tutorials/jpa-entity-versioning-version-and-optimistic-locking) +* [J2EE Design Patterns](http://ommolketab.ir/aaf-lib/axkwht7wxrhvgs2aqkxse8hihyu9zv.pdf) diff --git a/version-number/etc/version-number.urm.png b/version-number/etc/version-number.urm.png new file mode 100644 index 000000000..95a5819b4 Binary files /dev/null and b/version-number/etc/version-number.urm.png differ diff --git a/version-number/etc/version-number.urm.puml b/version-number/etc/version-number.urm.puml new file mode 100644 index 000000000..6f3c3364e --- /dev/null +++ b/version-number/etc/version-number.urm.puml @@ -0,0 +1,32 @@ +@startuml +package com.iluwatar.versionnumber { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class Book { + - author : String + - id : long + - title : String + - version : long + + Book() + + Book(book : Book) + + getAuthor() : String + + getId() : long + + getTitle() : String + + getVersion() : long + + setAuthor(author : String) + + setId(id : long) + + setTitle(title : String) + + setVersion(version : long) + } + class BookRepository { + - collection : Map + + BookRepository() + + add(book : Book) + + get(bookId : long) : Book + + update(book : Book) + } +} +@enduml \ No newline at end of file diff --git a/version-number/pom.xml b/version-number/pom.xml new file mode 100644 index 000000000..b5748c93b --- /dev/null +++ b/version-number/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.24.0-SNAPSHOT + + version-number + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.versionnumber.App + + + + + + + + + diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/App.java b/version-number/src/main/java/com/iluwatar/versionnumber/App.java new file mode 100644 index 000000000..cffba50d4 --- /dev/null +++ b/version-number/src/main/java/com/iluwatar/versionnumber/App.java @@ -0,0 +1,84 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.versionnumber; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Version Number pattern helps to resolve concurrency conflicts in applications. + * Usually these conflicts arise in database operations, when multiple clients are trying + * to update the same record simultaneously. + * Resolving such conflicts requires determining whether an object has changed. + * For this reason we need a version number that is incremented with each change + * to the underlying data, e.g. database. The version number can be used by repositories + * to check for external changes and to report concurrency issues to the users. + * + *

In this example we show how Alice and Bob will try to update the {@link Book} + * and save it simultaneously to {@link BookRepository}, which represents a typical database. + * + *

As in real databases, each client operates with copy of the data instead of original data + * passed by reference, that's why we are using {@link Book} copy-constructor here. + */ +public class App { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) throws + BookDuplicateException, + BookNotFoundException, + VersionMismatchException { + var bookId = 1; + + var bookRepository = new BookRepository(); + var book = new Book(); + book.setId(bookId); + bookRepository.add(book); // adding a book with empty title and author + LOGGER.info("An empty book with version {} was added to repository", book.getVersion()); + + // Alice and Bob took the book concurrently + final var aliceBook = bookRepository.get(bookId); + final var bobBook = bookRepository.get(bookId); + + aliceBook.setTitle("Kama Sutra"); // Alice has updated book title + bookRepository.update(aliceBook); // and successfully saved book in database + LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion()); + + // now Bob has the stale version of the book with empty title and version = 0 + // while actual book in database has filled title and version = 1 + bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author + try { + LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion()); + bookRepository.update(bobBook); // Bob tries to save his book to database + } catch (VersionMismatchException e) { + // Bob update fails, and book in repository remained untouchable + LOGGER.info("Exception: {}", e.getMessage()); + // Now Bob should reread actual book from repository, do his changes again and save again + } + } +} diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/Book.java b/version-number/src/main/java/com/iluwatar/versionnumber/Book.java new file mode 100644 index 000000000..93b35880b --- /dev/null +++ b/version-number/src/main/java/com/iluwatar/versionnumber/Book.java @@ -0,0 +1,78 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.versionnumber; + +public class Book { + private long id; + private String title = ""; + private String author = ""; + + private long version = 0; // version number + + public Book() { + + } + + /** + * We need this copy constructor to copy book representation in {@link BookRepository}. + */ + public Book(Book book) { + this.id = book.id; + this.title = book.title; + this.author = book.author; + this.version = book.version; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } +} diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java new file mode 100644 index 000000000..cd993b147 --- /dev/null +++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java @@ -0,0 +1,33 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.versionnumber; + +/** + * When someone has tried to add a book which repository already have. + */ +public class BookDuplicateException extends Exception { + public BookDuplicateException(String message) { + super(message); + } +} diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java new file mode 100644 index 000000000..f832f350f --- /dev/null +++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java @@ -0,0 +1,33 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.versionnumber; + +/** + * Client has tried to make an operation with book which repository does not have. + */ +public class BookNotFoundException extends Exception { + public BookNotFoundException(String message) { + super(message); + } +} diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java new file mode 100644 index 000000000..ef41e79ac --- /dev/null +++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java @@ -0,0 +1,86 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.versionnumber; + +import java.util.HashMap; +import java.util.Map; + +/** + * This repository represents simplified database. + * As a typical database do, repository operates with copies of object. + * So client and repo has different copies of book, which can lead to concurrency conflicts + * as much as in real databases. + */ +public class BookRepository { + private final Map collection = new HashMap<>(); + + /** + * Adds book to collection. + * Actually we are putting copy of book (saving a book by value, not by reference); + */ + public void add(Book book) throws BookDuplicateException { + if (collection.containsKey(book.getId())) { + throw new BookDuplicateException("Duplicated book with id: " + book.getId()); + } + + // add copy of the book + collection.put(book.getId(), new Book(book)); + } + + /** + * Updates book in collection only if client has modified the latest version of the book. + */ + public void update(Book book) throws BookNotFoundException, VersionMismatchException { + if (!collection.containsKey(book.getId())) { + throw new BookNotFoundException("Not found book with id: " + book.getId()); + } + + var latestBook = collection.get(book.getId()); + if (book.getVersion() != latestBook.getVersion()) { + throw new VersionMismatchException( + "Tried to update stale version " + book.getVersion() + + " while actual version is " + latestBook.getVersion() + ); + } + + // update version, including client representation - modify by reference here + book.setVersion(book.getVersion() + 1); + + // save book copy to repository + collection.put(book.getId(), new Book(book)); + } + + /** + * Returns book representation to the client. + * Representation means we are returning copy of the book. + */ + public Book get(long bookId) throws BookNotFoundException { + if (!collection.containsKey(bookId)) { + throw new BookNotFoundException("Not found book with id: " + bookId); + } + + // return copy of the book + return new Book(collection.get(bookId)); + } +} diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java b/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java new file mode 100644 index 000000000..94ea0fd9e --- /dev/null +++ b/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java @@ -0,0 +1,33 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.versionnumber; + +/** + * Client has tried to update a stale version of the book. + */ +public class VersionMismatchException extends Exception { + public VersionMismatchException(String message) { + super(message); + } +} diff --git a/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java b/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java new file mode 100644 index 000000000..7b4984901 --- /dev/null +++ b/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java @@ -0,0 +1,46 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.versionnumber; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Application test + */ +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } +} diff --git a/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java b/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java new file mode 100644 index 000000000..6b7b2b39a --- /dev/null +++ b/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java @@ -0,0 +1,87 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.versionnumber; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for {@link BookRepository} + */ +class BookRepositoryTest { + private final long bookId = 1; + private final BookRepository bookRepository = new BookRepository(); + + @BeforeEach + public void setUp() throws BookDuplicateException { + var book = new Book(); + book.setId(bookId); + bookRepository.add(book); + } + + @Test + void testDefaultVersionRemainsZeroAfterAdd() throws BookNotFoundException { + var book = bookRepository.get(bookId); + assertEquals(0, book.getVersion()); + } + + @Test + void testAliceAndBobHaveDifferentVersionsAfterAliceUpdate() throws BookNotFoundException, VersionMismatchException { + final var aliceBook = bookRepository.get(bookId); + final var bobBook = bookRepository.get(bookId); + + aliceBook.setTitle("Kama Sutra"); + bookRepository.update(aliceBook); + + assertEquals(1, aliceBook.getVersion()); + assertEquals(0, bobBook.getVersion()); + var actualBook = bookRepository.get(bookId); + assertEquals(aliceBook.getVersion(), actualBook.getVersion()); + assertEquals(aliceBook.getTitle(), actualBook.getTitle()); + assertNotEquals(aliceBook.getTitle(), bobBook.getTitle()); + } + + @Test + void testShouldThrowVersionMismatchExceptionOnStaleUpdate() throws BookNotFoundException, VersionMismatchException { + final var aliceBook = bookRepository.get(bookId); + final var bobBook = bookRepository.get(bookId); + + aliceBook.setTitle("Kama Sutra"); + bookRepository.update(aliceBook); + + bobBook.setAuthor("Vatsyayana Mallanaga"); + try { + bookRepository.update(bobBook); + } catch (VersionMismatchException e) { + assertEquals(0, bobBook.getVersion()); + var actualBook = bookRepository.get(bookId); + assertEquals(1, actualBook.getVersion()); + assertEquals(aliceBook.getVersion(), actualBook.getVersion()); + assertEquals("", bobBook.getTitle()); + assertNotEquals(aliceBook.getAuthor(), bobBook.getAuthor()); + } + } +} diff --git a/visitor/README.md b/visitor/README.md index 3b4be5082..75a032a47 100644 --- a/visitor/README.md +++ b/visitor/README.md @@ -9,13 +9,17 @@ tags: --- ## 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. + +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. ## Explanation Real world example -> Consider a tree structure with army units. Commander has two sergeants under it and each sergeant has three soldiers under them. Given that the hierarchy implements the visitor pattern, we can easily create new objects that interact with the commander, sergeants, soldiers or all of them. +> Consider a tree structure with army units. Commander has two sergeants under it and each sergeant +> has three soldiers under them. Given that the hierarchy implements the visitor pattern, we can +> easily create new objects that interact with the commander, sergeants, soldiers or all of them. In plain words @@ -23,7 +27,10 @@ In plain words Wikipedia says -> In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures. +> In object-oriented programming and software engineering, the visitor design pattern is a way of +> separating an algorithm from an object structure on which it operates. A practical result of this +> separation is the ability to add new operations to existing object structures without modifying +> the structures. **Programmatic Example** @@ -32,7 +39,7 @@ Given the army unit example from above, we first have the Unit and UnitVisitor b ```java public abstract class Unit { - private Unit[] children; + private final Unit[] children; public Unit(Unit... children) { this.children = children; @@ -111,7 +118,7 @@ public class Soldier extends Unit { } ``` -And then some concrete visitors. +Here are then some concrete visitors. ```java public class CommanderVisitor implements UnitVisitor { @@ -175,32 +182,39 @@ public class SoldierVisitor implements UnitVisitor { } ``` -Finally we can show the power of visitors in action. +Finally, we can show the power of visitors in action. ```java commander.accept(new SoldierVisitor()); -// Greetings soldier -// Greetings soldier -// Greetings soldier -// Greetings soldier -// Greetings soldier -// Greetings soldier commander.accept(new SergeantVisitor()); -// Hello sergeant -// Hello sergeant commander.accept(new CommanderVisitor()); -// Good to see you commander +``` + +Program output: + +``` +Greetings soldier +Greetings soldier +Greetings soldier +Greetings soldier +Greetings soldier +Greetings soldier +Hello sergeant +Hello sergeant +Good to see you commander ``` ## Class diagram + ![alt text](./etc/visitor_1.png "Visitor") ## 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 +* 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 diff --git a/visitor/pom.xml b/visitor/pom.xml index 00497b41f..a08f6ee8c 100644 --- a/visitor/pom.xml +++ b/visitor/pom.xml @@ -29,7 +29,7 @@ com.iluwatar java-design-patterns - 1.23.0-SNAPSHOT + 1.24.0-SNAPSHOT visitor diff --git a/visitor/src/main/java/com/iluwatar/visitor/Unit.java b/visitor/src/main/java/com/iluwatar/visitor/Unit.java index c890884b3..3597c4d60 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Unit.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Unit.java @@ -1,45 +1,45 @@ -/* - * The MIT License - * Copyright © 2014-2019 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.visitor; - -import java.util.Arrays; - -/** - * Interface for the nodes in hierarchy. - */ -public abstract class Unit { - - private Unit[] children; - - public Unit(Unit... children) { - this.children = children; - } - - /** - * Accept visitor. - */ - public void accept(UnitVisitor visitor) { - Arrays.stream(children).forEach(child -> child.accept(visitor)); - } -} +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.visitor; + +import java.util.Arrays; + +/** + * Interface for the nodes in hierarchy. + */ +public abstract class Unit { + + private final Unit[] children; + + public Unit(Unit... children) { + this.children = children; + } + + /** + * Accept visitor. + */ + public void accept(UnitVisitor visitor) { + Arrays.stream(children).forEach(child -> 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 c16f31195..45f7006a7 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/AppTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/AppTest.java @@ -25,13 +25,15 @@ package com.iluwatar.visitor; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + /** * Application test. */ -public class AppTest { +class AppTest { @Test - public void test() { - App.main(new String[]{}); + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); } } diff --git a/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java index 146ee22f8..fd2f1cb76 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java @@ -123,7 +123,7 @@ public abstract class VisitorTest { } private class InMemoryAppender extends AppenderBase { - private List log = new LinkedList<>(); + private final List log = new LinkedList<>(); public InMemoryAppender() { ((Logger) LoggerFactory.getLogger("root")).addAppender(this); diff --git a/zh/adapter/README.md b/zh/adapter/README.md new file mode 100644 index 000000000..b00d757a6 --- /dev/null +++ b/zh/adapter/README.md @@ -0,0 +1,138 @@ +--- +layout: pattern +title: Adapter +folder: adapter +permalink: /patterns/adapter/ +categories: Structural +tags: + - Gang of Four +--- + +## 又被称为 +包装器 + +## 目的 +将一个接口转换成另一个客户所期望的接口。适配器让那些本来因为接口不兼容的类可以合作无间。 + +## 解释 + +现实世界例子 + +> 考虑有这么一种情况,在你的存储卡中有一些照片,你想将其传到你的电脑中。为了传送数据,你需要某种能够兼容你电脑接口的适配器以便你的储存卡能连上你的电脑。在这种情况下,读卡器就是一个适配器。 +> 另一个例子就是注明的电源适配器;三脚插头不能插在两脚插座上,需要一个电源适配器来使其能够插在两脚插座上。 +> 还有一个例子就是翻译官,他翻译一个人对另一个人说的话。 + +用直白的话来说 + +> 适配器模式让你可以把不兼容的对象包在适配器中,以让其兼容其他类。 + +维基百科中说 + +> 在软件工程中,适配器模式是一种可以让现有类的接口把其作为其他接口来使用的设计模式。它经常用来使现有的类和其他类能够工作并且不用修改其他类的源代码。 + +**编程样例(对象适配器)** + +假如有一个船长他只会划船,但不会航行。 + +首先我们有接口`RowingBoat`和`FishingBoat` + +```java +public interface RowingBoat { + void row(); +} + +public class FishingBoat { + private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoat.class); + public void sail() { + LOGGER.info("The fishing boat is sailing"); + } +} +``` +船长希望有一个`RowingBoat`接口的实现,这样就可以移动 + +```java +public class Captain { + + private final RowingBoat rowingBoat; + // default constructor and setter for rowingBoat + public Captain(RowingBoat rowingBoat) { + this.rowingBoat = rowingBoat; + } + + public void row() { + rowingBoat.row(); + } +} +``` + +现在海盗来了,我们的船长需要逃跑但是只有一个渔船可用。我们需要创建一个可以让船长使用其划船技能来操作渔船的适配器。 + +```java +public class FishingBoatAdapter implements RowingBoat { + + private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoatAdapter.class); + + private final FishingBoat boat; + + public FishingBoatAdapter() { + boat = new FishingBoat(); + } + + @Override + public void row() { + boat.sail(); + } +} + +``` + +现在 `船长` 可以使用`FishingBoat`接口来逃离海盗了。 + +```java +var captain = new Captain(new FishingBoatAdapter()); +captain.row(); +``` + +## 类图 +![alt text](../../adapter/etc/adapter.urm.png "Adapter class diagram") + + +## 应用 +使用适配器模式当 + +* 你想使用一个已有类,但是它的接口不能和你需要的所匹配 +* 你需要创建一个可重用类,该类与不相关或不可预见的类进行协作,即不一定具有兼容接口的类 +* 你需要使用一些现有的子类,但是子类化他们每一个的子类来进行接口的适配是不现实的。一个对象适配器可以适配他们父类的接口。 +* 大多数使用第三方类库的应用使用适配器作为一个在应用和第三方类库间的中间层来使应用和类库解耦。如果必须使用另一个库,则只需使用一个新库的适配器而无需改变应用程序的代码。 + +## 后果: +类和对象适配器有不同的权衡取舍。一个类适配器 + +* 适配被适配者到目标接口,需要保证只有一个具体的被适配者类。作为结果,当我们想适配一个类和它所有的子类时,类适配器将不会起作用。 +* 可以让适配器重写一些被适配者的行为,因为适配器是被适配者的子类。 +* 只引入了一个对象,并且不需要其他指针间接访问被适配者。 + +对象适配器 + +* 一个适配器可以和许多被适配者工作,也就是被适配者自己和所有它的子类。适配器同时可以为所有被适配者添加功能。 +* 覆盖被适配者的行为变得更难。需要子类化被适配者然后让适配器引用这个子类不是被适配者。 + + +## 现实世界的案例 + +* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29) +* [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-) +* [java.util.Collections#enumeration()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#enumeration-java.util.Collection-) +* [javax.xml.bind.annotation.adapters.XMLAdapter](http://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html#marshal-BoundType-) + + +## 鸣谢 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) + +``` + +``` \ No newline at end of file diff --git a/zh/builder/README.md b/zh/builder/README.md new file mode 100644 index 000000000..fdbcb08fa --- /dev/null +++ b/zh/builder/README.md @@ -0,0 +1,139 @@ +--- +layout: pattern +title: Builder +folder: builder +permalink: /patterns/builder/ +categories: Creational +tags: + - Gang of Four + +--- + +## 目的 + +将复杂对象的构造与其表示分开,以便同一构造过程可以创建不同的表示。 + +## 解释 + +现实世界例子 + +> 想象一个角色扮演游戏的角色生成器。最简单的选择是让计算机为你创建角色。但是如果你想选择一些像专业,性别,发色等角色细节时,这个角色生成就变成了一个渐进的过程。当所有选择完成时,该过程也将完成。 + +用通俗的话说 + +> 允许你创建不同口味的对象同时避免构造器污染。当一个对象可能有几种口味,或者一个对象的创建涉及到很多步骤时会很有用。 + +维基百科说 + +> 建造者模式是一种对象创建的软件设计模式,旨在为伸缩构造器反模式寻找一个解决方案。 + +说了这么多,让我补充一下什么是伸缩构造函数反模式。我们肯定都见过像下面这样的构造器: + +```java +public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { +} +``` + +就像你看到的构造器参数的数量很快就会失控同时参数的排列方式可能变得难以理解。另外,如果您将来希望添加更多选项,则此参数列表可能会继续增长。这就被称伸缩构造器反模式。 + +**编程示例** + +明智的选择是使用建造者模式。首先我们有一个英雄要创建。 + +```java +public final class Hero { + private final Profession profession; + private final String name; + private final HairType hairType; + private final HairColor hairColor; + private final Armor armor; + private final Weapon weapon; + + private Hero(Builder builder) { + this.profession = builder.profession; + this.name = builder.name; + this.hairColor = builder.hairColor; + this.hairType = builder.hairType; + this.weapon = builder.weapon; + this.armor = builder.armor; + } +} +``` + +然后我们有创建者 + +```java + public static class Builder { + private final Profession profession; + private final String name; + private HairType hairType; + private HairColor hairColor; + private Armor armor; + private Weapon weapon; + + public Builder(Profession profession, String name) { + if (profession == null || name == null) { + throw new IllegalArgumentException("profession and name can not be null"); + } + this.profession = profession; + this.name = name; + } + + public Builder withHairType(HairType hairType) { + this.hairType = hairType; + return this; + } + + public Builder withHairColor(HairColor hairColor) { + this.hairColor = hairColor; + return this; + } + + public Builder withArmor(Armor armor) { + this.armor = armor; + return this; + } + + public Builder withWeapon(Weapon weapon) { + this.weapon = weapon; + return this; + } + + public Hero build() { + return new Hero(this); + } + } +``` + +然后可以这样使用 + +```java +var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); +``` + +## 类图 + +![alt text](../../builder/etc/builder.urm.png "Builder class diagram") + +## 适用性 + +使用建造者模式当 + +* 创建复杂对象的算法应独立于组成对象的零件及其组装方式 +* 构造过程必须允许所构造的对象具有不同的表示形式 + +## Java世界例子 + +* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) +* [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) as well as similar buffers such as FloatBuffer, IntBuffer and so on. +* [java.lang.StringBuffer](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html#append-boolean-) +* All implementations of [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) +* [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) +* [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html) + +## 鸣谢 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) \ No newline at end of file diff --git a/zh/chain/README.md b/zh/chain/README.md new file mode 100644 index 000000000..00d9ef012 --- /dev/null +++ b/zh/chain/README.md @@ -0,0 +1,159 @@ +--- +layout: pattern +title: Chain of responsibility +folder: chain +permalink: /patterns/chain/ +categories: Behavioral +tags: + - Gang of Four +--- + +## 目的 +通过给多个对象一个处理请求的机会,避免请求的发送者和它的接收者耦合。串联接收对象并在链条中传递请求直到一个对象处理它。 + +## 解释 + +真实世界例子 + +> 兽王大声命令他的军队。最近响应的是指挥官,然后是军官,然后是士兵。指挥官,军官,士兵这里就形成了一个责任链。 + +通俗的说 + +> 它帮助构建一串对象。请求从一个对象中进入并结束然后进入到一个个对象中直到找到合适的处理器。 + +维基百科说 + +> 在面向对象设计中,责任链模式是一种由源命令对象和一系列处理对象组成的设计模式。每个处理对象包含了其定义的可处理的命令对象类型的逻辑。剩下的会传递给链条中的下一个处理对象。 + +**程序示例** + +用上面的兽人来翻译我们的示例。首先我们有请求类 + +```java +public class Request { + + private final RequestType requestType; + private final String requestDescription; + private boolean handled; + + public Request(final RequestType requestType, final String requestDescription) { + this.requestType = Objects.requireNonNull(requestType); + this.requestDescription = Objects.requireNonNull(requestDescription); + } + + public String getRequestDescription() { return requestDescription; } + + public RequestType getRequestType() { return requestType; } + + public void markHandled() { this.handled = true; } + + public boolean isHandled() { return this.handled; } + + @Override + public String toString() { return getRequestDescription(); } +} + +public enum RequestType { + DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX +} +``` + +然后是请求处理器的层次结构 + +```java +public abstract class RequestHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class); + private final RequestHandler next; + + public RequestHandler(RequestHandler next) { + this.next = next; + } + + public void handleRequest(Request req) { + if (next != null) { + next.handleRequest(req); + } + } + + protected void printHandling(Request req) { + LOGGER.info("{} handling request \"{}\"", this, req); + } + + @Override + public abstract String toString(); +} + +public class OrcCommander extends RequestHandler { + public OrcCommander(RequestHandler handler) { + super(handler); + } + + @Override + public void handleRequest(Request req) { + if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) { + printHandling(req); + req.markHandled(); + } else { + super.handleRequest(req); + } + } + + @Override + public String toString() { + return "Orc commander"; + } +} + +// OrcOfficer和OrcSoldier的定义与OrcCommander类似 + +``` + +然后我们有兽王下达命令并形成链条 + +```java +public class OrcKing { + RequestHandler chain; + + public OrcKing() { + buildChain(); + } + + private void buildChain() { + chain = new OrcCommander(new OrcOfficer(new OrcSoldier(null))); + } + + public void makeRequest(Request req) { + chain.handleRequest(req); + } +} +``` + +然后这样使用它 + +```java +var king = new OrcKing(); +king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle")); // Orc commander handling request "defend castle" +king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner")); // Orc officer handling request "torture prisoner" +king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); // Orc soldier handling request "collect tax" +``` + +## 类图 +![alt text](../../chain/etc/chain.urm.png "Chain of Responsibility class diagram") + +## 适用性 +使用责任链模式当 + +* 多于一个对象可能要处理请求,并且处理器并不知道一个优先级。处理器应自动确定。 +* 你想对多个对象之一发出请求而无需明确指定接收者 +* 处理请求的对象集合应该被动态指定时 + +## Java世界例子 + +* [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) +* [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-) + +## 鸣谢 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/zh/decorator/README.md b/zh/decorator/README.md new file mode 100644 index 000000000..ef23eda0b --- /dev/null +++ b/zh/decorator/README.md @@ -0,0 +1,139 @@ +--- +layout: pattern +title: Decorator +folder: decorator +permalink: /patterns/decorator/ +categories: Structural +tags: + - Gang Of Four + - Extensibility +--- + +## 或称 +包装器 + +## 目的 +动态的为对象附加额外的职责。装饰器为子类提供了灵活的替代方案,以扩展功能。 + +## 解释 + +真实世界例子 + +> 附近的山丘上住着一个愤怒的巨魔。通常它是徒手的,但有时它有武器。为了武装巨魔不必创建新的巨魔,而是用合适的武器动态的装饰它。 + +通俗的说 + +> 装饰者模式让你可以在运行时通过把对象包装进一个装饰类对象中来动态的改变一个对象的行为。 + +维基百科说 + +> 在面向对象的编程中,装饰器模式是一种设计模式,它允许将行为静态或动态地添加到单个对象中,而不会影响同一类中其他对象的行为。装饰器模式通常对于遵守单一责任原则很有用,因为它允许将功能划分到具有唯一关注领域的类之间。 + +**程序示例** + +以巨魔的为例。首先我有有一个简单的巨魔,实现了巨魔接口。 + +程序mple. First of all we have a simple troll implementing the troll interface + +```java +public interface Troll { + void attack(); + int getAttackPower(); + void fleeBattle(); +} + +public class SimpleTroll implements Troll { + + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleTroll.class); + + @Override + public void attack() { + LOGGER.info("The troll tries to grab you!"); + } + + @Override + public int getAttackPower() { + return 10; + } + + @Override + public void fleeBattle() { + LOGGER.info("The troll shrieks in horror and runs away!"); + } +} +``` + +下面我们想为巨魔添加球棒。我们可以用装饰者来动态的实现。 + +```java +public class ClubbedTroll implements Troll { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClubbedTroll.class); + + private final Troll decorated; + + public ClubbedTroll(Troll decorated) { + this.decorated = decorated; + } + + @Override + public void attack() { + decorated.attack(); + LOGGER.info("The troll swings at you with a club!"); + } + + @Override + public int getAttackPower() { + return decorated.getAttackPower() + 10; + } + + @Override + public void fleeBattle() { + decorated.fleeBattle(); + } +} +``` + +这里是巨魔的实战 + +```java +// simple troll +var troll = new SimpleTroll(); +troll.attack(); // The troll tries to grab you! +troll.fleeBattle(); // The troll shrieks in horror and runs away! + +// change the behavior of the simple troll by adding a decorator +var clubbedTroll = new ClubbedTroll(troll); +clubbedTroll.attack(); // The troll tries to grab you! The troll swings at you with a club! +clubbedTroll.fleeBattle(); // The troll shrieks in horror and runs away! +``` + +## 类图 +![alt text](../../decorator/etc/decorator.urm.png "Decorator pattern class diagram") + +## 适用性 +使用装饰者 + +* 动态透明地向单个对象添加职责,即不影响其他对象 +* 对于可以撤销的责任 +* 当通过子类化进行扩展是不切实际的。有时可能会有大量的独立扩展,并且会产生大量的子类来支持每种组合。 否则类定义可能被隐藏或无法用于子类化。 + +## 教程 +* [Decorator Pattern Tutorial](https://www.journaldev.com/1540/decorator-design-pattern-in-java-example) + +## Java世界的例子 + * [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html), + [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) + * [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-) + * [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-) + * [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-) + + +## 鸣谢 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) diff --git a/zh/facade/README.md b/zh/facade/README.md new file mode 100644 index 000000000..3784a5be6 --- /dev/null +++ b/zh/facade/README.md @@ -0,0 +1,207 @@ +--- +layout: pattern +title: Facade +folder: facade +permalink: /patterns/facade/ +categories: Structural +tags: + - Gang Of Four + - Decoupling +--- + +## 目的 +为一个子系统中的一系列接口提供一个统一的接口。外观定义了一个更高级别的接口以便子系统更容易使用。 + +## 解释 + +真实世界的例子 + +> 一个金矿是怎么工作的?“嗯,矿工下去然后挖金子!”你说。这是你所相信的因为你在使用一个金矿对外提供的一个简单接口,在内部它要却要做很多事情。这个简单的接口对复杂的子系统来说就是一个外观。 + +用通俗的话说 + +> 外观模式为一个复杂的子系统提供一个简单的接口。 + +维基百科说 + +> 外观是为很大体量的代码(比如类库)提供简单接口的一种对象。 + +**程序示例** + +使用上面金矿的例子。这里我们有矮人的矿工等级制度。 + +```java +public abstract class DwarvenMineWorker { + + private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenMineWorker.class); + + public void goToSleep() { + LOGGER.info("{} goes to sleep.", name()); + } + + public void wakeUp() { + LOGGER.info("{} wakes up.", name()); + } + + public void goHome() { + LOGGER.info("{} goes home.", name()); + } + + public void goToMine() { + LOGGER.info("{} goes to the mine.", name()); + } + + private void action(Action action) { + switch (action) { + case GO_TO_SLEEP: + goToSleep(); + break; + case WAKE_UP: + wakeUp(); + break; + case GO_HOME: + goHome(); + break; + case GO_TO_MINE: + goToMine(); + break; + case WORK: + work(); + break; + default: + LOGGER.info("Undefined action"); + break; + } + } + + public void action(Action... actions) { + Arrays.stream(actions).forEach(this::action); + } + + public abstract void work(); + + public abstract String name(); + + enum Action { + GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK + } +} + +public class DwarvenTunnelDigger extends DwarvenMineWorker { + + private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenTunnelDigger.class); + + @Override + public void work() { + LOGGER.info("{} creates another promising tunnel.", name()); + } + + @Override + public String name() { + return "Dwarven tunnel digger"; + } +} + +public class DwarvenGoldDigger extends DwarvenMineWorker { + + private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenGoldDigger.class); + + @Override + public void work() { + LOGGER.info("{} digs for gold.", name()); + } + + @Override + public String name() { + return "Dwarf gold digger"; + } +} + +public class DwarvenCartOperator extends DwarvenMineWorker { + + private static final Logger LOGGER = LoggerFactory.getLogger(DwarvenCartOperator.class); + + @Override + public void work() { + LOGGER.info("{} moves gold chunks out of the mine.", name()); + } + + @Override + public String name() { + return "Dwarf cart operator"; + } +} + +``` + +为了操纵所有这些矿工我们有了这个外观 + +```java +public class DwarvenGoldmineFacade { + + private final List workers; + + public DwarvenGoldmineFacade() { + workers = List.of( + new DwarvenGoldDigger(), + new DwarvenCartOperator(), + new DwarvenTunnelDigger()); + } + + public void startNewDay() { + makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); + } + + public void digOutGold() { + makeActions(workers, DwarvenMineWorker.Action.WORK); + } + + public void endDay() { + makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); + } + + private static void makeActions(Collection workers, + DwarvenMineWorker.Action... actions) { + workers.forEach(worker -> worker.action(actions)); + } +} +``` + +现在来使用外观 + +```java +DwarvenGoldmineFacade facade = new DwarvenGoldmineFacade(); +facade.startNewDay(); +// Dwarf gold digger wakes up. +// Dwarf gold digger goes to the mine. +// Dwarf cart operator wakes up. +// Dwarf cart operator goes to the mine. +// Dwarven tunnel digger wakes up. +// Dwarven tunnel digger goes to the mine. +facade.digOutGold(); +// Dwarf gold digger digs for gold. +// Dwarf cart operator moves gold chunks out of the mine. +// Dwarven tunnel digger creates another promising tunnel. +facade.endDay(); +// Dwarf gold digger goes home. +// Dwarf gold digger goes to sleep. +// Dwarf cart operator goes home. +// Dwarf cart operator goes to sleep. +// Dwarven tunnel digger goes home. +// Dwarven tunnel digger goes to sleep. +``` + +## 类图 +![alt text](../../facade/etc/facade.urm.png "Facade pattern class diagram") + +## 适用性 +使用外观模式当 + +* 你想为一个复杂的子系统提供一个简单的接口。随着子系统的发展,它们通常会变得更加复杂。多数模式在应用时会导致更多和更少的类。这使子系统更可重用,更易于自定义,但是对于不需要自定义它的客户来说,使用它也变得更加困难。 外观可以提供子系统的简单默认视图,足以满足大多数客户端的需求。只有需要更多可定制性的客户才需要查看外观外的东西(原子系统提供的接口)。 +* 客户端与抽象的实现类之间存在许多依赖关系。 引入外观以使子系统与客户端和其他子系统分离,从而提高子系统的独立性和可移植性。 +* 您想对子系统进行分层。 使用外观来定义每个子系统级别的入口点。 如果子系统是相关的,则可以通过使子系统仅通过其外观相互通信来简化它们之间的依赖性。 + +## 鸣谢 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/zh/factory-method/README.md b/zh/factory-method/README.md new file mode 100644 index 000000000..420653d1b --- /dev/null +++ b/zh/factory-method/README.md @@ -0,0 +1,88 @@ +--- +layout: pattern +title: Factory Method +folder: factory-method +permalink: /patterns/factory-method/ +categories: Creational +tags: + - Extensibility + - Gang Of Four +--- + +## Also known as +# 或称 + +虚拟构造器 + +## 目的 +为创建一个对象定义一个接口,但是让子类决定实例化哪个类。工厂方法允许类将实例化延迟到子类。 + +## 解释 +真实世界例子 + +> 铁匠生产武器。精灵需要精灵武器,而兽人需要兽人武器。根据客户来召唤正确类型的铁匠。 + +通俗的说 + +> 它为类提供了一种把实例化的逻辑委托给子类的方式。 + +维基百科上说 + +> 在基于类的编程中,工厂方法模式是一种创建型设计模式用来解决创建对象的问题,而不需要指定将要创建对象的确切类。这是通过调用工厂方法创建对象来完成的,而不是通过调用构造器。该工厂方法在接口中指定并由子类实现,或者在基类实现并可以选择由子类重写。 + + **程序示例** + +以上面的铁匠为例,首先我们有铁匠的接口和一些它的实现。 + +```java +public interface Blacksmith { + Weapon manufactureWeapon(WeaponType weaponType); +} + +public class ElfBlacksmith implements Blacksmith { + public Weapon manufactureWeapon(WeaponType weaponType) { + return ELFARSENAL.get(weaponType); + } +} + +public class OrcBlacksmith implements Blacksmith { + public Weapon manufactureWeapon(WeaponType weaponType) { + return ORCARSENAL.get(weaponType); + } +} +``` + +现在随着客户的到来,会召唤出正确类型的铁匠并制造出要求的武器。 + +```java +var blacksmith = new ElfBlacksmith(); +blacksmith.manufactureWeapon(WeaponType.SPEAR); +blacksmith.manufactureWeapon(WeaponType.AXE); +// Elvish weapons are created +``` + +## 类图 +![alt text](../../factory-method/etc/factory-method.urm.png "Factory Method pattern class diagram") + +## 适用性 +使用工厂方法模式当 + +* 一个类无法预料它所要必须创建的对象的类 +* 一个类想要它的子类来指定它要创建的对象 +* 类将责任委派给几个帮助子类中的一个,而你想定位了解是具体之中的哪一个 + +## Java中的例子 + +* [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +* [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) +* [java.text.NumberFormat](http://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) +* [java.nio.charset.Charset](http://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) +* [java.net.URLStreamHandlerFactory](http://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html#createURLStreamHandler-java.lang.String-) +* [java.util.EnumSet](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of-E-) +* [javax.xml.bind.JAXBContext](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) + +## 鸣谢 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/zh/observer/README.md b/zh/observer/README.md new file mode 100644 index 000000000..8a4b80a07 --- /dev/null +++ b/zh/observer/README.md @@ -0,0 +1,159 @@ +--- +layout: pattern +title: Observer +folder: observer +permalink: /patterns/observer/ +categories: Behavioral +tags: + - Gang Of Four + - Reactive +--- + +## Also known as +## 又被称为 + +家属,发布订阅模式 + +## 目的 + +定义一种一对多的对象依赖关系这样当一个对象改变状态时,所有依赖它的对象都将自动通知或更新。 + +## 解释 + +真实世界例子 + +> 在遥远的土地上生活着霍比特人和兽人的种族。他们都是户外生活的人所以他们密切关注天气的变化。可以说他们不断地关注着天气。 + +通俗的说 + +> 注册成为一个观察者以接收对象状态的改变。 + +维基百科说 + +> 观察者模式是这样的一种软件设计模式:它有一个被称为主题的对象,维护着一个所有依赖于它的依赖者清单,也就是观察者清单,当主题的状态发生改变时,主题通常会调用观察者的方法来自动通知观察者们。 + +**编程示例** + +让我们先来介绍天气观察者的接口以及我们的种族,兽人和霍比特人。 + +```java +public interface WeatherObserver { + + void update(WeatherType currentWeather); +} + +public class Orcs implements WeatherObserver { + + private static final Logger LOGGER = LoggerFactory.getLogger(Orcs.class); + + @Override + public void update(WeatherType currentWeather) { + LOGGER.info("The orcs are facing " + currentWeather.getDescription() + " weather now"); + } +} + +public class Hobbits implements WeatherObserver { + + private static final Logger LOGGER = LoggerFactory.getLogger(Hobbits.class); + + @Override + public void update(WeatherType currentWeather) { + switch (currentWeather) { + LOGGER.info("The hobbits are facing " + currentWeather.getDescription() + " weather now"); + } +} +``` + +然后这里是不断变化的天气。 + +```java +public class Weather { + + private static final Logger LOGGER = LoggerFactory.getLogger(Weather.class); + + private WeatherType currentWeather; + private final List observers; + + public Weather() { + observers = new ArrayList<>(); + currentWeather = WeatherType.SUNNY; + } + + public void addObserver(WeatherObserver obs) { + observers.add(obs); + } + + public void removeObserver(WeatherObserver obs) { + observers.remove(obs); + } + + /** + * Makes time pass for weather. + */ + public void timePasses() { + var enumValues = WeatherType.values(); + currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; + LOGGER.info("The weather changed to {}.", currentWeather); + notifyObservers(); + } + + private void notifyObservers() { + for (var obs : observers) { + obs.update(currentWeather); + } + } +} +``` + +这是完整的示例。 + +```java + var weather = new Weather(); + weather.addObserver(new Orcs()); + weather.addObserver(new Hobbits()); + + weather.timePasses(); + // The weather changed to rainy. + // The orcs are facing rainy weather now + // The hobbits are facing rainy weather now + weather.timePasses(); + // The weather changed to windy. + // The orcs are facing windy weather now + // The hobbits are facing windy weather now + weather.timePasses(); + // The weather changed to cold. + // The orcs are facing cold weather now + // The hobbits are facing cold weather now + weather.timePasses(); + // The weather changed to sunny. + // The orcs are facing sunny weather now + // The hobbits are facing sunny weather now +``` + +## Class diagram +![alt text](../../observer/etc/observer.png "Observer") + +## 应用 +在下面任何一种情况下都可以使用观察者模式 + +* 当抽象具有两个方面时,一个方面依赖于另一个方面。将这些方面封装在单独的对象中,可以使你分别进行更改和重用 +* 当一个对象的改变的同时需要改变其他对象,同时你又不知道有多少对象需要改变时 +* 当一个对象可以通知其他对象而无需假设这些对象是谁时。换句话说,你不想让这些对象紧耦合。 + +## 典型用例 + +* 一个对象的改变导致其他对象的改变 + +## Java中的例子 + +* [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html) +* [java.util.EventListener](http://docs.oracle.com/javase/8/docs/api/java/util/EventListener.html) +* [javax.servlet.http.HttpSessionBindingListener](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionBindingListener.html) +* [RxJava](https://github.com/ReactiveX/RxJava) + +## 鸣谢 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Java Generics and Collections](https://www.amazon.com/gp/product/0596527756/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596527756&linkCode=as2&tag=javadesignpat-20&linkId=246e5e2c26fe1c3ada6a70b15afcb195) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/zh/strategy/README.md b/zh/strategy/README.md new file mode 100644 index 000000000..29fe2e4eb --- /dev/null +++ b/zh/strategy/README.md @@ -0,0 +1,136 @@ +--- +layout: pattern +title: Strategy +folder: strategy +permalink: /patterns/strategy/ +categories: Behavioral +tags: + - Gang of Four +--- + +## 又被称为 +政策(方针)模式 + +## 目的 + +定义一个家族算法,并封装好其中每一个,使它们可以互相替换。策略模式使算法的变化独立于使用它的客户。 + +## 解释 + +现实世界例子 + +> 屠龙是一项危险的职业。有经验将会使它变得简单。经验丰富的屠龙者对不同类型的龙有不同的战斗策略。 + +直白点说 + +> 策略模式允许在运行时选择最匹配的算法。 + +维基百科上说 + +> 在程序编程领域,策略模式(又叫政策模式)是一种启用在运行时选择算法的行为型软件设计模式。 + +**编程实例** + +让我们先介绍屠龙的策略模式接口和它的实现。 + +```java +@FunctionalInterface +public interface DragonSlayingStrategy { + + void execute(); +} + +public class MeleeStrategy implements DragonSlayingStrategy { + + private static final Logger LOGGER = LoggerFactory.getLogger(MeleeStrategy.class); + + @Override + public void execute() { + LOGGER.info("With your Excalibur you sever the dragon's head!"); + } +} + +public class ProjectileStrategy implements DragonSlayingStrategy { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProjectileStrategy.class); + + @Override + public void execute() { + LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!"); + } +} + +public class SpellStrategy implements DragonSlayingStrategy { + + private static final Logger LOGGER = LoggerFactory.getLogger(SpellStrategy.class); + + @Override + public void execute() { + LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!"); + } +} +``` + +现在有一个强力的屠龙者要基于上面的组件来选择他的战斗策略。 + +```java +public class DragonSlayer { + + private DragonSlayingStrategy strategy; + + public DragonSlayer(DragonSlayingStrategy strategy) { + this.strategy = strategy; + } + + public void changeStrategy(DragonSlayingStrategy strategy) { + this.strategy = strategy; + } + + public void goToBattle() { + strategy.execute(); + } +} +``` + +最后是屠龙者的行动。 + +```java + LOGGER.info("Green dragon spotted ahead!"); + var dragonSlayer = new DragonSlayer(new MeleeStrategy()); + dragonSlayer.goToBattle(); + LOGGER.info("Red dragon emerges."); + dragonSlayer.changeStrategy(new ProjectileStrategy()); + dragonSlayer.goToBattle(); + LOGGER.info("Black dragon lands before you."); + dragonSlayer.changeStrategy(new SpellStrategy()); + dragonSlayer.goToBattle(); + + // Green dragon spotted ahead! + // With your Excalibur you sever the dragon's head! + // Red dragon emerges. + // You shoot the dragon with the magical crossbow and it falls dead on the ground! + // Black dragon lands before you. + // You cast the spell of disintegration and the dragon vaporizes in a pile of dust! +``` + +## 类图 +![alt text](../../strategy/etc/strategy_1.png "Strategy") + +## 应用 +使用策略模式当 + +* 许多相关的类只是行为不同。策略模式提供了一种为一种类配置多种行为的能力。 +* 你需要一种算法的不同变体。比如,你可能定义反应不用时间空间权衡的算法。当这些算法的变体使用类的层次结构来实现时就可以使用策略模式。 +* 一个算法使用的数据客户不应该对其知晓。使用策略模式来避免暴露复杂的,特定于算法的数据结构。 +* 一个类定义了许多行为,这些行为在其操作中展现为多个条件语句。移动相关的条件分支到它们分别的策略类中来代替这些条件语句。 + +## 教学 + +* [Strategy Pattern Tutorial](https://www.journaldev.com/1754/strategy-design-pattern-in-java-example-tutorial) + +## 鸣谢 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)