[JAVA] Expliquer DI dans une histoire d'obésité

introduction

Cet article est l'article du 12ème jour de Java Advent Calendar 2018.

Cela fait environ six mois que je suis entré en contact avec la DI de Java. J'étais pressé d'enseigner à un nouveau membre de l'équipe, je vais donc le résumer dans un article pour ma critique.

Voici un exemple de projet. https://github.com/segurvita/FatnessChecker

Cible de cet article

Pour ceux qui comprennent les classes et les constructeurs, mais qui ne connaissent pas bien la DI.

Je vais essayer de l'expliquer uniquement avec des classes et des constructeurs afin que je puisse le comprendre sans connaître Bean ou ʻInterface`.

Je ne comprends pas le sens de "DI signifie injection de dépendance"

Le but de cet article est de comprendre la signification de cette ** injection de dépendances **.

DI signifie ne pas utiliser nouveau dans la classe!

N'ayez pas peur de mal comprendre, ** DI signifie ne pas utiliser nouveau dans une classe **. (Je comprends que ce n'est pas une expression précise, mais cet article sera expliqué en mettant l'accent sur la clarté.)

Modèle non DI

public class Hoge{
    //Champ (variable membre)
    Fuga piyo;
    
	//Constructeur
	public Hoge() {
	    //J'utilise du nouveau dans ma classe.
		this.piyo = new Fuga();
	}
}

Modèle DI

public class Hoge{
    //Champ (variable membre)
    Fuga piyo;
    
	//Constructeur
	public Hoge(Fuga mogera) {
	    //N'utilisez pas de nouveau. J'injecte de l'extérieur.
		this.piyo = mogera;
	}
}

Cependant, je n'en comprends pas le mérite seul. De là, j'expliquerai sous forme d'histoire.

Épisode 1: Je suis allé dans une société d'évaluation de l'obésité

Une histoire qui commence soudainement

: homme: Suis-je gros? C'est tout! Demandons une inspection à une société de jugement sur l'obésité!

Après plusieurs heures

: person_frowning: Bienvenue dans l'entreprise de détermination de l'obésité. Quel genre d'entreprise avez-vous aujourd'hui?

: homme: Je veux juger du degré d'obésité.

: person_frowning: Vous êtes un client qui juge le degré d'obésité. Est-il acceptable de demander la taille et le poids?

: man: Eh bien, je mesure 170 cm et pèse 70 kg.

: person_frowning: Je suis intelligent. Patientez s'il-vous-plait. Nous calculerons l'IMC avec notre dernier robot.

: robot: Calcul Shimasu ・ ・ ・ 70kg ÷ 1.70m de, IMC Ha ** 41.18 ** Mort!

: person_frowning: Si l'IMC est de 41,18 ... c'est 40 ou plus, donc c'est ** 4 degrés obèses **.

: man: Ouais! Tel!

Après plusieurs heures

: man: C'est absolument étrange que je sois obèse. Ce robot n'est-il pas un bug?

Qu'est-ce que l'IMC

L'IMC (indice physique) est calculé en fonction du poids (kg) ÷ taille (m) ÷ taille (m).

Le calcul de: robot: est-il faux?

Qu'est-ce que le jugement sur l'obésité?

Au Japon, les personnes ayant un IMC de 25 ou plus sont considérées comme ** obèses **. Il y a quatre stades d'obésité, de 1 à 4 degrés.

Citation de l'image: OMRON / vol.103 a révisé les critères de diagnostic de l'obésité pour la première fois en 11 ans

Ton rôle

Les personnes qui sont apparues dans le premier épisode sont les suivantes.

-: Homme: est une personne qui veut connaître son obésité. Puisqu'il s'agit d'un utilisateur, nous l'appellerons «Utilisateur». -: Person_frowning: est le bureau d'accueil de la société de jugement de l'obésité. Ne calculez pas l'IMC vous-même, mais demandez: robot: pour le faire. Le degré d'obésité peut être déterminé en fonction de la valeur de l'IMC. Appelons cela «FatnessChecker». -: Robot: est un robot qui calcule l'IMC. Il y a toujours un bug dans le prototype. Appelons cela «BmiRobot».

Problème du cas du premier épisode

: robot: BmiRobot a fait une erreur dans le calcul de l'IMC. Apparemment, il y a un bug.

Sur la base de ce mauvais résultat de calcul,: person_frowning: FatnessChecker a fait un jugement sur l'obésité, donc le résultat du jugement était également faux.

