Как сделать фабричный класс Python - PullRequest
0 голосов
/ 19 октября 2018

Я хочу иметь возможность создавать объекты на основе класса перечисления и использовать словарь.Примерно так:

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

По какой-то причине словарь не может быть создан.Куда я здесь не так?

Ответы [ 3 ]

0 голосов
/ 19 октября 2018

Python не является Java и не требует, чтобы все было в классе.Здесь у вашего IngredientFactory класса нет состояний и только статические методы, так что на самом деле это просто одноэлементное пространство имен, которое в Python канонически выполняется с использованием модуля в качестве одноэлементного пространства имен и простых функций.Также, поскольку классы Python уже доступны для вызова, перенос экземпляра в функцию не имеет смысла.Простая и понятная реализация 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, выможет, по крайней мере, избавиться от диктата choices, используя сами классы в качестве значений для перечисления, как предложено 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)

Но яна самом деле здесь тоже нет никакой пользы

EDIT: вы упомянули:

Я пытаюсь реализовать фабричный шаблон с намерением скрыть создание объектов.Затем пользователь фабрики просто обрабатывает «Ингредиенты», не зная о конкретном типе

Пользователь все еще должен указать, какие ингредиенты ему нужны (аргумент ingredient_type), поэтому я неуверен, что я понимаю выгоду здесь.Какой у вас реальный вариант использования на самом деле?(проблема с вымышленными / глупыми примерами заключается в том, что они не рассказывают всю историю).

0 голосов
/ 19 октября 2018

Посмотрев на книгу Брюса Экеля , я пришел к следующему:

#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()

Это заставляет пользователя выбирать тип класса из перечисления и блокирует любой другой тип.Это также означает, что пользователь с меньшей вероятностью будет писать «плохие строки» с орфографическими ошибками.Они просто используют перечисления.Выход из теста тогда, что-то вроде этого:

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
0 голосов
/ 19 октября 2018

Поместите ваше отображение в конец класса и напрямую обратитесь к методам, поскольку они находятся в одном и том же пространстве имен:

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

Объект класса не создается до тех пор, пока весь код в классетело, так что вы не можете получить доступ к самому классу.Однако, поскольку тело класса обрабатывается в выделенном пространстве имен, вы можете получить доступ к любому атрибуту, который вы определили до этого момента (именно поэтому сопоставление должно быть в конце).Также обратите внимание, что хотя вы можете получить доступ к глобальным и встроенным элементам, вы не можете получить доступ к пространствам имен включающих классов или функций.

Вот подробное, но все же вводное объяснение из официальных документов, объясняющее, как выполняются классы: https://docs.python.org/3/tutorial/classes.html#a-first-look-at-classes

...