Factory Method Pattern
2024-10-31  / 23 Design Patterns  / 1. Creational Pattern

0. Overview

Factory Pattern is primarily divided into three categories: Simple Factory Pattern, Factory Method Pattern, and Abstract Factory Pattern. All of them are classified as Creational Design Pattern, although the Simple Factory Pattern is not included in the GoF’s 23 design patterns.

Definition of Factory Pattern: It defines a factory interface for creating product objects, delegating the actual creation of these product objects to specific subclasses of the factory. This satisfies the characteristic of “separating creation from usage”, as required by Creational Design Pattern.

1. Simple Factory Pattern

Simple Factory Pattern, also known as the Static Factory Method Pattern, uses a single factory object to determine which type of product instance to create. Essentially, the Simple Factory Pattern relies on a factory class that dynamically decides which product class to instantiate based on the parameters provided (such as product type).

Pros:

  1. A single factory class provides access to all different product types, making the structure straightforward, easy to implement, and easy to understand.
  2. The client does not need to know the specific class name of the product being created. Instead, by simply passing the correct parameter (such as the product type), the client can obtain the desired object without needing to know the details of its creation.

Cons:

  1. The factory class is centralized, with heavy responsibilities; adding new products requires modifying the factory’s logic (making it hard to extend), which violates the Open-Closed Principle.
  2. The factory uses a static method to create products, preventing the formation of an inheritance-based hierarchy for the factory role.

Scenarios:

  1. When the factory class is responsible for creating a limited number of product types.
  2. When the client only needs to pass parameters to the factory class and does not need to know the logic or details of object creation.

1.1. Structure and Implementation

The main roles in the Simple Factory Pattern are as follows:

  • Simple Factory (SimpleFactory): This is the core of the Simple Factory Pattern, responsible for the internal logic to create all products. The factory class provides a public static method for creating products, which can be called directly from outside. This method uses a product type parameter to create the desired product object.
  • Abstract Product (IProduct): This is the parent interface for all product objects created by the simple factory, defining the common interface for all product instances.
  • Concrete Products (Phone, Laptop, Earphone): These are the specific products targeted by the Simple Factory, each implementing the common product interface.

The class diagram is as follows:

1.2. Code Example

Product Interface and Concrete Product Classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface IProduct {
// product description
void desc();
}

public class Phone implements IProduct {
@Override
public void desc() {
System.out.println("I am a phone.");
}
}

public class Laptop implements IProduct {
@Override
public void desc() {
System.out.println("I am a laptop.");
}
}

public class Earphone implements IProduct {
@Override
public void desc() {
System.out.println("I am a earphone.");
}
}

The factory class in the Simple Factory Pattern is responsible for creating various products and providing a unified public product interface for returning them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class SimpleFactory {

public enum PRODUCT_TYPE {
PHONE,
LAPTOP,
EARPHONE
}

/**
* Create a new product object according to the product type
*
* @param productType
* @return
*/
public static IProduct createProduct(PRODUCT_TYPE productType) {
if (null != productType) {
switch (productType) {
case PHONE:
return new Phone();
case LAPTOP:
return new Laptop();
case EARPHONE:
return new Earphone();
default:
break;
}
}
return null;
}
}

Usage of the Simple Factory:

1
2
3
4
5
6
7
8
9
10
11
12
public class SimpleFactoryMain {
public static void main(String[] args) {
IProduct phone = SimpleFactory.createProduct(SimpleFactory.PRODUCT_TYPE.PHONE);
phone.desc();

IProduct laptop = SimpleFactory.createProduct(SimpleFactory.PRODUCT_TYPE.LAPTOP);
laptop.desc();

IProduct earphone = SimpleFactory.createProduct(SimpleFactory.PRODUCT_TYPE.EARPHONE);
earphone.desc();
}
}

Execution Result:

1
2
3
I am a phone.
I am a laptop.
I am a earphone.

2. Factory Method Pattern

Factory Method Pattern, also known as the Polymorphic Factory Pattern, is a creational pattern classified among the three major categories of design patterns. It defines an interface for creating objects, but the subclasses determine which class to instantiate, meaning the Factory Method Pattern defers instantiation to the subclasses.