En d'autres termes, le problème est que: person_frowning: FatnessChecker reposait sur un robot peu fiable appelé: robot: BmiRobot.

Épisode 2: Apparition de BMI Sennin

: man: C'est absolument étrange que je sois obèse. Ce robot n'est-il pas un bug?

: old_man: Cette personne a-t-elle des problèmes avec le calcul de l'IMC?

: man: Qui êtes-vous!

: old_man: Je suis un ermite de l'IMC. Veuillez me raconter l'histoire si vous le souhaitez.

: man: Ouais, c'est en fait un secret ...

Quelques jours plus tard

: information_desk_person: Bienvenue dans l'entreprise de détermination de l'obésité. (Omis) Puis-je vous poser des questions sur votre taille et votre poids?

: homme: Il mesure 170 cm et pèse 70 kg. Cependant, je vais demander à cette personne de calculer l'IMC, pas au robot l'autre jour.

: plus âgé: Hohoho, dans ce cas 70kg ÷ 1,70m ÷ 1,70m, l'IMC est ** 24,22 **.

: information_desk_person: Je suis intelligent. Si l'IMC est de 24,22 ... il est supérieur à 18,5 et inférieur à 25, donc c'est un ** poids normal **.

: man: Je l'ai fait! Je ne suis pas à peine obèse!

Ton rôle

Les personnes qui sont apparues dans le deuxième épisode sont les suivantes.

-: Homme: est une personne qui veut connaître son obésité. Puisqu'il s'agit d'un utilisateur, nous l'appellerons «Utilisateur». -: Older_man: est un maître dans le calcul de l'IMC. Je me suis beaucoup entraîné, donc je ne ferai jamais d'erreur dans le calcul. Appelons cela «BmiMaster». -: Information_desk_person: est une personne qui utilise: plus vieux: pour déterminer l'obésité. C'est la même personne que: person_frowning:, mais pour des raisons d'explication, nous allons la distinguer et l'appeler FatnessCheckerDi.

Solution DI

: man: ʻUsera demandé à: information_desk_person:FatnessCheckerDi pour déterminer le degré d'obésité à la condition que: plus vieux: BmiMaster` calcule l'IMC.

En conséquence, le résultat du jugement de: information_desk_person: FatnessCheckerDi dépend de la personne fiable :: old_man: BmiMaster.

C'est ** DI ** (Dependency Injection)! En d'autres termes

: man: ʻUserinjecte: old_man:BmiMaster dans: information_desk_person: FatnessCheckerDi`!

Avantages de DI

L'avantage de DI est que vous pouvez ** tester unitaire la pièce en cours de développement même si la pièce dépendante n'est pas terminée **.

Dans ce cas, il y avait un bogue dans: robot: BmiRobot, donc si cela ne fonctionnait pas, ce serait bien si nous faisions suffisamment de tests et réduisions les bogues dans: robot: BmiRobot. ?? Je pense qu'il y a aussi une opinion. (Je le pensais au début.)

Cependant, il faudra un certain temps pour ** tester suffisamment **.

: person_frowning: FatnessChecker dépend de: robot: BmiRobot, donc vous ne pouvez pas tester: person_frowning: FatnessChecker jusqu'à ce que: robot: BmiRobot soit terminé. Cela augmentera le temps de développement.

D'autre part,: information_desk_person: FatnessCheckerDi permet aux parties dépendantes (: old_man: BmiMaster) d'être injectées de l'extérieur (: man: ʻUser). En faisant cela, même si la partie dépendante (: old_man: BmiMaster) n'est pas terminée, si vous injectez une maquette (test uniquement haribote) de la partie dépendante (: old_man: BmiMaster) ,: information_desk_person: FatnessCheckerDi` peut être testé unitaire.

Inconvénients de la DI

L'inconvénient est que ** le coût d'apprentissage est élevé **.

Dans le cas du développement d'équipe, si l'ID est adopté dans le projet en cours de développement, tous les membres devront apprendre l'ID. Pour les équipes avec beaucoup de changements de membres, le coût de la formation est élevé à un niveau qui ne peut être ignoré.

Exemple de code

Je pense que c'est difficile à transmettre avec juste des phrases, donc je vais l'expliquer avec du code Java.

Voici un exemple de projet. https://github.com/segurvita/FatnessChecker

