$timezone = 'America/Los_Angeles'; date_default_timezone_set($timezone); ?>
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.
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.
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
.
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
.
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).
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:
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.
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.
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. 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.
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.