Practical Java15 sealed interfaces with design patterns
By: Muhammad Durrah
Tags: Java, Java15, Sealed Classes, Design Patterns

As a quick review of template method and chain of responsibility patterns we can define a template of the chain (no to have a broken chain) with an abstract class:

abstract class MiddlewareBase {

static MiddlewareBase of(MiddlewareBase... middlewares) {

for (int i = 0; i < middlewares.length - 1; i++) {

middlewares[i].setNext(middlewares[i + 1]);

}

return middlewares[0];

} private MiddlewareBase next; public void setNext(MiddlewareBase next) {

this.next = next;

} public final void check(Request request) {

checkInternal(request);

if (next != null) {

next.checkInternal(request);

}

} abstract void checkInternal(Request request);

}

Until now this is good and seems perfect, now to add more abstraction and to achieve the program to interface not implementation simple principle let us define the Middleware interface to represent our chain from interface perspective

interface Middleware {

void check(Request request);

}

then we can re-write our MiddlewareBase as

abstract class MiddlewareBase implements Middleware {

static Middleware of(MiddlewareBase... middlewares) {

for (int i = 0; i < middlewares.length - 1; i++) {

middlewares[i].setNext(middlewares[i + 1]);

}

return middlewares[0];

} private MiddlewareBase next; public void setNext(MiddlewareBase next) {

this.next = next;

} public final void check(Request request) {

checkInternal(request);

if (next != null) {

next.checkInternal(request);

}

} abstract void checkInternal(Request request);

}

with this and the two following simple examples of Middleware

class CsrfMiddleware extends MiddlewareBase {

@Override

void checkInternal(Request request) {

System.out.println("csrf check");

}

} class XssMiddleware extends MiddlewareBase {

@Override

void checkInternal(Request request) {

System.out.println("xss check");

}

}

we can define and use our middleware chain like

Middleware middleware = MiddlewareBase.of(new CsrfMiddleware(),

new XssMiddleware());

middleware.check(new Request());

but nothing can prevent others from implementing the Middleware interface and break the chain implementation.

— — — — — —

Starting from version 15 and as a preview feature JDK presented the sealed class/interface feature to restrict which other classes or interfaces may extend or implement them.

we can define a sealed interface/class with sealed modifier, Then, after any extends and implements clauses, the permits clause specifies the classes that are permitted to extend the sealed class.

sealed interface Handler permits HandlerTemplate {

void handle(Handleable handleable);

}

with this definition no class except HandlerTemplate can implement the Handler interface.

Let us now redefine the Middleware example with sealed interface so no one can implement it and break the main purpose of it as a CoR implementation.

sealed interface Middleware permits MiddlewareBase {

void check(Request request);

}

The MiddlewareBase class will have no change except adding non-sealed modifier which means no restriction on implementation classes

abstract non-sealed class MiddlewareBase implements Middleware {

...

}

Also CsrfMiddleware and XssMiddleware will not change.

now if we tried to create another implementation of Middleware compiler will fail complaining: class is not allowed to extend sealed class: Middleware or with an IDE like intellij we get directly.

sealed classes/interfaces have many useful usecases and eventought it is new -still in preview- in Java it is presented in other languages like Kotlin (only sealed classes) and Scala (sealed trait) with different intents.

I approached the sealed interfaces with core design principles and patterns to express the fact that the richer the language features the better design we get, comments are welcomed.