Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Line No

Issue

Proposed Solution

20

TextFileSource - needs the declared as a generic

TextFileSource<T>

20

IDataSource - needs to be a generic

IDataSource<T>

22

Returning a, Iterable<String> is constraining. There are other collection types in the JDK that do not decend from Collection and do not implement the Iterable interface

Return the datatype that actually holds the data

24

The solution is too restrictive, binding the functor to an ArrayList<String>

I would like to tie the ICollectionLoader to the generic parameter type that will be passed into TextFileSouce

32

Same as line 22

Return the datatype that actually holds the data

32

ICollectionLoader should be bound to the generic type passed into TextFileSource

ICollectionLoader<T>

34

This is crazy, it’s such tightly coupled code to ArrayList<String>

The object should be passed in, so the method signature should look like this

public T loadData( String fname, T lines, ICollectionLoader<T> func )

The idea being that func will know how to operate on the collection lines that is being passed. So this will allow us to provide any collection type and a custom function to manipulate the items being added to the collection

The road to Generics

Before we start butchering the code let’s create a test or two to make sure things continue to work as they should as we drastically change the code

TextFileSourceTest

Add the following test code

Code Block
languagejava
package com.celestial.mocking_explored;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;

import static org.junit.jupiter.api.Assertions.*;

class TextFileSourceTest
{
    @Test
    void howto_use_loadData_method_with_single_param()
    {
        TextFileSource<ArrayList<String>> tfl = new TextFileSource<>();

        String fname = "C:\\tmp\\KeyboardHandler.java.txt";
        ArrayList<String> lines = tfl.loadData(fname);

        lines.forEach((element) ->{
            System.out.println(">> " + element);
        });
    }
}

After we’ve completed the steps below, we can run this test to see if the file still loads as expected

Onto Generics

  1. Start by checking out to git tag v1.5-tests_completed_code_refactored with the command git tag v1.5-tests_completed_code_refactored

  2. We’ll begin by modifying the TextFileSource, replacing references of Iterable<String> with ArrayList<String>

  3. Cut ArrayList<String> lines = new ArrayList<>(); in loadData( String name, ICollectionLoader func ) (left code line 36) and move it to the top of loadData( String name ); (right code line 25)

  4. In the loadData( String fname ) method add an extra parameter to loadData(...) (right code line 31), and modify the method signature to line 35 of the right-hand code fragment

  5. Once completed make sure the tests still pass - test_count_chars_in_BasicDataProcessor_no_mocking

  6. So your code should look like this

  7. You should be getting errors in App and TextFileSource if you’ve started from the git tag v1.5-tests_completed_code_refactored. We need to fix the method loadData() in IDataSource so it now matches the revised method signature in TextFileSource, so it should look like this now

    1. Code Block
      languagejava
      public interface IDataSource
      {
          public  ArrayList<String>  loadData(  String fname );
          public  ArrayList<String>  loadData(  String fname, ArrayList<String> storage, ICollectionLoader func );
      }
    2. Notice that we’ve updated the return type of each method so they match the methods in TextFileSource

  8. And App should now look like this

  9. We now need to templatize the class, methods, and parameters so the type is consistent across all three points

  10. You will get an error because the interface is out of sync with what we what the implementation class looks like, so update IDataSource to the following

  11. Code Block
    languagejava
    public interface IDataSource<T>
    {
        public  <T> T  loadData(  String fname );
        public  <T> T  loadData(  String fname, T storage, ICollectionLoader func );
    }
  12. You will still be getting an error in TextFileSource

  13. It is to do with the functor. ICollectionLoader is a Generic and it’s bound to ArrayList<String>, if we don’t specify a generic parameter type for ICollectionLoader, the Java compiler widens it to Object. Also in the line where we create the functor, we cannot use the generic param T because the Java compiler is unable to determine if the c.add() is a method on the type T. So in both cases we need to help the compiler. We need to update IDataSource again to the following

    1. Code Block
      languagejava
      public interface IDataSource<T>
      {
          public  <T> T  loadData(  String fname );
          public  <T> T  loadData(  String fname, T storage, ICollectionLoader<T> func );
      }
  14. We are now telling ICollectionLoader to expect a type T not a type Object

  15. Also update TextFileSource as shown here

  16. In both cases, we are helping the Java compiler

  17. Now complete the process by updating the App class

  18. There is one more file we need to update, BasicDataProcessor. It’s currently not a Generic but uses the TextFileLoader via IDataSource which is generic. Because we’ve typed the use IDataSource it’s going to default to type Object. Change BasicDataProcessor so the it looks like the code on the right

    1. Image Added
  19. You should be saying to yourself, it must be possible to make BasicDataProcessor a complete Generic. You would be correct in that assessment, make further changes to BasicDataProcessor so it now looks like the code on the right

    1. Image Added

TextFileSource test file

Now is a good time to add this test file, we should have added this at the beginningmore tests, each suing different collection and algorithms

Code Block
languagejava
package com.celestial.mocking_explored;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Stack;


class TextFileSourceTest
{
    @Test
    void howto_use_loadData_method_with_single_param()
    {
        TextFileSource<ArrayList<String>> tfl = new TextFileSource<>();

        String fname = "C:\\tmp\\KeyboardHandler.java.txt";
        ArrayList<String> lines = tfl.loadData(fname);

        lines.forEach((element) ->{
            System.out.println(">> " + element);
        });
    }

    @Test
    void howto_use_LoadData_method_with_3_params()
    {
        TextFileSource<ArrayList<String>> tfl = new TextFileSource<>();

        // We create a lambda expression to do the work in the TextFileLoader
        ICollectionLoader<ArrayList<String>> functor = (c, l) -> {
            c.add(l);
            return c;
        };

        String fname = "C:\\tmp\\KeyboardHandler.java.txt";
        ArrayList<String> data = new ArrayList<>();

        ArrayList<String> lines = tfl.loadData(fname, data, functor);

        lines.forEach((element) ->{
            System.out.println(">> " + element);
        });
    }

    @Test
    void howto_use_LoadData_method_with_3_params_using_queue()
    {
        TextFileSource<ArrayList<String>> tfl = new TextFileSource<>();

        // We create a lambda expression to do the work in the TextFileLoader
        ICollectionLoader<PriorityQueue<String>> functor = (c, l) -> {
            c.add(l);
            return c;
        };

        String fname = "C:\\tmp\\KeyboardHandler.java.txt";
        PriorityQueue<String> data = new PriorityQueue<>();

        PriorityQueue<String> lines = tfl.loadData(fname, data, functor);

        lines.forEach((element) ->{
            System.out.println(">> " + element);
        });
    }

    @Test
    void howto_use_LoadData_method_with_3_params_using_Stack()
    {
        TextFileSource<ArrayList<String>> tfl = new TextFileSource<>();

        // We create a lambda expression to do the work in the TextFileLoader
        ICollectionLoader<Stack<String>> functor = (c, l) -> {
            c.push(l);
            return c;
        };

        String fname = "C:\\tmp\\KeyboardHandler.java.txt";
        Stack<String> data = new Stack<>();

        Stack<String> lines = tfl.loadData(fname, data, functor);

        lines.forEach((element) ->{
            System.out.println(">> " + element);
        });
    }
}