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.
Top comments (0)