Creational Design Patterns: Factory Method Pattern#2

Tisha J.
8 min readJan 6, 2023

--

Hi again, following this post, let’s get rolling with the first category of GOF design pattern for software design and construction, the creational software design patterns. In this post, I will walk you through one of the common creational design patterns, Factory method pattern, along with code and UML.

credit: refactoringuru

Any design pattern has a unique name, the common kind of problem it solves, the definition of the solution and the consequences of its applications, that is the trade-offs or advantages and disadvantages a developer can perceive by its application. We will go through each of these sections, along with its implementation, yayyy🎉

  1. Creational Design Pattern

Creational design patterns are meant to solve any design smells or problems with bad code blocks that might arise while creation of classes or objects, by giving us extensible patterns to deal with the object or class creation process, and thus, increasing the flexibility and reusability of existing code. In other words, they are meant to abstract the instantiation process and thus make the system independent of how its objects are created, composed and represented. Class creational pattern uses inheritance to vary the class that’s instantiated and object creational pattern delegates instantiation to another object. It thus, provides flexibilty to what is created, who creates it, when and how it is created! Sounds exciting, yeah?

We have 5 creational design patterns out of the total 23 defined by the Gang of Four. Namely, Factory method, Abstract factory, Builder, Prototype and Singleton design pattern. Out of which Factory method has class-level creational scope while the rest has object-level creational scope. In simple terms, the Factory method pattern deals with the creation of classes in the OOP paradigm while the rest attempts to give a mechanism for object creation.

2. Factory Method Pattern

Let’s visualise a software development use case, say we have an application that, as of now, lets users write stories and letters. Now, we can have classes defined as below to handle this job of creating letters or story objects based on the type the user wants to create.

// interface to define a common interface for both story and letter type objects
public interface Demo{
public String toString();
}

// DemoLetter class implements Demo interface and represents Letter type object
public class DemoLetter implements Demo {
private String subject;
private String date;
private String body;
private String receiver;
private String sender;

public DemoLetter(String subject, String date, String body, String receiver, String sender){
this.subject = subject;
this.date = date;
this.body = body;
this.receiver = receiver;
this.sender = sender;
}

// .. getters and setters

public String toString(){
return "The Letter is written by "+ sender +" for "+ receiver +" on "+ date;
}

}

// DemoStory class implements Demo interface and represents Story type object
public class DemoStory implements Demo {
private String author;
private String dateReleased;
private String body;
private String title;

public DemoStory(String author, String dateReleased, String body, String title){
this.author = author;
this.title = title;
this.dateReleased = dateReleased;
this.body = body;
}

// .. getters and setters

public String toString(){
return "The Story "+ title +", written by "+ author +" has released on "+ dateReleased;
}
}

// client-side app class
import java.util.HashMap;

public class App {
// write story/letter handler
public static Demo handleWrite(String type, HashMap<String, String> data){
Demo object = null;
if(type.equals("letter")){
object = new DemoLetter(data.get("subject"), data.get("date"), data.get("body"), data.get("receiver"), data.get("sender"));;
}else if(type.equals("story")){
object = new DemoStory(data.get("author"), data.get("dateReleased"), data.get("body"), data.get("title"));
}

return object;
}

public static void main(String[] args){
// let's say we capture user input as a HashMap as follows
HashMap<String, String> dataForLetter = new HashMap<>();
dataForLetter.put("subject", "Factory method!");
dataForLetter.put("date", "06/01/23");
dataForLetter.put("body", "Define an interface for creating an object, but let sub-class decide which class to instantiate (class instantiation deferred to subclasses)");
dataForLetter.put("sender", "Tom");
dataForLetter.put("receiver", "Jessica");


DemoLetter letter = (DemoLetter) handleWrite("letter", dataForLetter);
System.out.println(letter);


// similarly for Story
HashMap<String, String> dataForStory = new HashMap<>();
dataForStory.put("title", "Factory method!");
dataForStory.put("dateReleased", "06/01/23");
dataForStory.put("body", "Define an interface for creating an object, but let sub-class decide which class to instantiate (class instantiation deferred to subclasses)");
dataForStory.put("author", "Tom");



DemoStory story = (DemoStory) handleWrite("story", dataForStory);
System.out.println(story);
}
}

Note: Each of the public class and interface are in seperate files.

When you compile the files and run the App class, the output will look like,

The Letter is written by Tom for Jessica on 06/01/23
The Story Factory method!, written by Tom has released on 06/01/23

To elaborate, the handleWrite method creates a DemoLetter or DemoStory type object based on the type “letter” or “story” specified. Well, yeah, logically it works, but what happens if we are to now add a new feature of writing a notice! Well, clearly, we’ll need to modify the existing code block by appending conditionals for “notice” type in the if-else branch of handleWrite method inside App class. Think about real-world applications having multiple features of writing blogs, articles, anecdotes etc, along with these, each having different properties. Well, in such a case the if-else branch will grow and make the code base highly complicated, unreadable, rigid and difficult to extend.

Here’s where the factory method pattern comes to our rescue! Let’s first understand its purpose and structure.

2.a. Definition, Purpose/Intent

Definition: It’s very commonly defined as “declares an interface for creating an object, but let sub-class decide which class to instantiate (class instantiation deferred to subclasses)”

Purpose: Just breaking the aforesaid fact down, factory method pattern

  • Defines an interface for creating set of related objects
  • Let subclasses decide which class to instantiate
  • Let a class defer instantiation to subclasses

It is also known as Virtual Constructor.

2.b. Structure[UML] and Participants

We often define some standard set of participants or components or candidates when talking about design patterns. Here, the candidates of factory method pattern are-

  • Product — Defines the interface of objects the factory method creates
  • ConcreteProduct — Implements the Product interface
  • Creator — Declares the factory method, which returns an object of type Product.
  • ConcreteCreator — Overrides the factory method to return an instance of the ConcreteProduct

