Skip to content

Handling complex object state with design

Some APIs that implement a complex object state management often violate the single responsibility principle and make the usage very difficult. When you do all aspects of the complex state management in one inferface the interface’s methods must be invoked in the chronological correct order. Such APIs are hard to understand and to integrate. What we really want are APIs that guide the client code programmers in such situations.

Lets take a look at an API that breaks the single responsibility principle and let us think about the consequences for the client code programmer.

If you take a look at the FTPClient API of apache commons you can see that all states of the ftp protocol are managed by one class. So the client code programmer must ensure that he configures the FTP client before he connects and that he conntects before he can log in.

FTPClient f=FTPClient();
FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
conf.setServerLanguageCode("da");
conf.setDefaultDateFormat("d MMM yyyy");
conf.setRecentDateFormat("d MMM HH:mm");
conf.setTimeZoneId("Europe/Copenhagen");
f.configure(conf);
InetAddress server = InetAddress.getByName("ftp.example.com")
f.connect(server);
f.login("user", "pass");
...

 

If you take a look at the example above you might think that the code is clear to understand. Maybe you know the ftp protocol well and therefore you can imagine which methods have to be invoked in the right order, but that API design doesn’t answer many of the client code programmer’s questions. Hopefully the javadoc answers them.

  1. Do I have to configure the FTPClient or is there a default configuration? If there is a default configuration, what is the default configuration?
  2. Do I have to connect or can I just call login and the FTPClient automatically connects if I’m not connected?
  3. What happens if I call configure a second time? Is it allowed?
  4. What happens if I call configure after I connected to the server?
  5. What happens if I try to connect to another server after I logged in? Am I automatically logged out?

Ok, you might think now that the way the API should be used is clear, because the ftp protocol is well-known to you. But what will happen if the protocol used is not well-known? Maybe you don’t know much about the ldap protocol. Would you know how to handle such an API or would you ask yourself similar questions like the ones I asked above? (What is a ldap bind, a base dn, and so on)

Like I told in the introduction the main problem arises because the single responsibility principle is violated.

Redesign the API to reflect the state management

Let’s redesign the API in order to guide the client programmer through the state management of the ftp protocol.
I will give you a recommendation of how to design such APIs to guide the programmers or in other words: Express the state handling thorugh APIs.

First I remove all constructors and only provide one that takes a FTPClientConfig. A client code programmer now knows that a FTPClient needs a FTPClientConfiguration to be constructed. Furthermore the configure(FTPClientConfig) method can be removed. There is no need to configure a FTPClient, because the constructor does it and ensures that the FTPClient is in a valid state – ready for use. This means that no unconfigured FTPClient can exist and therefore the state handling within the FTPClient and also the client code that uses it gets very simple.

public class FTPClient {

 public FTPClient(FTPClientConfig config){
   ...
 }

}

Second: Introduce the FTPConnection interface that encapsulates the fact that a connection is established. I will leave the interface empty for the moment to concentrate on our primary goal. The FTPClient.connect(InetAddress) method’s return type will be changed from void to FTPConnection.

public interface FTPConnection {

 ...
}

public class FTPClient {

 public FTPConnection connect(InetAddress server){
   ...
 }
}

Thrid: Now that we have encapsulated the connection state in a FTPConnection interface we can add the methods to the interface that are only available when a connection is established.

public interface FTPConnection {

 public FTPUserSession login(String username, String password);

 public void disconnect();
}

public interface FTPUserSession {

 ...
}

Fourth and so on: We can go on to move all methods that an logged in ftp user can do to the FTPUserSession. I will stop at this point, because it is enough to see the difference. So let’s put it all together and see how the client code would look like with such an API.

FTPClientConfig defaultConfig = new FTPClientConfig();
FTPClient ftpClient = new FTPClient(defaultConfig);
InetAddress server = InetAddress.getByName("ftp.example.com");
FTPConnection connection = ftpClient.connect(server);
FTPUserSession ftpUserSession = connection.login("user", "pass");
...

Whats the difference? The difference to the apache commons API is that the client code programmer is guided through the API usage, because the methods he can call are determined by the returned APIs that only contain the methods that are available in the certain state. So the client code programmer must not know in which order he must invoke methods on the FTPClient to bring it in a valid state. The API ensures that the methods are called in the right order.

Even the FTPClient implementation is easier, because the FTPConnection implementation’s login(…) method must only check if the connection has not been already closed. It must not check if the FTPClient has been configured yet.

Apply the single responsibility principle to third party or legacy APIs

If you are confronted with an API that does not respect the single responsibility principle you can still make your client code easier by implementing a small layer that respects it.

Do the following steps:

  1. Decompose the legacy API by defining fine-grained interfaces that encapsulate the several aspects.
  2. Implement that fine-grained interfaces by adapting the legacy API.
  3. Use it in your client code.

The advantage of that thin abstraction layer is

  • client code is decoupled from the thrird party or legacy API
  • client code is easier to understand

Recommended Reading

 

Leave a Reply

Your email address will not be published. Required fields are marked *

 

GDPR Cookie Consent with Real Cookie Banner