At my org we extensively use @Value.Immutable
annotation to generate Immutable
classes (usually with builder
s) for our internal data transfer objects (DTOs). In our codebase I've seen both interface
s and abstract class
es being used for such DTO definitions.
For example for a DeviceData
DTO we could have an interface as follows
@Value.Immutable
@JsonDeserialize(as = ImmutableDeviceData.class)
public interface DeviceData {
@Value.Parameter
DeviceIdentifier deviceIdentifier();
@Value.Parameter
DeviceType deviceType(); // enum
}
or equivalently we could have abstract class with identical body as above interface
public
abstract class
DeviceData {
Either ways, we instantiate the DTO as follows
final var myDeviceData = ImmutableDeviceData.builder()
.deviceIdentifier(ImmutableDeviceIdentifier.of("xxxx-xxxx")
.deviceType(DeviceType.Tablet)
.build();
At times we also add certain precondition checks using @Value.Check
annotations such as following, which again work identically with both interface
and abstract class
es
@Value.Immutable(builder = false)
public abstract class DeviceIdentifier {
@Value.Parameter
public String value();
@Value.Check
default void validate() {
Validate.notBlank(value(), "DeviceIdentifier cannot be blank");
}
}
Additionally there are situations where we have to declare static fields such as regex Pattern
in case of EmailAddress
DTO; here again Java17 makes it possible in both abstract class and interfaces alike.
Considering this specific use case of Immutable data-transfer objects, are there any pros or cons of preferring abstract class
over interface
or vice-versa?
1 Answer 1
There's not much in it, either way. The consistency you gain from picking one and sticking with it is likely to be more benefit than any potential drawbacks of either option.
That said, there are some minor differences:
- If any of these are inner classes, you need to remember to make them static (as per the documentation). If it was an inner interface, it would be static implicitly.
- Abstract methods on classes may have different access modifiers, whereas interface methods are implicitly public. Would you find a package access getter useful (bearing in mind the builder method will still be public), or would you prefer everything to be consistently and implicitly public?
- The library can be configured to use default interface methods to provide default values for parameters. If using abstract classes, you'd need to use an annotation on the method instead (this is also the default behaviour for interfaces).
Explore related questions
See similar questions with these tags.
interface
vsclass
is barely distinguishable.record
s is that there's limited support for implementing builder-pattern with them [ref-1, ref-2]. We have presently decided to userecord
s for the 'tiny' DTOs such asCoordinate
, but for complex ones having manyOptional
s we are continuing to useabstract class