├── .gitignore ├── src └── com │ └── pattern │ └── specification │ ├── traditional │ ├── structure │ │ ├── Specification.java │ │ ├── CompositeSpecification.java │ │ ├── AndSpecification.java │ │ └── OrSpecification.java │ └── validation │ │ ├── MinimumWeight.java │ │ ├── PackageMustHaveAtLeastItems.java │ │ └── RequestWithinTheTimeLimit.java │ ├── predicate │ ├── Specification.java │ └── ShipmentRules.java │ ├── Shipment.java │ └── Main.java ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | ########################## 2 | ## Java 3 | ########################## 4 | *.class 5 | .mtj.tmp/ 6 | *.jar 7 | *.war 8 | *.ear 9 | hs_err_pid* 10 | 11 | ########################## 12 | ## IntelliJ 13 | ########################## 14 | *.iml 15 | .idea/ 16 | *.ipr 17 | *.iws 18 | out/ 19 | .idea_modules/ -------------------------------------------------------------------------------- /src/com/pattern/specification/traditional/structure/Specification.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification.traditional.structure; 2 | 3 | public interface Specification { 4 | Boolean isSatisfiedBy(T t); 5 | Specification and(Specification other); 6 | Specification or(Specification other); 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Specification Pattern 2 | This repository contains some examples of using the Specification Pattern. In this [link](https://www.martinfowler.com/apsupp/spec.pdf), it is possible to read the Specification Pattern paper by Eric Evans and Martin Fowler, describing how to apply it. 3 | 4 | 5 | ### Specification Pattern in UML 6 | ![GitHub Logo](https://upload.wikimedia.org/wikipedia/commons/4/46/Specification_UML.png) 7 | -------------------------------------------------------------------------------- /src/com/pattern/specification/traditional/structure/CompositeSpecification.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification.traditional.structure; 2 | 3 | public abstract class CompositeSpecification implements Specification { 4 | 5 | @Override 6 | public Specification and(Specification other) { 7 | return new AndSpecification<>(this, other); 8 | } 9 | 10 | @Override 11 | public Specification or(Specification other) { 12 | return new OrSpecification<>(this, other); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/com/pattern/specification/traditional/validation/MinimumWeight.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification.traditional.validation; 2 | 3 | import com.pattern.specification.Shipment; 4 | import com.pattern.specification.traditional.structure.CompositeSpecification; 5 | 6 | public class MinimumWeight extends CompositeSpecification { 7 | 8 | private static final Long MINIMUM_WEIGHT = 10L; 9 | 10 | @Override 11 | public Boolean isSatisfiedBy(Shipment shipment) { 12 | return shipment.getWeight() >= MINIMUM_WEIGHT; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/com/pattern/specification/traditional/structure/AndSpecification.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification.traditional.structure; 2 | 3 | public class AndSpecification extends CompositeSpecification { 4 | private Specification leftRule; 5 | private Specification rightRule; 6 | 7 | public AndSpecification(Specification leftRule, Specification rightRule) { 8 | this.leftRule = leftRule; 9 | this.rightRule = rightRule; 10 | } 11 | 12 | @Override 13 | public Boolean isSatisfiedBy(T t) { 14 | return leftRule.isSatisfiedBy(t) && rightRule.isSatisfiedBy(t); 15 | } 16 | } -------------------------------------------------------------------------------- /src/com/pattern/specification/traditional/structure/OrSpecification.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification.traditional.structure; 2 | 3 | public class OrSpecification extends CompositeSpecification { 4 | private Specification leftRule; 5 | private Specification rightRule; 6 | 7 | public OrSpecification(Specification leftRule, Specification rightRule) { 8 | this.leftRule = leftRule; 9 | this.rightRule = rightRule; 10 | } 11 | 12 | @Override 13 | public Boolean isSatisfiedBy(T t) { 14 | return leftRule.isSatisfiedBy(t) || rightRule.isSatisfiedBy(t); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/com/pattern/specification/traditional/validation/PackageMustHaveAtLeastItems.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification.traditional.validation; 2 | 3 | import com.pattern.specification.Shipment; 4 | import com.pattern.specification.traditional.structure.CompositeSpecification; 5 | 6 | public class PackageMustHaveAtLeastItems extends CompositeSpecification { 7 | 8 | private Long total; 9 | public PackageMustHaveAtLeastItems(Long total){ 10 | this.total = total; 11 | } 12 | 13 | @Override 14 | public Boolean isSatisfiedBy(Shipment shipmentEntity) { 15 | return shipmentEntity.getQuantity() >= total; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/com/pattern/specification/traditional/validation/RequestWithinTheTimeLimit.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification.traditional.validation; 2 | 3 | import com.pattern.specification.Shipment; 4 | import com.pattern.specification.traditional.structure.CompositeSpecification; 5 | 6 | import java.time.LocalDateTime; 7 | import java.time.temporal.ChronoUnit; 8 | 9 | public class RequestWithinTheTimeLimit extends CompositeSpecification { 10 | 11 | private static final Integer TWO_WEEKS = 14; 12 | 13 | @Override 14 | public Boolean isSatisfiedBy(Shipment shipmentEntity) { 15 | return ChronoUnit.DAYS.between(shipmentEntity.getRequest(), LocalDateTime.now()) <= TWO_WEEKS; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/pattern/specification/predicate/Specification.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification.predicate; 2 | 3 | import java.util.function.Predicate; 4 | 5 | public class Specification { 6 | 7 | private Predicate rules; 8 | 9 | public Specification(){ 10 | rules = (r) -> true; 11 | } 12 | 13 | public Specification and(Predicate rule) { 14 | rules = rules.and(rule); 15 | return this; 16 | } 17 | 18 | public Specification or(Predicate rule) { 19 | rules = rules.or(rule); 20 | return this; 21 | } 22 | 23 | public Specification not(Predicate rule) { 24 | rules = rules.and(rule).negate(); 25 | return this; 26 | } 27 | 28 | public boolean isSatisfiedBy(E object) { 29 | return rules.test(object); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/com/pattern/specification/Shipment.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public final class Shipment { 6 | 7 | private final Long quantity; 8 | private final Double weight; 9 | private final LocalDateTime request; 10 | 11 | private Shipment(LocalDateTime request, Long quantity, Double weight){ 12 | this.request = request; 13 | this.quantity = quantity; 14 | this.weight = weight; 15 | } 16 | 17 | public LocalDateTime getRequest() { 18 | return request; 19 | } 20 | 21 | public Long getQuantity() { 22 | return quantity; 23 | } 24 | 25 | public Double getWeight() { 26 | return weight; 27 | } 28 | 29 | public static Shipment newInstance(final LocalDateTime request, final Long quantity, final Double weight){ 30 | return new Shipment(request, quantity, weight); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/com/pattern/specification/predicate/ShipmentRules.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification.predicate; 2 | 3 | import com.pattern.specification.Shipment; 4 | 5 | import java.time.LocalDateTime; 6 | import java.time.temporal.ChronoUnit; 7 | import java.util.function.Predicate; 8 | 9 | public class ShipmentRules { 10 | 11 | private static final Long MINIMUM_WEIGHT = 10L; 12 | private static final Integer TWO_WEEKS = 14; 13 | 14 | public static Predicate packageMustHaveAtLeastItems(long total){ 15 | return shipment -> shipment.getQuantity() >= total; 16 | } 17 | 18 | public static Predicate minimumWeight(){ 19 | return shipment -> shipment.getWeight() >= MINIMUM_WEIGHT; 20 | } 21 | 22 | 23 | public static Predicate requestWithinTheTimeLimit(){ 24 | return shipment -> ChronoUnit.DAYS.between(shipment.getRequest(), LocalDateTime.now()) <= TWO_WEEKS; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Murillo Grübler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/com/pattern/specification/Main.java: -------------------------------------------------------------------------------- 1 | package com.pattern.specification; 2 | 3 | import com.pattern.specification.predicate.Specification; 4 | import com.pattern.specification.traditional.validation.*; 5 | 6 | import java.time.LocalDateTime; 7 | import java.time.temporal.ChronoUnit; 8 | 9 | import static com.pattern.specification.predicate.ShipmentRules.*; 10 | 11 | public class Main { 12 | 13 | private static final String SUCCESS_MESSAGE = "Shipment ran successfully"; 14 | private static final String ERROR_MESSAGE = "Shipment failed"; 15 | 16 | public static void main(String[] args) { 17 | LocalDateTime requestDate = LocalDateTime.now().minus(5, ChronoUnit.DAYS); 18 | Shipment shipment = Shipment.newInstance(requestDate, 50000L, 10D); 19 | 20 | // Traditional structure 21 | Boolean resultTradSpec = new MinimumWeight() 22 | .and(new PackageMustHaveAtLeastItems(25L)) 23 | .and(new RequestWithinTheTimeLimit()) 24 | .isSatisfiedBy(shipment); 25 | System.out.println(resultTradSpec ? SUCCESS_MESSAGE : ERROR_MESSAGE); 26 | 27 | // Specification using predicate abstracted in a class 28 | Boolean resultLambdaSpec = new Specification() 29 | .and(minimumWeight()) 30 | .and(packageMustHaveAtLeastItems(25L)) 31 | .and(requestWithinTheTimeLimit()) 32 | .isSatisfiedBy(shipment); 33 | System.out.println(resultLambdaSpec ? SUCCESS_MESSAGE : ERROR_MESSAGE); 34 | 35 | // Using only Predicate Functional Interface 36 | Boolean resultPredicate = minimumWeight() 37 | .and(packageMustHaveAtLeastItems(1000000L)) 38 | .and(requestWithinTheTimeLimit()) 39 | .test(shipment); 40 | System.out.println(resultPredicate ? SUCCESS_MESSAGE : ERROR_MESSAGE); 41 | 42 | } 43 | } 44 | --------------------------------------------------------------------------------