Handle Temporal Coupling Using Fluent Interfaces In Java Applications

Vinod Madubashana
unibench
Published in
4 min readJan 30, 2024
Photo by Karine Avetisyan on Unsplash

To get the true benefit of object-oriented programming we have to define our class API cleanly by hiding the internals. Still, classes have many methods that perform specific tasks, but what if one function invocation assumes that a different function invocation already happens? This is what we call temporal coupling. This coupling of methods is not very clearly visible and hence needs more care when we implement a flow using object method invocation which needs to make it impossible to break the order of invocation. If we can eliminate temporal coupling, it’s good but reality is not that simple and we have to deal with it.

Let's consider a simple example to understand temporal coupling. Assume there is a class named File that allows us to open a file and then read or modify it and then close it. We can easily implement the methods like open, read, write, and close easily. But now the the client that uses the File class is responsible for making sure that they open the method first and then perform an action and then close it. The fact that you are not allowed to call the close method without calling the open method is a simple example of temporal coupling.

Don’t confuse this example as a resource opening which can be handled with a try with resources pattern in Java. Think of this as a generic problem that can span across different classes, for example, we need to make sure that operation x in object a can be performed only if operation y in object b is performed.

It would be nice if we could force the client to use the objects and invoke methods in the desired order. We can achieve this with the help of lambda functions and fluent API which can not be invoked in out of order.

Let’s take the simple example of the File class. Before seeing the code of the FIle class let's see how a client will use the File class to operate.

package org.example;

public class Main {
public static void main(String[] args) {
File.open("file1")
.manipulateFile(Main::process)
.executeAndClose();
}

private static void process(File file) {
System.out.println("processing " + file.getPath());
}
}

Now the API is designed in a way that the order is preserved and also this is a very readable code. Here you can assume that the manipulateFile method gets a Consumer as its argument which passes the File object to a method where we can do the reading and writing operations.

Now let’s see the File class,

package org.example;

import java.util.function.Consumer;

public class File {
private final String path;

private File() {
this.path = null;
}
private File(String path) {
this.path = path;
this.open();
}
private void open() {
System.out.println("open file " + path);
}
public void close() {
System.out.println("close file " + path);
}

public String getPath() {
return path;
}

public static X open(String filePath) {
return fileConsumer -> () -> {
File file = new File(filePath);
fileConsumer.accept(file);
file.close();
};
}

public interface X {
Y manipulateFile(Consumer<File> fileConsumer);
}

public interface Y {
void executeAndClose();
}

// More methods to work with file content
}

Here I have made this File class constructor private, the only way to deal with a File is by using the static open method. Now to create a fluent API we can use used internal interface, in this case, X and Y. Why these weird names, there are not made to be reused in client application code they are the gateway to create this fluent API chain. But without making them public we can’t call the methods of these types. This is the reason I used names like X and Y which are internal implementation details but we can't hide completely. We don’t even need to import these interfaces in our client code.

One question you might ask is can’t we eliminate these interfaces and return the File object itself? Then again you haven’t solved anything File objects have more than one public method that can call in out of order.

Since we creating order, we can limit our interfaces to have only one method which makes them functional interfaces. Now we can use lambda expressions to represent the implementations which is what has been done in the method open. X interface accepts a consumer and returns Y, which is also can be implemented using the lambda function.

Another benefit of using the lambda function is we can delay the execution (lazy execution). In the above example, nothing will get executed until you call the executeAndClose method.

This code might bit confusing if you are new to these functional programming concepts, but once you get this this will become simple more readable, and understandable for you. Hence spend some time if you didn’t get this at first sight. You might get way better ideas in the process to improve this further.

Although I highlight that this technique to handle temporal coupling you can use this technique to build clean class APIs. I know that the example is a very simple one, but if you get the point of what I am trying to express you can easily convert your complex class APIs to be more cleaner by applying these techniques. Don’t be limited to this solution and take it as a boilerplate, explore on your own and come up with more creative solutions, and share it with other developers. Happy coding!!!!

--

--

Vinod Madubashana
unibench

Full-stack Developer, Java, Spring Boot, React, Angular, AWS ...