Refactoring: introduce new class with Parallel Change

This post is the first in a series of refactorings of the command line video store exercise. You can find the original version in Jave on Rick’s repository. I will be working on the corresponding C# version.

The goal of this series is to start with procedural code and turn it into object-oriented code. This series presumes you are using Resharper.

This exercise is divided into 8 steps. In this post, I will go over the first step. You can find the full code on the E1 branch. When this step is done, the code looks like the one on the E2 branch.

In this step, we will introduce a new Movie class and move the corresponding data there.

Before you start, I suggest you take a look at the full main class here.

The movies are loaded from a file:

public void Run()
{
    // read movies from file
    var movies = new Dictionary<int, string[]>();
    using (FileStream fs = File.Open(@"movies.cvs", FileMode.Open, FileAccess.Read))
    using (BufferedStream bs = new BufferedStream(fs))
    using (StreamReader reader = new StreamReader(bs))
    {
        while (!reader.EndOfStream)
        {
            string line = reader.ReadLine();
            string[] movie = line.Split(';');
            movies.Add(int.Parse(movie[0]), movie);
            _out.WriteLine(movie[0] + ": " + movie[1]);
        }
    }
    // ...
}

The code uses a dictionary to store the movies by an integer key. You can see that a movie is represented by a primitive structure: an array of string. This array contains the following information about a movie:

  • movie[0]: the key
  • movie[1]: the name
  • movie[2]: the category

We want to replace this structure (primitive obession smell) with a Movie class that contains the data above.

To do so, we will proceed by introducing a parallel change: create a new dictionary of Movie instead of string[].

The key to the parallel change is to find all writes to the old structure and duplicating those writes to the new structure. When all writes also go to the new structure, we can start replacing the reads from the old structure to the new one.

Introduce Movie class

To introduce the Movie class, duplicate the movies variable (Ctrl + D) to movies1 Dictionary<int, Movie>. Since the Movie class does not exist yet, the code does not compile. We will fix that soon.

In the loop where the movies dictionary is filled, we add a new Movie to movies1. The Movie constructor takes the three elements from the string array as parameters: key, name and category.

Once this is done, we are ready to generate the Movie class. Place the cursor on the Movie constructor and press Alt + Enter, Create class ‘Movie’ to let Resharper create a class for us:

Create Movie

Note that we introduced a local variable for key to avoid having the parse code three times inside the loop, and that we renamed the movie[] array to movieData[] for more clarity.

Now that we have the Movie class, we have to remove the NotImplementedException from the constructor.

By default, Resharper will create the new class inside the current file, so we will need to use Alt + Enter on the class name to move the class to a separate file.

We create and initialise fields by hitting Alt + Enter on each constructor parameter and by choosing the option Introduce and intialise field.

Since we know that these fields will be used by the MainClass, we can encapsulate them so that we have public getters. This is the result:

Now the code compiles again and the tests are still green.

Parallel change: replace reads

The next step is to replace the reads on the old string array structure with our new Movie accessors. In the while loop, we replace all reads to movie[] with the corresponding property from the movie1 instance of Movie.

Every thing looks good, the tests are still green. Now we can remove the reads and writes to the old structure, since we are now using the new one everywhere.

We notice that movie[] is not used anymore in the while loop: remove it. The old movies dictionary is now only used to add the old string array to it. Remove it as well. Now that the old structure is gone, we can rename movies1 and movie1 to movies and movie:

public void Run()
{
    // read movies from file
    var movies = new Dictionary<int, Movie>();
    using (FileStream fs = File.Open(@"movies.cvs", FileMode.Open, FileAccess.Read))
    using (BufferedStream bs = new BufferedStream(fs))
    using (StreamReader reader = new StreamReader(bs))
    {
        while (!reader.EndOfStream)
        {
            string line = reader.ReadLine();
            string[] movieData = line.Split(';');
            int key = int.Parse(movieData[0]);
            var movie = new Movie(key, movieData[1], movieData[2]);
            movies.Add(key, movie);

            _out.WriteLine(movie.Key + ": " + movie.Name);
        }
    }
    // ...
    while (true)
    {
        string input = _in.ReadLine();
        if (string.IsNullOrEmpty(input))
        {
            break;
        }
        string[] rental = input.Split(' ');
        Movie movie = movies[int.Parse(rental[0])];
        decimal thisAmount = 0;

        int daysRented = int.Parse(rental[1]);
        //determine amounts for rental
        switch (movie.Category)
        {
            case "REGULAR":
                thisAmount += 2;
                if (daysRented > 2)
                    thisAmount += (daysRented - 2)*1.5m;
                break;
            case "NEW_RELEASE":
                thisAmount += daysRented*3;
                break;
            case "CHILDRENS":
                thisAmount += 1.5m;
                if (daysRented > 3)
                    thisAmount += (daysRented - 3)*1.5m;
                break;
        }

        // add frequent renter points
        frequentRenterPoints++;
        // add bonus for a two day new release rental
        if (movie.Category.Equals("NEW_RELEASE") && daysRented > 1)
        {
            frequentRenterPoints++;
        }
        // show figures for this rental
        result += "\t" + movie.Name + "\t" + thisAmount.ToString("0.0", CultureInfo.InvariantCulture) + "\n";
        totalAmount += thisAmount;
    }
    // ...
}

There is no behaviour that we can move to the Movie class, so we will leave it at that for this step.

Summary

In this step, we saw how to:

  • recognise the code smell primitive obsession.
  • quickly creating a new class by typing “new MyClass(parameters)” and using Resharper to generate the class.
  • using the parallel change strategy:
    1. introduce a new structure,
    2. duplicate all writes to the old structure for the new structure,
    3. replace reads from the old structure to use the new structure,
    4. remove the old structure.

You can find the full code on this branch.

After this refactoring, the MainClass is still pretty messy and does too many different things. In the next step, we will introduce a new class for the concept of rental.