Factory Pattern: Replacing the Conditional Statement with Lambda Expressions

The “Factory Pattern” falls under the creational design patterns and is one of the widely used in Java applications. Similar to the other patterns, this pattern helps us write organized and clean code.

But, for me, the conditional statement in the “Factory Pattern” presents a possible breach of the clean code principles especially as the number of conditions increases.

In this post, I will be sharing a technique of replacing the conditional statement with a map of lambda calls in Java.

Definition

It is not the purpose of this post to explain what is the “Factory Pattern” or how it works in details. Thus, I am referring to famous book “Design Patterns: Elements of Reusable Object-Oriented Software” to quote the pattern’s intent:

Intent:
Define and interface for creating an object, but let the subclasses decide which class to instantiate. Factory method lets class defer instantiation to subclasses.

Use Case

For this post, I am assuming that we have an interface “Car,” 4 sub-classes (DieselCar, ElectricalCar, HybridCar, and PetrolCar) and one Factory class “CarFactory.” The diagram for those classes would be as follows:

Traditional Implementation

The traditional implementation of this pattern requires us to have either an if-else or a switch statement in the method CarFactory.getCar().

So, we end up with one of the below implementations:

The IF-ELSE Block

public class CarFactoryIfStatement {

private CarFactoryIfStatement() {
}

static Car getCar(String type, String brand) {
if (type.equals("Diesel")) {
return new DieselCar(brand);
} else if (type.equals("Electrical")) {
return new ElectricalCar(brand);
} else if (type.equals("Hybrid")) {
return new HybridCar(brand);
} else if (type.equals("Petrol")) {
return new PetrolCar(brand);
}
throw new UnsupportedOperationException();
}
}

The Switch Block

public class CarFactorySwitchStatement {

private CarFactorySwitchStatement() {
}

static Car getCar(String type, String brand) {
switch (type) {
case "Diesel":
return new DieselCar(brand);
case "Electrical":
return new ElectricalCar(brand);
case "Hybrid":
return new HybridCar(brand);
case "Petrol":
return new PetrolCar(brand);
default:
throw new UnsupportedOperationException();
}
}
}

The above code snippets might not look very bad, but, as we add more care types the code will become messy, and sonar will complain!

So, what can we do about this code?

Lambda to the Rescue

As a replacement, I will be using a HashMap where the Key is of type String, and the Value is an interface “CarCreator.”

First, we write the interface CarCreator that has only a single method ‘initialize‘ that takes a String and return the Car instance (as shown below):

private interface CarCreator {
Car initialize(String brand);
}

Second, we initialize our map ‘CAR_FACTORY_MAP.’ Our value in this map is a lambda expression that initializes the correct Car instance corresponding to the key.

private static final Map<String, CarCreator> CAR_FACTORY_MAP = ImmutableMap.of(
"Diesel", brand -> new DieselCar(brand),
"Electrical", brand -> new ElectricalCar(brand),
"Hybrid", brand -> new HybridCar(brand),
"Petrol", brand -> new PetrolCar(brand)
);

Note: I am using the Guava library from Google to ease the initialization of the map.

Finally, we implement the method ‘CarFactory.getCar.‘ Instead of the huge bulk of the code we wrote previously with If-Else and Switch blocks; we will call the initialize method of the ‘CarCreator‘ interface.

static Car getCar(String type, String brand) {
if (CAR_FACTORY_MAP.containsKey(type)) {
return CAR_FACTORY_MAP.get(type).initialize(brand);
}
throw new UnsupportedOperationException();
}

Following those three steps, the code of the ‘CarFactory‘ class would look something like this:

public class CarFactory {
private CarFactory() {
}
private static final Map<String, CarCreator> CAR_FACTORY_MAP = ImmutableMap.of(
"Diesel", brand -> new DieselCar(brand),
"Electrical", brand -> new ElectricalCar(brand),
"Hybrid", brand -> new HybridCar(brand),
"Petrol", brand -> new PetrolCar(brand)
);

static Car getCar(String type, String brand) {
if (CAR_FACTORY_MAP.containsKey(type)) {
return CAR_FACTORY_MAP.get(type).initialize(brand);
}
throw new UnsupportedOperationException();
}

private interface CarCreator {
Car initialize(String brand);
}
}

Note: I have uploaded the full code to a repository on my Github account.

References

  1. Design Patterns: Elements of Reusable Object-Oriented Software
  2. Tutorials Point – Factory Pattern
  3. [Featured Image] Pixabay – Wheel Valve Heating Line Turn
  4. Source Code on Github

Mars Rover – Refactoring Kata

On several occasions, I have used the Mars Rover challenge to train developers on the TDD principles. This time, I am using it as a refactoring Kata!

On my GitHub repository, I submitted the initial code that covers all the cases except the edge ones. But, the code I’ve written is in bad shape.

How to solve this Kata?

