joubin jabbari
  • home
  • blog
  • work
  • reading
  • photography
  • contact
  • resume

Binary Compatible Generification

Intro

Over the past few months, I've been implementing a Data Distribution System into a project at work (this post is not about the Data Distribution System, so if you'd like to learn more about it, take a look at this wikipedia article).

While exploring the project, I found parts of it to be a bit lacking in proper OO design which led me to go on a detour of rewriting portions of the data model where the View or Controller objects were intermixed.

Not following OO design happens all the time. Even the best programmers get caught between a rock (management) and a hard place (deadlines) which leads to bad design.

In this post, I will be talking about the power Polymorphism within Java Generics.

Spoiler and tl;dr:

EventWithPosition<T extends Task & SignlePosition> extends Event<T>
{
}

The subject of this post is the & for the generics.

The problem

I firmly believe that without having a problem that requires this solution, it is impossible to grasp the beauty of the approach. In this section, I'm going to show you a simple inheritance tree that existed in the original code that I am not able to modify.

At this point, I should note that this project has a public API and other projects have been built on top of it. Simply yanking a chunk of code or classes was not possible.

Consider a class called Event. Events are things that an object in the game could have. For example, a Vehicle can be given an Event to execute in the future. I came to find out, like many other things in software development, that the noun Event was way too overloaded which made it hard to talk about the project. This began an effort to rename Event to Task. However, this was not a simple refactor job. The Event class and all of its children had Model, View, and Controllers mixed into it.

For the time being, the project became about removing the parts of the code we consider to be the Model from the Event class and placing it into the Task class.

The problem design

UML generated by Jetbrains IntelliJ

On top of the problems mentioned above with the lack of boundries between the Model, View and Controller , we observe that everything inherently has a start and end time associated with it.

In an effort to retain the existing API and effectively remove the Data Model, we set out to create a mirror object for each Event object presented in the image above with the name Task. Tasks are simply the Data Model portion of Events.

The problem's we create

While the original design had many design related issues associated with it, we made the matter a bit worse trying to retain the same API.

To retain the same API, we made the assertion that each Event has-a Task. This way, the many Tasks that might exist can share Events as their ViewController. Of course, some Tasks will need special ViewControllers which we will handle by extending Event.

Note: In the first iteration of this, we will simply create a M-VC instead of a full-on MVC

While designing Task, we decided that we don't want all Tasks to have a time; some Tasks simply need to take place at a Location or after the completion of another Task, not based on Time.

Here is the first iteration of creating Tasks.

UML generated by Jetbrains IntelliJ

Obviously the objects with the word Position in them have position related fields in common. We don't want to duplicate code between two position classes, yet we need a way for them to share code. With the introduction of Default Methods in Java 8 Interface, we can cleanly have the essence of extending multiple classes without directly extending them (since Java only allows you to extend one class at a time).

Position's

Since Positions are not directly related to the purpose of this post, I will briefly explain Position without providing a UML.

class Position{...}
class PositionWithTime extends Position{...}
class PositionWithTempature extends Position{...}
class PositionWithSomething extends Position{...}

interface SinglePosition<T extends Position>{
    T getPosition();

    default T getCenter(){
        return getPosition();
    }

    ...
}

interface MultiPosition<T extends Position>{
    List<T> getPositions();

    default T getCenter{
        // Some method that calculates multiple positions
    }

    ...
}

With positions in mind, we have the following structure:

UML generated by Jetbrains IntelliJ

The old Event implementation required Time with duration due to the TemporalObject. We've created TaskWithoutTime only as a means for future extension as it's not required right now.

I think it's important to note at this point that the names displayed in the UML above were chosen for this post with the intention of keeping the discussion free of technicalities.

Integrating Tasks into Events

To recap, we've removed Data-Model related fields from Event and added them to Task; Event being the ViewController for Task, must now accept a Task.

Using Java Generics we can architect the Event to accept a Task. This change will demand an API change, but it's minor in practice and easy to port.

Original Event Class:

public class Event{
    // field that should belong to Task
    // another field that should belong to Task // another field that should belong to Task // another field that should belong to Task // another field that should belong to Task
    ...
    // bunch of code that does task stuff
    ....
    // bunch of code that does view/controller stuff
}

New Event Class:

public class Event<T extends Task>{
    private T task;

    public T getTask(){
        return task;
    }

    public void setTask(T task){
        this.task = task;
    }

    ....
    // other view controller code that was here before
    // however, any calls that use to refer to task fields 
    // originally in Event now simply call the proper get
    // on the Task object
}

The above approach is perfect because it imposes minimal change to the API.

This is where the real problem shows its ugly head. I didn't want to create more Event based ViewController classes for two reasons.

  1. Events are going to be deprecated and replaced with a cleaner API. Implementing another Event based class which is going to be removed seems mean to the users of the API.
  2. There may be many extensions of the Task class which should work with the few Event classes that already exist.

After reimplementing each Event with generic T that extends it's respective Task implementation we get into our real dilemma. Our original EventWithPosition needed to take Tasks that have-a position. However, the implementations of Tasks that have-a Position extend two distinctly different inheritance trees, and what they do share as a base class isn't specific enough.

What we have

public class EventWithPosition<T extends Task> extends Event<T> {
}

What we want

public class EventWithPosition<T extends SinglePosition> extends Event<T> {
}

The above code will not compile since Event expects it's generic to extend Task and not SinglePosition. Not to mention this is too Generic for lack of a better term. We want Tasks which extend SinglePosition.

The Lazy approach: NSFW

public class EventWithPosition<T extends Task> extends Event<T> {
    // Then cast T to SinglePosition or MultiPosition 
}

I felt a little dirty writing the above sample code; if you felt dirty reading it, I'm sorry. Not to mention this is also too generic.

So, here is my solution that I, after 6 years of Java development, did not know existed.

The good approach

public class EventWithPosition<T extends Task & SignlePosition> extends Event<T> {
}

What?

Yes, we are telling the pre-compiler to accept generics that extend Task and SinglePosition with the (&) instruction.

When and why would I use this? and tl;dr

Well, as discussed above, you would generically use this when an Object like TaskWithPositionAndDuration and TaskWithPositionAndTime have a common parrent (TaskWithTime) that you would want to repersent generically with T. However, the statment <T extends TaskWithTime> is too generic for some use cases. To restrict the type T, you would use the &.

This is simply the power of Polymorphism added to Java Generics.

Hope you've enjoyed this post. If I haven't clearly explained something here, please hit me up through my Contact page.


March 10 2016

Joubin Jabbari | Github | Twitter