I need to document my design, in particular, the design patterns used, and would like to use the standard terminology.
From Refactoring Guru, "Factory Method defines a method, which should be used for creating objects instead of direct constructor call. Subclasses can override this method to change the class of objects that will be created".
I have a CraneInterface
class with an abstract method, and the signature of this method enforces that its return type is an implementation of an AxisInterface
. Essentially, subclasses of CraneInterface
"override this method to change the class of objects that will be created". The only diference with my version is that it does not necessarily "create" a new instance, it could also return one that already exists. Is this still the Factory Pattern? And if not, does this design pattern have a common name?
i.e: A traditional factory looks like this:
class IAnimal(ABC):
@abstractmethod
def speak(self):
pass
class Dog(IAnimal):
#overide
def speak(self):
print('Woof')
class Cat(IAnimal):
#overide
def speak(self):
print('Meow')
class AnimalFactory(ABC):
@abstractmethod
def make_animal(self) -> IAnimal:
pass
class CatFactory(AnimalFactory):
#overide
def make_animal(self) -> IAnimal:
return Cat()
class DogFactory(AnimalFactory):
#overide
def make_animal(self) -> IAnimal:
return Dog()
My code looks more like this:
class AnimalFactory2(ABC):
@property
@abstractmethod
def animal(self) -> IAnimal:
pass
class CatFactory2(AnimalFactory2):
def __init__(self):
self.__cat = Cat()
#overide
@property
def animal(self) -> IAnimal:
return self.__cat
class DogFactory2(AnimalFactory2):
def __init__(self):
self.__dog = Dog()
#overide
@property
def animal(self) -> IAnimal:
return self.__dog
Does the second example use the Factory Pattern? Does it have a different name or even have a name at all? The main difference is that it does not create a new instance each time it is called.
Extra info:
In my actual code, I have a 3 axis CraneInterface
class that has abstract methods for all the ways you can interact with a crane. It is implemented by a CraneSimulator
and a CraneOpcuaClient
that actually talks to a real crane. The original design of the simulator implemented the abstract method inside the CraneSimulator
class, however, this had lots of duplicated code, as every function of the crane was repeated for each of the 3 axes. To solve this, I created an AxisSimulation
class which had methods to interact with it, and then there are 3 instantiations inside the CraneSimulator
, and the implementations of the CraneInterface
abstract methods simply forward the request to one of the 3 axis objects.
The problem was that the CraneInterface
also needed the ability to notify "observers" whenever the state of the crane changed, for example, a position or temperature change. I.e the CraneInterface
needed to have a function add_on_x_position_changed_callback(self, callback: Callable[[Position],None])
. To do this, the CraneInterface
had properties with custom setters that notified a list of observers whenever the value was set. By putting the AxisSimulation
inside the CraneSimulator
the properties had moved out of the CraneInterface
, and the add_on_changed_callback
methods of the CraneInterface
would no longer work.
So to solve this, the CraneInterface
had an abstract property to return an abstract AxisInterface
class (like the AnimalFactory2
example). The AxisInterface
then had the observable properties with a custom setter (and methods to add observers), so that users of the CraneInterface
can add observers to the data.
I know that the "observable" part is an example of the Observer pattern, but is the deferring of the type of Axis implementation returned, an example of the Factory Pattern?
Thanks.
-
4The purpose of design patterns is to communicate. If you have to explain why a particular maneuver you've accomplished fits a particular design pattern, then the communication advantage of using the pattern name is lost.Robert Harvey– Robert Harvey12/19/2020 15:05:42Commented Dec 19, 2020 at 15:05
-
@Robert Harvey I agree, and this is exactly what I'm trying to do. I want to succinctly communicate my design. I.e, Say, "The Crane Interface has a factory method to retrieve the Axis class". But I'm not sure if that is true. Perhaps it isn't a Factory method. Perhaps this maneuver doesn't have a name at all. In that case I will have to think of another way to succinctly communicate the design.Blue7– Blue712/19/2020 15:17:08Commented Dec 19, 2020 at 15:17
-
Just explain what the method does. You don't have to communicate every last detail of how the method works; just give the broad brush strokes. Your developers know how to read code.Robert Harvey– Robert Harvey12/19/2020 15:25:26Commented Dec 19, 2020 at 15:25
2 Answers 2
After some thought, I think the second code snippet does not use the Factory method as it is not concerned with the creation of objects.
However, the code can be refactored to use the Factory method and better communicate the intent:
class CraneInterface(ABC):
def __init__(self):
self.__axis = self._make_axis()
@property
def axis(self) -> AxisInterface:
return self.__axis
@abstractmethod
def _make_axis(self) -> AxisInterface:
"""
Factory method: "Subclasses override to change the class of object that will be created"
"""
pass
-
1You are focusing on the wrong things. Patterns aren't defined by the details of a particular implementation, it's the intent and the relationships between elements that count. Factory Method doesn't require inheritance, or prevent you from caching. The idea behind it is that the code that needs to invoke the method that creates some other object doesn't know what to create, so you supply it with an object that does know - the FactoryMethod. It's literally an object that represents a creation function. In Python, you can do this with a simple lambda - it would still be the same pattern.Filip Milovanović– Filip Milovanović12/19/2020 15:56:55Commented Dec 19, 2020 at 15:56
-
So it's enough to just say that your clients should pass one of these objects if they wish to allow some other code to invoke it as a factory, or to derive from the base class if they wish to supply their own factory. Regarding caching a previously created instance: you need to think about whether this behavior is surprising to the client or not. E.g., it might be OK if the returned object is immutable, or if client code doesn't make the assumption that it will always get a new instance.Filip Milovanović– Filip Milovanović12/19/2020 15:56:59Commented Dec 19, 2020 at 15:56
-
@FilipMilovanović is right. Patterns are not about structure. They’re about intent. The best proof is this is that some patterns have exactly the same structure yet different names for their different intents.candied_orange– candied_orange12/19/2020 16:07:35Commented Dec 19, 2020 at 16:07
To re-iterate the most important part of your quote from Refactoring Guru:
Factory Method defines a method, which should be used for creating objects instead of direct constructor call.
If the function that returns an AxisInterface
does not have the purpose of creating an axis for the caller, then you are not using the Factory Method pattern, regardless of how much your code resembles the implementation of the pattern.
Design patterns are not defined by their structure but by the intent of the code.
Explore related questions
See similar questions with these tags.