Now, refer to the UML diagram below to understand the relationship between different participants of factory method.

UML

Note: The Product and Creator interfaces can also be abstract classes. Then, the realization relation will be replaced by inheritence. And, there can be several ConcreteProduct and ConcreteProduct that can be extended from the abstract Product and Creator.

Now, let’s look at the code while applying this design pattern and refactor the above codebase!

Inside Demo.java: —

public interface Demo{
public String toString();
}

Inside DemoLetter.java: —

public class DemoLetter implements Demo {
private String subject;
private String date;
private String body;
private String receiver;
private String sender;

public DemoLetter(String subject, String date, String body, String receiver, String sender){
this.subject = subject;
this.date = date;
this.body = body;
this.receiver = receiver;
this.sender = sender;
}

// .. getters and setters

public String toString(){
return "The Letter is written by "+ sender +" for "+ receiver +" on "+ date;
}

}

Inside DemoStory.java: —

public class DemoStory implements Demo {
private String author;
private String dateReleased;
private String body;
private String title;

public DemoStory(String author, String dateReleased, String body, String title){
this.author = author;
this.title = title;
this.dateReleased = dateReleased;
this.body = body;
}

// .. getters and setters

public String toString(){
return "The Story "+ title +", written by "+ author +" has released on "+ dateReleased;
}
}

Inside Writer.java: —

import java.util.HashMap;

public interface Writer{
public Demo createDemo(HashMap<String, String> data);
}

Inside LetterWriter.java: —

import java.util.HashMap;

public class LetterWriter implements Writer{
// String subject, String date, String body, String receiver, String sender
public DemoLetter createDemo(HashMap<String, String> data){
return new DemoLetter(data.get("subject"), data.get("date"), data.get("body"), data.get("receiver"), data.get("sender"));
}
}

Inside StoryWriter.java: —

import java.util.HashMap;

public class StoryWriter implements Writer{
// String author, String dateReleased, String body, String title

public DemoStory createDemo(HashMap<String, String> data){
return new DemoStory(data.get("author"), data.get("dateReleased"), data.get("body"), data.get("title"));
}
}

Inside App.java: —

import java.util.HashMap;

public class App {

// let's define some click handlers for submit letter post
public static DemoLetter handleWriteLetter(HashMap<String, String> dataForLetter){
LetterWriter lw = new LetterWriter();
return lw.createDemo(dataForLetter);
}

// let's define some click handlers for submit story post
public static DemoStory handleWriteStory(HashMap<String, String> dataForStory){
StoryWriter sw = new StoryWriter();
return sw.createDemo(dataForStory);
}

public static void main(String[] args){
// let's say we capture user input as a HashMap as follows
HashMap<String, String> dataForLetter = new HashMap<>();
dataForLetter.put("subject", "Factory method!");
dataForLetter.put("date", "06/01/23");
dataForLetter.put("body", "Define an interface for creating an object, but let sub-class decide which class to instantiate (class instantiation deferred to subclasses)");
dataForLetter.put("sender", "Tom");
dataForLetter.put("receiver", "Jessica");


DemoLetter letter = handleWriteLetter(dataForLetter);
System.out.println(letter);


// similarly for Story
HashMap<String, String> dataForStory = new HashMap<>();
dataForStory.put("title", "Factory method!");
dataForStory.put("dateReleased", "06/01/23");
dataForStory.put("body", "Define an interface for creating an object, but let sub-class decide which class to instantiate (class instantiation deferred to subclasses)");
dataForStory.put("author", "Tom");



DemoStory story = handleWriteStory(dataForStory);
System.out.println(story);
}
}

The above codeblock of compiling and running gives the same output as above. But, do note the cleaner and extensible code this time. Can you map these classes to the candidates of the factory method pattern listed above?

Let me clarify. Firstly, the logic flow is like, when we invoke the handleWriteLetter and handleWriteStory method inside the main class with the data (hashmaps), they initialises the required ConcreteCreators and calls the createDemo method on them, which returns the required concrete product type, i.e DemoLetter or DemoStory.

Thus, the Demo interface maps to the Product interface. DemoLetter or DemoStory maps to concreteProducts. While Writer maps to Creator candidate, LetterWriter and StoryWriter maps to concreteCreator candidate. I hope that it’s intuitive.

2.c. Applicability

  • When a class cannot anticipate the other class’s objects it must create. And thus, class wants its subclasses to specify the objects it creates. Say, when the object we are meant to create depends on the user clicking a button, for example, say the user clicks “Write a story”, then we need to create a story object.
  • Classes delegate responsibility to one of several helper subclasses, and you want to localise the knowledge of which helper subclass is the delegate. As you see in the UML, the abstraction Creator interface delegates the responsibility of creating the concrete products to the ConcreteCreator(s).

2.d. Benefits and Limitations

  • Benefits: It makes the code base flexible as subclasses get a hook for providing an extension to an object and help connect parallel class hierarchies. It also helps us get rid of rigid code and poor coding styles like to make if-else branches!
  • Limitations: It can require subclassing just to get an implementation, and it’s just an implementation overhead as the application gets larger and larger!

If you find similar use cases as discussed. It is worth considering a refactor of your codebase to make things more maintainable over time.

Hope you had fun time reading this. Feel free to share your ideas or reach out 🙌 at linkedIn, github. And please do subscribe and follow me here and share this or buy me a coffee ❤. Till then happy refactoring, Cheers!

--

--

Tisha J.
0 Followers

I am an experienced programmer, with enormous curiosity❤ for machine learning, DSA; and an open-source enthusiast