如何实现一个工厂class?

How to implement a factory class?

我希望能够基于枚举创建对象 class,并使用字典。像这样:

class IngredientType(Enum):
    SPAM        = auto() # Some spam
    BAKE_BEANS  = auto() # Baked beans
    EGG         = auto() # Fried egg

class Ingredient(object):
    pass    
class Spam(Ingredient):
    pass
class BakedBeans(Ingredient):
    pass
class Egg(Ingredient):
    pass


class IngredientFactory(object):
    """Factory makes ingredients"""

    choice = {
        IngredientType.SPAM: IngredientFactory.MakeSpam,
        IngredientType.BAKED_BEANS: IngredientFactory.MakeBakedBeans,
        IngredientType.EGG: IngredientFactory.MakeEgg
    }

    @staticmethod
    def make(type):
        method = choice[type]
        return method()

    @staticmethod
    def makeSpam():
        return Spam()

    @staticmethod
    def makeBakedBeans():
        return BakedBeans()

    @staticmethod
    def makeEgg():
        return Egg()

但我收到错误消息:

NameError: name 'IngredientFactory' is not defined

由于某种原因无法创建字典。 我哪里错了?

将您的映射放在 class 的末尾,并直接引用这些方法,因为它们在同一个命名空间中:

choice = {
    IngredientType.SPAM: makeSpam,
    IngredientType.BAKED_BEANS: makeBakedBeans,
    IngredientType.EGG: makeEgg
}

class 对象直到 class 主体中的所有代码才创建,因此您无法访问 class 本身。但是,由于 class 主体是在专用名称空间中处理的,因此您可以访问到那时为止定义的任何属性(这就是映射必须放在最后的原因)。另请注意,虽然您可以访问全局变量和内置变量,但不能访问封闭 classes 或函数的命名空间。

这是来自官方文档的详细但仍然是介绍性的解释,解释了 classes 是如何执行的:https://docs.python.org/3/tutorial/classes.html#a-first-look-at-classes

Python 不是 Java 并且不需要所有内容都在 class 中。这里你的 IngredientFactory class 没有状态,只有静态方法,所以它实际上只是一个单例命名空间,在 python 中,规范地使用模块作为单例命名空间和普通函数来完成。此外,由于 Python classes 已经是可调用的,因此将实例化包装在函数中没有意义。简单直接的 pythonic 实现是:

# ingredients.py

class IngredientType(Enum):
    SPAM        = auto() # Some spam
    BAKE_BEANS  = auto() # Baked beans
    EGG         = auto() # Fried egg

class Ingredient(object):
    pass    

class Spam(Ingredient):
    pass

class Beans(Ingredient):
    pass

class Egg(Ingredient):
    pass


_choice = {
        IngredientType.SPAM: Spam,
        IngredientType.BAKED_BEANS: Beans,
        IngredientType.EGG: Egg
    }

def make(ingredient_type):
    cls = _choice[ingredient_type]
    return cls()

和客户端代码:

import ingredients
egg = ingredients.make(ingredients.IngredientType.EGG)

# or much more simply:

egg = ingredients.Egg()

FWIW IngredientType 枚举在这里并没有带来太多,甚至使事情变得更复杂——你可以只使用纯字符串:

# ingredients.py

class Ingredient(object):
    pass    

class Spam(Ingredient):
    pass

class Beans(Ingredient):
    pass

class Egg(Ingredient):
    pass


_choice = {
        "spam": Spam,
        "beans": Beans,
        "egg": Egg
    }

def make(ingredient_type):
    cls = _choice[ingredient_type]
    return cls()

和客户端代码:

import ingredients
egg = ingredients.make("egg")

或者如果你真的想使用 Enum,你至少可以通过使用 classes 本身作为建议的枚举值来摆脱 choices dict来自 MadPhysicist:

# ingredients.py

class Ingredient(object):
    pass    

class Spam(Ingredient):
    pass

class Beans(Ingredient):
    pass

class Egg(Ingredient):
    pass

class IngredientType(Enum):
    SPAM = Spam
    BEANS = Beans
    EGG = Egg

    @staticmethod
    def make(ingredient_type):
        return ingredient_type.value()

和客户端代码

 from ingredients import IngredientType
 egg = IngredientType.make(IngredientType.EGG)

但我也确实看不到任何好处

编辑:你提到:

I am trying to implement the factory pattern, with the intent of hiding the creation of objects away. The user of the factory then just handles 'Ingredients' without knowledge of the concrete type

用户仍然必须指定他想要什么样的成分(ingredient_type 参数)所以我不确定我是否理解这里的好处。您的真实用例实际上是什么? (编造/简化示例的问题在于它们没有讲述全部故事)。

看了 Bruce Eckel's book 之后,我想到了这个:

#Based on Bruce Eckel's book Python 3 example
# A simple static factory method.
from __future__ import generators
import random
from enum import Enum, auto

class ShapeType(Enum):
    CIRCLE  = auto() # Some circles
    SQUARE  = auto() # some squares

class Shape(object):
    pass

class Circle(Shape):
    def draw(self): print("Circle.draw")
    def erase(self): print("Circle.erase")

class Square(Shape):
    def draw(self): print("Square.draw")
    def erase(self): print("Square.erase")

class ShapeFactory(object):

    @staticmethod
    def create(type):
        #return eval(type + "()") # simple alternative
        if type in ShapeFactory.choice:
            return ShapeFactory.choice[type]()

        assert 0, "Bad shape creation: " + type    

    choice = { ShapeType.CIRCLE:  Circle,
               ShapeType.SQUARE:  Square                
             }

# Test factory
# Generate shape name strings:
def shapeNameGen(n):

    types = list(ShapeType)

    for i in range(n):
        yield random.choice(types)

shapes = \
  [ ShapeFactory.create(i) for i in shapeNameGen(7)]

for shape in shapes:
    shape.draw()
    shape.erase()

这让用户从枚举中 select 一个 class 类型,并阻止任何其他类型。这也意味着用户写 'bad strings' 时出现拼写错误的可能性较小。他们只是使用枚举。 然后测试的输出是这样的:

Circle.draw
Circle.erase
Circle.draw
Circle.erase
Square.draw
Square.erase
Square.draw
Square.erase
Circle.draw
Circle.erase
Circle.draw
Circle.erase
Square.draw
Square.erase