To solve this kata, you have two tasks to do. Firstly, you need to refactor the code and ensure it follows the clean code principles. Secondly, you need to write code that covers the edge cases for the Mars Rover.

Note: Before proceeding, make sure to fork the repository to your own Github account.

Task 1: Code Refactoring

  1. Start by writing unit tests to cover all the cases of this challenge. This is an important step to ensure that the refactoring doesn’t break your code. In total, you should write around 12 unit tests to have good coverage of the code.
  2. Now it’s time to refactor the existing code. While doing that:
    1. Think of the design patterns that might help improve the code
    2. Apply some of the S.O.L.I.D principles to have clean classes and methods

Task 2: Missing Features

There is a missing feature with my code. Currently, the rover can move out of the plateau if given the wrong commands. It’s your task to write the code that handles this case. The rover’s position should not be modified if the next spot is outside the plateau.

Finally…

This kata is relatively easy, but, I think it is a good first step into the world of code refactoring and writing tests for existing code.

Looking forward to your feedback…

References & Links

How to handle Java exceptions in clean code? – Part 2

In my previous post, I explained the different types of exceptions in Java and left two questions for this post!

Which exception to use?

Java documentation and Uncle Bob’s book (Clean Code) were my references when preparing this tutorial. I noticed a slight difference between what is recommended in each!

Java Documentation

While reading the Java documentation, you sense a preference for Checked Exceptions. The documentation states that the usage of UnChecked Exceptions should be restricted to the case where crashing the system is intentional if an exception occurs. In other cases where recovery is still possible, Checked Exceptions should be used.

Robert Martin’s opinion

Uncle Bob has a different opinion!  He argues that although Checked Exceptions might have some benefits and can be useful in some special cases like writing critical libraries, they are not a necessity to have a robust software.

Breaking the ‘Open/Closed Principle‘ and ‘Encapsulation‘ are the main two reasons that make using Checked Exceptions a bad idea!

Let’s see how!

In the below example, the MainBookReader.main method is calling the method MainBookReader.readJsonObject to get the book’s description from a JSON file. And since readJsonObject throws an exception we had to add throws FileNotFoundException clause to the signature of the main method and the interface JsonLoader!

import java.io.FileNotFoundException;

public class MainBookReader {
    public static void main(String[] args) throws FileNotFoundException {
        BookJsonLoader jsonLoader = new BookJsonLoader();
        Book bookFromJson = jsonLoader.readJsonObject("books.json");
        System.out.println(bookFromJson.name());
    }
}
import com.google.gson.Gson;
import java.io.FileNotFoundException;
import java.io.FileReader;

public class BookJsonLoader implements JsonLoader {
    public Book readJsonObject(String fileName) throws FileNotFoundException {
        FileReader jsonFile = new FileReader(fileName);
        Gson gson = new Gson();
        return transformToBook(gson.fromJson(jsonFile, JsonBook.class));
    }

    private Book transformToBook(JsonBook jsonBook) {
        return new Book(jsonBook.getName());
    }
}
import java.io.FileNotFoundException;

public interface JsonLoader {
    Book readJsonObject(String fileName) throws FileNotFoundException;
}

Adding the throws clause to most of our methods means two things!

  1. Our top methods & classes had to know a lot about the lower methods & classes!
  2. If any change is to be applied to the lower methods, this change has to be replicated to all above methods!

This is a violation of the ‘Open/Closed Principle‘ and ‘Encapsulation‘!

Is there a better way?

No matter how careful we are, things can still go wrong. Thus we still need to deal the exceptions when they occur! The alternative to the code above is writing clean code!

In this section, I will refactor the above code to make the code look better, although it might involve more code!

Wrap the Exception

The first step is to wrap the exception in one place! In our case, we can replace the throws clause with a try-catch!

In the catch clause, I’m throwing a JsonFileNotFoundException (UncheckedException) that I have created based on the needs of this system (i.e. providing an informative message to the user).

public class BookJsonLoader implements JsonLoader {
    public Book readJsonObject(String fileName){
        try {
            FileReader jsonFile = new FileReader(fileName);
            Gson gson = new Gson();
            return transformToBook(gson.fromJson(jsonFile, JsonBook.class));
        } catch (FileNotFoundException e) {
            throw new JsonFileNotFoundException("Can't find the file " + fileName + ". Please make sure it exists!", e);
        }
    }

    private Book transformToBook(JsonBook jsonBook) {
        return new Book(jsonBook.getName());
    }
}

The benefits of this refactoring are:

  • Remove all ‘throws clauses.’
  • Hide the logic and information of our class from the outer classes
  • Send an informative message to the user in case the file was not found
  • Minimize the dependency (remove imports from other classes)

Don’t return null

In some cases, developers don’t want to throw an UnCheckedException, so they tend to return a ‘null’ instead! That is a bad practice because it will eventually result in a NullPointerException later.