Compared to 1. Simple Factory Pattern, the core factory class no longer handles the creation of all products; instead, it delegates the actual work (such as creating products) to specific subclasses. The core factory class becomes an abstract factory role, which only defines the public interface that concrete factory subclasses must implement, without handling the specific details of any particular product.

Pros:

  1. Users only need to concern themselves with the factory corresponding to the desired product to obtain the necessary product, without worrying about the specific creation logic of the product.
  2. When new products are added, only the corresponding factory class needs to be added, providing good extensibility and adhering to the Open-Closed Principle.

Cons:

  1. As the number of products increases, it can lead to a proliferation of classes, increasing complexity and making the system harder to understand.
  2. The abstract product can only produce one type of product; this issue can be resolved using the Abstract Factory Pattern.

Scenarios:

  1. The client only knows the factory name corresponding to the product and not the specific product names, such as Phone Factory, Laptop Factory, etc.
  2. The task of creating objects is completed by one of several concrete subclasses, while the abstract factory only provides the interface for creating products.
  3. The client is not concerned with the details of product creation but only cares about the production factory.
  4. When you need a laptop, you can directly obtain it from the factory that produces laptops, without needing to know how the laptop is manufactured or the specific implementation of that laptop.

2.1 Structure and Implementation

The main roles in the Factory Method Pattern are as follows:

  • Abstract Factory (IFactory): Provides the interface for creating products, allowing the caller to access the specific factory’s factory method createProduct() to create products.
  • Concrete Factories (PhoneFactory, LaptopFactory, EarphoneFactory): Implement the methods defined in the abstract factory, completing the creation of specific products.
  • Abstract Product (IProduct): Defines the specifications for the product, describing the main characteristics and functionalities of the product.
  • Concrete Products (Phone, Laptop, Earphone): Implement the interfaces defined by the abstract product role and are created by specific factories, corresponding one-to-one with the concrete factories.

The class diagram is as follows:

2.2 Code Example

Product Interface and Concrete Product Classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface IProduct {
// product description
void desc();
}

public class Phone implements IProduct {
@Override
public void desc() {
System.out.println("I am a phone.");
}
}

public class Laptop implements IProduct {
@Override
public void desc() {
System.out.println("I am a laptop.");
}
}

public class Earphone implements IProduct {
@Override
public void desc() {
System.out.println("I am a earphone.");
}
}

Abstract Factory Interface and Its Concrete Factories:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// abstract factory interface
public interface IFactory {
IProduct createProduct();
}

public class PhoneFactory implements IFactory {
@Override
public IProduct createProduct() {
return new Phone();
}
}

public class LaptopFactory implements IFactory{
@Override
public IProduct createProduct() {
return new Laptop();
}
}

public class EarphoneFactory implements IFactory{
@Override
public IProduct createProduct() {
return new Earphone();
}
}

Usage of the Factory Method Pattern:

1
2
3
4
5
6
7
8
9
10
11
12
public class FactoryMethodMain {
public static void main(String[] args) {
IProduct phone = new PhoneFactory().createProduct();
phone.desc();

IProduct laptop = new LaptopFactory().createProduct();
laptop.desc();

IProduct earphone = new EarphoneFactory().createProduct();
earphone.desc();
}
}

Execution Result:

1
2
3
I am a phone.
I am a laptop.
I am a earphone.

3. Application in Source Code

3.1. JDK-java.util.Calendar (Simple Factory Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// java.util.Calendar
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
public static Calendar getInstance() {
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}

private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
CalendarProvider provider = LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale).getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}

Calendar cal = null;

if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
// Generate different subclass instances of `Calendar` based on different `calType` values.
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
// Decide to generate different subclass instances of `Calendar` based on conditional checks.
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}

// ...
}

In the createCalendar() static method, the Calendar class returns different subclass instances based on conditional checks, involving the following concrete subclasses:

