Factory Method – Separation of Concerns

Mar 12, 2020 | , , , , | Programming | 0 comments

The Factory Method Design pattern is one the GOF creational design patterns. If I was to summarize what this is in as simple terms as possible, the Factory Pattern is essentially a wrapper that provides a consistent means for instantiating a particular type of object and its subtypes. The Factory Pattern can be broken down into 3 variations, simple, method and abstract. This post will cover the method variation of the factory design pattern.

Pattern Overview

I’ll be using Java to demonstrate what this pattern looks like in code. In its simplest form, the factory pattern is essentially a wrapper around the new keyword. The next logical question then is why would you ever want to wrap the instantiation of an object, just instantiate it. This is a great question and there are a number of reasons that can be given in response.

First, when considering dependency injection, at the time you expect to call a method, you may not know which object you’ll wan’t to use within the method. Rather than instantiating it in the method implementation you could use a Factory to abstract the instantiation away from the method.

Second, sometimes the instantiate of an object is complex. Using a Factory encapsulates that complexity so that it can be reused and improves the readability and maintenance of your code.

Third, polymorphism means you can swap factory types which makes your solution more extensible.

Factory Pattern Composition

The following URL Diagram represents the various parts that compose an implementation of the factory pattern.

factory method uml diagram

Product – This is an abstraction of the types of objects that the factory will create.

ConcreteProduct – These are the specific types that will actually be created by the factory.

Creator – This is the abstraction of the factory that will create the ConcreteProduct objects.

ConcreteCreator – These are the specific factory objects that will be used to create the ConcreteProduct objects.

Pet Shop Services Example

I’ve chosen to use the example of the services offered by a pet shop to show one way that the Factory Method pattern can be implemented. In the case of this example the logic for determining which Service to create is held with the ServiceFactory class. An alternative implementation would be to write multiple ServiceFactory classes that implement the ServiceFactory interface but for brevity I chose the following design and implementation. Here is the UML for the example:

factory method example uml diagram

In the case of the pet shop services example we will be looking at the create invoice use case in which pet shop services are specified and added to an invoice that eventually gets printed once we are finished adding services. The following list is an explanation of each class in the example.

Invoice – This is the controller class that orchestrates the collection of input and printing the invoice.

AbstractService – This is the product class of the pattern for which all petshop services will extend and implement the calculateCost() method.

WashPetService/GroomPetService/WalkPetService – These are all concrete implementations of the AbstractService each of which could have its own implementation of the calculateCost method.

ServiceFactory – This is the Factory interface which the concrete factory classes will implement the createService() method that contains the logic for determining how an instance of an AbstractService should be created and returned.

PetServiceFactory – This is the concrete implementation of the ServiceFactory and contains the createService() method implementation which determines which Pet Shop Service should be created based on the user input.

Controller – Invoice.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
public class Invoice {
	private static ArrayList<String> supportedPetServices = new ArrayList<String>(
			Arrays.asList("wash", "walk", "groom"));
	private static DecimalFormat df = new DecimalFormat("#0.00");
	public static void main(String[] args) {
		ServiceFactory aServiceFactory = new PetServiceFactory();
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String inputString = "";
		ArrayList<AbstractService> lineItems = new ArrayList<AbstractService>();
		try {
			do {
				System.out.println("Add a line item? (y/n)");
				inputString = br.readLine();
				if (inputString.equals("y")) {
					String petServiceType = handlePetServiceTypeInput(br);
					int hours = handleHoursInput(br);
					AbstractService aService = aServiceFactory.createService(petServiceType, hours);
					if (aService != null) {
						lineItems.add(aService);
					}
				}
			} while (inputString != null && (!inputString.equalsIgnoreCase("n")));
		} catch (IOException ioe) {
			System.out.println(ioe.getMessage());
		}
		printInvoice(lineItems);
	}
	private static void printInvoice(ArrayList<AbstractService> lineItems) {
		System.out.println();
		System.out.println("*****************************************************");
		System.out.println("*                     INVOICE                       *");
		System.out.println("*****************************************************");
		System.out.println();
		double[] totalCost = { 0.00 };
		lineItems.forEach(aLineItem -> {
			double lineItemCost = aLineItem.calculateCost();
			totalCost[0] += lineItemCost;
			System.out.println("    " + aLineItem.getType() + "  PRICE: $" + df.format(lineItemCost) + "  RATE: $"
					+ df.format(aLineItem.getRate()) + "  HOURS: " + aLineItem.getHours());
		});
		System.out.println("    _____________________________________________");
		System.out.println("    TOTAL: $" + df.format(totalCost[0]));
		System.out.println();
		System.out.println("*****************************************************");
	}
	private static String handlePetServiceTypeInput(BufferedReader br) throws IOException {
		System.out.println("Service Type (wash, walk, groom)");
		String petServiceType = br.readLine();
		if (supportedPetServices.contains(petServiceType.toLowerCase())) {
			return petServiceType;
		} else {
			System.out.println(petServiceType
					+ " is not a supported Pet Service, please use one of the following values; wash, walk or groom.");
			return handlePetServiceTypeInput(br);
		}
	}
	private static int handleHoursInput(BufferedReader br) throws IOException {
		try {
			System.out.println("Number of service hours");
			String inputString = br.readLine();
			return Integer.parseInt(inputString);
		} catch (NumberFormatException nfe) {
			System.out.println("Invalid Value for hours, please retry.");
			return handleHoursInput(br);
		}
	}
}

