Abstract Factory Design Pattern

An Abstract Factory provides a set of Factories at one place
because we love to use their products together!

An Abstract Factory lets us group a related set of product families(or factories) based on our usage pattern.

It may directly instantiate the products or may source them from their respective product factories. And, like a factory, it exposes abstract products to its clients, so that they can replace them with appropriate concrete versions at runtime.

Whereas a factory creates products belonging to single family; an abstract factory provide a meaningful sets of these products from different families from a single interface.

Examples in Brief

Let us look at couple of examples in brief, in order to understand the grouping better. Later we will discuss on these in more detail for clarifying it further.

Example 1 : Lets say we have loan processing application where each loan type has its own processor, pre-processor, validator etc.

So, ideally we will have separate factories for these components. But, since we use them together, how about providing a single interface to our clients for creating these components ? This is where the abstract factory comes in handy.

Example 2 : Lets say we are creating a PC health analyzer module for multiple platforms. Even though we need platform specific analyzers, installation on any platform needs access to only its own set of analyzers. So, how can we group these platform specific analyzers and filter out the unnecessary ones ?

 

How does the pattern work?

In this part we will restrict our discussions only to the above two examples. But, we will analyze them with diagrams and code samples, so as to understand how the pattern works and its benefits.

Example -1

Lets say we have a loan processing application where we have many similar components like a processor, pre-processor, validator for each of the loan types. Hence, instead of getting these components from so many factories, we may need a single interface that provides each of these required components.

Abstract Factory Design Pattern

As in option-1, we can build an abstract factory that composes all the abstract version of our product set. Then, build loan specific concrete factories that returns the concrete products, specific to the loan type. For example, the ‘HomeLoanFactory’ will return the processors and the validators specific to the home loan as shown.

But, we have so many loan types and many more are going to be added. Hence, the challenge is that we will end up with lot many concrete factories. Therefore, to retain the benefits without the challenge, we can go with option-2.

In option-2, we have simply added the loan type as a parameter to the factory methods, to avoid creating multiple classes. But, the underlying concept of grouping products from different factories for our business cases remains the same.

Having seen this, we will see when will the option-1 be useful in our other example. But, before that here are the code for this demo example.

The first set shows how our abstract factory, UnifiedLoanFactory works and makes use of the other factories. And, the second set lists the related abstract and the concrete products.

package spectutz.dp.creational.absfactory.loan;

import spectutz.dp.creational.absfactory.loan.processor.ILoanProcessor;
import spectutz.dp.creational.absfactory.loan.processor.LoanProcessorFactory;
import spectutz.dp.creational.absfactory.loan.validator.ILoanValidator;
import spectutz.dp.creational.absfactory.loan.validator.LoanValidatorFactory;
import spectutz.dp.creational.factory.loan.vo.LoanType;

public class UnifiedLoanFactory{
	private static UnifiedLoanFactory unifiedLoanFactory = new UnifiedLoanFactory();
	
	private UnifiedLoanFactory() {}
	public static UnifiedLoanFactory getInstance() {
		return unifiedLoanFactory;
	}
	
	// *** Abstract Factory : Uses the respective factories to retrieve the products! ***//
	
	public ILoanProcessor getLoanProcessor(LoanType loanType){	
		return LoanProcessorFactory.getInstance().getLoanProcessor(loanType);
	}
	public ILoanValidator getLoanValidator(LoanType loanType){	
		return LoanValidatorFactory.getInstance().getLoanValidator(loanType);
	}
}
package spectutz.dp.creational.absfactory.loan.validator;

import java.util.HashMap;
import java.util.Map;

import spectutz.dp.creational.absfactory.loan.vo.LoanType;

public class LoanValidatorFactory {
	private static LoanValidatorFactory instance = new LoanValidatorFactory();
	private static Map<LoanType, ILoanValidator> registry;
	
