2
\$\begingroup\$

I wanted to have a factory-method, but without the if statements that are often seen in examples of this pattern. So I came up with this, and would like to know: is this a right approach, should there be any impovements?

some things I considered:

  • It is using Enum because that makes it possible to iterate or list the possible options.
  • using getattr to call the class dynamically
class Red:
    name = "red"
    
class Blue:
    name = "blue"
    
class Colors(Enum):
    red = Red
    blue = Blue
    
    @classmethod
    def factory(cls, name):
        if any(e.name == name for e in cls):
            target = cls[name]
        else:
            #default
            target = cls.blue
        return getattr(sys.modules[__name__], target.value.__name__)()

print(Colors.factory("red"))
print(Colors.factory("red"))
print(Colors.factory("red2"))
print(list(Colors))
### result
<__main__.Red object at 0x7fd7cc2e2250>
<__main__.Red object at 0x7fd7cc2e2430>
<__main__.Blue object at 0x7fd7cc30c700>
[<Colors.red: <class '__main__.Red'>>, <Colors.blue: <class '__main__.Blue'>>]
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

Using enums in Python

The posted code doesn't take advantage of most of the benefits provides by enums in Python.

The typical use case of enums is to make decisions depending on a given enum member. For example, in the context of the posted code, I can imagine a create_foo method which takes as parameter a value of Color type that is an enum, and callers would use create_foo(Color.RED) to create a foo object with one of the supported colors.

Another common use case would be iterating over an enum type, for example to display the available choices to users.

In the posted code, only the iteration capability is used, and only internally. For this simpler purpose, a simpler and better option would be using a dictionary:

SUPPORTED_COLORS = {
    "red": Red,
    "blue": Blue,
}

def create_color(name):
    if name in SUPPORTED_COLORS:
        return SUPPORTED_COLORS[name]()

    return Blue()

Since the parameter of the factory method is a string, it doesn't help to find the accepted values. I have to read the implementation to know, which violates good encapsulation.

You may argue that taking a string parameter makes it possible to accept unsupported values and fall back to a default. I think that without a specific use case to justify this need, it's likely to be more harmful than beneficial to bypass type safety this way.

I find it unexpected to have a factory method as part of an enum type. I would not look for a factory method there. When I see an enum, I expect to use its members, or iterate over it.


Based on the documentation of enum support, enum members should be named with UPPER_CASE, for example:

class Color(Enum):
    RED = auto
    BLUE = auto

Based on the examples on the page, it seems that singular noun is recommended as the name of the enum (Color rather than Colors).

Implementing the factory method pattern in Python

I wanted to have a factory-method, but without the if statements that are often seen in examples of this pattern.

Why is that, really? What is the real problem you're trying to solve? When you try to do something that is different from what is "often seen", I suggest to take a step back, and ask yourself if you really have a good reason to not follow a well established pattern. Chances are that you are missing something, or you don't have a real need.

Also keep in mind that "often seen" patterns are easier for everyone to understand, because they are often seen. When you take a different approach, there is a high risk that readers will be surprised by it. So it had better have a good justification to exist, and be rock solid.

There is nothing fundamentally wrong with if statements in the factory method in general. On the other hand, I can imagine situations where a complex if-else chain could be replaced with something better, but I would have to be more specific about the problem to justify the need for improvement.

I was surprised by the posted code, at multiple points. When I read the question title "Factory pattern using enum", I expected to see:

  • A factory method that takes an enum parameter
  • The factory method decides what to return based on the enum parameter

Reading on, when I saw "but without the if statements", I expected a dictionary mapping enum members to some value or creator function, replacing if-else chain with dictionary lookup.


I think this tutorial on realpython.com explains very nicely how to implement this pattern well in Python.

\$\endgroup\$
1
  • \$\begingroup\$ "I find it unexpected to have a factory method as part of an enum type." So do I , that is why I posted the code here. But the reasoning was simple: first I used a regular class, but that would require some extra methods to make it iterable - Enum is a quick way to solve this, and works well. \$\endgroup\$
    – Alex
    Oct 31, 2021 at 16:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.