Et enfin fini la validation du calcul.
Cette fois, on passe à la Factory (le Design Pattern du GOFF).
Pour commencer, comme on fait de l'introspection, on utilise cette librairie (dans le POM service):
Le métier (data et calcul) n'aimant pas avoir des librairies (sauf Apache Common Math pour le calcul), l'injection se fait chez service où on a le célèbre framework Spring.
Pour commencer, on définit la factory par un contrat simple:
Et son implémentation:
Si l'utilisateur envoie de la merde, on lui chie dessus:
Le tout testé, contrairement à certains rigolos et leurs projets à 60 Millions d'€:
Selon le principe TDD, le test a été écrit avant l'implémentation.
Reste à injecter ça dans les services de Spring (indirectement testé, vu que j'ai aussi des tests Back-end).
Pour cela, on définit le paramétrage:
Et l'implémentation concrète:
Pour construire ce truc, on utilise un autre Design Pattern du GOFF: Builder.
Reste plus qu'à injecter ça avec les beans de Spring.
Pour savoir ce qu'il faut injecter, on fait une classe d'introspection:
On a utilisé les annotations précédentes. Comme ça, on peut toujours ajouter dynamiquement des validations, au cas où j'en ai oublié. Sait-on jamais.
La conf de Spring devient:
Si il y a une erreur lors de l'introspection, le serveur web ne démarre même pas. Comme les tests back-end fonctionnent...
Cette fois, on passe à la Factory (le Design Pattern du GOFF).
Pour commencer, comme on fait de l'introspection, on utilise cette librairie (dans le POM service):
Code:
<!--Introspection-->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>${jar.reflexion}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
Pour commencer, on définit la factory par un contrat simple:
Code:
package com.calculateur.warhammer.calcul.mort.factory.validation;
import java.util.List;
import com.calculateur.warhammer.calcul.mort.factory.exception.SimulationException;
import com.calculateur.warhammer.calcul.mort.validation.IValidationCalcul;
import com.calculateur.warhammer.data.enumeration.ESimule;
import com.calculateur.warhammer.data.identifiable.IArmeDeCorpsACorps;
import com.calculateur.warhammer.data.identifiable.IArmeDeTir;
/**
* Factory pour les validations avant le calcul
* @author phili
*
*/
public interface IFactoryValidation {
/**
*
* @param simule Ce qui est simulé.
* @return La liste des validations à effectuer avant la simulation/calcul.
* @throws SimulationException Si ce qui est demandé est incohérent.
*/
List<IValidationCalcul<IArmeDeTir>> getValidationsActionTir(ESimule simule) throws SimulationException;
/**
*
* @param simule Ce qui est simulé.
* @return La liste des validations pour un corps à corps.
* @throws SimulationException Si ce qui est demandé est incohérent.
*/
List<IValidationCalcul<IArmeDeCorpsACorps>> getValidationsActionCorpsACorps(ESimule simule) throws SimulationException;
}
Et son implémentation:
Code:
package com.calculateur.warhammer.calcul.mort.factory.validation;
import java.util.List;
import com.calculateur.warhammer.calcul.mort.factory.exception.SimulationException;
import com.calculateur.warhammer.calcul.mort.validation.IValidationCalcul;
import com.calculateur.warhammer.data.enumeration.ESimule;
import com.calculateur.warhammer.data.identifiable.IArmeDeCorpsACorps;
import com.calculateur.warhammer.data.identifiable.IArmeDeTir;
public class FactoryValidation implements IFactoryValidation{
private final List<IValidationCalcul<IArmeDeTir>> validationsPhaseTir;
private final List<IValidationCalcul<IArmeDeTir>> validationsTirContreCharge;
private final List<IValidationCalcul<IArmeDeCorpsACorps>> validationsCorpsACorps;
public FactoryValidation(List<IValidationCalcul<IArmeDeTir>> validationsPhaseTir,
List<IValidationCalcul<IArmeDeTir>> validationsTirContreCharge,
List<IValidationCalcul<IArmeDeCorpsACorps>> validationsCorpsACorps) {
this.validationsPhaseTir = validationsPhaseTir;
this.validationsTirContreCharge = validationsTirContreCharge;
this.validationsCorpsACorps = validationsCorpsACorps;
}
@Override
public List<IValidationCalcul<IArmeDeTir>> getValidationsActionTir(ESimule simule) throws SimulationException {
if(simule.isActionCorpACorp()) {
Object[] param = {simule};
throw new SimulationException(param, "simule.tir.demande.melee");
}
return (simule == ESimule.TIR_PHASE_TIR)?validationsPhaseTir:validationsTirContreCharge;
}
@Override
public List<IValidationCalcul<IArmeDeCorpsACorps>> getValidationsActionCorpsACorps(ESimule simule)
throws SimulationException {
if(simule.isActionTir()) {
Object[] param = {simule};
throw new SimulationException(param, "simule.melee.demande.tir");
}
return validationsCorpsACorps;
}
}
Si l'utilisateur envoie de la merde, on lui chie dessus:
Code:
package com.calculateur.warhammer.calcul.mort.factory.exception;
import com.calculateur.warhammer.base.exception.ParameterizedFunctionnalException;
public class SimulationException extends ParameterizedFunctionnalException{
/**
*
*/
private static final long serialVersionUID = 1L;
private static final String RES = "com.calculateur.warhammer.calcul.mort.simulation.simulation";
public SimulationException(Object[] objets, String key) {
super(RES,objets, key);
}
}
Le tout testé, contrairement à certains rigolos et leurs projets à 60 Millions d'€:
Code:
package com.calculateur.warhammer.calcul.mort.factory.validation;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import com.calculateur.warhammer.calcul.mort.factory.exception.SimulationException;
import com.calculateur.warhammer.calcul.mort.parametres.ParametresBuilder;
import com.calculateur.warhammer.calcul.mort.validation.IValidationCalcul;
import com.calculateur.warhammer.calcul.mort.validation.ValidationArmeCorpsACorps;
import com.calculateur.warhammer.calcul.mort.validation.ValidationArmeTir;
import com.calculateur.warhammer.calcul.mort.validation.ValidationDefenseur;
import com.calculateur.warhammer.calcul.mort.validation.ValidationTirContreChargeMelee;
import com.calculateur.warhammer.calcul.mort.validation.ValidationTypeArme;
import com.calculateur.warhammer.data.enumeration.ESimule;
import com.calculateur.warhammer.data.identifiable.IArmeDeCorpsACorps;
import com.calculateur.warhammer.data.identifiable.IArmeDeTir;
public class FactoryValidationTest {
private static final Locale[] LOCALES = {Locale.FRANCE,Locale.ENGLISH};
private final List<IValidationCalcul<IArmeDeTir>> validationsPhaseTir;
private final List<IValidationCalcul<IArmeDeTir>> validationsTirContreCharge;
private final List<IValidationCalcul<IArmeDeCorpsACorps>> validationsCorpsACorps;
private final IFactoryValidation factory;
public FactoryValidationTest() {
validationsPhaseTir = Arrays.asList(new ValidationArmeTir(),new ValidationDefenseur<>());
validationsTirContreCharge = Arrays.asList(new ValidationTypeArme<>(),new ValidationTirContreChargeMelee());
validationsCorpsACorps = Arrays.asList(new ValidationArmeCorpsACorps(),new ValidationDefenseur<>());
ParametresBuilder builder = ParametresBuilder.getInstance();
validationsPhaseTir.forEach(builder::addValidationPhaseTir);
validationsTirContreCharge.forEach(builder::addValidationTirContreCharge);
validationsCorpsACorps.forEach(builder::addValidationCorpsACorps);
factory = builder.build().getFactoryValidation();
}
@Test
public void testContextTir() {
List<IValidationCalcul<IArmeDeTir>> lTest;
List<IValidationCalcul<IArmeDeTir>> lAttendue;
for(ESimule simule:ESimule.values()) {
try {
lTest = factory.getValidationsActionTir(simule);
Assertions.assertThat(simule.isActionTir()).isTrue();
lAttendue = (simule == ESimule.TIR_PHASE_TIR)?validationsPhaseTir:validationsTirContreCharge;
Assertions.assertThat(lTest).isEqualTo(lAttendue);
}catch(SimulationException ex) {
Assertions.assertThat(simule.isActionCorpACorp()).isTrue();
valideException(ex);
}
}
}
@Test
public void testContextMelee() {
List<IValidationCalcul<IArmeDeCorpsACorps>> lTest;
for(ESimule simule:ESimule.values()) {
try {
lTest = factory.getValidationsActionCorpsACorps(simule);
Assertions.assertThat(lTest).isEqualTo(validationsCorpsACorps);
}catch(SimulationException ex) {
Assertions.assertThat(simule.isActionTir()).isTrue();
valideException(ex);
}
}
}
private void valideException(SimulationException ex) {
String key = ex.getMessage();
Object[] params = ex.getObjets();
Assertions.assertThat(key).isNotNull();
ResourceBundle res;
for(Locale locale:LOCALES) {
res = ex.getResourceBundle(locale);
ex.traduireLibelles(locale);
Assertions.assertThat(ex.getMessage()).isEqualTo(MessageFormat.format(res.getString(key), params));
}
}
}
Selon le principe TDD, le test a été écrit avant l'implémentation.
Reste à injecter ça dans les services de Spring (indirectement testé, vu que j'ai aussi des tests Back-end).
Pour cela, on définit le paramétrage:
Code:
package com.calculateur.warhammer.calcul.mort.parametres;
import com.calculateur.warhammer.calcul.mort.factory.validation.IFactoryValidation;
/**
* Paramètres pour fabriquer les factory nécessaires au calcul.
* @author phili
*
*/
public interface IParametres {
IFactoryValidation getFactoryValidation();
}
Et l'implémentation concrète:
Code:
package com.calculateur.warhammer.calcul.mort.parametres;
import java.util.List;
import com.calculateur.warhammer.calcul.mort.factory.validation.FactoryValidation;
import com.calculateur.warhammer.calcul.mort.factory.validation.IFactoryValidation;
import com.calculateur.warhammer.calcul.mort.validation.IValidationCalcul;
import com.calculateur.warhammer.data.identifiable.IArmeDeCorpsACorps;
import com.calculateur.warhammer.data.identifiable.IArmeDeTir;
public class Parametres implements IParametres{
private List<IValidationCalcul<IArmeDeTir>> validationsPhaseTir;
private List<IValidationCalcul<IArmeDeTir>> validationsTirContreCharge;
private List<IValidationCalcul<IArmeDeCorpsACorps>> validationsCorpsACorps;
@Override
public IFactoryValidation getFactoryValidation() {
return new FactoryValidation(validationsPhaseTir, validationsTirContreCharge, validationsCorpsACorps);
}
//Getter Setter
public List<IValidationCalcul<IArmeDeTir>> getValidationsPhaseTir() {
return validationsPhaseTir;
}
public void setValidationsPhaseTir(List<IValidationCalcul<IArmeDeTir>> validationsPhaseTir) {
this.validationsPhaseTir = validationsPhaseTir;
}
public List<IValidationCalcul<IArmeDeTir>> getValidationsTirContreCharge() {
return validationsTirContreCharge;
}
public void setValidationsTirContreCharge(List<IValidationCalcul<IArmeDeTir>> validationsTirContreCharge) {
this.validationsTirContreCharge = validationsTirContreCharge;
}
public List<IValidationCalcul<IArmeDeCorpsACorps>> getValidationsCorpsACorps() {
return validationsCorpsACorps;
}
public void setValidationsCorpsACorps(List<IValidationCalcul<IArmeDeCorpsACorps>> validationsCorpsACorps) {
this.validationsCorpsACorps = validationsCorpsACorps;
}
}
Pour construire ce truc, on utilise un autre Design Pattern du GOFF: Builder.
Code:
package com.calculateur.warhammer.calcul.mort.parametres;
import java.util.ArrayList;
import java.util.List;
import com.calculateur.warhammer.base.builder.IBuilder;
import com.calculateur.warhammer.calcul.mort.validation.IValidationCalcul;
import com.calculateur.warhammer.data.identifiable.IArmeDeCorpsACorps;
import com.calculateur.warhammer.data.identifiable.IArmeDeTir;
public class ParametresBuilder implements IBuilder<IParametres>{
private final List<IValidationCalcul<IArmeDeTir>> validationsPhaseTir;
private final List<IValidationCalcul<IArmeDeTir>> validationsTirContreCharge;
private final List<IValidationCalcul<IArmeDeCorpsACorps>> validationsCorpsACorps;
private ParametresBuilder() {
validationsPhaseTir = new ArrayList<>();
validationsTirContreCharge = new ArrayList<>();
validationsCorpsACorps = new ArrayList<>();
}
public static ParametresBuilder getInstance() {
return new ParametresBuilder();
}
@Override
public IParametres build() {
Parametres pReturn = new Parametres();
pReturn.setValidationsPhaseTir(validationsPhaseTir);
pReturn.setValidationsTirContreCharge(validationsTirContreCharge);
pReturn.setValidationsCorpsACorps(validationsCorpsACorps);
return pReturn;
}
public ParametresBuilder addValidationPhaseTir(IValidationCalcul<IArmeDeTir> validation) {
validationsPhaseTir.add(validation);
return this;
}
public ParametresBuilder addValidationTirContreCharge(IValidationCalcul<IArmeDeTir> validation) {
validationsTirContreCharge.add(validation);
return this;
}
public ParametresBuilder addValidationCorpsACorps(IValidationCalcul<IArmeDeCorpsACorps> validation) {
validationsCorpsACorps.add(validation);
return this;
}
}
Reste plus qu'à injecter ça avec les beans de Spring.
Pour savoir ce qu'il faut injecter, on fait une classe d'introspection:
Code:
package com.calculateur.warhammer.service.configuration.introspection;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.reflections.Reflections;
import com.calculateur.warhammer.calcul.mort.validation.IValidationCalcul;
import com.calculateur.warhammer.calcul.mort.validation.annotations.ValidationCorpsACorps;
import com.calculateur.warhammer.calcul.mort.validation.annotations.ValidationPhaseTir;
import com.calculateur.warhammer.calcul.mort.validation.annotations.ValidationTirContreCharge;
import com.calculateur.warhammer.data.identifiable.IArmeDeCorpsACorps;
import com.calculateur.warhammer.data.identifiable.IArmeDeTir;
public class ValidationIntrospectionUtil {
private static final String PACKAGE = "com.calculateur.warhammer.calcul.mort.validation";
private ValidationIntrospectionUtil() {
}
@SuppressWarnings("unchecked")
public static List<IValidationCalcul<IArmeDeTir>> getListValidationPhaseTir() throws SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
List<IValidationCalcul<IArmeDeTir>> lReturn = new ArrayList<>();
Reflections reflections = new Reflections(PACKAGE);
Set<Class<?>> liste =
reflections.getTypesAnnotatedWith(ValidationPhaseTir.class);
Constructor<?> constructor;
IValidationCalcul<IArmeDeTir> validation;
for(Class<?> aClass:liste) {
constructor = aClass.getConstructors()[0];
validation = (IValidationCalcul<IArmeDeTir>)constructor.newInstance();
lReturn.add(validation);
}
return lReturn;
}
@SuppressWarnings("unchecked")
public static List<IValidationCalcul<IArmeDeTir>> getListValisationTirContreCharge()throws SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
List<IValidationCalcul<IArmeDeTir>> lReturn = new ArrayList<>();
Reflections reflections = new Reflections(PACKAGE);
Set<Class<?>> liste =
reflections.getTypesAnnotatedWith(ValidationTirContreCharge.class);
Constructor<?> constructor;
IValidationCalcul<IArmeDeTir> validation;
for(Class<?> aClass:liste) {
constructor = aClass.getConstructors()[0];
validation = (IValidationCalcul<IArmeDeTir>)constructor.newInstance();
lReturn.add(validation);
}
return lReturn;
}
@SuppressWarnings("unchecked")
public static List<IValidationCalcul<IArmeDeCorpsACorps>> getListValisationCorpsACorps()throws SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
List<IValidationCalcul<IArmeDeCorpsACorps>> lReturn = new ArrayList<>();
Reflections reflections = new Reflections(PACKAGE);
Set<Class<?>> liste =
reflections.getTypesAnnotatedWith(ValidationCorpsACorps.class);
Constructor<?> constructor;
IValidationCalcul<IArmeDeCorpsACorps> validation;
for(Class<?> aClass:liste) {
constructor = aClass.getConstructors()[0];
validation = (IValidationCalcul<IArmeDeCorpsACorps>)constructor.newInstance();
lReturn.add(validation);
}
return lReturn;
}
}
On a utilisé les annotations précédentes. Comme ça, on peut toujours ajouter dynamiquement des validations, au cas où j'en ai oublié. Sait-on jamais.
La conf de Spring devient:
Code:
package com.calculateur.warhammer.service.configuration.general;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import com.calculateur.warhammer.base.traduction.ILangues;
import com.calculateur.warhammer.base.traduction.LanguesImplementation;
import com.calculateur.warhammer.calcul.mort.parametres.IParametres;
import com.calculateur.warhammer.calcul.mort.parametres.ParametresBuilder;
import com.calculateur.warhammer.calcul.pouvoir.psy.CalculProbaPouvoirPsy;
import com.calculateur.warhammer.calcul.pouvoir.psy.ICalculProbaPouvoirPsy;
import com.calculateur.warhammer.data.regles.factory.factory.FactoryFactoryRegles;
import com.calculateur.warhammer.data.regles.factory.factory.IFactoryFactoryRegle;
import com.calculateur.warhammer.service.configuration.introspection.ValidationIntrospectionUtil;
/**
* Configuration des services par Spring boot
* @author phili
*
*/
@ComponentScan(basePackages = {"com.calculateur.warhammer.service.service","com.calculateur.warhammer.service.mapping"})
public class AbstractConfigurationService {
@Bean(name = "langues")
public ILangues getLangues() {
Locale defaut = Locale.FRANCE;
Map<String, Locale> map = new HashMap<>();
map.put("fr", Locale.FRANCE);
map.put("en", Locale.ENGLISH);
return new LanguesImplementation(defaut, map);
}
//Fabrication de règle
@Bean(name = "factoryFactoryRegle")
public IFactoryFactoryRegle getFactoryFactoryRegle() {
return new FactoryFactoryRegles();
}
//Paramètres calcul
@Bean(name = "parametres")
public IParametres getParametres() throws SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
final ParametresBuilder builder = ParametresBuilder.getInstance();
ValidationIntrospectionUtil.getListValidationPhaseTir().forEach(builder::addValidationPhaseTir);
ValidationIntrospectionUtil.getListValisationTirContreCharge().forEach(builder::addValidationTirContreCharge);
ValidationIntrospectionUtil.getListValisationCorpsACorps().forEach(builder::addValidationCorpsACorps);
return builder.build();
}
//Calcul proba phase psychique
@Bean("calculProbaPouvoirPsy")
public ICalculProbaPouvoirPsy getCalculProbaPouvoirPsy() {
return new CalculProbaPouvoirPsy();
}
}
Si il y a une erreur lors de l'introspection, le serveur web ne démarre même pas. Comme les tests back-end fonctionnent...