Factory Object or Factory Method?

Lorenzo Panzeri
7 min readDec 10, 2023

--

This article focus more on the “theory” of software development and less on the practical part. I want to start mixing building working things with some technical deep dive on more theoretical topics.

Usually I try to stay away from these matters because the risk of triggering people touching their opinionated convinctions is very high but… who cares? 🤠

We software developers like to answer to questions is a standardized way.

You ask us an opinion on something? You will get an assertive expression and an “It depends”. You ask us if we see a way to improve some messy object creation logic? You will get our best “The Expert look” and an “Just put a factory there and you are good to go”.

Yep but… factory what?

Factory Object Variation (Factory Pattern)

The factory object is a declination of creational pattern that defines a single object that encapsulates the creation of objects. This pattern variation promotes loose coupling and encapsulates the creation logic, decoupling the client from the creation of concrete objects.

  • Encapsulates Object Creation: It defines a single object responsible for creating instances of related classes.
  • Loose Coupling: Decouples the client from the concrete classes, promoting flexibility and maintainability.
  • Centralized Creation Logic: Encapsulates the creation logic within the factory object, simplifying object instantiation.

Use case example:

Consider a manufacturing plant that produces different types of electronic components. A factory object could be implemented to manage the creation of these components. The factory object would have methods for creating specific components, such as resistors, capacitors, and transistors. The client code would simply call the appropriate method to obtain the required component without knowing the underlying implementation details.

Code implementation example:

// Interface for electronic components
interface ElectronicComponent {
void produce();
}

// Concrete implementations of electronic components
class Resistor implements ElectronicComponent {
@Override
public void produce() {
System.out.println("Producing a resistor...");
// Additional resistor-specific implementation
}
}

class Capacitor implements ElectronicComponent {
@Override
public void produce() {
System.out.println("Producing a capacitor...");
// Additional capacitor-specific implementation
}
}

class Transistor implements ElectronicComponent {
@Override
public void produce() {
System.out.println("Producing a transistor...");
// Additional transistor-specific implementation
}
}

// Factory object managing the creation of components
class ComponentFactory {
public ElectronicComponent createResistor() {
return new Resistor();
}

public ElectronicComponent createCapacitor() {
return new Capacitor();
}

public ElectronicComponent createTransistor() {
return new Transistor();
}
}

// Client code to obtain components from the factory
public class ManufacturingPlant {
public static void main(String[] args) {
ComponentFactory factory = new ComponentFactory();

// Obtain different types of components from the factory
ElectronicComponent resistor = factory.createResistor();
ElectronicComponent capacitor = factory.createCapacitor();
ElectronicComponent transistor = factory.createTransistor();

// Produce the components
resistor.produce();
capacitor.produce();
transistor.produce();
}
}

This code implements the Factory Object for creating electronic components. The ElectronicComponent interface represents the common behavior for all electronic components. Concrete implementations (Resistor, Capacitor, Transistor) provide specific functionality for each type of component.

The ComponentFactory class contains methods for creating different components. The client code (ManufacturingPlant class) uses the factory object to obtain specific components without needing to know the internal details of their creation, calling the appropriate factory methods to obtain the desired components and perform operations on them.

Factory Method Variation (Factory Pattern)

The factory method is another declination of creational pattern that defines an interface for creating objects, but lets subclasses decide which class to instantiate. This pattern variation promotes flexibility and facilitates class inheritance, allowing subclasses to define their concrete product classes without modifying the base factory class.

  • Interface for Object Creation: Defines an interface for creating objects, but delegates the actual object creation to subclasses.
  • Subclass Responsibility: Subclasses define the concrete product classes to be created.
  • Abstraction and Flexibility: Encapsulates the object creation logic within subclasses, promoting flexibility and extensibility.

Use case example:

Imagine a bakery that produces different types of cakes: chocolate, vanilla, and red velvet. The factory method pattern could be used to create these cakes. The base factory class would define an interface for creating cakes, while subclasses would implement this interface to create concrete cake objects. The client code would call the factory method, passing any necessary parameters, and receive the appropriate cake object.

Code implementation example:

// Interface for Cake
interface Cake {
void bake();
}

// Concrete implementations of Cake
class ChocolateCake implements Cake {
@Override
public void bake() {
System.out.println("Baking a chocolate cake...");
// Additional chocolate cake specific implementation
}
}

class VanillaCake implements Cake {
@Override
public void bake() {
System.out.println("Baking a vanilla cake...");
// Additional vanilla cake specific implementation
}
}

class RedVelvetCake implements Cake {
@Override
public void bake() {
System.out.println("Baking a red velvet cake...");
// Additional red velvet cake specific implementation
}
}

// Factory interface for creating cakes
interface CakeFactory {
Cake createCake();
}

// Concrete factories implementing the CakeFactory interface
class ChocolateCakeFactory implements CakeFactory {
@Override
public Cake createCake() {
return new ChocolateCake();
}
}

class VanillaCakeFactory implements CakeFactory {
@Override
public Cake createCake() {
return new VanillaCake();
}
}

class RedVelvetCakeFactory implements CakeFactory {
@Override
public Cake createCake() {
return new RedVelvetCake();
}
}

