Designing Interfaces and Abstractions

1. Designing Interface Contracts

ElementSpecify
PreconditionsInput constraints (Javadoc @param)
PostconditionsReturn guarantees
ExceptionsWhat thrown when
Side effectsState changes, I/O
Thread-safetyDocument explicitly

2. Defining Abstract Classes

Use WhenReason
Shared stateCommon fields across subtypes
Template methodSkeleton + abstract steps
Partial implementationDefault behavior + extension points
Constructor logicForced initialization

3. Understanding Interface vs Abstract Class

AspectInterfaceAbstract Class
Multiple inheritanceYesNo
State (fields)Constants onlyYes
ConstructorNoYes
Default methodsYes Java 8+Yes
VersioningAdd defaultAdd concrete

4. Implementing Multiple Interfaces (composition)

Example: Combine roles

interface Readable<T> { T read(long id); }
interface Writable<T> { void write(T t); }
interface CrudRepo<T> extends Readable<T>, Writable<T> {}
ConflictResolution
Same default methodOverride and call I.super.method()
Diamond defaultCompiler forces override

5. Designing Marker Interfaces

MarkerPurpose
SerializableOpt-in serialization
CloneablePermit clone()
Custom markerType-level tagging
Modern alternativeAnnotations + retention

6. Implementing Default Methods

UseDetail
API evolutionAdd method without breaking implementers
ConvenienceDerived methods on top of abstract
Mixin-likeBehavior across unrelated classes

Example: Default convenience

interface Repository<T> {
    Optional<T> findById(long id);
    default T getOrThrow(long id) {
        return findById(id).orElseThrow(() -> new NotFoundException(id));
    }
}

7. Implementing Functional Interfaces

StandardSignature
Function<T,R>R apply(T)
Predicate<T>boolean test(T)
Consumer<T>void accept(T)
Supplier<T>T get()
BiFunction<T,U,R>R apply(T,U)
CustomAnnotate @FunctionalInterface

8. Designing API Contracts

PrincipleDetail
Stable signatureDon't change params on existing methods
Add, don't removeNew methods OK; deprecate before removal
Semantic versioningMAJOR for breaking
Document errorsException types and conditions

9. Using Interface Segregation

Example: Split a fat interface

// Before
interface UserService { create(); update(); delete(); search(); export(); audit(); }
// After
interface UserCommands { create(); update(); delete(); }
interface UserQueries  { search(); }
interface UserOps      { export(); audit(); }

10. Implementing Dependency Abstraction

PatternWhere defined
PortDomain/application module
AdapterInfrastructure module
Wire-upComposition root (main / DI config)

11. Designing Sealed Interfaces

Example: Closed hierarchy Java 17+

public sealed interface Result<T> permits Ok, Err {}
public record Ok<T>(T value) implements Result<T> {}
public record Err<T>(String message) implements Result<T> {}

// Exhaustive switch
String describe(Result<Integer> r) {
    return switch (r) {
        case Ok<Integer> ok -> "value=" + ok.value();
        case Err<Integer> e -> "error=" + e.message();
    };
}

12. Implementing Interface Evolution

ChangeStrategy
Add methodProvide default implementation
Deprecate method@Deprecated(since, forRemoval)
Replace methodAdd new + delegate from old
Breaking changeNew interface (V2), keep old until cutover
Note: Adding a method without default breaks all implementers — always provide a default for backward compatibility.