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

6 minute read

Introduction

There are many people who are learning java but don’t know how to use the interface. In fact, programs work without an interface, and I think it’s easier not to just run them. In this article, I’ll write about an interface that might seem unnecessary.

Below, normal Java code and code using Spring appear alternately. Spring is a very good framework for understanding the interface, so I dare to mention it here. Both have the same execution result, so please see the one you like.

Sample project

gradle does a lot of good things. If you don’t actually run 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 you need to do. This time, we defined today method to get today’s day of the week and 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 does nothing. 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();
}
}

We will use Clock later, but this is also a kind of dependency injection.

Run as normal Java

It’s not important here yet, but assign the instance of CalculatorImpl to the variable of DayOfWeekCalculator type.

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, you can exclude from the source code that the content of the interface is the CalculatorImpl class. First, define the bean, this time write 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 have Spring prepare a bean for the DayOfWeekCalculator class.

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

The CalculatorImpl is not visible on the executed code. I was able to hide that the content of DayOfWeekCalculator is CalculatorImpl.

What are you glad

The same process can be performed with just the CalculatorImpl class without preparing the interface. Also for the CalcuratorImpl class, the LocalDate.now method will automatically use Clock. systemDefaultZone if there is no argument. There are two main advantages, why the interface was prepared and why the Clock was received from the outside.

1. Mock test is possible

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

1-1. Mocking the DayOfWeekCalculator class

Let’s add a method that determines if today is a holiday.

DayOfWeekCalculator.java


public interface DayOfWeekCalculator {
DayOfWeek today();
DayOfWeek dayOffset(int dayOffset);
boolean isHolidayToday();
}

Take the DayOfWeekCalculator class as an argument and add a method that acquires the character string according to the holiday judgment result.

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 of the implementation class CalculatorImpl to complete. However, since the argument is the interface DayOfWeekCalculator, 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, I was able to test without waiting for the completion of processing of the method added to the interface.

1-2. Date change test by externalizing Clock class

Testing for dates is tedious. Even if you want to test on a specific date, there are many environments where you cannot change the system time. The standard API Clock is very useful for date change test. The reason why I didn’t take the time to take Clock as an argument in the CalculatorImpl constructor is to make it easier to test.

CalculatorImplTest.java


// mock time
Clock mock = Clock.fixed(
LocalDate.of(2020, 7, 7) // Fixed to 2020/7/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 is working properly regardless of today’s date.

2. Ease of modifying the implementation

For example, I shouldn’t use the CalculatorImpl class because of adulthood, which is unreasonable. There is no help for it, so let’s use the provided CalcImplSecond class instead.

CalcImplSecond.java


public class CalcImplSecond implements DayOfWeekCalculator {
// Implementation details omitted
}

2-1. Normal Java modificationJust change the class assigned to the variable of DayOfWeekCalculator type.

Main.java


// DayOfWeekCalculator calculator = new CalculatorImpl(Clock.systemDefaultZone());
DayOfWeekCalculator calculator = new CalcImplSecond();
System.out.println(calculator.today());
System.out.println(calculator.dayOffset(7));

Since both implement the DayOfWeekCalculator interface, it can be assigned to a variable of DayOfWeekCalculator type. Also, the fact that the interface is common means that the methods that can be called are the same, and basically there is no need to change the part that uses the variable that is the assignment destination.

2-2. Spring fixes

All you have to do is modify the bean definition. No java code modifications are 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 properly 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 buffer between the side using the process and the process actually executed. Let’s take each position.

Caller of processing

You don’t need to know what you are actually doing to use the process. 1 The most troublesome thing is that the calling method changes, such as changing the arguments and return value. The interface acts as a promise to the implementor. 2

Implementation side

Switching from CalculatorImpl to CalcImplSecond makes it very easy to switch the substance of a class. This time, I am doing new directly, but if you use the factory method etc., you will not let the caller understand that the substance of the class has been switched at the time of version upgrade. You can minimize the modifications, such as when you have to deprecate a class.

Actual use of the interface

Honestly, the merit of using the interface is not so great with the sample code this time. Finally, I would like to introduce where and how they are used.

java.sql package (JDBC)

It is used to access the database, but if you look inside the package, most of the classes are interfaces. Connection,PreparedStatement,ResultSet, etc. All frequently used classes 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 lets the code play without having to know what database it is connected to. 3

Servlet

Most of the classes such as Request, Response, and Session also have interfaces. Implementation class is prepared by application server such as tomcat. Also, the HttpServlet class that we usually write in is an abstract class. By overriding the necessary http method and writing the URL mapping in the xml file and annotations, the AP server will do all the setup and cleaning up.

  1. For performance reasons, there are times when we are aware of the internal implementation, but ideally, it should not be noticed to the caller. 

  2. Actually, the “request” to the implementation side is more accurate in terms of expression, and more about the responsibility is involved. 

  3. Since there are dialects depending on the RDBMS, you will often be aware of the connection destination when writing queries. On the contrary, I think that there is almost no concern about the connection destination other than the query.