This is by far the most complex class in the example but when it’s distilled down all this is doing is handling the user input and for each service that the user wants to add, the Factory is used to create the service and store it in a list of invoice line items which are then printed after the user finishes adding services. The key thing to note is that this class has no idea what the different types of services are nor what kind of factory is used to instantiate them because this is all encapsulated in the Factory.

Product – AbstractService.java

public abstract class AbstractService {
	protected int hours;
	protected double rate;
	protected String type;
	abstract double calculateCost();
	public AbstractService(int hours, double rate, String type) {
		super();
		this.hours = hours;
		this.rate = rate;
		this.type = type;
	}
	public String getType() {
		return this.type;
	}
	public int getHours() {
		return this.hours;
	}
	public double getRate() {
		return this.rate;
	}
}

This is an abstract class that contains the key properties of a service that are needed to calculate the cost. The formula use to determine the cost is left to the subtypes of this class.

The following three classes are the subtype implementations of the AbstractService.java class. Notice that there is a slightly different formula used to calculate grooming costs that takes into consideration the basic cost of the products used to groom the animal. Also notice that each subclass knows what its individual rate is and uses this in calculating the overall cost.

Concrete Product – WashPetService.java

public class WashPetService extends AbstractService {
	public WashPetService(int hours) {
		super(hours, 5.00, "Wash ");
	}
	@Override
	double calculateCost() {
		return this.hours * this.rate;
	}
}

Concrete Product – GroomPetService.java

public class GroomPetService extends AbstractService {
	private double productsUsedCost = 10.00;
	public GroomPetService(int hours) {
		super(hours, 15.75, "Groom");
	}
	@Override
	double calculateCost() {
		return (this.hours * this.rate) + this.productsUsedCost;
	}
}

Concrete Product – WalkPetService.java

public class WalkPetService extends AbstractService {
	public WalkPetService(int hours) {
		super(hours, 2.50, "Walk ");
	}
	@Override
	double calculateCost() {
		return this.hours * this.rate;
	}
}

Creator – ServiceFactory.java

public interface ServiceFactory {
	public AbstractService createService(String serviceType, int hours);
}

Here we see the interface for the factory classes. This may seem like overkill with only one factory implementing it but consider the scenario where you might have a number of different factory classes that are being used. This interface makes it possible to leverage polymorphism to decouple the creation of these objects from the Invoice.java if that becomes necessary.

Concrete Creator – PetServiceFactory.java

public class PetServiceFactory implements ServiceFactory {
	public AbstractService createService(String serviceType, int hours) {
		AbstractService aPetService = null;
		switch (serviceType.toLowerCase()) {
		case "wash":
			aPetService = new WashPetService(hours);
			break;
		case "walk":
			aPetService = new WalkPetService(hours);
			break;
		case "groom":
			aPetService = new GroomPetService(hours);
			break;
		default:
			System.out.println("ERROR: " + serviceType + "is not a supported type of Pet Service.");
			break;
		}
		return aPetService;
	}
}

Here we have the implementation of the concrete creator and it’s creation method. In this case a simple switch/case statement is used to determine what type of Service to return. It is in this class alone that the logic for Service class creation is encapsulated decoupling it from the Invoice controller.

The output of this example is as follows:

factory method output

Conclusion

The Factory Method pattern is a very useful tool that can be used to decouple the logic required to create logic from the rest of the solution. This is one way of implementing the Single Responsibility Principle of the SOLID best practices for writing software. Another pattern that is closely related to the Factory Method pattern is the Abstract Factory pattern for which I plan to write a future post. I have posted my code on GitHub in the following repository (https://github.com/bradnjones/FactoryMethodDesignPatternPost). Feel free to clone this and play around with the code yourself as this is a great way to learn more about how the code works.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *