Фабричный метод для объектов - лучшая практика?

Это вопрос о наилучшей практике создания экземпляра класса или типа из разных форм одних и тех же данных с использованием python. Лучше использовать метод класса или лучше вообще использовать отдельную функцию? Допустим, у меня есть класс, используемый для описания размера документа. (Примечание. Это просто пример. Я хочу узнать, как лучше всего создать экземпляр класса а не как лучше всего описать размер документа.)

class Size(object):
    """
    Utility object used to describe the size of a document.
    """

    BYTE = 8
    KILO = 1024

    def __init__(self, bits):
        self._bits = bits

    @property
    def bits(self):
        return float(self._bits)

    @property
    def bytes(self):
        return self.bits / self.BYTE

    @property
    def kilobits(self):
        return self.bits / self.KILO

    @property
    def kilobytes(self):
        return self.bytes / self.KILO

    @property
    def megabits(self):
        return self.kilobits / self.KILO

    @property
    def megabytes(self):
        return self.kilobytes / self.KILO

Мой метод __init__ принимает значение размера, представленное в битах (биты и только биты, и я хочу оставить его таким), но допустим, что у меня есть значение размера в байтах, и я хочу создать экземпляр своего класса. Лучше использовать метод класса или лучше вообще использовать отдельную функцию?

class Size(object):
    """
    Utility object used to describe the size of a document.
    """

    BYTE = 8
    KILO = 1024

    @classmethod
    def from_bytes(cls, bytes):
        bits = bytes * cls.BYTE
        return cls(bits)

OR

def create_instance_from_bytes(bytes):
    bits = bytes * Size.BYTE
    return Size(bits)

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


person Yani    schedule 21.02.2013    source источник
comment
Я бы подбросил монетку. Похоже, что большинство библиотек Python предпочитают процедурные API, но это потому, что они предназначены для использования не так, как многократно используемый код, который является внутренним для вашей кодовой базы.   -  person millimoose    schedule 21.02.2013
comment
PS, не называйте переменную bytes; это встроенный тип (в 2.6 и более поздних версиях).   -  person abarnert    schedule 21.02.2013
comment
И, конечно, я только что понял, что сделал ту же ошибку в своем ответе: Size(bytes=20). Не делай, как я, делай, как я говорю. :)   -  person abarnert    schedule 21.02.2013
comment
Неверное имя переменной bytes отмечено должным образом. Спасибо что подметил это.   -  person Yani    schedule 21.02.2013


Ответы (1)


Во-первых, большую часть времени вы думаете, что вам нужно что-то подобное, но это не так; это признак того, что вы пытаетесь относиться к Python как к Java, и решение состоит в том, чтобы сделать шаг назад и спросить, зачем вам нужна фабрика.

Часто проще всего иметь конструктор с аргументами по умолчанию/необязательными/ключевыми словами. Даже случаи, которые вы никогда не написали бы таким образом на Java — даже случаи, когда перегруженные конструкторы будут казаться неправильными в C++ или ObjC — могут выглядеть совершенно естественно в Python. Например, size = Size(bytes=20) или size = Size(20, Size.BYTES) выглядят разумно. Если на то пошло, класс Bytes(20), который наследуется от Size и не добавляет абсолютно ничего, кроме перегрузки __init__, выглядит разумным. И их тривиально определить:

def __init__(self, *, bits=None, bytes=None, kilobits=None, kilobytes=None):

Or:

BITS, BYTES, KILOBITS, KILOBYTES = 1, 8, 1024, 8192 # or object(), object(), object(), object()
def __init__(self, count, unit=Size.BITS):

Но иногда вам нужны фабричные функции. Итак, что вы делаете тогда? Что ж, есть два вида вещей, которые часто объединяют в «фабрики».

@classmethod — это идиоматический способ создания «альтернативного конструктора» — во всей стандартной библиотеке есть примеры — itertools.chain.from_iterable, datetime.datetime.fromordinal и т. д.

Функция - это идиоматический способ сделать фабрику "мне все равно, какой класс на самом деле". Посмотрите, например, на встроенную функцию open. Вы знаете, что он возвращает в 3.3? Тебя волнует? Неа. Вот почему это функция, а не io.TextIOWrapper.open или что-то в этом роде.

Приведенный вами пример кажется совершенно законным вариантом использования и довольно четко вписывается в корзину «альтернативный конструктор» (если он не вписывается в корзину «конструктор с дополнительными аргументами»).

person abarnert    schedule 21.02.2013
comment
Хотя я согласен с этим, справедливо отметить, что другой вариант дизайна вместо метода @classmethod - это сделать __init__(kilobytes=345) и т.д... - person Jon Clements♦; 21.02.2013
comment
@JonClements: Хорошее замечание - хотя я думаю, что это действительно соответствует первому предложению, оно определенно неясно, как написано, поэтому я отредактирую его. - person abarnert; 21.02.2013
comment
Да, я думал об этом варианте использования: dpaste.com/958194 (за исключением того, что это должно быть n * база кашель) - person Jon Clements♦; 21.02.2013
comment
@JonClements: В последний раз, когда я видел 56 килобит и вариант использования в том же контексте, речь шла о необходимости быстрее загружать прогрессивные JPG-файлы телезвезд. :) - person abarnert; 21.02.2013
comment
Итак, кажется, что лучше всего избегать этого, где это возможно. Мой следующий вопрос, вероятно, больше связан со статическими методами, а не с методами класса, но он довольно тесно связан с этим вопросом. Я благодарю вас обоих за ваши полезные отзывы, и я был бы признателен, если бы вы могли взглянуть на следующий вопрос и прокомментировать, очищаю ли я свой код надлежащим образом. (См. stackoverflow.com/questions /15017734/) - person Yani; 22.02.2013