issue #335 Monad pattern introduced
This commit is contained in:
		
							
								
								
									
										29
									
								
								monad/index.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								monad/index.md
									
									
									
									
									
										Normal file
									
								
							@@ -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.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## 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)
 | 
			
		||||
							
								
								
									
										43
									
								
								monad/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								monad/pom.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
<?xml version="1.0"?>
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    The MIT License
 | 
			
		||||
    Copyright (c) 2014 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.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
 | 
			
		||||
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.iluwatar</groupId>
 | 
			
		||||
        <artifactId>java-design-patterns</artifactId>
 | 
			
		||||
        <version>1.11.0-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>monad</artifactId>
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>junit</groupId>
 | 
			
		||||
            <artifactId>junit</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
</project>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								monad/src/main/java/com/iluwatar/monad/App.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								monad/src/main/java/com/iluwatar/monad/App.java
									
									
									
									
									
										Normal file
									
								
							@@ -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());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								monad/src/main/java/com/iluwatar/monad/Sex.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								monad/src/main/java/com/iluwatar/monad/Sex.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
package com.iluwatar.monad;
 | 
			
		||||
 | 
			
		||||
public enum Sex {
 | 
			
		||||
  MALE, FEMALE
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								monad/src/main/java/com/iluwatar/monad/User.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								monad/src/main/java/com/iluwatar/monad/User.java
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								monad/src/main/java/com/iluwatar/monad/Validator.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								monad/src/main/java/com/iluwatar/monad/Validator.java
									
									
									
									
									
										Normal file
									
								
							@@ -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 <T> Placeholder for an object.
 | 
			
		||||
 */
 | 
			
		||||
public class Validator<T> {
 | 
			
		||||
  private final T t;
 | 
			
		||||
  private final List<Throwable> 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 <T> object's type
 | 
			
		||||
   * @return new instance of a validator
 | 
			
		||||
   */
 | 
			
		||||
  public static <T> Validator<T> 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<T> validate(Predicate<T> 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 <U>        see {@link Validator#validate(Function, Predicate, String)}
 | 
			
		||||
   * @return this
 | 
			
		||||
   */
 | 
			
		||||
  public <U> Validator<T> validate(Function<T, U> projection, Predicate<U> 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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								monad/src/test/java/com/iluwatar/monad/AppTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								monad/src/test/java/com/iluwatar/monad/AppTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
package com.iluwatar.monad;
 | 
			
		||||
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
public class AppTest {
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void testMain() {
 | 
			
		||||
    String[] args = {};
 | 
			
		||||
    App.main(args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								monad/src/test/java/com/iluwatar/monad/MonadTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								monad/src/test/java/com/iluwatar/monad/MonadTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user