geek yapping

Twitter












Refactoring, Creation Methods

Lately, I’ve been engulfed in several design books, one of which is Refactoring to Patterns by Joshua Kerievsky. The book is an extension of Martin Fowler’s book, Refactoring. It emphasizes learning to use patterns by working with existing code, identifying code that “smells” and then finding a pattern to refactor the code “to, towards or away from.” Smelly code typically involves duplication, complexity and/or ambiguity.

Joshua believes, rather than heavy pattern use up front, to let code evolve into or away from patterns. I thought it might be fun to share some of these refactorings and examples from my code past (gasp).

One of the first refactorings, Replace Constructors with Creation Methods. Simply put, when we have lots of overloads or parameters for our constructor(s), ambiguity arises as to the intended use. Often we add conditional plumbing to construct different instances of the same class with different run time behavior. The problem is that the constructors are called the same way and we have no way of providing intention through naming conventions like we can with methods and other members.

I went back to some old code and found an example of where this refactoring could help. The code was doing various types of charting by abstracting the creation of lines to separate instances before rendering the chart. One of the classes, scrubbed, had constructors as follows:
public class Line
{
	...
	public Line(int year, int month, int productId)
	{
		Initialize(year, month, productId);
		LoadData();
	}

	public Line(int month, int productId)
	{
		Initialize(2000, month, productId);
	}
	...
}
The first overload has an extra parameter to provide the year, that is all a user of this class would see when they come across instantiations of this class. With out digging deeper they wouldn’t realize that one overload queries and loads actual data points for the specified product while the other was intended to provide a way to manipulate and map existing lines to new lines.

The refactoring seeks to use methods to create instances of the class and subsequently, if appropriate, hiding the original constructors so users are forced to use the new, hopefully less ambiguous, Creation Methods.

Every refactoring has a set of steps, which should be performed one at at time until the desired changes are complete. Joshua emphasizes that not all steps must be completed. Sometimes only part of a refactoring is sufficient to clean up smelly code.

The “Mechanics,” as Joshua refers to in his book, are as follows:
  1. Find a spot where the class is instantiated and apply the refactoring Extract Method.
    Line line = new Line(2009, 2, productId);
    

    Refactors to (note the extracted method should be static):
    Line line = CreateLine(2009, 2, productId);
    ...
    public static Line CreateLine(int month, int year, int productId)
    {
    	return new Line(month, year, productId);
    }
    
  2. Next, apply Move Method and move the new method to the class it instantiates.
    Line line = Line.CreateLine(2009, 2, productId);
    ...
    public class Line
    {
    	...
    	public static Line CreateLine(int month, int year, int productId)
    	{
    		return new Line(month, year, productId);
    	}
    	...
    }
    
  3. Find all code that uses this specific constructor, for this specific intended use, and refactor it to use the new Creation Method. Be careful to check for constructors that are used for several different intended purposes, these should be broken out into their own separate Creation Methods. Red flags for this include conditional logic in the constructor and/or allowing null/default values for a parameter that drive different class behaviors.
  4. Joshua mentions to repeat steps 1 to 3 for all other constructor overloads. I’d rather switch his last two steps. So, in this step, make any constructors that are no longer publicly used, private or internal to avoid users trying to call the constructors. Remeber, each step is optional, and in some cases you may not want to re-scope the constructor.
    public class Line
    {
    	...
    	private Line(int year, int month, int productId)
    	{
    		Initialize(year, month, productId);
    		LoadData();
    	}
    	...
    }
    
  5. Repeat for all applicable constructors. To save time, I will show the final form of my Line class:
    public class Line
    {
    	...
    	private Line(int year, int month, int productId)
    	{
    		Initialize(year, month, productId);
    		LoadData();
    	}
    
    	private Line(int month, int productId)
    	{
    		Initialize(2000, month, productId);
    	}
    
    	public static Line CreateLine(int month, int year, int productId)
    	{
    		return new Line(month, year, productId);
    	}
    
    	public static Line CreateMappingLine(int year, int productId)
    	{
    		return new Line(year, productId);
    	}
    	...
    }
    

It is easy to see how the intentions of the different constructors are present in the names of their Creation Methods. The second overload’s Creation Method now clearly creates a mapping line whereas the first creates an actual line. This helps the developer avoid assumptions when reading the client code!

Joshua lists pros and cons for each refactoring. For this refactoring, the positives are that we can express intentions and increase the readability/maintainability of our code. Also, in cases where a single constructor creates two instances that behave differently, we can get around this by using the exact same parameter set but showing the intended differences in the Creation Method names. Whereas with constructors we can only have one constructor per parameter set. Finally, a word of caution, using Creation Methods is rather non standard. I would personally, and Joshua hints at it too, use some type of Factory/pattern with this instead of just a Creation Method. I feel that developers are likely to be familiar with the Factory/pattern(s) and know to look for that when constructors are missing, at least more so than Creation Methods.
Happy Coding!

Feedback

No comments posted yet.