Design Pattern (6) Factory Method Pattern Complete Tutorial - Extensible Object Creation
You can download the Design Pattern series code from this design_pattern repo.
Introduction: From Success to Challenge
Imagine your beverage ordering system has become tremendously popular globally. As your business expands, you face a new challenge: how to satisfy the specific preferences of customers in different regions?
In the previous article, we used the Simple Factory Pattern to successfully separate code that needs to change from code that doesn’t need to change. This pattern works well in a single region, but when we want to expand to the global market, we encounter new limitations.
Today, we’ll explore how to use the Factory Method Pattern to further enhance system flexibility and extensibility.
Requirements: Satisfying Global Tastes
The beverage ordering system has been loved by customers and has performed very well, so the client is rapidly expanding stores worldwide. However, problems soon emerge - customers in different regions have vastly different preferences.
Market Research Findings
- US Market: Customers prefer the rich taste of Ceylon black tea
- European Market: Customers favor the elegant aroma of Earl Grey black tea
Business Goals
Our goal is to satisfy these diverse regional requirements without significantly increasing operational costs. Based on cost considerations, we’ve decided that each region will only use tea varieties that best match local taste preferences to make black tea, rather than adding all varieties to the menu.
Object-Oriented Analysis (OOA)
Initial Solution Approach
Facing the demands of globalization, our first thought is to extend the Simple Factory Pattern. Let’s look at the preliminary design:

We modified the Simple Factory code, adding USBeverageFactory
and EUBeverageFactory
to respectively produce beverages that match American and European local tastes. This way, stores in each region can obtain beverages that suit local preferences from their corresponding factories.
-
public class CeylonBlackTea: Beverage { } public class EarlGreyBlackTea: Beverage { } public class GyokuroGreenTea: Beverage { } public class SenchaGreenTea: Beverage { } open class USBeverageFactory { public init() {} func createBeverage(beverageName: String) -> Beverage? { var beverage: Beverage? switch beverageName { case "black tea": beverage = CeylonBlackTea() case "green tea": beverage = GyokuroGreenTea() default: break } return beverage } } open class EUBeverageFactory { public init() {} class func createBeverage(beverageName: String) -> Beverage? { var beverage: Beverage? switch beverageName { case "black tea": beverage = EarlGreyBlackTea() case "green tea": beverage = SenchaGreenTea() default: break } return beverage } }
-
class CeylonBlackTea: Beverage { } class EarlGreyBlackTea: Beverage { } class GyokuroGreenTea: Beverage { } class SenchaGreenTea: Beverage { } class USBeverageFactory { fun createBeverage(beverageName: String): Beverage? { return when (beverageName) { "black tea" -> CeylonBlackTea() "green tea" -> GyokuroGreenTea() else -> null } } } class EUBeverageFactory { fun createBeverage(beverageName: String): Beverage? { return when (beverageName) { "black tea" -> EarlGreyBlackTea() "green tea" -> SenchaGreenTea() else -> null } } }
Recognizing Problems (Forces)
Limitations of the Initial Approach
Although the above approach can satisfy stores obtaining region-specific beverages from different factories, upon deeper analysis, we discover a serious problem:
Extensibility Issue: Every time a new regional store joins (such as Japan or Korea), we must modify the BeverageShop
code to add new store factories. This violates the Open Closed Principle.
Maintenance Costs: As regions increase, the scope of code modifications will grow larger, and maintenance costs will rise accordingly.
We need a more elegant solution that can support expansion to new regions without modifying existing code.
Applying the Factory Method Pattern (Solution)
Pattern Introduction
After clearly understanding the entire problem context and recognizing the problem points (Forces), we can apply the Factory Method Pattern to solve this issue.
Let’s first understand the standard structure of the Factory Method Pattern:

Core Concept: Provide an interface for creating objects, but let subclasses decide the actual instantiation process. This way, we can extend new product types through inheritance without modifying existing code.
Applying to Our Beverage System
Let’s apply the Factory Method Pattern to the beverage system:

