Python中的类工厂

时间:2009-01-19 06:08:17

标签: python factory

我是Python新手,需要一些建议来实现下面的方案。

我有两个类用于管理两个不同注册商的域名。两者都具有相同的界面,例如

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

    def lookup(self):
        ...

    def register(self, info):
        ...

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

    def lookup(self):
        ...

    def register(self, info):
        ...

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

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

biz = Domain('test.biz') #load RegistrarB
biz.lookup()

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

def factory(domain):
  if ...:
    return RegistrarA(domain)
  else:
    return RegistrarB(domain)

8 个答案:

答案 0 :(得分:72)

我认为使用函数很好。

更有趣的问题是如何确定要加载哪个注册商?一种选择是拥有一个抽象的基础注册器类,具体实现子类,然后迭代其__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')

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

答案 1 :(得分:21)

假设您需要针对不同注册商的单独课程(虽然在您的示例中并不明显),但您的解决方案看起来还不错,但注册商A 注册商B 可能共享功能并且可以派生来自Abstract Base Class

作为factory功能的替代方案,您可以指定一个dict,映射到您的注册商类:

Registrar = {'test.com': RegistrarA, 'test.biz': RegistrarB}

然后:

registrar = Registrar['test.com'](domain)

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

答案 2 :(得分:10)

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

class Domain(object):
  def __init__(self, domain):
    self.domain = domain
    if ...:
      self.__class__ = RegistrarA
    else:
      self.__class__ = RegistrarB

接下来会有效。

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

我正在成功使用这种方法。

答案 3 :(得分:6)

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

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

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

答案 4 :(得分:2)

我一直有这个问题。如果您的应用程序(及其模块)中嵌入了类,那么您可以使用一个函数;但是如果你动态加载插件,你需要更动态的东西 - 自动通过元类向工厂注册类。

这是一个我确定最初从StackOverflow解除的模式,但是我还没有原始帖子的路径

_registry = {}

class PluginType(type):
    def __init__(cls, name, bases, attrs):
        _registry[name] = cls
        return super(PluginType, cls).__init__(name, bases, attrs)

class Plugin(object):
    __metaclass__  = PluginType # python <3.0 only 
    def __init__(self, *args):
        pass

def load_class(plugin_name, plugin_dir):
    plugin_file = plugin_name + ".py"
    for root, dirs, files in os.walk(plugin_dir) :
        if plugin_file in (s for s in files if s.endswith('.py')) :
            fp, pathname, description = imp.find_module(plugin_name, [root])
            try:
                mod = imp.load_module(plugin_name, fp, pathname, description)
            finally:
                if fp:
                    fp.close()
    return

def get_class(plugin_name) :
    t = None
    if plugin_name in _registry:
        t = _registry[plugin_name]
    return t

def get_instance(plugin_name, *args):
    return get_class(plugin_name)(*args)

答案 5 :(得分:1)

这样的东西
class Domain(object):
  registrars = []

  @classmethod
  def add_registrar( cls, reg ):
    registrars.append( reg )

  def __init__( self, domain ):
    self.domain = domain
    for reg in self.__class__.registrars:
       if reg.is_registrar_for( domain ):
          self.registrar = reg  
  def lookup( self ):
     return self.registrar.lookup()    

Domain.add_registrar( RegistrarA )
Domain.add_registrar( RegistrarB )

com = Domain('test.com')
com.lookup()

答案 6 :(得分:0)

此处元类隐式收集 ENTITIES dict中的注册表类

<select>

答案 7 :(得分:0)

因为方法可能是共享的,所以使用一些基类是有意义的。 getattr 可以在工厂函数中用于动态调用另一个类。

确定注册类型的逻辑不应该是这些类的一部分,而应该在一些辅助函数中。

import sys

class RegistrarBase():
    """Registrar Base Class"""
    def __init__(self, domain):
        self.name = domain

    def register(self, info):
        pass

    def lookup(self):
        pass
    def __repr__(self):
        return "empty domain"


class RegistrarA(RegistrarBase):
    def __repr__(self):
        return ".com domain"
                    
class RegistrarB(RegistrarBase):
    def __repr__(self):
        return ".biz domain"


def create_registrar(domainname, registrartype):
    try: 
        registrar = getattr(sys.modules[__name__], registrartype)
        return registrar(domainname)
    except:
        return RegistrarBase(domainname)

    
    
domain = create_registrar(domainname = 'test.com', registrartype='RegistrarA')    

print(domain)    
print(domain.name)
#.com domain
#test.com