Many people are learning java but don't know how to use the interface. In fact, the program works without an interface, and I think it's easier to just run it. In this article, I'll write about such a seemingly unnecessary interface.
Below, normal Java code and Spring code will appear alternately. Spring is a very good framework for understanding the interface, so I've included it here. The execution results are the same for both, so please see the one you like.
gradle does a lot of good things. If you don't actually do it, you can skip here.
$ git clone https://github.com/reta-ygs/interface-demo.git
$ cd interface-demo
Run as normal java
$ ./gradlew pojo
Run spring
$ ./gradlew spring
Define "what processing is required". This time, I defined a today method to get today's day of the week and a dayOffset method to get the day of the week such as 3 days later.
DayOfWeekCalculator.java
public interface DayOfWeekCalculator {
DayOfWeek today();
DayOfWeek dayOffset(int dayOffset);
}
The interface alone doesn't do anything. Prepare a class that implements the defined interface, and write the contents of the actual processing there.
CalculatorImpl.java
public class CalculatorImpl implements DayOfWeekCalculator {
final Clock clock;
public CalculatorImpl(Clock clock) {
this.clock = clock;
}
@Override
public DayOfWeek today() {
return LocalDate.now(clock)
.getDayOfWeek();
}
@Override
public DayOfWeek dayOffset(int dayOffset) {
return LocalDate.now(clock)
.plusDays(dayOffset)
.getDayOfWeek();
}
}
Clock will be used later, but this is also a type of dependency injection.
It's not important here yet, but I'll assign an instance of CalculatorImpl to a variable of type DayOfWeekCalculator.
Main.java
DayOfWeekCalculator calculator = new CalculatorImpl(Clock.systemDefaultZone());
System.out.println(calculator.today());
System.out.println(calculator.dayOffset(7));
By using Spring, it is possible to exclude from the source code that the content of the interface is the CalculatorImpl class. First, define the bean, this time write it in xml.
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="calculator" class="com.example.CalculatorImpl">
<constructor-arg ref="clock"/>
</bean>
<bean id="clock" class="java.time.Clock" factory-method="systemDefaultZone"/>
</beans>
In the code, use the ApplicationContext class to prepare the Bean of the DayOfWeekCalculator class in Spring.
SpringMain.java
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
DayOfWeekCalculator calculator = context.getBean(DayOfWeekCalculator.class);
System.out.println(calculator.today());
System.out.println(calculator.dayOffset(7));
You can't see the CalculatorImpl in the code being executed. I was able to hide that the contents of the DayOfWeekCalculator is a CalculatorImpl.
The same processing can be done with the CalculatorImpl class alone without having to prepare an interface. For the CalcuratorImpl class, the LocalDate.now method will use Clock. SystemDefaultZone without any arguments. There are two major merits, why we prepared the interface and why we received the Clock from the outside.
In this case, two types of mock tests are possible.
Let's add a method to determine if today is a holiday.
DayOfWeekCalculator.java
public interface DayOfWeekCalculator {
DayOfWeek today();
DayOfWeek dayOffset(int dayOffset);
boolean isHolidayToday();
}
Let's take the DayOfWeekCalculator class as an argument and add a method to get the character string according to the judgment result of holiday.
Main.java
static String holiday(DayOfWeekCalculator calculator) {
if (calculator.isHolidayToday()) {
return "Holiday!";
} else {
return "Work";
}
}
If you want to test this holiday method, you usually have to wait for the implementation class CalculatorImpl to complete its implementation. However, since the argument is the DayOfWeekCalculator of the interface, it is possible to test by temporarily preparing the mock class.
MainTest.java
@Test
void holiday() {
MockCalculator mockCalculator = new MockCalculator(true);
assertEquals(Main.holiday(mockCalculator), "Holiday!");
}
@Test
void notHoliday() {
MockCalculator mockCalculator = new MockCalculator(false);
assertEquals(Main.holiday(mockCalculator), "Work");
}
class MockCalculator implements DayOfWeekCalculator {
final boolean holiday;
MockCalculator(boolean holiday) {
this.holiday = holiday;
}
@Override
public boolean isHolidayToday() {
return holiday;
}
}
As a result of mocking, we were able to test without waiting for the processing of the method added to the interface to be completed.
Date-related testing is tedious. Even if you want to test on a specific date, you probably can't change the system time. The standard API Clock is very useful for date change testing. The reason I didn't have to take Clock as an argument in the constructor of CalculatorImpl is to make it easier to test.
CalculatorImplTest.java
//mock time
Clock mock = Clock.fixed(
LocalDate.of(2020, 7, 7) // 2020/7/Fixed to 7
.atStartOfDay(ZoneId.systemDefault()).toInstant(),
ZoneId.systemDefault());
CalculatorImpl calculator = new CalculatorImpl(mock);
@Test
void today() {
assertEquals(calculator.today(), DayOfWeek.TUESDAY);
}
@Test
void dayOffset() {
assertEquals(calculator.dayOffset(7), DayOfWeek.TUESDAY);
}
I was able to test that the class was working, regardless of today's date.
For example, due to adult circumstances, I decided not to use the CalculatorImpl class, which is unreasonable. It can't be helped, so let's use the prepared CalcImplSecond class instead.
CalcImplSecond.java
public class CalcImplSecond implements DayOfWeekCalculator {
//Implementation details omitted
}
Just change the class you assign to a variable of type DayOfWeekCalculator.
Main.java
// DayOfWeekCalculator calculator = new CalculatorImpl(Clock.systemDefaultZone());
DayOfWeekCalculator calculator = new CalcImplSecond();
System.out.println(calculator.today());
System.out.println(calculator.dayOffset(7));
Both implement the DayOfWeekCalculator interface, so you can assign to a variable of type DayOfWeekCalculator. Also, the fact that the interface is common means that the methods that can be called are the same, and there is basically no need to change the part that uses the variable to which it is assigned.
All you have to do is modify the bean definition. No modification of java code is required.
spring.xml
<bean id="calculator" class="com.example.CalcImplSecond"/>
I haven't changed the code, but the type assigned to the variable has been changed to the CalcImplSecond type.
SpringMain.java
DayOfWeekCalculator calculator = context.getBean(DayOfWeekCalculator.class);
System.out.println(calculator.getClass()); // class com.example.CalcImplSecond
Here's a little talk about what an interface is.
In this sample project, the interface acts as a cushioning material between the process user and the process actually executed. Let's think in each position.
You don't need to know what you're actually doing to use the process. [^ 1] The most troublesome thing is that the calling method changes, such as changing arguments and return values. The interface acts as a promise to the implementer. [^ 2]
Switching from CalculatorImpl to CalcImplSecond makes it very easy to switch between class entities. This time it is new directly, but if you use the factory method etc., you will not let the caller know that the entity of the class has been switched at the time of version upgrade etc. You can minimize the fix, for example, if you have to deprecate a class.
To be honest, the merit of using the interface is not so great with this sample code. Finally, I would like to introduce where and how it is actually used.
It is used to access the database, but if you look at the contents of the package, most of the classes are interfaces. Connection, PreparedStatement / 8 / docs / api / java / sql / PreparedStatement.html), ResultSet, etc. All the classes I use often are interfaces. The connection method with DB is defined by the interface, and the connection process to each RDBMS such as MySQL and Postgres is distributed as a JDBC driver. It's the interface that allows you to code without knowing which database you're connecting to. [^ 3]
Servlet Most of the classes such as Request, Response, and Session are also interfaces. The implementation class is provided by an application server such as tomcat. Also, the HttpServlet class that we usually write is an abstract class. Override the required http methods, write the URL mapping in an xml file or annotation, and the AP server will do all the setting and tidying up for you.
[^ 1]: For performance reasons, we may be aware of the internal implementation, but ideally, we should not make it aware of the caller. [^ 2]: Actually, the "request" to the implementation side is more accurate in terms of expression, and the story of responsibility can be involved any more. [^ 3]: Since there are dialects depending on the RDBMS, you will often be aware of the difference in connection destination when writing a query. On the contrary, I think that there is almost no awareness of the connection destination other than the query.
Recommended Posts