	static {
		registry = new HashMap<LoanType, ILoanValidator>();
		registry.put(LoanType.HOME_LOAN, new HomeLoanValidator());
		registry.put(LoanType.PERSONAL_LOAN, new PersonalLoanValidator());
		registry.put(LoanType.CAR_LOAN, new CarLoanValidator());
		//For any new loan processor - Add them to this registry !
	}
	
	public ILoanValidator getLoanValidator(LoanType loanType) {//throws Exception{		
		return registry.get(loanType);		
	}
	
	public static LoanValidatorFactory getInstance() {
		return instance;
	}
}
package spectutz.dp.creational.absfactory.loan.processor;

import java.util.HashMap;
import java.util.Map;

import spectutz.dp.creational.absfactory.loan.vo.LoanType;

public class LoanProcessorFactory {
	private static LoanProcessorFactory instance = new LoanProcessorFactory();
	private static Map<LoanType, ILoanProcessor> registry;
	
	static {
		registry = new HashMap<LoanType, ILoanProcessor>();
		registry.put(LoanType.HOME_LOAN, new HomeLoanProcessor());
		registry.put(LoanType.PERSONAL_LOAN, new PersonalLoanProcessor());
		registry.put(LoanType.CAR_LOAN, new CarLoanProcessor());
		//For any new loan processor - Add them to this registry !
	}
	
	public ILoanProcessor getLoanProcessor(LoanType loanType) {		
		return registry.get(loanType);		
	}
	
	public static LoanProcessorFactory getInstance() {
		return instance;
	}
}
package spectutz.dp.creational.absfactory.loan;

import spectutz.dp.creational.absfactory.loan.processor.ILoanProcessor;
import spectutz.dp.creational.absfactory.loan.processor.LoanProcessorFactory;
import spectutz.dp.creational.absfactory.loan.validator.ILoanValidator;
import spectutz.dp.creational.absfactory.loan.validator.LoanValidatorFactory;
import spectutz.dp.creational.absfactory.loan.vo.LoanApplication;
import spectutz.dp.creational.absfactory.loan.vo.LoanType;

public class WithAndWithoutAbstractFactoryDemo {
	
	public static void main(String[] args) {
		LoanApplication loanApplication = new LoanApplication();
		loanApplication.setLoanType(LoanType.PERSONAL_LOAN);
		
		WithAndWithoutAbstractFactoryDemo demoClient = new WithAndWithoutAbstractFactoryDemo();
		demoClient.processLoanWithAbstractFactory(loanApplication);
		demoClient.processLoanWithoutAbstractFactory(loanApplication);
		
	}
	
    public void processLoanWithAbstractFactory(LoanApplication loanApplication) {
    	LoanType loanType = loanApplication.getLoanType();

    	//Get all products from a single interface..
    	UnifiedLoanFactory unifiedLoanFactory = UnifiedLoanFactory.getInstance();    	
    	ILoanValidator loanValidator = unifiedLoanFactory.getLoanValidator(loanType);
    	ILoanProcessor loanProcessor = unifiedLoanFactory.getLoanProcessor(loanType);
    	
    	if(loanValidator.validate(loanApplication)) {
    		loanProcessor.process(loanApplication);
    	}else {
    		//throw exception with validation errors
    	}
    }
    
    public void processLoanWithoutAbstractFactory(LoanApplication loanApplication) {
    	System.out.println("\n Start : processLoanWithFactory.....");
    	LoanType loanType = loanApplication.getLoanType();
    	
    	//Get the  products from its respective factories..
    	ILoanValidator loanValidator = LoanValidatorFactory.getInstance()
    									.getLoanValidator(loanType);
    	ILoanProcessor loanProcessor = LoanProcessorFactory.getInstance()
    									.getLoanProcessor(loanType);
    	
    	if(loanValidator.validate(loanApplication)) {
    		loanProcessor.process(loanApplication);
    	}else {
    		//throw exception with validation errors
    	}
    }
}

The code for the abstract and concrete products in the demo for our reference.

