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?
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.
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.
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.
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.
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