[Java] Why do you bother to use the interface (Spring is also available)

Introduction

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.

Sample project

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

Code prepared this time

interface

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);
}

Implementation class

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.

Run as normal Java

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));

Run as Spring

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.

What are you happy about

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.

1. Mock test is possible

In this case, two types of mock tests are possible.

1-1. DayOfWeekCalculator class mock

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.

1-2. Date change test by externalizing Clock class

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.

2. Ease of modification of implementation

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
}

2-1. Normal Java modification

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.

2-2. Spring modification

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

What is an interface

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.

The side that calls the process

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]

Mounting side

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.

Actual use of the interface

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.

java.sql package (JDBC)

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

[Java] Why do you bother to use the interface (Spring is also available)
What to do if you can't use the rails command
Do you use Stream in Java?
[Java] How to use the File class
[Java Servlet] The road of Senri is also one step to the first
[Java] How to use the toString () method
Studying how to use the constructor (java)
[Processing × Java] How to use the loop
[Processing × Java] How to use the class
[Processing × Java] How to use the function
[Java] How to use the Calendar class
I want you to use Scala as Better Java for the time being
You use context to use MDC with Spring WebFlux
When you want to use the method outside
How to use the replace () method (Java Silver)
What do you use when converting to String?
You also need to specify the host when debugging remotely with Java 9 or later
What to do if you cannot execute with the command "Java package name / class name"
[Java] [Spring] [Spring Batch] Do not create / use Spring Batch metadata table
[JAVA] What is the difference between interface and abstract? ?? ??
Java: Use Stream to sort the contents of the collection
JAWJAW is convenient if you use WordNet from Java
The story that .java is also built in Unity 2018
I want to find out which version of java the jar file I have is available
[Java] [Spring] What to do if you cannot Autowire with Type Mismatch after annotating Spring Security
If you want to make a Java application a Docker image, it is convenient to use jib.
Use JLine when you want to handle keystrokes on the console character by character in Java
What do you need after all to create a web application using java? Explain the mechanism and what is necessary for learning
[Java] Deploy the Spring Boot application to Azure App Service
I want to use the Java 8 DateTime API slowly (now)
How to call and use API in Java (Spring Boot)
[Java] Use ResolverStyle.LENIENT to handle the date and time nicely
Delegate is convenient to use when you want to reuse parts
What to do when you want to know the source position where the method is defined in binding.pry
How to use the function implemented in Kotlin Interface introduced in Maven as the default implementation from Java 8
If you do not call shutdownNow when the transfer is completed in the Java SDK of AWS S3, threads will continue to remain