Through this design, we get a completely new and more flexible solution (Resulting Context).
Object-Oriented Programming (OOP)
Implementing the Factory Method Pattern
Now let’s convert the design into code implementation. The key change is introducing the BeverageFactory
interface, allowing regional factories to implement this common interface.
-
public protocol BeverageFactory { func createBeverage(beverageName: String) -> Beverage? } open class USBeverageFactory: BeverageFactory { public init() {} public func createBeverage(beverageName: String) -> Beverage? { var beverage: Beverage? switch beverageName { case "black tea": beverage = CeylonBlackTea() case "green tea": beverage = GyokuroGreenTea() default: break } return beverage } } open class EUBeverageFactory: BeverageFactory { public init() {} public func createBeverage(beverageName: String) -> Beverage? { var beverage: Beverage? switch beverageName { case "black tea": beverage = EarlGreyBlackTea() case "green tea": beverage = SenchaGreenTea() default: break } return beverage } } let usBeverageShop = BeverageShop(factory: USBeverageFactory()) let usBlackTea = usBeverageShop.order(beverageName: "black tea") let usGreenTea = usBeverageShop.order(beverageName: "green tea") let euBeverageShop = BeverageShop(factory: EUBeverageFactory()) let euBlackTea = euBeverageShop.order(beverageName: "black tea") let euGreenTea = euBeverageShop.order(beverageName: "green tea")
-
interface BeverageFactory { fun createBeverage(beverageName: String): Beverage? } class USBeverageFactory: BeverageFactory { override fun createBeverage(beverageName: String): Beverage? { return when (beverageName) { "black tea" -> CeylonBlackTea() "green tea" -> GyokuroGreenTea() else -> null } } } class EUBeverageFactory: BeverageFactory { override fun createBeverage(beverageName: String): Beverage? { return when (beverageName) { "black tea" -> EarlGreyBlackTea() "green tea" -> SenchaGreenTea() else -> null } } } val usBeverageShop = BeverageShop(USBeverageFactory()) val usBlackTea = usBeverageShop.order("black tea") val usGreenTea = usBeverageShop.order("green tea") val euBeverageShop = BeverageShop(EUBeverageFactory()) val euBlackTea = euBeverageShop.order("black tea") val euGreenTea = euBeverageShop.order("green tea")
Pattern Advantages Demonstrated
Through the Factory Method Pattern, we successfully achieved true extensibility by abstracting the factory:
Expanding to New Regions Becomes Simple: If we want to expand to Japan stores, we only need to:
- Add a
JPBeverageFactory
that implements theBeverageFactory
interface - Implement beverage creation logic that suits Japanese tastes within it
No Need to Modify Existing Code: Other unchanged code is completely unaffected, perfectly adhering to the Open Closed Principle.
Clear Separation of Responsibilities: Each regional factory is only responsible for that region’s product creation logic, conforming to the Single Responsibility Principle.
Summary
Pattern Value
Through the Factory Method Pattern, we successfully solved the challenge of global expansion. This pattern allows us to flexibly expand product lines to meet the diverse needs of the global market without sacrificing the overall system architecture.
Key Benefits
- Enhanced Maintainability: Logic for each region is independently encapsulated, easy to maintain
- Increased Extensibility: Adding new regions requires no modification of existing code
- Reduced Coupling: Achieve loose coupling through interfaces
- Adheres to Design Principles: Follows multiple important object-oriented design principles
Applied Design Principles
The Factory Method Pattern embodies the following important Design Principles:
- Encapsulate What Varies: Encapsulate changing product creation logic in respective factories
- Loose Coupling: Reduce coupling between components through interfaces
- Program to Interfaces: Depend on abstract interfaces rather than concrete implementations
- Single Responsibility Principle: Each factory is only responsible for specific regional product creation
- Open Closed Principle: Open to extension, closed to modification
- Dependency Inversion Principle: High-level modules don’t depend on low-level modules; both depend on abstractions
Future Outlook
In the next article, we’ll introduce the Abstract Factory Pattern, exploring how to further enhance factory pattern applications when we need to create a series of related products.
References
- Head First Design Patterns
- Design Patterns
- Advanced Design Patterns: Design Principles
- Programming Foundations: Design Patterns
- Design Patterns: Creational
- refactoring
- Waterball Pan - Design Pattern Journey
Note: If you have any suggestions, questions, or different ideas, feel free to leave a comment or send me an email. We can discuss and learn together🙂
Enjoy Reading This Article?
Here are some more articles you might like to read next: