To split, or not to split: that is the question

Introduction

As you already know interfaces are a crucial part of object-oriented programming. In todays article I am going to talk about a concept of how to use them properly in order to achieve loose coupling and make your classes reusable. Same as god classes big interfaces are a code smell too and a sign for bad conceptional work. Here is where the Interface Segregation Principle comes into play.

A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.

Pretty simple huh?

The Problem with this problem

Let us assume our customer wants to convert some video files from one format into another. Before that the files should be copied from one direction into the media directory. The first scratch could look like this.

[pastacode lang=”java” manual=”public%20interface%20MediaConverter%20%7B%0A%09%0A%09void%20convert()%3B%0A%09%0A%09void%20copy()%3B%0A%09%09%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20class%20LocalMediaConverter%20implements%20MediaConverter%7B%0A%09%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09copy()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%7D%0A%09%0A%09%40Override%0A%09public%20void%20copy()%20%7B%0A%09%09%2F%2F%20copy%20files%20from%20one%20directory%20to%20another%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

The Product Owner is happy, developers are happy, the customer is happy, a big salary lands onto my bank account. A couple of sprints later the customer also wants to download the media files from his FTP server. So we need to have two classes. One that copies the files locally and one that downloads them from FTP before converting. But first, let us quickly change our interface.

[pastacode lang=”java” manual=”public%20interface%20MediaConverter%20%7B%0A%09%0A%09void%20convert()%3B%0A%09%0A%09void%20downloadFromFtp()%3B%0A%09%0A%09void%20copy()%3B%0A%09%09%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20class%20LocalMediaConverter%20implements%20MediaConverter%7B%0A%09%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09copy()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%7D%0A%09%0A%09%40Override%0A%09public%20void%20copy()%20%7B%0A%09%09%2F%2F%20copy%20files%20from%20one%20directory%20to%20another%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20downloadFromFtp()%20%7B%0A%09%09throw%20new%20UnsupportedOperationException()%3B%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20class%20MediaConverterFtpSource%20implements%20MediaConverter%7B%0A%09%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09downloadFromFtp()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20downloadFromFtp()%20%7B%0A%09%09%2F%2F%20download%20source%20from%20FTP%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20copy()%20%7B%0A%09%09throw%20new%20UnsupportedOperationException()%3B%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

The Product Owner is happy, developers are almost happy, the customer is happy. Again a big salary lands onto my bank account so I am happy.

Next two weeks, next sprint, next tasks. In addition to the local copy and the ftp download conversion the customer wants to have a third option. He wants to be able to upload the content to his own FTP server after converting the downloaded media files. Pretty easy. Like the first time we simply extend our interface and implement one new class.

[pastacode lang=”java” manual=”public%20interface%20MediaConverter%20%7B%0A%09%0A%09void%20convert()%3B%0A%09%0A%09void%20downloadFromFtp()%3B%0A%09%0A%09void%20copy()%3B%0A%09%0A%09void%20uploadToFtp()%3B%0A%09%09%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20class%20LocalMediaConverter%20implements%20MediaConverter%7B%0A%09%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09copy()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%7D%0A%09%0A%09%40Override%0A%09public%20void%20copy()%20%7B%0A%09%09%2F%2F%20copy%20files%20from%20one%20directory%20to%20another%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20downloadFromFtp()%20%7B%0A%09%09throw%20new%20UnsupportedOperationException()%3B%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20uploadToFtp()%20%7B%0A%09%09throw%20new%20UnsupportedOperationException()%3B%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20class%20MediaConverterFtpSource%20implements%20MediaConverter%7B%0A%09%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09downloadFromFtp()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20downloadFromFtp()%20%7B%0A%09%09%2F%2F%20download%20source%20from%20FTP%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20copy()%20%7B%0A%09%09throw%20new%20UnsupportedOperationException()%3B%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20uploadToFtp()%20%7B%0A%09%09throw%20new%20UnsupportedOperationException()%3B%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20class%20FtpMediaConverter%20implements%20MediaConverter%20%7B%0A%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09downloadFromFtp()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%09uploadToFtp()%3B%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20downloadFromFtp()%20%7B%0A%09%09%2F%2F%20download%20source%20from%20FTP%0A%09%7D%0A%0A%09%40Override%20%0A%09public%20void%20copy()%20%7B%0A%09%09throw%20new%20UnsupportedOperationException()%3B%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20uploadToFtp()%20%7B%0A%09%09%2F%2F%20upload%20converted%20file%20to%20FTP%0A%09%09%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

The Product Owner is happy, developers are not happy, the customer is happy. Again a big salary lands onto my bank account but I am not happy at all. So why is that?

Have you seen all the UnsupportedOperationExceptions? These are horrible. Imagine the confusion you are having when you are creating a new LocalMediaConverter object and finding downloadFromFtp and uploadToFtp methods which only throw exceptions. Pretty darn good huh? As the Interface Segregation Principle states clients shouldn’t be forced to depend on methods they do not use which seems to be the case here. What can we do about that?

Refactoring baby

Basically the problem is the same as with huge classes. Having to many functionalities put into one class is a bad coding style and also violates the SRP. Solving this is pretty straight forward though. Simply tear apart the big class or interface into smaller chunks of logically connected functionalities which makes your classes and interfaces more cohesive and reusable.

What are the logically connected functionalities in our example? First there is the conversion. Second we have the local copying of files and third the FTP upload and download. So let us try to divide it like that.

[pastacode lang=”java” manual=”public%20interface%20MediaConverter%20%7B%0A%09%0A%09void%20convert()%3B%0A%09%09%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20interface%20LocalCopyTask%20%7B%0A%09%0A%09void%20copy()%3B%0A%09%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20interface%20FtpTask%20%7B%0A%09%0A%09void%20uploadToFtp()%3B%0A%0A%09void%20downloadFromFtp()%3B%0A%7D” message=”” highlight=”” provider=”manual”/]

And these are the new implementations.

[pastacode lang=”java” manual=”public%20class%20LocalMediaConverter%20implements%20MediaConverter%2C%20LocalCopyTask%7B%0A%09%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09copy()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%7D%0A%09%0A%09%40Override%0A%09public%20void%20copy()%20%7B%0A%09%09%2F%2F%20copy%20files%20from%20one%20directory%20to%20another%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20class%20MediaConverterFtpSource%20implements%20MediaConverter%2C%20FtpTask%7B%0A%09%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09downloadFromFtp()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20downloadFromFtp()%20%7B%0A%09%09%2F%2F%20download%20source%20from%20FTP%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20uploadToFtp()%20%7B%0A%09%09throw%20new%20UnsupportedOperationException()%3B%0A%09%7D%0A%7D%0A” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20class%20FtpMediaConverter%20implements%20MediaConverter%2C%20FtpTask%20%7B%0A%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09downloadFromFtp()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%09uploadToFtp()%3B%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20downloadFromFtp()%20%7B%0A%09%09%2F%2F%20download%20source%20from%20FTP%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20uploadToFtp()%20%7B%0A%09%09%2F%2F%20upload%20converted%20file%20to%20FTP%0A%09%09%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

This is actually much better since we reduce the number of UnsupportedOperationException methods from 5 to 1. Some of you might be happy with that and leave it as it is. But I want to go even further. I want to split apart the upload and download methods into separate interfaces to get rid of the unwanted behaviour in MediaConverterFtpSource.class.

After doing that I end up with:

[pastacode lang=”java” manual=”public%20interface%20MediaConverter%20%7B%0A%09%0A%09void%20convert()%3B%0A%09%09%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20interface%20LocalCopyTask%20%7B%0A%09%0A%09void%20copy()%3B%0A%09%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20interface%20FtpDownloadTask%20%7B%0A%09%0A%09void%20downloadFromFtp()%3B%0A%09%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20interface%20FtpUploadTask%20%7B%0A%09%0A%09void%20uploadToFtp()%3B%0A%09%0A%7D” message=”” highlight=”” provider=”manual”/]

As well as the new implementations

[pastacode lang=”java” manual=”public%20class%20LocalMediaConverter%20implements%20MediaConverter%2C%20LocalCopyTask%7B%0A%09%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09copy()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%7D%0A%09%0A%09%40Override%0A%09public%20void%20copy()%20%7B%0A%09%09%2F%2F%20copy%20files%20from%20one%20directory%20to%20another%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20class%20MediaConverterFtpSource%20implements%20MediaConverter%2C%20FtpDownloadTask%7B%0A%09%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09downloadFromFtp()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20downloadFromFtp()%20%7B%0A%09%09%2F%2F%20download%20source%20from%20FTP%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

[pastacode lang=”java” manual=”public%20class%20FtpMediaConverter%20implements%20MediaConverter%2C%20FtpUploadTask%2C%20FtpDownloadTask%20%7B%0A%0A%09%40Override%0A%09public%20void%20convert()%20%7B%0A%09%09downloadFromFtp()%3B%0A%09%09%2F%2F%20convert%20media%20file%0A%09%09uploadToFtp()%3B%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20downloadFromFtp()%20%7B%0A%09%09%2F%2F%20download%20source%20from%20FTP%0A%09%7D%0A%0A%09%40Override%0A%09public%20void%20uploadToFtp()%20%7B%0A%09%09%2F%2F%20upload%20converted%20file%20to%20FTP%0A%09%09%0A%09%7D%0A%7D” message=”” highlight=”” provider=”manual”/]

After refactoring our code all involved parties, even the grumpy programmers, are happy. We end up with nice implementations of easy to reuse interfaces and implementations. If the customer wouldn’t have wanted to be able to upload the files we could easily be happy with letting the uploadToFtp and downloadFromFtp methods in one interface. But since the requirements have changed we needed to adjust our code the best we could. So we split it into separate interfaces to avoid every single unwanted UnsupportedOperationException. Awesome job guys!

What have we learned?

Same as avoiding huge classes it is a good practice to avoid big interfaces. By putting logically connected functionalities into separate interfaces we end up with more cohesive and reusable code.

Oh, and in addition, we also did not violate the SRP, which also is a huge benefit.

 

Categories: Blog Posts