This pattern is a Creational Design Pattern that provides an interface or abstract for creating the type of object in a superclass but allows the subclass to change the type of object that will be created.
It suggests that developers should replace object creation using
the constructor
or using the new
operator. This sounds strange but an object will be created using only the constructor
that is called inside a factory method.
This may look complex at first glance but believe me, it promotes loose coupling and code reuse.
Let's understand with an Example
Problem Statement
Imagine you want to add different logging behavior for different environments of applications i.e Production
, Development
, Test
, and Debug
.
- In
Production
: Onlyloglevel=error|warn
is displayed. - In
Development
: Allloglevel=info|error|warn|debug
is displayed. - In
Debug
: Onlyloglevel=debug
must be displayed.
For simplicity let's only work with two environments i.e
Production
andDevelopment
.
Implementation without using a pattern
// Concrete implementation
class Logger is
env: string
public method LogError(message: string): void is
print message
public method LogInfo(message: string): void is
if env = ("Development" or "Test" or "Debug") then
print message
public method LogWarning(message: string): void is
print message
public method LogDebug(message: string): void is
if env = ("Development" or "Test" or "Debug") then
print message
// Implementation of Logger
function DoSomeWork(): void is
...
logger = new Logger()
...
var condition = isValid();
if condition is true then
logger.LogInfo("Some work is completed");
return
...
logger.LogError("Unable to complete work")
Implementation of Logger
involves so much of if-else
or 'if
(the same thing can be performed using the switch
).
Imagine in the future
- Some new environment is added let's say
Cloud
. So it brings new problems and conditions with it.- Making developer change implementation of class
Logger
. - Updating condition according to it
- Making developer change implementation of class
- Business rules changed for logger
- So again developer has to change the internal of
Logger
- So again developer has to change the internal of
Thus, this implementation is not maintainable and prone to unwanted behavior to occur.
Implementing Factory Method
The developer creates an abstract or interface called Logger
and provides the concrete implementation for various subclasses of the common superclass Logger
for various environments i.e ProductionLogger
, DevelopmentLogger
, TestingLogger
and DebugLogger
.
So each method of the subclass of Logger
must contain its implementation and business rules.
I know it's hard to grasp to understand because adds up complexity.
For those who don't understand the UML diagram, it shows the IS-A relationship between the abstract Logger
class and subclasses (DevelopmentLogger
, TestingLogger
etc) such that DevelopmentLogger
is a Logger
.
The developer creates an interface
named ILoggerBuilder
. That is responsible to provide an object of type Logger
according to the requirement or some logic. the UML diagram shows the IS-A relationship between the abstract Logger
class and subclasses (DevelopmentLogger
, TestingLogger
etc) such that DevelopmentLogger
is a Logger
.
The developer creates an interface
named ILoggerFactory
. That is responsible to provide an object of type Logger
according to the requirement or some logic.
EnvironmentLoggerFactory
implements ILoggerFactory
that is responsible for creating an appropriate object of Subclass of Logger
according to the environment on which the application is executing or say there is complex logic for building some object;
Let's write some pseudo-code
abstract class Logger is
public abstract method LogInfo(message: string): void
public abstract method LogError(message: string): void
public abstract method LogDebug(message: string): void
public abstract method LogWarn(message: string): void
class ProductionLogger inherits Logger is
public method LogInfo(message: string): void is
// Does Nothing
return
public method LogError(message: string): void is
print message
public method LogWarn(message: string): void is
print message
public method LogDebug(message: string): void is
// Does Nothing
return
class DevelopmentLogger inherits Logger is
public method LogInfo(message: string): void is
print message
public method LogError(message: string): void is
print message
public method LogWarn(message: string): void is
print message
public method LogDebug(message: string): void is
return
class TestingLogger inherits Logger is
public method LogInfo(message: string): void is
// Does Nothing
return
public method LogError(message: string): void is
print message
public method LogWarn(message: string): void is
print message
public method LogDebug(message: string): void is
// Does Nothing
return
class DebugLogger inherits Logger is
public method LogInfo(message: string): void is
// Does Nothing
return
public method LogError(message: string): void is
// Does Nothing
return
public method LogWarn(message: string): void is
// Does Nothing
return
public method LogDebug(message: string): void is
print message
Now developer adds EnvironmentLoggerFactory
that implements ILoggerFactory
interface ILoggerFactory is
CreateLogger(): Logger
class EnvironmentLoggerFactory implements ILoggerFactory
public CreateLogger(): Logger is
env = getCurrentEnv()
if env
is "Production" then return new ProductionLogger()
is "Development" then return new DevelopmentLogger()
is "Testing" then return new TestingLogger()
is "Debug" then return new DebugLogger()
throw new Exception("No logger attached to current environment")
Implementation of Logger
function DoSomeWork(): void is
...
logger = new EnvironmentLoggerFactory().CreateLogger()
...
var condition = isValid();
if condition is true then
logger.LogInfo("Some work is completed");
return
...
logger.LogError("Unable to complete work")
Again imagine the future
- Some new environment is added let's say
Cloud
.-
SOLUTION: Now the developer has to develop a new subclass
CloudLogger
that inheritsLogger
. - Rules can be implemented without thinking that they will break in the development or production environment.
-
SOLUTION: Now the developer has to develop a new subclass
- Business rules changed for the logger, for example, all the logs in the cloud must be stored in a database
-
SOLUTION: Just implement a concrete class
CloudDatabaseLogger
that inheritsLogger
.
-
SOLUTION: Just implement a concrete class
Applicability
-
When developers do not know the exact type of objects that code should work with
- Factory method separates object creation from the code that uses it
- It provides an ease to extending the construction of an object
-
When the developer wants to save resources by reusing existing objects rather than by rebuilding them
- This can be achieved using caching or memoization
Generic Implementation of Pattern
- All the products must be must inherit or implement
Product
- Add a factory method inside
CreatorFactory
that always returns an object of base typeProduct
- Replace all constructor calls in the code with the factory method.
Developer can pass argument inside this factory method to control object creation behavior
Advantages
- Avoid tight coupling
- Single Responsibility Principal, thus all object creation code is separated, allowing code management easy.
- Open/Close Principal, the developer can introduce a new product according to changes without breaking client code.
Conclusion
The factory method is a powerful way to create complex objects it promotes loose coupling, the single responsibility principle, and the open/close principle.
Follow me on GitHub
To know more about this pattern, check this video by Christopher Okhravi
Top comments (0)