package spectutz.dp.creational.absfactory.loan.validator;

import spectutz.dp.creational.absfactory.loan.vo.LoanApplication;

public interface ILoanValidator {
	
	public boolean validate(LoanApplication loanApplication);

}
package spectutz.dp.creational.absfactory.loan.validator;

import spectutz.dp.creational.absfactory.loan.vo.LoanApplication;

public class HomeLoanValidator implements ILoanValidator{

	@Override
	public boolean validate(LoanApplication loanApplication) {
		System.out.println("Validate the home loan");
		return true; //validated successfully
	}
	
}
package spectutz.dp.creational.absfactory.loan.validator;

import spectutz.dp.creational.absfactory.loan.vo.LoanApplication;

public class PersonalLoanValidator implements ILoanValidator{

	@Override
	public boolean validate(LoanApplication loanApplication) {
		System.out.println("Validate personal loan");
		return true; //validated successfully
	}
}
package spectutz.dp.creational.absfactory.loan.validator;

import spectutz.dp.creational.absfactory.loan.vo.LoanApplication;

public class CarLoanValidator implements ILoanValidator{

	@Override
	public boolean validate(LoanApplication loanApplication) {
		System.out.println("Validate the car loan");
		return true; //validated successfully
	}

}
package spectutz.dp.creational.absfactory.loan.processor;

import spectutz.dp.creational.absfactory.loan.vo.LoanApplication;

public interface ILoanProcessor {
	
	public void process(LoanApplication loanApplication);

}
package spectutz.dp.creational.absfactory.loan.processor;

import spectutz.dp.creational.absfactory.loan.vo.LoanApplication;

public class HomeLoanProcessor implements ILoanProcessor{

	@Override
	public void process(LoanApplication loanApplication) {
		System.out.println("Process the home loan");		
	}
	
}
package spectutz.dp.creational.absfactory.loan.processor;

import spectutz.dp.creational.absfactory.loan.vo.LoanApplication;

public class PersonalLoanProcessor implements ILoanProcessor{

	@Override
	public void process(LoanApplication loanApplication) {
		System.out.println("Process the personal loan");		
	}
}
package spectutz.dp.creational.absfactory.loan.processor;

import spectutz.dp.creational.absfactory.loan.vo.LoanApplication;

public class CarLoanProcessor implements ILoanProcessor{

	@Override
	public void process(LoanApplication loanApplication) {
		System.out.println("Process the car loan");
		
	}

}
package spectutz.dp.creational.absfactory.loan.vo;


public enum LoanType {
   HOME_LOAN, PERSONAL_LOAN, CAR_LOAN   
}
package spectutz.dp.creational.absfactory.loan.vo;

public class LoanApplication {
	LoanType loanType;

	public LoanType getLoanType() {
		return loanType;
	}

	public void setLoanType(LoanType loanType) {
		this.loanType = loanType;
	}
}
Example -2

Lets say we are developing a PC health analyzer module supporting multiple platforms.

Since each platform provides its own APIs to retrieve its component information, we need platform specific analyzers as shown below.

Abstract Factory – Platform Specific Component Sets

However, when we install this module, we only need the analyzers specific to that platform. Hence, creating a generic implementation, as in option-2 in example-1, does not make sense.

That’s why the option-1 is better as it provides only the platform specific products. Additionally, it also does not provide access to wrongfully use the analyzers meant for other platforms. For example, on a Mac system we will use ‘MacHealthAnalyzer’. And, as shown in the diagram, it will include the set of analyzers specific to Mac alone.

Moreover, since we are analyzing similar components, the ‘AbstractHealthAnalyzer’ standardizes the analyzer set for all platforms.

Conclusion

An abstract factory is an useful pattern to group the products together based on our usage pattern. Like factories, it keeps the client loosely coupled with the products it uses.

When our use case involves multiple factories, an abstract factory can consolidate them into a single interface.

Moreover, like in example-2 above, it can also help us build platform or product variant specific, component sets.