Core Java

Template Method Pattern Example Using Java Generics

If you find that a lot of your routines are exactly the same except for certain sections, you might want to consider the Template Method to eliminate error-prone code duplication. Here’s an example: Below are two classes that do similar things:
 
 
 
 
 
 
 
 

  1. Instantiate and initialize a Reader to read from a CSV file.
  2. Read each line and break it up into tokens.
  3. Unmarshal the tokens from each line into an entity, either a Product or a Customer.
  4. Add each entity into a Set.
  5. Return the Set.

As you can see, it’s only in the third step that there’s a difference – unmarshalling to one entity or another. All other steps are the same. I’ve highlighted the line where the code is different in each of the snippets.

ProductCsvReader.java

public class ProductCsvReader {
 
    Set<Product> getAll(File file) throws IOException {
        Set<Product> returnSet = new HashSet<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(file))){
            String line = reader.readLine();
            while (line != null && !line.trim().equals("")) {
                String[] tokens = line.split("\\s*,\\s*");
                Product product = new Product(Integer.parseInt(tokens[0]), tokens[1],
                        new BigDecimal(tokens[2]));
                returnSet.add(product);
                line = reader.readLine();
            }
        }
        return returnSet;
    }
}

CustomerCsvReader.java

public class CustomerCsvReader {
 
    Set<Customer> getAll(File file) throws IOException {
        Set<Customer> returnSet = new HashSet<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(file))){
            String line = reader.readLine();
            while (line != null && !line.trim().equals("")) {
                String[] tokens = line.split("\\s*,\\s*");
                Customer customer = new Customer(Integer.parseInt(tokens[0]), tokens[1],
                        tokens[2], tokens[3]);
                returnSet.add(customer);
                line = reader.readLine();
            }
        }
        return returnSet;
    }
}

For this example, there are only two entities, but a real system might have dozens of entities, so that’s a lot of error-prone duplicate code. You might find a similar situation with DAOs, where the select, insert, update, and delete operations of each DAO would do the same thing, only work with different entities and tables. Let’s start refactoring this troublesome code. According to one of the design principles found in the first part of the GoF Design Patterns book, we should “Encapsulate the concept that varies.” Between ProductCsvReader and CustomerCsvReader, what varies is the highlighted code. So our goal is to encapsulate what varies into separate classes, while moving what stays the same into a single class. Let’s start editing just one class first, ProductCsvReader. We use Extract Method to extract the line into its own method:

ProductCsvReader.java after Extract Method

public class ProductCsvReader {
 
    Set<Product> getAll(File file) throws IOException {
        Set<Product> returnSet = new HashSet<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(file))){
            String line = reader.readLine();
            while (line != null && !line.trim().equals("")) {
                String[] tokens = line.split("\\s*,\\s*");
                Product product = unmarshall(tokens);
                returnSet.add(product);
                line = reader.readLine();
            }
        }
        return returnSet;
    }

    Product unmarshall(String[] tokens) {
        Product product = new Product(Integer.parseInt(tokens[0]), tokens[1], 
                new BigDecimal(tokens[2]));
        return product;
    }
}

Now that we have separated what varies with what stays the same, we will create a parent class that will hold the code that stays the same for both classes. Let’s call this parent class AbstractCsvReader. Let’s make it abstract since there’s no reason for the class to be instantiated on its own. We’ll then use the Pull Up Method refactoring to move the method that stays the same to this parent class.

AbstractCsvReader.java

abstract class AbstractCsvReader {

    Set<Product> getAll(File file) throws IOException {
        Set<Product> returnSet = new HashSet<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(file))){
            String line = reader.readLine();
            while (line != null && !line.trim().equals("")) {
                String[] tokens = line.split("\\s*,\\s*");
                Product product = unmarshall(tokens);
                returnSet.add(product);
                line = reader.readLine();
            }
        }
        return returnSet;
    }
}

ProductCsvReader.java after Pull Up Method

public class ProductCsvReader extends AbstractCsvReader {

    Product unmarshall(String[] tokens) {
       Product product = new Product(Integer.parseInt(tokens[0]), tokens[1], 
                new BigDecimal(tokens[2]));
        return product;
    }
}

This class won’t compile since it calls an “unmarshall” method that’s found in the subclass, so we need to create an abstract method called unmarshall.

AbstractCsvReader.java with abstract unmarshall method

abstract class AbstractCsvReader {

    Set<Product> getAll(File file) throws IOException {
        Set<Product> returnSet = new HashSet<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(file))){
            String line = reader.readLine();
            while (line != null && !line.trim().equals("")) {
                String[] tokens = line.split("\\s*,\\s*");
                Product product = unmarshall(tokens);
                returnSet.add(product);
                line = reader.readLine();
            }
        }
        return returnSet;
    }

    abstract Product unmarshall(String[] tokens);
}

Now at this point, AbstractCsvReader will make a great parent for ProductCsvReader, but not for CustomerCsvReader. CustomerCsvReader will not compile if you extend it from AbstractCsvReader. To fix this, we use Generics.

AbstractCsvReader.java with Generics

abstract class AbstractCsvReader<T> {

    Set<T> getAll(File file) throws IOException {
        Set<T> returnSet = new HashSet<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(file))){
            String line = reader.readLine();
            while (line != null && !line.trim().equals("")) {
                String[] tokens = line.split("\\s*,\\s*");
                T element = unmarshall(tokens);
                returnSet.add(product);
                line = reader.readLine();
            }
        }
        return returnSet;
    }

    abstract T unmarshall(String[] tokens);
}

ProductCsvReader.java with Generics

public class ProductCsvReader extends AbstractCsvReader<Product> {

    @Override
    Product unmarshall(String[] tokens) {
       Product product = new Product(Integer.parseInt(tokens[0]), tokens[1], 
                new BigDecimal(tokens[2]));
        return product;
    }
}

CustomerCsvReader.java with Generics

public class CustomerCsvReader extends AbstractCsvReader<Customer> {

    @Override
    Customer unmarshall(String[] tokens) {
        Customer customer = new Customer(Integer.parseInt(tokens[0]), tokens[1], 
                tokens[2], tokens[3]);
        return customer;
    }
}

And that’s it! No more duplicate code! The method in the parent class is the “template”, which holds the code that stays the same. The things that change are left as abstract methods, which are implemented in the child classes. Remember that when you refactor, you should always have automated Unit Tests to make sure you don’t break your code. I used JUnit for mine. You can find the code I’ve posted here, as well as a few other Design Patterns examples, at this Github repository. Before I go, I’d like to leave a quick note on the disadvantage of the Template Method. The Template Method relies on inheritance, which suffers from the the Fragile Base Class Problem. In a nutshell, the Fragile Base Class Problem describes how changes in base classes get inherited by subclasses, often causing undesired effects. In fact, one of the underlying design principles found at the beginning of the GoF book is, “favor composition over inheritance”, and many of the other design patterns show how to avoid code duplication, complexity or other error-prone code with less dependence on inheritance. Please give me feedback so I can continue to improve my articles.

Calen Legaspi

Calen is CEO and founder of Orange & Bronze Software Labs (http://orangeandbronze.com), an outsourcing, consulting & training firm based in Manila, specializing in Agile Software Development, Spring Framework and Grails. He is an advocate and trainer of Test-Driven Development, Object-Oriented Design, and Domain-Driven Design.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
PiotrL
PiotrL
10 years ago

Why then promote pattern that IS bad?

Marcelo
Marcelo
10 years ago

Great article, Calen! Well done!

Many developers and notorious experts say we should avoid using inheritance (as you mentioned as well), but… on the way you’ve developed your code, this “bad principle” worked fine, is there any other solution without using inheritance? I have the same issue with in my own code design and I didn’t find a better one.

Thanks!

Valery
Valery
10 years ago

Marcelo, Obviuosly, the most common alternative to the inheritance is a delegation. 1. Create (single abstract method) interface for unmarshalling: interface Unmarshaller { T unmarshall(String[] tokens) } 2. Refactor AbstractCsvReader to GenericCsvReader: public class GenericCsvReader { final private Unmarshaller unmarshaller; public GenericCsvReader(Unmarshaller unmarshaller) { this.unmarshaller = unmarshaller; } … public Set getAll(File file) throws IOException { … //line 09 should be delegate call: T element = unmarshaller.unmarshall(tokens); … } } 3. Create separate implementations of the Unmarshaller interface for Product and Customer; if you are using Java 8 you may use lambda expressions for this. As a result, you have… Read more »

Calen Legaspi
10 years ago

Thank you, Marcelo, Valery and PiotrL, for commenting on my post! Your feedback helps me improve my article. Inheritance per se is not bad, but just like any tool you need to be aware of the dangers, and then weigh the tradeoffs. Inheritance is dangerous when there is a significant probability that the parent class might be modified in the future, causing undesired or breaking changes in the child classes. It is also dangerous when the child classes inherit members that they don’t use, which could lead to error prone code. Read up on Open-Closed Principle and Refused Bequest Code… Read more »

Bryan Basham
Bryan Basham
10 years ago

Excellent article. Nice simple example. Great use of refactoring; as well. I’m going to recommend your article to my RIT students.

Calen Legaspi
10 years ago
Reply to  Bryan Basham

Thank you, Bryan!

Chris Jansen
Chris Jansen
9 years ago

Great write up Calen! I was looking for a practical example of using generics at a class level and this write up was exactly what I needed. Keep up the great work.

Back to top button