From 3e526cb5da1693c3a6ec30caeb1b8cab2eab360b Mon Sep 17 00:00:00 2001 From: Crossy147 Date: Fri, 19 Feb 2016 17:56:09 +0100 Subject: [PATCH] issue #335 Monad pattern introduced --- monad/index.md | 29 +++++++ monad/pom.xml | 43 ++++++++++ .../src/main/java/com/iluwatar/monad/App.java | 19 +++++ .../src/main/java/com/iluwatar/monad/Sex.java | 5 ++ .../main/java/com/iluwatar/monad/User.java | 39 +++++++++ .../java/com/iluwatar/monad/Validator.java | 81 +++++++++++++++++++ .../test/java/com/iluwatar/monad/AppTest.java | 13 +++ .../java/com/iluwatar/monad/MonadTest.java | 42 ++++++++++ pom.xml | 1 + 9 files changed, 272 insertions(+) create mode 100644 monad/index.md create mode 100644 monad/pom.xml create mode 100644 monad/src/main/java/com/iluwatar/monad/App.java create mode 100644 monad/src/main/java/com/iluwatar/monad/Sex.java create mode 100644 monad/src/main/java/com/iluwatar/monad/User.java create mode 100644 monad/src/main/java/com/iluwatar/monad/Validator.java create mode 100644 monad/src/test/java/com/iluwatar/monad/AppTest.java create mode 100644 monad/src/test/java/com/iluwatar/monad/MonadTest.java diff --git a/monad/index.md b/monad/index.md new file mode 100644 index 000000000..a11360a2e --- /dev/null +++ b/monad/index.md @@ -0,0 +1,29 @@ +--- +layout: pattern +title: Monad +folder: monad +permalink: /patterns/monad/ +categories: Presentation Tier +tags: + - Java + - Difficulty-Advanced +--- + +## Intent + +Monad pattern based on monad from linear algebra represents the way of chaining operations +together step by step. Binding functions can be described as passing one's output to another's input +basing on the 'same type' contract. + +![alt text](./etc/monad.png "Monad") + +## Applicability + +Use the Monad in any of the following situations + +* when you want to chain operations easily +* when you want to apply each function regardless of the result of any of them + +## Credits +* [Design Pattern Reloaded by Remi Forax](https://youtu.be/-k2X7guaArU) +* [Brian Beckman: Don't fear the Monad](https://channel9.msdn.com/Shows/Going+Deep/Brian-Beckman-Dont-fear-the-Monads) diff --git a/monad/pom.xml b/monad/pom.xml new file mode 100644 index 000000000..ec5a14a8c --- /dev/null +++ b/monad/pom.xml @@ -0,0 +1,43 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.11.0-SNAPSHOT + + monad + + + junit + junit + test + + + + diff --git a/monad/src/main/java/com/iluwatar/monad/App.java b/monad/src/main/java/com/iluwatar/monad/App.java new file mode 100644 index 000000000..2ab376201 --- /dev/null +++ b/monad/src/main/java/com/iluwatar/monad/App.java @@ -0,0 +1,19 @@ +package com.iluwatar.monad; + +import java.util.Objects; + +public class App { + + /** + * Program entry point. + * + * @param args @param args command line args + */ + public static void main(String[] args) { + User user = new User("user", 24, Sex.FEMALE, "foobar.com"); + System.out.println(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::getAge, age -> age > 20 && age < 30, "age isn't between...").get().toString()); + } +} diff --git a/monad/src/main/java/com/iluwatar/monad/Sex.java b/monad/src/main/java/com/iluwatar/monad/Sex.java new file mode 100644 index 000000000..8b7e43f3c --- /dev/null +++ b/monad/src/main/java/com/iluwatar/monad/Sex.java @@ -0,0 +1,5 @@ +package com.iluwatar.monad; + +public enum Sex { + MALE, FEMALE +} diff --git a/monad/src/main/java/com/iluwatar/monad/User.java b/monad/src/main/java/com/iluwatar/monad/User.java new file mode 100644 index 000000000..de1be5a45 --- /dev/null +++ b/monad/src/main/java/com/iluwatar/monad/User.java @@ -0,0 +1,39 @@ +package com.iluwatar.monad; + +public class User { + + private String name; + private int age; + private Sex sex; + private String email; + + /** + * + * @param name - name + * @param age - age + * @param sex - sex + * @param email - email + */ + 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 new file mode 100644 index 000000000..7cc84298d --- /dev/null +++ b/monad/src/main/java/com/iluwatar/monad/Validator.java @@ -0,0 +1,81 @@ +package com.iluwatar.monad; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Class representing Monad design pattern. Monad is a way of chaining operations on the + * given object together step by step. In Validator each step results in either success or + * failure indicator, giving a way of receiving each of them easily and finally getting + * validated object or list of exceptions. + * @param Placeholder for an object. + */ +public class Validator { + private final T t; + private final List exceptions = new ArrayList<>(); + + /** + * @param t object to be validated + */ + private Validator(T t) { + this.t = t; + } + + /** + * Creates validator against given object + * + * @param t object to be validated + * @param object's type + * @return new instance of a validator + */ + public static Validator of(T t) { + return new Validator<>(Objects.requireNonNull(t)); + } + + /** + * @param validation one argument boolean-valued function that + * represents one step of validation. Adds exception to main validation exception + * list when single step validation ends with failure. + * @param message error message when object is invalid + * @return this + */ + public Validator validate(Predicate validation, String message) { + if (!validation.test(t)) { + exceptions.add(new IllegalStateException(message)); + } + return this; + } + + /** + * Extension for the {@link Validator#validate(Function, 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)} + * @return this + */ + public Validator validate(Function projection, Predicate validation, + String message) { + return validate(projection.andThen(validation::test)::apply, message); + } + + /** + * To receive validated object. + * + * @return object that was validated + * @throws IllegalStateException when any validation step results with failure + */ + public T get() throws IllegalStateException { + if (exceptions.isEmpty()) { + return t; + } + IllegalStateException 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 new file mode 100644 index 000000000..5d23b41a6 --- /dev/null +++ b/monad/src/test/java/com/iluwatar/monad/AppTest.java @@ -0,0 +1,13 @@ +package com.iluwatar.monad; + +import org.junit.Test; + +public class AppTest { + + @Test + public void testMain() { + String[] args = {}; + App.main(args); + } + +} diff --git a/monad/src/test/java/com/iluwatar/monad/MonadTest.java b/monad/src/test/java/com/iluwatar/monad/MonadTest.java new file mode 100644 index 000000000..5ef2ecc69 --- /dev/null +++ b/monad/src/test/java/com/iluwatar/monad/MonadTest.java @@ -0,0 +1,42 @@ +package com.iluwatar.monad; + + +import junit.framework.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.Objects; + +public class MonadTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testForInvalidName() { + thrown.expect(IllegalStateException.class); + User tom = new User(null, 21, Sex.MALE, "tom@foo.bar"); + Validator.of(tom).validate(User::getName, Objects::nonNull, "name cannot be null").get(); + } + + @Test + public void testForInvalidAge() { + thrown.expect(IllegalStateException.class); + User tom = new User("John", 17, Sex.MALE, "john@qwe.bar"); + Validator.of(tom).validate(User::getName, Objects::nonNull, "name cannot be null") + .validate(User::getAge, age -> age > 21, "user is underaged") + .get(); + } + + @Test + public void testForValid() { + User tom = new User("Sarah", 42, Sex.FEMALE, "sarah@det.org"); + User validated = Validator.of(tom).validate(User::getName, Objects::nonNull, "name cannot be null") + .validate(User::getAge, age -> age > 21, "user is underaged") + .validate(User::getSex, sex -> sex == Sex.FEMALE, "user is not female") + .validate(User::getEmail, email -> email.contains("@"), "email does not contain @ sign") + .get(); + Assert.assertSame(validated, tom); + } +} diff --git a/pom.xml b/pom.xml index c15ca20cf..0b587fd06 100644 --- a/pom.xml +++ b/pom.xml @@ -121,6 +121,7 @@ event-driven-architecture feature-toggle value-object + monad