diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ba016365a0c19353050f156b68c0bdeb24a6801..12434bf73616e26254f786f222bdc7b22aeb130e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,3 +16,4 @@ include: - local: "/trip-service-kata/.gitlab-ci.yml" - local: "/tcr-roman-numerals/.gitlab-ci.yml" - local: "/java-h2g2/.gitlab-ci.yml" + - local: "/factory-patterns/.gitlab-ci.yml" diff --git a/README.md b/README.md index 7604eb69cd8c5cd5510df2930b946346299a0ed2..b082dea0bb17bccfac75812a36780047472ce258 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,4 @@ Ce dépôt Git a pour but de partager les différents ateliers pouvant être ré | [Dev pour des data scientists, un sale boulot](https://www.youtube.com/watch?v=QK3OJGAresE) | Discussion | | | [TCR - Roman Numerals](/tcr-roman-numerals) | Kata | Moyenne | | [H2G2 en Java](/java-h2g2) | Kata | Moyenne | +| [Factory patterns](/factory-patterns) | Code&coffee | Facile | diff --git a/factory-patterns/.gitlab-ci.yml b/factory-patterns/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..4309563748fe7e147829ef09f731421ba429aafa --- /dev/null +++ b/factory-patterns/.gitlab-ci.yml @@ -0,0 +1,11 @@ +package-factory-patterns: + variables: + PROJECT_FOLDER: "factory-patterns" + extends: .java + only: + refs: + - master + - merge_requests + changes: + - ".gitlab-common-ci.yml" + - "factory-patterns/**/*" diff --git a/factory-patterns/pom.xml b/factory-patterns/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..0990191fba221c7273feb955aa525b45e2d1fc3a --- /dev/null +++ b/factory-patterns/pom.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <version>1.0.0</version> + <groupId>fr.ippon.kata</groupId> + <artifactId>java-parent</artifactId> + <relativePath>../java-parent</relativePath> + </parent> + + <version>1.0.0-SNAPSHOT</version> + <artifactId>factory-patterns</artifactId> + + <name>FactoryPatterns</name> + + <developers> + <developer> + <email>arey@ippon.fr</email> + <name>Anthony REY</name> + </developer> + <developer> + <email>cdamon@ippon.fr</email> + <name>Colin DAMON</name> + </developer> + </developers> +</project> diff --git a/factory-patterns/readme.md b/factory-patterns/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..675d4e27b16a847c7b33c0550a624d8f3190048d --- /dev/null +++ b/factory-patterns/readme.md @@ -0,0 +1,28 @@ +# "factory" patterns + +Live code & coffee. Sujet proposé par le chat : les patterns factory. + +But de ces patterns : constuire des objets cohérents + +## Static factory + +Utilisation de méthodes statiques pour construire nos objets. Exemple dans `Firstname`. + +Cas d'utilisation : + +- Constuire un objet en ajoutant du sens grace au nom de la méthode ; +- Fermer l'extension (avec un constructeur privé) ; +- Permettre l'utilisation d'un cache ; +- Renvoyer un type différent (ex: Optional<T>). + +## Builder + +Objet mutable pour constuire des objets cohérents. Ils fournissent une API fluent (comme une phrase) pour construire sans ambiguité des objets complexes. Exemple dans `Person`. + +## Factory method + +On délégue une partie des responsabilité d'un objet à un tiers. Les enfants de notre objet doivent implémenter une méthode fournissant ce tiers. De cette manière on peut avoir une logique générale dans le parent et des spécificité dans chaque enfant. + +## Abstract factory + +Abstract factory permet de construire des objets abstraits en choisissant l'implémentation en fonction d'une clé. Dans ce cas on va créer une implémentation de factory par type d'objet a créer et utiliser cette implémentation pour construire nos objets en fonction de la clé. diff --git a/factory-patterns/src/main/java/fr/ippon/factory/Firstname.java b/factory-patterns/src/main/java/fr/ippon/factory/Firstname.java new file mode 100644 index 0000000000000000000000000000000000000000..ed7c1654c7ca6d98611881512fa1a4cbc5665295 --- /dev/null +++ b/factory-patterns/src/main/java/fr/ippon/factory/Firstname.java @@ -0,0 +1,31 @@ +package fr.ippon.factory; + +import java.util.Optional; + +public class Firstname { + private final String firstname; + + public Firstname(String firstname) { + assertFirstname(firstname); + + this.firstname = firstname; + } + + private void assertFirstname(String firstname) { + if (firstname == null) { + throw new IllegalArgumentException(); + } + } + + public static Optional<Firstname> of(String firstname) { + if (firstname == null) { + return Optional.empty(); + } + + return Optional.of(new Firstname(firstname)); + } + + public String get() { + return firstname; + } +} diff --git a/factory-patterns/src/main/java/fr/ippon/factory/Lastname.java b/factory-patterns/src/main/java/fr/ippon/factory/Lastname.java new file mode 100644 index 0000000000000000000000000000000000000000..b5fc20ef01a132a9903daa1ac01d19ef2ef38df2 --- /dev/null +++ b/factory-patterns/src/main/java/fr/ippon/factory/Lastname.java @@ -0,0 +1,21 @@ +package fr.ippon.factory; + +public class Lastname { + private final String lastname; + + public Lastname(String lastname) { + assertLastname(lastname); + + this.lastname = lastname; + } + + private void assertLastname(String lastname) { + if (lastname == null) { + throw new IllegalArgumentException(); + } + } + + public String get() { + return lastname; + } +} diff --git a/factory-patterns/src/main/java/fr/ippon/factory/Person.java b/factory-patterns/src/main/java/fr/ippon/factory/Person.java new file mode 100644 index 0000000000000000000000000000000000000000..77c9854759e1f16e9ee7fd07d588b8da298bbee5 --- /dev/null +++ b/factory-patterns/src/main/java/fr/ippon/factory/Person.java @@ -0,0 +1,44 @@ +package fr.ippon.factory; + +public class Person { + private final Firstname firstname; + private final Lastname lastname; + + private Person(PersonBuilder builder) { + firstname = new Firstname(builder.firstname); + lastname = new Lastname(builder.lastname); + } + + public static PersonBuilder builder() { + return new PersonBuilder(); + } + + public Firstname getFirstname() { + return firstname; + } + + public Lastname getLastname() { + return lastname; + } + + public static class PersonBuilder { + private String firstname; + private String lastname; + + public PersonBuilder firstname(String firstname) { + this.firstname = firstname; + + return this; + } + + public PersonBuilder lastname(String lastname) { + this.lastname = lastname; + + return this; + } + + public Person build() { + return new Person(this); + } + } +} diff --git a/factory-patterns/src/test/java/fr/ippon/factory/FirstnameTest.java b/factory-patterns/src/test/java/fr/ippon/factory/FirstnameTest.java new file mode 100644 index 0000000000000000000000000000000000000000..208283199613c2cf9a7b419395c3cc7725a48d15 --- /dev/null +++ b/factory-patterns/src/test/java/fr/ippon/factory/FirstnameTest.java @@ -0,0 +1,28 @@ +package fr.ippon.factory; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class FirstnameTest { + + @Test + void shouldNotBuildWithoutFirstname() { + assertThatThrownBy(() -> new Firstname(null)).isExactlyInstanceOf(IllegalArgumentException.class); + } + + @Test + void shouldGetFirstname() { + assertThat(new Firstname("Jean").get()).isEqualTo("Jean"); + } + + @Test + void shouldGetEmptyFirstnameWithoutFirstname() { + assertThat(Firstname.of(null)).isEmpty(); + } + + @Test + void shouldGetFirstnameFromActualFirstname() { + assertThat(Firstname.of("Jean").get().get()).isEqualTo("Jean"); + } +} diff --git a/factory-patterns/src/test/java/fr/ippon/factory/LastnameTest.java b/factory-patterns/src/test/java/fr/ippon/factory/LastnameTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e0345f3960d359a1429fe7c597f13ef470ed9682 --- /dev/null +++ b/factory-patterns/src/test/java/fr/ippon/factory/LastnameTest.java @@ -0,0 +1,18 @@ +package fr.ippon.factory; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class LastnameTest { + + @Test + void shouldNotBuildWithoutLastname() { + assertThatThrownBy(() -> new Lastname(null)).isExactlyInstanceOf(IllegalArgumentException.class); + } + + @Test + void shouldGetLastname() { + assertThat(new Lastname("Dupont").get()).isEqualTo("Dupont"); + } +} diff --git a/factory-patterns/src/test/java/fr/ippon/factory/PersonTest.java b/factory-patterns/src/test/java/fr/ippon/factory/PersonTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0ce32af78855ded7548a0aabee730d20aa6a2a34 --- /dev/null +++ b/factory-patterns/src/test/java/fr/ippon/factory/PersonTest.java @@ -0,0 +1,17 @@ +package fr.ippon.factory; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class PersonTest { + + @Test + void shouldGetPersonInformation() { + Person person = Person.builder().firstname("Jean").lastname("Dupont").build(); + + assertThat(person.getFirstname()).usingRecursiveComparison().isEqualTo(new Firstname("Jean")); + + assertThat(person.getLastname()).usingRecursiveComparison().isEqualTo(new Lastname("Dupont")); + } +}