Delegate pattern with Consumer interface in Java7 min read

As a Java programmer, sometimes you run into a situation when you want to take a task of an object and give it to another object, meaning the original object receives a request, but the one that handles this request will be another. If you haven’t heard about the delegate pattern before, you might have done this through inheritance, where you delegate the parent’s task to the child class. However, in this article, I want to introduce another way of doing it. The delegate pattern concept might be clear with C# or Swift when there is even a delegate keyword. We don’t have such keywords in Java, but we can still effectively create the delegate pattern in other ways. So first, let’s define what delegate pattern means: In simple terms, with delegation, an object receives a request and then “delegate” this request to another object to handle. We usually achieve this by object composition, meaning you create an instance variable of the second object and plug in the first object instead of making an inheritance.

There are different ways that we can do to form the delegate pattern in Java, but one candidate introduced in this article is the Consumer interface, a functional interface that first appeared in Java 8.

Now let’s jump to an example. Let’s say I have a WebSocket server and also a WebSocket client. In the server, the server takes the request and replies to this request with some message. On the client side, I have two different classes, one named WebSocketListener, which takes the message sent from the server, and the second class WebSocketConsumer, which takes the message from the first class and actually handles these messages.

On the server side, I use the Spring Boot framework to demonstrate the Websocket server. It will digest a message from the client, and if the message is not empty, it gives the message back to the client along with the time the message is sent if the message is blank, the server replies with an error message. It doesn’t sound right when I do this kind of validation on the server side, but I use this simple program to demonstrate the concept.

First, we have the WebSocketServer class:

public class WebSocketServer {

    public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
        log.info("Receive message {}", message.getPayload());
        var msg = new JsonParser().parse(message.getPayload()).getAsJsonObject();
        String resp;
        if ("".equals(msg.get("message").getAsString())) {
           resp = ResponseMessage.buildFailRespMsg();
        } else {
            resp = ResponseMessage.buildSuccessRespMsg(message.getPayload());
        }
        log.info("Response msg {}", resp);
        session.sendMessage(new TextMessage(resp));
    }

}

Then we have the ResponseMessage class, which we then use to create a message and send it to the client:

public class ResponseMessage {
    enum RESPONSE_STATUS {
        OK,
        ERROR
    }

    public static String buildSuccessRespMsg(String message) {
        var msg = new JsonParser().parse(message).getAsJsonObject();
        var username = msg.get("username").getAsString();
        var content = msg.get("message").getAsString();
        var timeSendMsg = LocalDateTime.parse(msg.get("time").getAsString());

        var resp = new JsonObject();
        var result = new JsonObject();

        result.addProperty("user", username);
        result.addProperty("time", timeSendMsg.format(DateTimeFormatter.ofPattern("MM-dd-yyyy, hh:mm:ss")));
        result.addProperty("message", content);
        resp.addProperty("status", String.valueOf(RESPONSE_STATUS.OK));
        resp.addProperty("result", result.toString());
        return resp.toString();
    }

    public static String buildFailRespMsg() {
        var resp = new JsonObject();
        resp.addProperty("status", String.valueOf(RESPONSE_STATUS.ERROR));

        var failMsg = new JsonObject();
        failMsg.addProperty("message","You have submitted an empty text, try again!");
        resp.add("message", failMsg);
        return resp.toString();
    }
}

OK, we’re done with the server-side, the interesting part is on the client-side, I create a new Android application for the WebSocket client, in this app, I have 2 main classes one is WebSocketListener which listen to new messages sent from the server, and another class WebSocketConsumer which takes the message from WebSocketListener and then do something with it.

This is the WebSocketConsumer class:

public class SocketListener extends WebSocketListener {
    private final String TAG = SocketListener.class.getName();
    private Consumer<String> msgConsumer;
    @RequiresApi(api = Build.VERSION_CODES.N)
    @Override
    public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
        Log.d(TAG, "Receive msg from the server " + text);
        consumeMessage(text);
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    public void setMsgConsumer(Consumer<String> msgConsumer) {
        this.msgConsumer = msgConsumer;
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    public void consumeMessage(String msg) {
        if (this.msgConsumer != null)  {
            this.msgConsumer.accept(msg);
        } else {
            throw new IllegalStateException("No handler");
        }
    }

    @Override
    public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
        Log.d(TAG, "Open web socket");
        super.onOpen(webSocket, response);
    }

