Использование конструктора базового класса в качестве фабрики в Python?

Я использую конструктор базового класса в качестве фабрики и меняю класс в этом конструкторе/фабрике, чтобы выбрать соответствующий класс - является ли этот подход хорошей практикой 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()

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


person bialix    schedule 13.02.2009    source источник


Ответы (6)


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

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

person scottynomad    schedule 13.02.2009
comment
+1: не пытайтесь заставить базовый класс делать что-то большее, чем просто быть базовым классом. Фабрики всегда разделены. - person S.Lott; 13.02.2009
comment
Еще один хороший вариант для определения фабрик — это classmethods — они дают вам бесплатную параметризацию класса через аргумент cls. - person andreypopp; 23.06.2010

Для этого вам не нужны метаклассы. Взгляните на метод __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
person Brian    schedule 13.02.2009
comment
Я думаю, это то, что я искал, но заблудился в метаклассах. Спасибо. - person bialix; 13.02.2009

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

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

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

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

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

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

person batbrat    schedule 13.02.2009
comment
+1, new — «правильный» способ сделать это, если вам действительно нужно. Однако не рекомендуется для начинающих. Придерживайтесь фабричной функции для ясности или реорганизуйте классы, чтобы локальный/удаленный определялся чем-то другим, кроме класса (например, составом). - person bobince; 13.02.2009
comment
Спасибо за подсказку bobince. Я ни в коем случае не эксперт, поэтому мои ответы основаны только на том, что я изучал в книгах. Очень приятно получить дельный совет. Спасибо еще раз. - person batbrat; 13.02.2009
comment
Хотя у меня есть практический опыт работы с Python. Мне нравится программировать в нем. Однако я не очень разбираюсь в шаблонах проектирования в Python. - person batbrat; 13.02.2009

Да, как упомянул @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 dict и из config файла:

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

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

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

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

person andreypopp    schedule 23.06.2010

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

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

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

person Ber    schedule 13.02.2009

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

person Community    schedule 13.02.2009