Skip to content

Commit 3e526cb

Browse files
committed
issue iluwatar#335 Monad pattern introduced
1 parent f1122f7 commit 3e526cb

9 files changed

Lines changed: 272 additions & 0 deletions

File tree

monad/index.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
layout: pattern
3+
title: Monad
4+
folder: monad
5+
permalink: /patterns/monad/
6+
categories: Presentation Tier
7+
tags:
8+
- Java
9+
- Difficulty-Advanced
10+
---
11+
12+
## Intent
13+
14+
Monad pattern based on monad from linear algebra represents the way of chaining operations
15+
together step by step. Binding functions can be described as passing one's output to another's input
16+
basing on the 'same type' contract.
17+
18+
![alt text](./etc/monad.png "Monad")
19+
20+
## Applicability
21+
22+
Use the Monad in any of the following situations
23+
24+
* when you want to chain operations easily
25+
* when you want to apply each function regardless of the result of any of them
26+
27+
## Credits
28+
* [Design Pattern Reloaded by Remi Forax](https://youtu.be/-k2X7guaArU)
29+
* [Brian Beckman: Don't fear the Monad](https://channel9.msdn.com/Shows/Going+Deep/Brian-Beckman-Dont-fear-the-Monads)

monad/pom.xml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
4+
The MIT License
5+
Copyright (c) 2014 Ilkka Seppälä
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in
15+
all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
THE SOFTWARE.
24+
25+
-->
26+
<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"
27+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
28+
<modelVersion>4.0.0</modelVersion>
29+
<parent>
30+
<groupId>com.iluwatar</groupId>
31+
<artifactId>java-design-patterns</artifactId>
32+
<version>1.11.0-SNAPSHOT</version>
33+
</parent>
34+
<artifactId>monad</artifactId>
35+
<dependencies>
36+
<dependency>
37+
<groupId>junit</groupId>
38+
<artifactId>junit</artifactId>
39+
<scope>test</scope>
40+
</dependency>
41+
</dependencies>
42+
</project>
43+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.iluwatar.monad;
2+
3+
import java.util.Objects;
4+
5+
public class App {
6+
7+
/**
8+
* Program entry point.
9+
*
10+
* @param args @param args command line args
11+
*/
12+
public static void main(String[] args) {
13+
User user = new User("user", 24, Sex.FEMALE, "foobar.com");
14+
System.out.println(Validator.of(user).validate(User::getName, Objects::nonNull, "name is null")
15+
.validate(User::getName, name -> !name.isEmpty(), "name is empty")
16+
.validate(User::getEmail, email -> !email.contains("@"), "email doesn't containt '@'")
17+
.validate(User::getAge, age -> age > 20 && age < 30, "age isn't between...").get().toString());
18+
}
19+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.iluwatar.monad;
2+
3+
public enum Sex {
4+
MALE, FEMALE
5+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.iluwatar.monad;
2+
3+
public class User {
4+
5+
private String name;
6+
private int age;
7+
private Sex sex;
8+
private String email;
9+
10+
/**
11+
*
12+
* @param name - name
13+
* @param age - age
14+
* @param sex - sex
15+
* @param email - email
16+
*/
17+
public User(String name, int age, Sex sex, String email) {
18+
this.name = name;
19+
this.age = age;
20+
this.sex = sex;
21+
this.email = email;
22+
}
23+
24+
public String getName() {
25+
return name;
26+
}
27+
28+
public int getAge() {
29+
return age;
30+
}
31+
32+
public Sex getSex() {
33+
return sex;
34+
}
35+
36+
public String getEmail() {
37+
return email;
38+
}
39+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.iluwatar.monad;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.Objects;
6+
import java.util.function.Function;
7+
import java.util.function.Predicate;
8+
9+
/**
10+
* Class representing Monad design pattern. Monad is a way of chaining operations on the
11+
* given object together step by step. In Validator each step results in either success or
12+
* failure indicator, giving a way of receiving each of them easily and finally getting
13+
* validated object or list of exceptions.
14+
* @param <T> Placeholder for an object.
15+
*/
16+
public class Validator<T> {
17+
private final T t;
18+
private final List<Throwable> exceptions = new ArrayList<>();
19+
20+
/**
21+
* @param t object to be validated
22+
*/
23+
private Validator(T t) {
24+
this.t = t;
25+
}
26+
27+
/**
28+
* Creates validator against given object
29+
*
30+
* @param t object to be validated
31+
* @param <T> object's type
32+
* @return new instance of a validator
33+
*/
34+
public static <T> Validator<T> of(T t) {
35+
return new Validator<>(Objects.requireNonNull(t));
36+
}
37+
38+
/**
39+
* @param validation one argument boolean-valued function that
40+
* represents one step of validation. Adds exception to main validation exception
41+
* list when single step validation ends with failure.
42+
* @param message error message when object is invalid
43+
* @return this
44+
*/
45+
public Validator<T> validate(Predicate<T> validation, String message) {
46+
if (!validation.test(t)) {
47+
exceptions.add(new IllegalStateException(message));
48+
}
49+
return this;
50+
}
51+
52+
/**
53+
* Extension for the {@link Validator#validate(Function, Predicate, String)} method,
54+
* dedicated for objects, that need to be projected before requested validation.
55+
* @param projection function that gets an objects, and returns projection representing
56+
* element to be validated.
57+
* @param validation see {@link Validator#validate(Function, Predicate, String)}
58+
* @param message see {@link Validator#validate(Function, Predicate, String)}
59+
* @param <U> see {@link Validator#validate(Function, Predicate, String)}
60+
* @return this
61+
*/
62+
public <U> Validator<T> validate(Function<T, U> projection, Predicate<U> validation,
63+
String message) {
64+
return validate(projection.andThen(validation::test)::apply, message);
65+
}
66+
67+
/**
68+
* To receive validated object.
69+
*
70+
* @return object that was validated
71+
* @throws IllegalStateException when any validation step results with failure
72+
*/
73+
public T get() throws IllegalStateException {
74+
if (exceptions.isEmpty()) {
75+
return t;
76+
}
77+
IllegalStateException e = new IllegalStateException();
78+
exceptions.forEach(e::addSuppressed);
79+
throw e;
80+
}
81+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.iluwatar.monad;
2+
3+
import org.junit.Test;
4+
5+
public class AppTest {
6+
7+
@Test
8+
public void testMain() {
9+
String[] args = {};
10+
App.main(args);
11+
}
12+
13+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.iluwatar.monad;
2+
3+
4+
import junit.framework.Assert;
5+
import org.junit.Rule;
6+
import org.junit.Test;
7+
import org.junit.rules.ExpectedException;
8+
9+
import java.util.Objects;
10+
11+
public class MonadTest {
12+
13+
@Rule
14+
public ExpectedException thrown = ExpectedException.none();
15+
16+
@Test
17+
public void testForInvalidName() {
18+
thrown.expect(IllegalStateException.class);
19+
User tom = new User(null, 21, Sex.MALE, "tom@foo.bar");
20+
Validator.of(tom).validate(User::getName, Objects::nonNull, "name cannot be null").get();
21+
}
22+
23+
@Test
24+
public void testForInvalidAge() {
25+
thrown.expect(IllegalStateException.class);
26+
User tom = new User("John", 17, Sex.MALE, "john@qwe.bar");
27+
Validator.of(tom).validate(User::getName, Objects::nonNull, "name cannot be null")
28+
.validate(User::getAge, age -> age > 21, "user is underaged")
29+
.get();
30+
}
31+
32+
@Test
33+
public void testForValid() {
34+
User tom = new User("Sarah", 42, Sex.FEMALE, "sarah@det.org");
35+
User validated = Validator.of(tom).validate(User::getName, Objects::nonNull, "name cannot be null")
36+
.validate(User::getAge, age -> age > 21, "user is underaged")
37+
.validate(User::getSex, sex -> sex == Sex.FEMALE, "user is not female")
38+
.validate(User::getEmail, email -> email.contains("@"), "email does not contain @ sign")
39+
.get();
40+
Assert.assertSame(validated, tom);
41+
}
42+
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
<module>event-driven-architecture</module>
122122
<module>feature-toggle</module>
123123
<module>value-object</module>
124+
<module>monad</module>
124125
</modules>
125126

126127
<dependencyManagement>

0 commit comments

Comments
 (0)