issue #335 Monad pattern introduced
This commit is contained in:
parent
f1122f78e3
commit
3e526cb5da
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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user