Are you using the default method of the interface properly?

Introduction

The default method of the interface was introduced in Java 8, and from Java 9, private methods can also be defined in the interface. It's a useful feature, but misuse can reduce the maintainability of your application. So what kind of usage is desirable?

Bad example

public interface BadUsage {

    void someMethod();

    // (1)Utility-like things for mixins and implementation inheritance
    default String normalize(String str) {
        return str.toUpperCase().trim();
    }

    // (2)Processing that depends on other objects
    default int getSomeValue() {
        return Singleton.getInstance().getValue();
    }
}

(1) of the above sample code uses the default method for the purpose of adding functions as a mixin to the class that implements the interface, or providing utility functions that can be commonly used on the implementation class side. I will. Transfer rather than inheritance is an object-oriented rule of thumb. It is best to avoid this usage and use utility classes and helper classes. (2) is similar to (1) in terms of usage, but what is more problematic is that it describes processing that depends on other objects. In this example, the singleton object is accessed, but the following processing can be mentioned as an example.

If you write such a process, unit testing will be very difficult. The interface should not depend on the concrete class in the first place.

How to identify bad usage

The default method of the interface is public, which is an operation that is exposed to the user like an abstract method. So the default method should also be part of the responsibility of the interface. In object-oriented, object is a collection of data and behavior, so the instance method that represents the behavior should give access to the object's data (instance variables).

Of course, the interface itself doesn't have instance variables, so the default method indirectly accesses the instance variables of the implementation class by calling other abstract methods. Conversely, you should question the default method, which does not call other abstract methods.

Correct usage example

Suppose you have the following interface: In the interface of the Composite pattern, there are Composite and Leaf in the concrete class.

Component.java


public interface Component {

    String getName();

    int getPoint();

    List<Component> getChildren();

    boolean isLeaf();

}

Let's say you want to add a find method to this interface that returns a list of components that meet your criteria. Since this method can be described as a common process that does not depend on the concrete class, implement it as the default method.

Component.java


    default List<Component> find(Predicate<Component> p) {
        return getChildren().stream().filter(p).collect(Collectors.toList());
    }

Thus, regardless of what the concrete class really is, the default method should implement common behavior that would be universal if the Component interface was implemented correctly.

Exceptional case

There are exceptional cases where it is acceptable (not to call other abstract methods). That's the case where you only need an interface instead of creating an abstract base class that provides a default implementation. Let's take a concrete example. Suppose you want to introduce a Visitor pattern that scans the composite structure.

Visitor.java


public interface Visitor {

    void visit(Composite composite);

    void visit(Leaf leaf);
}

Component.java


public interface Component {
    // ...
    void accept(Visitor visitor);
}

The following is an example of a concrete Visitor implementation class.

LeafCounterVisitor.java


public class LeafCounterVisitor implements Visitor {

    private int count = 0;

    @Override
    public void visit(Composite composite) {
        //I'm not interested in Composite nodes, so an empty implementation
    }

    @Override
    public void visit(Leaf leaf) {
        count ++;
    }

    public int getCount() {
        return count;
    }
}

Visits to nodes that are not of interest to the visitor are empty implementations as described above. There are only two types of nodes this time, but if there are ten, overriding all visit methods and putting in an empty implementation can be a daunting task. In that case, there is a way to prepare an abstract base class with an empty implementation prepared in advance and inherit this class as shown below. (In derived classes, you only need to override the visit method of interest)

BaseVisitor.java


public abstract class BaseVisitor implements Visitor {

    @Override
    public void visit(Composite composite) {
        //Empty implementation
    }

    @Override
    public void visit(Leaf leaf) {
        //Empty implementation
    }
}

The interface's default method eliminates the need for the above abstract base class.

Visitor.java(Modified version)


public interface Visitor {

    default void visit(Composite composite) {}

    default void visit(Leaf leaf) {}
}

I think this usage does not deviate from the original intention of the default method in the sense that it provides a default implementation.

Summary

The default method of the interface should be treated like any other abstract method and used like an object-oriented design, except that it has a default implementation.

Recommended Posts

Are you using the default method of the interface properly?
Is the version of Elasticsearch you are using compatible with Java 11?
Traps brought about by the default implementation of the Java 8 interface
About the default behavior of decimal point rounding using java.text.NumberFormat
Are you still exhausted by the sample video search? A button to send FANZA videos to Slack when pressed.
Introduce docker to the application you are creating
Are you using the default method of the interface properly?
[Java] Coverage report could not be created with the combination of default method of Cobertura + interface
Java comparison using the compareTo () method
[Rails 6] destroy using the resources method
What you are doing in the confirmation at the time of gem update
[Rails] Check the instance variables and local variables of the file you are browsing
Iterative processing of Ruby using each method (find the sum from 1 to 10)
Try using || instead of the ternary operator
[Ruby] Obtaining even values ​​using the even? Method
Test the integrity of the aggregation using ArchUnit ②
What are the updated features of java 13
Deepened my understanding of the merge method
Get the error message using the any? method
Check the operation of the interface through threads
Test the integrity of the aggregation using ArchUnit ③
[Java] Are you reading the error message properly? [How to read the stack trace]
If you are using Android Room and want to change the column definition
[Order method] Set the order of data in Rails
[Java] Handling of JavaBeans in the method chain
The order of Java method modifiers is fixed
When you want to use the method outside
Output of how to use the slice method
What are the advantages of DI and Thymeleaf?
I tried using the profiler of IntelliJ IDEA
Arguments with default values Take the full_title method of the Rails tutorial as an example
Which man do you like? Identifying love targets using the Chain of Responsibility pattern