3.2. JDK-java.text.NumberFormat (Simple Factory Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// java.text.NumberFormat
public abstract class NumberFormat extends Format {

// Constants used by factory methods to specify a style of format.
private static final int NUMBERSTYLE = 0;
private static final int CURRENCYSTYLE = 1;
private static final int PERCENTSTYLE = 2;
private static final int SCIENTIFICSTYLE = 3;
private static final int INTEGERSTYLE = 4;

public final static NumberFormat getInstance() {
return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE);
}

private static NumberFormat getInstance(Locale desiredLocale, int choice) {
LocaleProviderAdapter adapter;
adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class, desiredLocale);
NumberFormat numberFormat = getInstance(adapter, desiredLocale, choice);
if (numberFormat == null) {
numberFormat = getInstance(LocaleProviderAdapter.forJRE(), desiredLocale, choice);
}
return numberFormat;
}

// Obtain different `NumberFormat` instances for various purposes based on the `choice`.
private static NumberFormat getInstance(LocaleProviderAdapter adapter, Locale locale, int choice) {
NumberFormatProvider provider = adapter.getNumberFormatProvider();
NumberFormat numberFormat = null;
switch (choice) {
case NUMBERSTYLE:
numberFormat = provider.getNumberInstance(locale);
break;
case PERCENTSTYLE:
numberFormat = provider.getPercentInstance(locale);
break;
case CURRENCYSTYLE:
numberFormat = provider.getCurrencyInstance(locale);
break;
case INTEGERSTYLE:
numberFormat = provider.getIntegerInstance(locale);
break;
}
return numberFormat;
}

// ...
}

The getInstance() static method in NumberFormat generates different subclass instances of NumberFormat based on the provided locale parameter. By default, it calls sun.util.locale.provider.NumberFormatProviderImpl#getInstance to create a new java.text.DecimalFormat instance, applying specific formatting settings as required, such as currency format, percentage format, and others.

3.3. JDK-java.net.URLStreamHandlerFactory (Factory Method Pattern)

The URLStreamHandlerFactory interface (abstract factory) defines a factory for URL stream protocol handlers, serving as a common base for all stream protocol handlers. Stream protocol handlers understand how to establish connections for specific protocol types, such as HTTP or HTTPS.

The URLStreamHandler abstract class acts as an abstract product, where instances of its subclasses (concrete products) are not created directly by the application. Instead, they are managed and created by classes that implement the URLStreamHandlerFactory factory interface (concrete factories).

Abstract Factory and Its Subclasses (Concrete Factories):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// abstract factory
public interface URLStreamHandlerFactory {
/**
* Create a new `URLStreamHandler` instance for a specified protocol.
* @param protocol e.g. "ftp", "http", "nntp"
* @return
*/
URLStreamHandler createURLStreamHandler(String protocol);
}

public class Launcher {
// concrete factory
private static class Factory implements URLStreamHandlerFactory {
private static String PREFIX = "sun.net.www.protocol";

private Factory() {
}

// var1: Create instances based on different protocols for the specified `protocol`.
public URLStreamHandler createURLStreamHandler(String protocol) {
String var2 = PREFIX + "." + protocol + ".Handler";
try {
Class var3 = Class.forName(var2);
return (URLStreamHandler)var3.newInstance();
} catch (ReflectiveOperationException var4) {
throw new InternalError("could not load " + protocol + "system protocol handler", var4);
}
}
}

// ...
}

Abstract Product and Its Subclasses (Concrete Products):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// abstract product
public abstract class URLStreamHandler {
abstract protected URLConnection openConnection(URL u) throws IOException;
// ...
}

// concrete product:e.g. sun.net.www.protocol.http.Handler
public class Handler extends sun.net.www.protocol.http.Handler {
public Handler() {
}

protected URLConnection openConnection(URL var1) throws IOException {
URLConnection var2 = super.openConnection(var1);
URLUtil.setTimeouts(var2);
return var2;
}
}

The specific subclasses of the URLStreamHandler abstract product are shown in the red box in the figure below:
URLStreamHandler_subclasses.png

3.4. slf4j-org.slf4j.ILoggerFactory (Factory Method Pattern)

