The Python version of the Factory pattern in Refactoring Guru is:
from __future__ import annotations
from abc import ABC, abstractmethod
class Creator(ABC):
@abstractmethod
def factory_method(self) -> Product:
pass
def some_operation(self) -> str:
product = self.factory_method()
result = f"Creator: The same creator's code has just worked with {product.operation()}"
return result
class ConcreteCreator1(Creator):
def factory_method(self) -> Product:
return ConcreteProduct1()
class ConcreteCreator2(Creator):
def factory_method(self) -> Product:
return ConcreteProduct2()
class Product(ABC):
@abstractmethod
def operation(self) -> str:
pass
class ConcreteProduct1(Product):
def operation(self) -> str:
return "{Result of the ConcreteProduct1}"
class ConcreteProduct2(Product):
def operation(self) -> str:
return "{Result of the ConcreteProduct2}"
def client_code(creator: Creator) -> None:
print(f"Client: I'm not aware of the creator's class, but it still works.n"
f"{creator.some_operation()}", end="")
if __name__ == "__main__":
client_code(ConcreteCreator1())
client_code(ConcreteCreator2())
Honestly, I’m a bit hesitant to ask this here because it looks like a very simple question, but here I go:
- What’s the benefit of introducing the
ConcreteCreatorX
class as opposed to passing theProduct
type directly like below?
from abc import ABC, abstractmethod
from typing import Any
class Product(ABC):
@abstractmethod
def operation(self) -> Any:
pass
class ConcreteProduct1(Product):
def operation(self) -> str:
return "{Result of the ConcreteProduct1}"
class ConcreteProduct2(Product):
def operation(self) -> str:
return "{Result of the ConcreteProduct2}"
def client_code(product: Product) -> None:
print(f"Client: I'm not aware of the creator's class, but it still works.n"
f"{product.operation()}")
if __name__ == "__main__":
client_code(ConcreteProduct1())
client_code(ConcreteProduct2())
In both cases, client_code
expects some type that implements an operation
method. The Product
type is already providing such method in an abstract way so that each Product
can implement its own variant of the method. So, from the client_code
‘s perspective I see no difference in using one or the other.
Now, there is a difference between the two codes: the first example (actual Factory pattern example) is delegating the instantiation of each ConcreteProductX
class to the ConcreteCreatorX
class (which I understand is the whole point of the pattern).
We are separating concerns that way, sure, if we want to modify the factory method we don’t need to modify directly the Product
class. I can see the benefit in case we want to modify the factory_method
to do something else apart from instantiating the ConcreteProductX
class, like notifying something somewhere, keeping track of how many products are created, etc. But then wouldn’t we be breaking the Single Responsibility Principle? Because now the Creator
is not only responsible for creating a product. In other words, should it be qualified to be called Creator
?
So I guess my question could be reformulated in a more precise way:
- Is there any benefit of using
Creator
andConcreteCreatorX
classes that does not involve breaking the Single Responsability Principle?