This article is the 12th day article of Java Advent Calendar 2018.
It's been about half a year since I came into contact with Java DI. I was in a hurry to teach new members of the team, so I will summarize it in an article for my review.
Here is a sample project. https://github.com/segurvita/FatnessChecker
For those who understand classes and constructors, but don't know DI well.
I'll try to explain it with only classes and constructors so that I can understand it without knowing Bean
or ʻInterface`.
―― “DI is an abbreviation for Dependency Injection” ← Hmm Hmm ―― “Dependency Injection means Dependency Injection” ←? ?? ?? -"Strictly speaking, it is the injection of dependency objects" ←? ??
The purpose of this article is to understand the meaning of this ** Dependency Injection **.
new
in the class!Don't be afraid to misunderstand, ** DI means don't use new
in a class **. (I understand that it is not an accurate expression, but this article will be explained with an emphasis on clarity.)
public class Hoge{
//Field (member variable)
Fuga piyo;
//Constructor
public Hoge() {
//I'm using new in my class.
this.piyo = new Fuga();
}
}
public class Hoge{
//Field (member variable)
Fuga piyo;
//Constructor
public Hoge(Fuga mogera) {
//Do not use new. I'm injecting from the outside.
this.piyo = mogera;
}
}
However, I do not understand the merit with this alone. From here, I will explain in a story format.
A story that starts suddenly
: man: Am I fat? That's it! Let's ask an obesity judgment company for inspection!
After several hours
: person_frowning: Welcome to the obesity assessment company. What kind of business do you have today?
: man: I want to judge the degree of obesity.
: person_frowning: You are a customer who judges the degree of obesity. Is it okay to ask for height and weight?
: man: Well, I'm 170 cm tall and weigh 70 kg.
: person_frowning: I'm clever. Please wait a moment. We will calculate BMI with our latest robot.
: robot: Calculation Shimasu ・ ・ ・ 70kg ÷ 1.70m de, BMI Ha ** 41.18 ** Death!
: person_frowning: If your BMI is 41.18 ... it's over 40, so it's ** 4 degrees obese **.
: man: Yeah! Such!
After several hours
: man: It's absolutely strange that I'm obese. Isn't that robot a bug?
BMI (body mass index) is calculated by weight (kg) ÷ height (m) ÷ height (m).
Is the calculation of: robot: wrong?
In Japan, people with a BMI of 25 or above are considered ** obese **. There are four stages of obesity, from 1 to 4 degrees.
Image quote: OMRON / vol.103 Revised diagnostic criteria for obesity for the first time in 11 years
The characters who appeared in the first episode are as follows.
-: Man: is a person who wants to know his or her obesity level. Since it is a user, we will call it ʻUser. -: Person_frowning: is the reception desk of the obesity judgment company. Do not calculate BMI yourself, but ask: robot: to do it. The degree of obesity can be determined based on the BMI value. Let's call it
FatnessChecker. -: Robot: is a robot that calculates BMI. There is still a bug in the prototype. Let's call it
BmiRobot`.
: robot: BmiRobot
made a mistake in calculating BMI. Apparently there is a bug.
Based on this wrong calculation result,: person_frowning: FatnessChecker
made an obesity judgment, so the judgment result was also wrong.
In other words, the problem is that: person_frowning: FatnessChecker
relied on an unreliable robot called: robot: BmiRobot
.
: man: It's absolutely strange that I'm obese. Isn't that robot a bug?
: older_man: Is that person having trouble with BMI calculation?
: man: Who are you!
: older_man: I'm a BMI hermit. Please tell me the story if you like.
: man: Yeah, it's actually a secret ...
A few days later
: information_desk_person: Welcome to the obesity assessment company. (Omitted) May I ask you about your height and weight?
: man: Height is 170 cm and weight is 70 kg. However, I will ask this person to calculate the BMI, not the robot the other day.
: older_man: Hohoho, in this case 70kg ÷ 1.70m ÷ 1.70m, BMI is ** 24.22 **.
: information_desk_person: I'm clever. If your BMI is 24.22 ... it's more than 18.5 and less than 25, so it's ** normal weight **.
: man: I did it! I'm not obese at the last minute!
The characters who appeared in the second episode are as follows.
-: Man: is a person who wants to know his or her obesity level. Since it is a user, we will call it ʻUser. -: Older_man: is a master at calculating BMI. I've trained a lot, so I'll never make a mistake in the calculation. Let's call it
BmiMaster. -: Information_desk_person: is a person who uses: older_man: to determine the degree of obesity. It is the same person as: person_frowning :, but for the sake of explanation, we will distinguish it and call it
FatnessCheckerDi`.
: man: ʻUserasked: information_desk_person:
FatnessCheckerDi to determine the degree of obesity under the condition that: older_man:
BmiMaster` should calculate the BMI.
As a result, the judgment result of: information_desk_person: FatnessCheckerDi
depends on the reliable person:: older_man: BmiMaster
.
This is ** DI ** (Dependency Injection)! In other words
injects: older_man:
BmiMaster into: information_desk_person:
FatnessCheckerDi`!The advantage of DI is that ** you can unit test the part under development even if the dependent part is not completed **.
In this case, there was a bug in: robot: BmiRobot
, so if it didn't work, it would be okay if we did enough testing and reduced the bug in: robot: BmiRobot
. ?? I think there is also an opinion. (I thought so at first.)
However, it will take some time to ** test enough **.
: person_frowning: FatnessChecker
depends on: robot: BmiRobot
, so you can't test: person_frowning: FatnessChecker
until: robot: BmiRobot
is complete. This will increase development time.
On the other hand,: information_desk_person: FatnessCheckerDi
allows the dependent parts (: older_man: BmiMaster
) to be injected from the outside (: man: ʻUser). By doing this, even if the dependent part (: older_man:
BmiMaster) is not completed, if you inject a mock (test-only haribote) of the dependent part (: older_man:
BmiMaster),: information_desk_person:
Unit testing of FatnessCheckerDi` is possible.
The disadvantage is that ** the learning cost is high **.
In the case of team development, if DI is adopted in the project under development, all members will need to learn DI. Education costs can be non-negligible for teams with a lot of member turnover.
I think that it is difficult to convey with just sentences, so I will explain it with Java code.
Here is a sample project. https://github.com/segurvita/FatnessChecker
BmiRobot
(before DI introduction)First,: man: ʻUser` looks like this.
User.java
/**
*People who want to know the degree of obesity
*/
public class User {
/**
*Ask an obesity judgment company to make a judgment without a BMI master.
*/
public void runWithoutBmiMaster() {
//Start a conversation with a person from an obesity judgment company.
FatnessChecker fatnessChecker = new FatnessChecker();
//Tell them your height and weight and ask them to determine your obesity.
String result = fatnessChecker.check(170.0, 70.0);
//The judgment result of the degree of obesity is displayed.
System.out.println("Obesity judgment result (without BMI master):" + result);
}
}
You are requesting work at fatnessChecker.check
.
Then: person_frowning: FatnessChecker
looks like this:
FatnessChecker.java
/**
*Obesity judgment company (using BMI robot)
*/
public class FatnessChecker {
/**
*Determine BMI
* @param height height[cm]
* @param weight weight[kg]
* @return obesity
*/
public String check(double height, double weight) {
//Secure one of the latest BMI robots in the company.
BmiRobot bmiRobot = new BmiRobot();
//Ask the BMI robot to calculate BMI.
double bmi = bmiRobot.calc(height, weight);
//Determine the degree of obesity from the BMI calculation results.
if (bmi < 18.5) {
return "Underweight";
} else if (bmi < 25.0) {
return "Normal weight";
} else if (bmi < 30.0) {
return "Once obese";
} else if (bmi < 35.0) {
return "Twice obese";
} else if (bmi < 40.0) {
return "3rd degree obesity";
} else {
return "4th degree obesity";
}
}
}
In the check
method,new BmiRobot ()
is used to reserve one: robot: BmiRobot
.
After that, BMI calculation is requested by BmiRobot.calc (height, weight)
, and the degree of obesity is judged based on the calculation result.
If there is a bug in: robot: BmiRobot
, the judgment result will be wrong. : robot: Depends on BmiRobot
.
That: robot: BmiRobot
is implemented, for example:
BmiRobot.java
/**
*BMI robot (with bugs)
*/
public class BmiRobot {
/**
*Calculate BMI
* @param height height[cm]
* @param weight weight[kg]
* @return BMI
*/
public double calc(double height, double weight) {
//body weight[kg]÷ Height[m]
return weight * 100 / height;
}
}
The formula is Weight [kg] ÷ Height [m]
, which is incorrect.
If you run the program in this state, the result will be
Execution result
Obesity judgment result (without BMI master): 4th degree obesity
It will be.
BmiMaster
(after DI introduction)First,: man: ʻUser` looks like this.
User.java
/**
*People who want to know the degree of obesity
*/
public class User {
/**
*With a BMI master, ask an obesity judgment company to make a judgment.
*/
public void runWithBmiMaster() {
//Secure one BMI master.
BmiMaster bmiCalculator = new BmiMaster();
//Ask a person from an obesity assessment company to make the BMI calculation a BMI master.
FatnessCheckerDi fatnessCheckerDi = new FatnessCheckerDi(bmiCalculator);
//Tell them your height and weight and ask them to determine your obesity.
String result = fatnessCheckerDi.check(170.0, 70.0);
//The judgment result of the degree of obesity is displayed.
System.out.println("Obesity judgment result (with BMI master):" + result);
}
}
The big difference from the previous one is that: man: ʻUser uses
new BmiMaster () and reserves one: older_man:
BmiMaster. This is passed to: information_desk_person:
FatnessCheckerDi with
new FatnessCheckerDi (bmiCalculator) `.
Then: information_desk_person: FatnessCheckerDi
looks like this:
FatnessCheckerDi.java
/**
*Obesity judgment company (using BMI master)
*/
public class FatnessCheckerDi {
/**
*BMI master
*/
final private BmiMaster bmiMaster;
/**
*constructor
* @param bmiCalculator BMI master named by the user
*/
public FatnessCheckerDi(BmiMaster bmiMaster) {
//We welcome the BMI master nominated by the user.
this.bmiMaster = bmiMaster;
}
/**
*Determine BMI
* @param height height[cm]
* @param weight weight[kg]
* @return obesity
*/
public String check(double height, double weight) {
//Ask the BMI master nominated by the user to calculate the BMI.
double bmi = this.bmiMaster.calc(height, weight);
//Determine the degree of obesity from the BMI calculation results.
if (bmi < 18.5) {
return "Underweight";
} else if (bmi < 25.0) {
return "Normal weight";
} else if (bmi < 30.0) {
return "Once obese";
} else if (bmi < 35.0) {
return "Twice obese";
} else if (bmi < 40.0) {
return "3rd degree obesity";
} else {
return "4th degree obesity";
}
}
}
The big difference from the previous one is the appearance of the constructor.
Earlier, in the check
method, we reserved one: robot: BmiRobot
, but this time we receive: older_man: BmiMaster
as an argument of the constructor and use it as a field (member variable). ) Is assigned.
After that, we request BMI calculation with this.bmiMaster.calc (height, weight)
and judge the degree of obesity based on the calculation result.
Now it depends on: older_man: BmiMaster
.
That: older_man: BmiMaster
is implemented, for example:
BmiMaster.java
/**
*BMI Master (Master)
*/
public class BmiMaster {
/**
*Calculate BMI
* @param height height[cm]
* @param weight weight[kg]
* @return BMI
*/
public double calc(double height, double weight) {
//body weight[kg] ÷ (height[m])^2
return weight * 10000 / (height * height);
}
}
The formula is weight [kg] ÷ (height [m]) ^ 2
. This is the correct formula.
If you run the program in this state, the result will be
Execution result
Obesity judgment result (with BMI master): Normal weight
It will be.
new
?In this story,: man: ʻUser prepares: older_man:
BmiMaster (
new) and injects it into: information_desk_person:
FatnessCheckerDi`.
So if: man: ʻUseritself was also designed with a DI pattern, who would prepare: older_man:
BmiMaster`?
: man: ʻWill there be another class outside of User`? What if even that class was designed with a DI pattern?
That's where the ** DI container ** comes in. Undertakes the processing of new
at once.
In the case of Java Spring, BeanFactory
corresponds to this.
What did you think. I tried to explain DI in a story style.
When I first touched DI half a year ago, I was buried in unknown terms such as ʻAutowired and
BeanFactory`, and it became "DI difficult ...". (At that time, there was no distinction between DI and DI container.)
However, when I coded it for half a year and looked back, I thought, "Isn't it possible to explain DI without knowing that it's actually Bean
? ", So I wrote this article for myself half a year ago. I tried it.
I hope it will be helpful to you.
I referred to the following site.