    @Override
    public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
        Log.d(TAG, Objects.requireNonNull(t.getMessage()));
        try {
            throw t;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        super.onFailure(webSocket, t, response);
    }
   
}

Notice at the third line, where I declare a Consumer interface that takes a string, basically, the semantic of this interface is simple, you provide your data as a parameter (in our case it’s a string) and then consume it without returning any value. Generally, if you want to implement this functional interface, you take an input of type T and do something with it in the method body and return nothing:

public interface Consumer<T> {
    void accept(T t);
}

What we can do with this interface in our example is that the server sends us a message to the WebSocketListener class, and then you can see consumeMessage, and also the setMsgConsumer methods, the first one we use to consume the message, but everything you can see from there is the msgConsumer.accept(msg), meaning this interface is already implemented, but the implementation is somewhere else that we delegate to the WebSocketConsumer class, the second method setMsgConsumer, obviously, we use to assign the event handler when we receive a message from the server.

Here is the second class, WebSocketConsumer, this is the actual class we use to consume messages from the WebSocketListener:

public class WebSocketConsumer extends AppCompatActivity {
    private final String username = "namvdo";
    private final String TAG = WebSocketConsumer.class.getName();
    private WebSocket websocket;
    private Button sendBtn;
    private TextView editableText;
    private TextView serverResponseView;

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        serverResponseView = findViewById(R.id.server_response_view);
        sendBtn = findViewById(R.id.send);
        editableText = findViewById(R.id.textarea);
        sendBtn = findViewById(R.id.send);

        sendBtn.setOnClickListener((View v) -> sendMessage());
        WebSocketListener webSocketListener = new WebSocketListener();

        webSocketListener.setMsgConsumer(this::consumeMessage); // 
        String url = "http://10.0.2.2:8081/ws";
        this.websocket = new WebSocketClient(url, webSocketListener).getWebSocket();
    }

    public void consumeMessage(String msg) {
        Log.d(TAG, "Consume message sent from the server: " + msg);
        JsonObject resp = JsonParser.parseString(msg).getAsJsonObject();

        StringBuilder displayedMsg = new StringBuilder();
        String textMsgFromServer = resp.get("result").getAsJsonObject().get("message").getAsString(); 
        runOnUiThread(() -> serverResponseView.setTextSize(28));

        if (!"OK".equals(resp.get("status").getAsString())) {
            String text = "<font color='#EE0000'>" + textMsgFromServer + "</font>";
            serverResponseView.append(Html.fromHtml(text));
        } else {
            String time = resp.get("result").getAsJsonObject().get("time").getAsString();
            displayedMsg.append(username).append(" ");
            displayedMsg.append("(").append(time).append("): ");
            displayedMsg.append(textMsgFromServer);
            String text = "<font color='#382bf1'>" + displayedMsg + "</font>";
            serverResponseView.append(Html.fromHtml(text));
        }

        serverResponseView.append("\n");
        Log.d(TAG, "text: " + serverResponseView.getText().toString());
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    public void sendMessage() {
        sendBtn.setOnClickListener((View v) -> {
            String text = editableText.getText().toString();
            JsonObject message = new JsonObject();
            message.addProperty("username", username);
            message.addProperty("time", LocalDateTime.now().toString());
            message.addProperty("message", text);
            Log.d(TAG, "Send request message: " + message.toString());
            websocket.send(message.toString());
        });
    }

}

Notice in the onCreate method, when an app instance is created, I create a new WebSocketListener instance and use its setMsgConsumer method. Then inside the WebSocketConsumer class, we provide the message handler for the WebSocketListener class by this following line: webSocketListener.setMsgConsumer(this::consumeMessage). In this line, we actually implement the accept method of the Consumer class using method reference, which I said before the implementation is in the WebSocketConsumer class, traditionally, you can use lambda expression for this:

webSocketListener.setMsgConsumer(msg -> consumeMessage(msg));

We can use method reference because what’s required for implementing this Consumer interface is met in our consumeMessage method, since this method takes a string, do something with it and return nothing.

Now, let’s look at the consumeMessage method itself, in this method, it takes a JSON response which it receives from the WebSocketListener class, and if the result was OK, then the message is displayed with the color blue, otherwise, there will be an error with red color.

Here is the output when I enter a valid message:

Here is the output when I enter nothing:

The complete example is available on Github.

0 0 votes
Article Rating
Previous Article
Next Article
Subscribe
Notify of
guest
0 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Every support is much appreciated ❤️

Buy Me a Coffee

0
Would love your thoughts, please comment.x
()
x