// Client code to demonstrate the Factory Method Pattern
public class Bakery {
public static void main(String[] args) {
// Create factories for each type of cake
CakeFactory chocolateFactory = new ChocolateCakeFactory();
CakeFactory vanillaFactory = new VanillaCakeFactory();
CakeFactory redVelvetFactory = new RedVelvetCakeFactory();

// Create cakes using respective factories
Cake chocolateCake = chocolateFactory.createCake();
Cake vanillaCake = vanillaFactory.createCake();
Cake redVelvetCake = redVelvetFactory.createCake();

// Bake cakes
chocolateCake.bake();
vanillaCake.bake();
redVelvetCake.bake();
}
}

In this implementation, the Cake interface defines the behavior of cakes, while concrete classes (ChocolateCake, VanillaCake, RedVelvetCake) implement this interface with specific functionalities for each type of cake.

The CakeFactory interface declares the factory method createCake(), which is implemented by concrete factory classes (ChocolateCakeFactory, VanillaCakeFactory, RedVelvetCakeFactory). Each factory class is responsible for creating a specific type of cake.

The client code in the Bakery class demonstrates how to use these factories to create different cakes without knowing the specifics of their creation, allowing the client to call the factory method to obtain the desired cake objects.

Conclusion & takeaways

Both the factory object and the factory method variations are valuable tools for creating objects in Java, the choice between them depends on the specific requirements of the application.

The factory object is suitable when there are a limited number of object types and the creation logic is straightforward. The factory method is better suited when there are multiple object types or when the creation logic is more complex and can benefit from subclass specialization.

  • Centralization: The factory object centralizes the creation logic within a single object, while the factory method pattern delegates object creation to subclasses.
  • Subclass Specialization: The factory method allows subclasses to specialize the creation logic by defining their concrete product classes, while the factory object pattern typically creates objects of a single type.
  • Adaptability: The factory method is more adaptable to changes in product types as subclasses can be easily added without modifying the base factory class.

Bonus: Java Functional Interfaces for Factories

Java functional interfaces can be used effectively on the Factory Method or Factory Object in certain scenarios because they offer a more concise way to create objects or define factories, especially when dealing with single-method interfaces.

Let’s try to refactor the code related to the Factory Object:

import java.util.function.Supplier;

// Interface for electronic components
interface ElectronicComponent {
void produce();
}

// Concrete implementations of electronic components
class Resistor implements ElectronicComponent {
@Override
public void produce() {
System.out.println("Producing a resistor...");
// Additional resistor-specific implementation
}
}

class Capacitor implements ElectronicComponent {
@Override
public void produce() {
System.out.println("Producing a capacitor...");
// Additional capacitor-specific implementation
}
}

class Transistor implements ElectronicComponent {
@Override
public void produce() {
System.out.println("Producing a transistor...");
// Additional transistor-specific implementation
}
}

// Factory object managing the creation of components using functional interfaces
class ComponentFactory {
public ElectronicComponent createComponent(Supplier<ElectronicComponent> componentSupplier) {
return componentSupplier.get();
}
}

// Client code to obtain components from the factory
public class ManufacturingPlant {
public static void main(String[] args) {
ComponentFactory factory = new ComponentFactory();

// Obtain different types of components from the factory using functional interfaces
ElectronicComponent resistor = factory.createComponent(Resistor::new);
ElectronicComponent capacitor = factory.createComponent(Capacitor::new);
ElectronicComponent transistor = factory.createComponent(Transistor::new);

// Produce the components
resistor.produce();
capacitor.produce();
transistor.produce();
}
}

Let’s also do the same refactoring on the Factory Method code:

// Interface for Cake
interface Cake {
void bake();
}

// Concrete implementations of Cake
class ChocolateCake implements Cake {
@Override
public void bake() {
System.out.println("Baking a chocolate cake...");
// Additional chocolate cake specific implementation
}
}

class VanillaCake implements Cake {
@Override
public void bake() {
System.out.println("Baking a vanilla cake...");
// Additional vanilla cake specific implementation
}
}

class RedVelvetCake implements Cake {
@Override
public void bake() {
System.out.println("Baking a red velvet cake...");
// Additional red velvet cake specific implementation
}
}

// Functional interface for creating cakes
interface CakeSupplier {
Cake create();
}

// Client code to demonstrate the Factory Method Pattern using functional interfaces
public class Bakery {
public static void main(String[] args) {
// Define suppliers for each type of cake
CakeSupplier chocolateSupplier = ChocolateCake::new;
CakeSupplier vanillaSupplier = VanillaCake::new;
CakeSupplier redVelvetSupplier = RedVelvetCake::new;

// Create cakes using respective suppliers
Cake chocolateCake = chocolateSupplier.create();
Cake vanillaCake = vanillaSupplier.create();
Cake redVelvetCake = redVelvetSupplier.create();

// Bake cakes
chocolateCake.bake();
vanillaCake.bake();
redVelvetCake.bake();
}
}

Limitations & Considerations

While functional interfaces provide a way to create objects or handle factories concisely, they might not fit all scenarios of the Factory Method or Factory Object variations.

In complex scenarios involving multiple methods or dependencies, the traditional Factory Method or Factory Object might still be more appropriate for better organization and manageability.

Refactoring using Functional interfaces suits for simple scenarios where a single method suffices to create an object or to define a factory.

Hope this will be useful: as self-thought developer that usually avoids these detailed dissertions i would gladly read your feedbacks: there is always room for improvements! See you next time! 😊

--

--

Lorenzo Panzeri
Lorenzo Panzeri

Written by Lorenzo Panzeri

Passionate Developer - Compulsive learner - Messy maker

No responses yet