Avoiding Inheritance Hell With Kotlin


I’d like to thank Matt Wear and Kirill Rakhman for their feedback on this post.

I’ve many times found myself working on a project where a Fragment or an Activity has been over inherited. For example I was working a project one time that had something like the following:

Class Hierarchy

But then the project starts needing a new dependency in some places. For example let’s say Detail Fragment will be using RxJava. Of course we want to start using RxLifeCycle we have a few options.

Option 1)

Class Hierarchy


This isn’t ideal as fragments start getting logic that they don’t need. In this case List Fragment wasn’t using RxJava so it doesn’t need the additional RX logic of RxFragment. This can get messy easily because you can’t mix and match which ancestors to pull from in Java.

Option 2)

Just put Rx Logic in the detail fragment.
Class Hierarchy

This works for now but what happens when you have the following?

Class Hierarchy

This is bad because it violates the DRY principle(Don’t Repeat Yourself). By putting the same RxLifeCycle logic into our ImageFragment we are duplicating logic.

Option 3)

Move the WebSocket Logic into separate class and just dependency inject it where you need it.
This is an ideal scenario, but sometimes that isn’t possible because you are using a framework and you don’t have control of the logic you are inheriting from, or you need access to the fragment lifecycle as is the case with RxLifeCycle.

Option 4)

Use the delegate pattern to keep the WebSocket Logic in one class but extend the fragment to have the same interfaces so that it appears to be a websocket and acts as one, but doesn’t directly contain the logic.

First let us look at how this would look in Java.

public interface IWebSocket {

    Void initialize(String host, int port, String username, String password);
    Void connect();
}

public class Websocket implements IWebSocket {
    @Override
    public void initialize(String host, int port, String username, String password) {
    ...
    }

    @Override
    public void connect() {
    ...
    }
}

public class ListFragment extends Fragment implements IWebSocket {
    IWebSocket socket;

    public void onCreate(Bundle savedInstanceState) {
        socket = ...;
        initialize(...);
        connect();
    }

    @Override
    public void initialize(String host, int port, String username, String password) {
        socket.initialize(host, port, username, password);
    }

    @Override
    public void connect() {
        socket.connect();
    }
}

Kotlin has a really nice way of handling this scenario. It’s called Class Delegation and it greatly simplifies the the process:

interface IWebSocket {
    fun initialize(host: String, port: Int, username: String, password: String)
    fun connect()
}

class Websocket : IWebSocket {
    override fun initialize(host: String, port: Int, username: String, password: String) {
    ...
    }

    override fun connect() {
    ...
    }
}


val socket: IWebSocket = Websocket()

class ListFragment : Fragment(), IWebSocket by socket {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initialize("http://localhost", 8080, "Jim", "Password")
        connect()
    }

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater?.inflate(R.layout.text_layout, container, false);
    }
}

Notice anything interesting? onCreate is calling initialize and connect directly, but we didn’t define those functions explicitly. When we use IWebSocket by socket we are saying that ListFragment inherits from IWebSocket but delegate all of the function calls to var socket. This is super nice because it is a concise modular way to extend fragments without getting a messy inheritance tree.

Caveats

From my testing I haven’t been able to change out the delegate at runtime in the above code. Even if I changed the above val socket to var socket(I don’t know if this is expected behavior from kotlin, it feels strange to me). The reason I found this important is that the delegated val has to be declared at compile time. In the event your delegate needs info that you don’t have at compile time then you’d have problems. Fortunately you can delegate your delegate to get around that problem.

Here is what it’d look like:

class DelegateWebSocket : IWebSocket {
    lateinit var socket: IWebSocket

    override fun initialize(host: String, port: Int, username: String, password: String) {
        socket.initialize(host, port, username, password)
    }

    override fun connect() {
        socket.connect();
    }
}

var delegate: IWebSocket = DelegateWebSocket()

class ListFragment : Fragment(), IWebSocket by delegate {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        (delegate as DelagateSocket)?.socket = AsyncWebsocket()
        initialize("http://localhost", 8080, "Jim", "Password")
        connect()
        (delegate as DelagateSocket)?.socket = Websocket()
        initialize("http://localhost", 8080, "Jim", "Password")
        connect()
    }

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater?.inflate(R.layout.text_layout, container, false);
    }
}

Which begs the question how is this different from the Java version? The difference is that the code in the DelegateWebSocket only is written once in Kotlin regardless of how many fragments need that delegate. In Java every fragment we have would need to have essentially the same code written out in every single fragment that implements IWebSocket(Which is still a violation of the DRY principle 😉 ).

E.g. if we wanted to add another fragment that uses the delegate it’s the difference between this:

class DetailFragment : Fragment(), IWebSocket by delegate {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        (delegate as DelagateSocket)?.socket = AsyncWebsocket()
        initialize("http://localhost", 8080, "Jim", "Password")
        connect()
    }
}

and this:

public class DetailFragment extends Fragment implements IWebSocket {
    IWebSocket socket;

    public void onCreate(Bundle savedInstanceState) {
        socket = ...;
        initialize(...);
        connect();
    }

    @Override
    public void initialize(String host, int port, String username, String password) {
        socket.initialize(host, port, username, password);
    }

    @Override
    public void connect() {
        socket.connect();
    }
}

Imagine how monotonous this becomes in java if your interface has 5 to 10 methods and you have to add this to several classes :[

Comments 1

  1. Hi Jim, nice article!

    Last week I saw the following post Exploring Delegation in Kotlin where basically it creates a new delegated object on construction:


    class Person(n:String): HasName by NameMixin(n) {
    }

    Also, the delegated object can be given in the constructor and constructed elsewhere (similar example can also be seen in Kotlin reference):


    class Person(n:NameMixin): HasName by n {
    }
    val p = Person(NameMixin("Stephan"))

    It still doesn’t allow to change it after object has been constructed, but I think it should cover many common cases.

Leave a Reply

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