I love Ely’s DragTile component (demo). However, I have always wished that it were easier to extend its behavior to encompass different layouts. To me, that immediately suggests a (big) refactoring. For those of you who have never done refactoring or who don’t refactor as often as you should, I thought it might be fun to walk through this step by step.
What is refactoring?
Refactoring is the process of incrementally changing the structure of your code without changing the outward functionality. It may seem silly to focus on not changing the functionality, but it is actually pretty important.
The idea is that there are two distinct phases of work: coding and refactoring. During coding, you add new functionality. During refactoring, you restructure your code while making sure that it continues to work properly. Not changing functionality during refactoring gives you a strong reference point as you make lots of iterative changes to the structure. You know your refactoring was successful if the new code still does what the old code did.
Refactoring for better reuse
Usually, you refactor code so that it can be more flexible or more reusable that it currently is. Sometimes, the change that is required is obvious. In other cases, you have to think a little before figuring out how to transform your code.
In this particular case, we want to make it easier to change layouts. Looking at the DragTile code, there is quite a bit that would be the same no matter what the layout is, and relatively little that needs to change with each new layout algorithm. Because the layout algorithm is something that is likely to change in various use cases, it is probably a good idea to isolate this functionality into a separate class.
Tip 1: Cleanly separate out the code that you think will need to change often into a separate class.
The most obvious way to divide up the DragTile layout code from the rest of the DragTile code is through inheritance:
Splitting things up in this way allows us to create a new subclass with a different layout (say, a circle) while reusing the logic that might be common to both, such as the code to talk to item renderers and do animation.
The other main way to split things up is through composition:
In this case, the container delegates to another object in order to do layout. There are a number of advantages to this approach:
- Using delegates often makes it possible to modify behavior at runtime. After we are finished with this particular refactoring, we should be able to change the layout of one of our containers without reparenting. Woohoo!!
- Decomposing larger classes into smaller classes can make it easier to evolve different parts of the system separately as needs change. For example, let’s say we later decide to split up FlexibleContainer into two classes: one that is lightweight for easy download, and another that is robust, to handle caching, localization, etc. Should “DragTile” inherit from the light one or the robust one? If layout is handled through delegation, you may not need to choose.
- Composition often allows for greater decoupling. For example, let’s say you are building a photo organizer module. With the inheritance approach above, it would be difficult to create the module in such a way that the photo module knows absolutely nothing about the layout. With the composition approach, you could provide a default layout (say, Tile), while allowing the user of the module to pass in a different layout if needed.
All of this leads to the next tip, which is:
Tip 2: Think hard before using inheritance. Composition is almost always a better way to separate out the flexible part of a class from the invariant part.
So now we have a general idea of what we want to separate out, and how we want to do it. We’ll start digging into actual code in the next post.