物体的工厂方法 - 最佳实践?

时间:2013-02-21 00:21:35

标签: python coding-style decorator class-method factory-method

这是一个关于使用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)

这似乎不是一个问题,也许这两个例子都是有效的,但每次我需要实现这样的事情时我都会考虑它。很长一段时间,我更喜欢类方法方法,因为我喜欢将类和工厂方法绑在一起的组织好处。此外,使用类方法保留了创建任何子类的实例的能力,因此它更加面向对象。另一方面,一位朋友曾说过“当有疑问时,做标准库所做的事情”,我还没有在标准库中找到这样的例子。

1 个答案:

答案 0 :(得分:27)

首先,大多数时候你认为你需要这样的东西,你不需要;这表明你正在尝试像Java一样对待Python,而解决方案就是退后一步,问你为什么需要工厂。

通常,最简单的方法是使用带有默认/可选/关键字参数的构造函数。即使你在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):

或者:

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

但是,有时你需要工厂功能。那么,那你做什么?嗯,有两种常常被混为“工厂”的东西。

@classmethod是执行“替代构造函数”的惯用方法 - stdlib遍布示例 - itertools.chain.from_iterabledatetime.datetime.fromordinal等。

一个函数是做“我不关心实际类是什么”工厂的惯用方法。例如,查看内置的open函数。你知道它在3.3中的回报吗?你关心?不。这就是为什么它是一个函数,而不是io.TextIOWrapper.open或其他什么。

您给出的示例似乎是一个完全合法的用例,非常清楚地适用于“备用构造函数”bin(如果它不适合“带有额外参数的构造函数”bin)。