So, what to do? The answer is simple, return a SPECIAL CASE!

The below class ‘MissingBook‘ is our special case!

public class MissingBook extends Book {

    private MissingBook(String name) {
        super(name);
    }

    public static MissingBook aMissingBook() {
        return new MissingBook("MISSING BOOK");
    }
}

This allows us to return an instance of the MissingBook in the catch instead of throwing an exception!

public class BookJsonLoader implements JsonLoader {
    public Book readJsonObject(String fileName){
        try {
            FileReader jsonFile = new FileReader(fileName);
            Gson gson = new Gson();
            return transformToBook(gson.fromJson(jsonFile, JsonBook.class));
        } catch (FileNotFoundException e) {
            return MissingBook.aMissingBook();
        }
    }

    private Book transformToBook(JsonBook jsonBook) {
        return new Book(jsonBook.getName());
    }
}

Don’t pass null

In your code, you shouldn’t pass null as parameters and instead use the Special Cases as before. But, it gets harder to prevent your clients from passing nulls thus you might need to assert some function parameters before proceeding with your code execution!

Finally

Doing the refactoring for the simple example above might look like over-engineering! But, it becomes beneficial when writing more complex code!

I think that the excessive usage of CheckedExceptions will pollute the code and thus should be avoided! For me, the benefits of CheckedExceptions can still be achieved through:

  1. Following the rules of Clean Code
  2. Increase test coverage of the code by following the TDD practice! With the use of TDD, all the edge cases should be covered and thus eliminating the possibility of having unexpected exceptions during execution!

To understand better the importance and how to write clean code, it is highly recommended that you read the book Clean Code by Robert Martin!

References:

  1. Clean Code: A Handbook of Agile Software Craftsmanship
  2. Unchecked Exceptions — The Controversy
  3. Java Exceptions
  4. The Clean Code Blog

Newcomers’ Training Program

Recently, I was in charge of training two fresh-graduate newcomers to our department. My mission was to prepare a two weeks program to ease the integration process with their teams.

After a short brainstorming, I decided to break the training into the following seven topics:

  1. Agile Practices: Their first assignment was getting acquainted with the Agile methodologies (mainly XP our working process). For that, I asked them to read a couple of chapters from two books “The Art of Agile Development” and “Extreme Programming Explained.”
  2. Dev Tools: Configuring some dev tools on their machines was the second step. This involved the installation and configuration of Java, IntelliJ, Maven and Perforce. Some of those tools such as Perforce and Maven were relatively new to them; so they took some time to learn more about it.
  3. TDDBy now, they were ready to write some code! And what would be better than following TDD to do that? Most of our teams started adopting TDD, thus coaching newcomers on TDD for simple dev problems is a must! For that purpose I picked the following two problems:
    • Mars Rover: This might be an easy problem, but I find it well suited to practice TDD especially for TDD newbies as it has a lot of cases to be covered by tests.
    • Coffee Machine: The beauty of this problem, is that it simulates what happens in the life cycle of an agile project, such as:
      • Defining new requirements at the start of each iteration
      • Writing the minimum code to implement the required features
      • Continuous code refactoring
      • Write the sufficient tests at each iteration
  4. Design Pattern and Code Refactoring: The two problems above may not be complex and can be solved in a short time, but the solution wasn’t the primary purpose rather it was introducing new concepts and practices to them. To make sure this purpose was achieved, I was performing multiple code review sessions during each iteration and suggesting enhancement at each time. This process elongated the time for each iteration, but it was worth. Some of the concepts I focused on were:
    • Test coverage
    • Builder pattern
    • Visitor pattern
    • Factory pattern
    • Bad and good code practices
    • Mocking
  5. Maven: They used Maven to build the code they wrote previously, but it was only maven’s basic commands. At this phase of the training, I asked them to dig deeper into maven to have a better understanding how it works; mainly focusing on:
    • Phases of build lifecycle
    • Dependency management
    • Plugins
    • Local and remote repositories
  6. SCM: Whether it is Git or Perforce, there are a couple of must know operations for any developer to be part of a development team. As a practice on those operations, they simulated a real dev cycle scenario by:
    • Sharing a common working directory on Perforce
    • Creating branches
    • Merging/Integrating changes
    • Resolving conflicts
  7. Continuous Integration (CI)As fresh graduates, the continuous integration was a new concept for them. Whereas for us, it is an essential process of our development cycle. It wasn’t possible to use an existing Jenkins instance to perform their testing; thus they executed the below steps:
    • Download and configure Jenkins locally on their machines
    • Submit their code to Perforce
    • Add a new job that syncs, compile code and execute the tests

 

I noticed the benefit of this training from the emails they sent me at the end of the program. They detailed what they learned and most importantly they were able to highlight the advantages of those practices and tools.

I hope you find this post helpful for your next newcomers’ training!