Python中的类工厂

64 python factory

我是Python的新手,需要一些建议来实现下面的场景.

我在两个不同的注册商处有两个用于管理域名的课程.两者都具有相同的界面,例如

class RegistrarA(Object):
    def __init__(self, domain):
        self.domain = domain

    def lookup(self):
        ...

    def register(self, info):
        ...
Run Code Online (Sandbox Code Playgroud)

class RegistrarB(object):
    def __init__(self, domain):
        self.domain = domain

    def lookup(self):
        ...

    def register(self, info):
        ...
Run Code Online (Sandbox Code Playgroud)

我想创建一个Domain类,给定一个域名,根据扩展名加载正确的注册器类,例如

com = Domain('test.com') #load RegistrarA
com.lookup()

biz = Domain('test.biz') #load RegistrarB
biz.lookup()
Run Code Online (Sandbox Code Playgroud)

我知道这可以使用工厂函数来完成(见下文),但这是最好的方法吗?还是有更好的方法使用OOP功能?

def factory(domain):
  if ...:
    return RegistrarA(domain)
  else:
    return RegistrarB(domain)
Run Code Online (Sandbox Code Playgroud)

Ale*_*mas 75

我认为使用函数很好.

更有趣的问题是如何确定要加载哪个注册商?一种选择是拥有一个抽象的基础注册器类,具体实现子类,然后迭代__subclasses__()调用一个is_registrar_for()类方法:

class Registrar(object):
  def __init__(self, domain):
    self.domain = domain

class RegistrarA(Registrar):
  @classmethod
  def is_registrar_for(cls, domain):
    return domain == 'foo.com'

class RegistrarB(Registrar):
  @classmethod
  def is_registrar_for(cls, domain):
    return domain == 'bar.com'


def Domain(domain):
  for cls in Registrar.__subclasses__():
    if cls.is_registrar_for(domain):
      return cls(domain)
  raise ValueError


print Domain('foo.com')
print Domain('bar.com')
Run Code Online (Sandbox Code Playgroud)

这将允许您透明地添加新的Registrars并将每个支持的域的决定委托给它们.

  • 除非具体的子类"is_registrar_for()"是互斥的,否则这种方法是不安全的,并且_将在未来保持如此.`__subclasses __()`返回的值的顺序是任意的.总的来说,这个顺序很重要.因此,如果代码中的某些内容(可能与类定义的顺序一样小)发生变化,您可能会得到不同的结果.这种错误的成本,IMO,是巨大的,远远超过这种方法的好处.我会改用OP使用的方法,其中单个函数包含子类选择的整个逻辑. (17认同)
  • 另请注意,`__subclasses__`****仅适用于活动对象.如果某个类尚未导入,则它不会出现在结果中(因为它不存在). (11认同)
  • 如果你确实有互斥测试,或者出于其他原因觉得这种方法是安全的,请注意`__subclasses__`只返回直接的子类; 所以多级继承需要一个小的调整才能正确处理. (8认同)
  • 我认为@AlecThomas,`@ staticmethod`在这种情况下可能稍微好一些 (5认同)
  • 嗨@Alec。在这种特殊情况下,类中的装饰器(@classmethod)是否必要?如果是,他们在这种情况下发挥什么作用? (2认同)

Jef*_*uer 23

假设您需要为不同的注册商提供单独的类(虽然在您的示例中并不明显),但您的解决方案看起来还不错,尽管RegistrarARegistrarB可能共享功能并且可以从Abstract Base Class派生.

作为您的factory函数的替代方法,您可以指定一个dict,映射到您的注册器类:

Registrar = {'test.com': RegistrarA, 'test.biz': RegistrarB}
Run Code Online (Sandbox Code Playgroud)

然后:

registrar = Registrar['test.com'](domain)
Run Code Online (Sandbox Code Playgroud)

一个狡辩:你不是在这里做一个类工厂,因为你正在返回实例而不是类.


bia*_*lix 11

在Python中,您可以直接更改实际的类:

class Domain(object):
  def __init__(self, domain):
    self.domain = domain
    if ...:
      self.__class__ = RegistrarA
    else:
      self.__class__ = RegistrarB
Run Code Online (Sandbox Code Playgroud)

接下来会有效.

com = Domain('test.com') #load RegistrarA
com.lookup()
Run Code Online (Sandbox Code Playgroud)

我成功地使用了这种方法.

  • 实际上,这种方法带来了[更严重的危险](http://docs.python.org/reference/datamodel.html#id5),而不是我意识到:特殊方法可能无法正确调用等等.我现在已经说服了不应该这样做,因为弄清楚这可能导致什么问题可能会因Python的版本而有所不同,并且不值得提供它带来的任何好处. (28认同)

Ion*_*san 7

您可以创建一个"包装器"类并重载其__new__()方法以返回专用子类的实例,例如:

class Registrar(object):
    def __new__(self, domain):
        if ...:
            return RegistrarA(domain)
        elif ...:
            return RegistrarB(domain)
        else:
            raise Exception()
Run Code Online (Sandbox Code Playgroud)

另外,为了处理非互斥条件,在其他答案中提出的问题,首先要问自己的问题是你是否想要扮演调度员角色的包装类来管理条件,或者它会将它委托给专门的类.我可以建议一个共享机制,其中专门的类定义它们自己的条件,但是包装器执行验证,就像这样(假设每个专用类公开一个类方法来验证它是否是特定域的注册器,is_registrar_for(. ..)如其他答案所示):

class Registrar(object):
    registrars = [RegistrarA, RegistrarB]
    def __new__(self, domain):
        matched_registrars = [r for r in self.registrars if r.is_registrar_for(domain)]

        if len(matched_registrars) > 1:
            raise Exception('More than one registrar matched!')
        elif len(matched_registrars) < 1:
            raise Exception('No registrar was matched!')
        else:
            return matched_registrars[0](domain)
Run Code Online (Sandbox Code Playgroud)