Используя конструктор базового класса как фабрику в Python? - PullRequest
7 голосов
/ 13 февраля 2009

Я использую конструктор базового класса в качестве фабрики и меняю класс в этом конструкторе / фабрике, чтобы выбрать подходящий класс - это хороший подход к Python или есть более элегантные способы?

Я пытался прочитать справку о метаклассах, но без особого успеха.

Вот пример того, что я делаю.

class Project(object):
  "Base class and factory."
  def __init__(self, url):
      if is_url_local(url):
        self.__class__ = ProjectLocal
      else:
        self.__class__ = ProjectRemote
      self.url = url

class ProjectLocal(Project):
  def do_something(self):
    # do the stuff locally in the dir pointed by self.url

class ProjectRemote(Project):
  def do_something(self):
    # do the stuff communicating with remote server pointed by self.url

Имея этот код, я могу создать экземпляр ProjectLocal / ProjectRemote через базовый класс Project:

project = Project('http://example.com')
project.do_something()

Я знаю, что альтернативным способом является использование фабричной функции, которая будет возвращать объект класса на основе URL, тогда код будет выглядеть примерно так:

def project_factory(url):
      if is_url_local(url):
        return ProjectLocal(url)
      else:
        return ProjectRemote(url)

project = project_factory(url)
project.do_something()

Мой первый подход - дело вкуса или в нем есть какие-то скрытые подводные камни?

Ответы [ 6 ]

18 голосов
/ 13 февраля 2009

Для этого вам не нужны метаклассы. Взгляните на метод __new__. Это позволит вам взять на себя управление созданием объекта, а не только инициализацию, и таким образом вернуть объект по вашему выбору.

class Project(object):
  "Base class and factory."
  def __new__(cls, url):
    if is_url_local(url):
       return super(Project, cls).__new__(ProjectLocal, url) 
    else:
       return super(Project, cls).__new__(ProjectRemote, url) 

  def __init__(self, url):
    self.url = url
13 голосов
/ 13 февраля 2009

Я бы придерживался подхода фабричной функции. Это очень стандартный Python, и его легко читать и понимать. Вы можете сделать его более универсальным для обработки большего количества опций несколькими способами, например, передав функцию дискриминатора и отображение результатов в классы.

Если первый пример работает, то это скорее удача, чем дизайн. Что если вы хотите, чтобы в вашем подклассе был определен __init__?

3 голосов
/ 13 февраля 2009

Следующие ссылки могут быть полезны: http://www.suttoncourtenay.org.uk/duncan/accu/pythonpatterns.html#factory http://code.activestate.com/recipes/86900/

Кроме того, поскольку вы используете новые классы стилей, то обычно используется __new__ в качестве фабричной функции (а не в базовом классе, лучше использовать отдельный класс) (насколько я знаю).

Заводская функция, как правило, проще (как уже писали другие люди)

Кроме того, не рекомендуется устанавливать атрибут __class__ так, как вы это сделали.

Надеюсь, вы найдете ответ и полезные ссылки.

Всего наилучшего.

2 голосов
/ 23 июня 2010

Да, как упомянул @scooterXL, фабричная функция - лучший подход в этом случае, но я бы хотел отметить случай для заводов как методы класса.

Рассмотрим следующую иерархию классов:

class Base(object):

    def __init__(self, config):
        """ Initialize Base object with config as dict."""
        self.config = config

    @classmethod
    def from_file(cls, filename):
        config = read_and_parse_file_with_config(filename)
        return cls(filename)

class ExtendedBase(Base):

    def behaviour(self):
        pass # do something specific to ExtendedBase

Теперь вы можете создавать базовые объекты из config config и из конфигурационного файла:

>>> Base({"k": "v"})
>>> Base.from_file("/etc/base/base.conf")

Но вы также можете сделать то же самое с ExtendedBase бесплатно:

>>> ExtendedBase({"k": "v"})
>>> ExtendedBase.from_file("/etc/extended/extended.conf")

Итак, эту фабрику методов класса можно также рассматривать как вспомогательный конструктор.

1 голос
/ 13 февраля 2009

У меня обычно есть отдельный заводской класс для этого. Таким образом, вам не нужно использовать мета-классы или присваивания для self.__class__

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

Очень хорошо работает для меня, а также реализует такие вещи, как инкапсуляция и сокрытие информации.

0 голосов
/ 13 февраля 2009

Я думаю, что второй подход с использованием фабричной функции намного чище, чем зависимость реализации вашего базового класса от его подклассов.

...