Abstract Factory and Its Concrete Factories are as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// abstract factory
public interface ILoggerFactory {
// Used for creating `Logger` objects
Logger getLogger(String name);
}

// concrete factory:NOPLoggerFactory
public class NOPLoggerFactory implements ILoggerFactory {
public NOPLoggerFactory() {
}

public Logger getLogger(String name) {
return NOPLogger.NOP_LOGGER;
}
}

// concrete factory: log4j
public class Log4jLoggerFactory implements ILoggerFactory {
public Logger getLogger(String name) {
Logger slf4jLogger = (Logger)this.loggerMap.get(name);
if (slf4jLogger != null) {
return slf4jLogger;
} else {
org.apache.log4j.Logger log4jLogger;
if (name.equalsIgnoreCase("ROOT")) {
log4jLogger = LogManager.getRootLogger();
} else {
log4jLogger = LogManager.getLogger(name);
}

Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
Logger oldInstance = (Logger)this.loggerMap.putIfAbsent(name, newInstance);
return (Logger)(oldInstance == null ? newInstance : oldInstance);
}
}

//...
}

Abstract Product and Its Concrete Products are as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// abstract product
public interface Logger {
}

// concrete product: `NOPLogger`, corresponding to the factory `NOPLoggerFactory`.
public class NOPLogger extends MarkerIgnoringBase {
}

// concrete product: Log4j's `Logger` subclass, corresponding to the factory `Log4jLoggerFactory`.
public final class Log4jLoggerAdapter extends MarkerIgnoringBase implements LocationAwareLogger, Serializable {
}

public abstract class MarkerIgnoringBase extends NamedLoggerBase implements Logger {
}

3.5. JDK-javax.xml.bind.JAXBContext (Factory Method Pattern)

Abstract Factory and Its Concrete Factories are as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// abstract factory
public abstract class JAXBContext {
public abstract Marshaller createMarshaller() throws JAXBException;
// ...
}

// concrete factory
public final class JAXBContextImpl extends JAXBRIContext {
public MarshallerImpl createMarshaller() {
return new MarshallerImpl(this, (AssociationMap)null);
}
// ...
}
public abstract class JAXBRIContext extends JAXBContext {
// ...
}

Abstract Product and Its Concrete Products are as follows:

1
2
3
4
5
6
7
8
9
// abstract product
public interface Marshaller {
}

// Concrete Product, corresponding to the factory method `JAXBContextImpl.createMarshaller()`.
public final class MarshallerImpl extends AbstractMarshallerImpl implements ValidationEventHandler {
}
public abstract class AbstractMarshallerImpl implements Marshaller {
}

3.6. Iterator-java.util.Collection (Factory Method Pattern)

Abstract Factory and Its Concrete Factories are as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// abstract factory
public interface Collection<E> extends Iterable<E> {
// Obtain an iterator instance.
Iterator<E> iterator();
}

// Concrete Factory: `ArrayList` implements the `List` interface and inherits from the `Collection` interface.
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// ...

// Concrete implementation method: Obtain an iterator instance of `Iterator`: `Itr`.
public Iterator<E> iterator() {
return new Itr();
}

// ...
}

// concrete factory:ArrayDeque
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
{
// Concrete implementation method: Obtain an iterator instance of `Iterator`: `DeqIterator`.
public Iterator<E> iterator() {
return new DeqIterator();
}
}

Abstract Product and Its Concrete Products are as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// abstract product
public interface Iterator<E> {

boolean hasNext();

E next();

// ...
}

// Concrete Product `Itr`: A private inner class of `ArrayList`.
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// ...

// concrete product
private class Itr implements Iterator<E> {
Itr() {}

public boolean hasNext() {
return cursor != size;
}

public E next() {
// ...
}

// ...
}

// ...
}

// Concrete Product `ArrayDeque`: A private inner class of `ArrayDeque`.
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable
{
// ...

// concrete product
private class DeqIterator implements Iterator<E> {
Itr() {}

public boolean hasNext() {
return cursor != size;
}

public E next() {
// ...
}

// ...
}

// ...
}

References