: robot: calculé avec BmiRobot (avant l'introduction de DI)

Premièrement,: man: ʻUser` ressemble à ceci.

User.java


/**
 *Les personnes qui veulent connaître le degré d'obésité
 */
public class User {
	/**
	 *Demandez à une société d'évaluation de l'obésité de porter un jugement sans maître IMC.
	 */
	public void runWithoutBmiMaster() {
		//Entamez une conversation avec une personne d'une société d'évaluation de l'obésité
		FatnessChecker fatnessChecker = new FatnessChecker();

		//Dites-leur votre taille et votre poids et demandez-leur de déterminer leur obésité.
		String result = fatnessChecker.check(170.0, 70.0);

		//Le résultat du jugement du degré d'obésité est affiché.
		System.out.println("Résultat du jugement d'obésité (sans maître IMC):" + result);
	}
}

Vous demandez du travail sur fatnessChecker.check.

Ensuite: person_frowning: FatnessChecker ressemble à ceci:

FatnessChecker.java


/**
 *Société d'évaluation de l'obésité (utilisant le robot BMI)
 */
public class FatnessChecker {
	/**
	 *Déterminer l'IMC
	 * @hauteur de paramètre hauteur[cm]
	 * @poids param poids[kg]
	 * @retour Obésité
	 */
	public String check(double height, double weight) {
		//Sécurisez l'un des derniers robots BMI de l'entreprise.
		BmiRobot bmiRobot = new BmiRobot();

		//Demandez au robot BMI de calculer l'IMC.
		double bmi = bmiRobot.calc(height, weight);

		//Déterminez le degré d'obésité à partir du résultat du calcul de l'IMC.
		if (bmi < 18.5) {
			return "Faible poids";
		} else if (bmi < 25.0) {
			return "Poids normal";
		} else if (bmi < 30.0) {
			return "Une fois obèse";
		} else if (bmi < 35.0) {
			return "Deux fois obèses";
		} else if (bmi < 40.0) {
			return "Obèse au 3e degré";
		} else {
			return "Obèse au 4e degré";
		}
	}
}

Dans la méthode check,new BmiRobot () ʻest utilisé pour en sécuriser un: robot:BmiRobot`.

Après cela, le calcul de l'IMC est demandé par BmiRobot.calc (taille, poids), et le degré d'obésité est jugé en fonction du résultat du calcul.

S'il y a un bogue dans: robot: BmiRobot, le résultat du jugement sera faux. : robot: dépend de BmiRobot.

Que: robot: BmiRobot est implémenté, par exemple:

BmiRobot.java


/**
 *Robot BMI (avec bugs)
 */
public class BmiRobot {
	/**
	 *Calculer l'IMC
	 * @hauteur de paramètre hauteur[cm]
	 * @poids param poids[kg]
	 * @return BMI
	 */
	public double calc(double height, double weight) {
		//poids[kg]÷ Hauteur[m]
		return weight * 100 / height;
	}
}

La formule est «poids [kg] ÷ taille [m]», ce qui est incorrect.

Si vous exécutez le programme dans cet état, le résultat sera

Résultat d'exécution


Résultat du jugement sur l'obésité (sans maître IMC): 4 degrés d'obésité

Ce sera.

: old_man: Lorsque calculé avec BmiMaster (après introduction DI)

Premièrement,: man: ʻUser` ressemble à ceci.

User.java


/**
 *Les personnes qui veulent connaître le degré d'obésité
 */
public class User {
	/**
	 *Avec un maître IMC, demandez à une société d'évaluation de l'obésité de porter un jugement.
	 */
	public void runWithBmiMaster() {
		//Sécurisez un maître BMI.
		BmiMaster bmiCalculator = new BmiMaster();

		//Demandez à une personne de la société d'évaluation de l'obésité de faire du calcul de l'IMC un maître IMC.
		FatnessCheckerDi fatnessCheckerDi = new FatnessCheckerDi(bmiCalculator);

		//Dites-leur votre taille et votre poids et demandez-leur de déterminer leur obésité.
		String result = fatnessCheckerDi.check(170.0, 70.0);

		//Le résultat du jugement du degré d'obésité est affiché.
		System.out.println("Résultat du jugement d'obésité (avec BMI master)" + result);
	}
}

La grande différence par rapport au précédent est que: man: ʻUserutilisenew BmiMaster ()et en réserve un: plus vieux_man:BmiMaster. Ceci est passé à: information_desk_person: FatnessCheckerDi avec nouveau FatnessCheckerDi (bmiCalculator)`.

Ensuite: information_desk_person: FatnessCheckerDi ressemble à ceci:

FatnessCheckerDi.java


/**
 *Société d'évaluation de l'obésité (à l'aide du maître BMI)
 */
public class FatnessCheckerDi {
	/**
	 *Maître IMC
	 */
	final private BmiMaster bmiMaster;

	/**
	 *constructeur
	 * @param bmiCalculator BMI master nommé par l'utilisateur
	 */
	public FatnessCheckerDi(BmiMaster bmiMaster) {
		//Nous accueillons le maître BMI désigné par l'utilisateur.
		this.bmiMaster = bmiMaster;
	}

	/**
	 *Déterminer l'IMC
	 * @hauteur de paramètre hauteur[cm]
	 * @poids param poids[kg]
	 * @retour Obésité
	 */
	public String check(double height, double weight) {
		//Demandez au maître BMI désigné par l'utilisateur de calculer l'IMC.
		double bmi = this.bmiMaster.calc(height, weight);

		//Déterminez le degré d'obésité à partir du résultat du calcul de l'IMC.
		if (bmi < 18.5) {
			return "Faible poids";
		} else if (bmi < 25.0) {
			return "Poids normal";
		} else if (bmi < 30.0) {
			return "Une fois obèse";
		} else if (bmi < 35.0) {
			return "Deux fois obèses";
		} else if (bmi < 40.0) {
			return "Obèse au 3e degré";
		} else {
			return "Obèse au 4e degré";
		}
	}
}

La grande différence par rapport au précédent est l'apparence du constructeur.

Auparavant, dans la méthode check, nous en avions réservé un: robot: BmiRobot, mais cette fois nous recevons: plus ancien_man: BmiMaster comme argument du constructeur et l'utilisons comme champ (variable membre). ) Est assigné.

Après cela, le calcul de l'IMC est demandé par «this.bmiMaster.calc (taille, poids)», et le degré d'obésité est jugé en fonction du résultat du calcul.

Maintenant, cela dépend de: old_man: BmiMaster.

Cela: plus ancien_man: BmiMaster est implémenté, par exemple:

BmiMaster.java


/**
 *Maître BMI (Master)
 */
public class BmiMaster {
	/**
	 *Calculer l'IMC
	 * @hauteur de paramètre hauteur[cm]
	 * @poids param poids[kg]
	 * @return BMI
	 */
	public double calc(double height, double weight) {
		//poids[kg] ÷ (la taille[m])^2
		return weight * 10000 / (height * height);
	}
}

La formule est «poids [kg] ÷ (taille [m]) ^ 2». C'est la bonne formule.

Si vous exécutez le programme dans cet état, le résultat sera

Résultat d'exécution


Résultat du jugement d'obésité (avec BMI master): poids normal

Ce sera.

Bonus: Où diable devrais-je "nouveau"?

Dans cette histoire,: man: ʻUserprépare: old_man:BmiMaster ( new) et l'injecte dans: information_desk_person: FatnessCheckerDi`.

Donc, si: man: ʻUserlui-même était également conçu avec le modèle DI, qui préparerait: plus vieux_man:BmiMaster`?

: man: Y a-t-il une autre classe en dehors de ʻUser`? Et si même cette classe était conçue avec un modèle DI?

C'est là que le ** conteneur DI ** entre en jeu. Entreprend le traitement de «nouveau» à la fois.

Dans le cas de Java Spring, BeanFactory correspond à cela.

à la fin

Qu'as-tu pensé. J'ai essayé d'expliquer la DI avec la personnalisation de l'histoire.

Quand je suis entré en contact avec DI il y a six mois, j'ai été enterré dans des termes inconnus tels que «Autowired» et «BeanFactory», et c'est devenu «DI difficile ...». (À ce moment-là, il n'y avait aucune distinction entre les conteneurs DI et DI.)

Cependant, quand je l'ai codé pendant six mois et que j'ai regardé en arrière, je me suis dit: "N'est-il pas possible d'expliquer DI sans savoir que c'est en fait" Bean "?", J'ai donc écrit cet article pour moi-même il y a six mois. Je l'ai essayé.

J'espère que cela vous sera utile.

Site de référence

Je me suis référé au site suivant.

Recommended Posts

Expliquer DI dans une histoire d'obésité
L'histoire d'une exception d'état illégale dans Jetty.
[Docker] Une histoire sur une erreur dans la composition de docker