Python中的类工厂

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

我有两个class级来pipe理两个不同注册商的域名。 两者都有相同的接口,例如

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): ... 

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

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

我知道这可以使用工厂函数来完成(见下文),但是这是做这件事的最佳方式还是使用OOPfunction有更好的方法?

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

我认为使用一个function是好的。

更有趣的问题是你如何确定加载哪个注册商? 一个select是有一个抽象的基础注册器类,具体的实现子类,然后遍历其__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 ,并将他们各自支持哪些域的决定委托给他们。

假设你需要为不同的注册商分别安装不同的类(虽然在你的例子中不是很明显),但你的解决scheme看起来没问题,尽pipe注册商A注册商B可能共享function,并且可能来自抽象基类 。

作为您的factoryfunction的替代scheme,您可以指定一个字典,映射到您的注册商类别:

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

然后:

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

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

在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() 

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

你可以创build一个'wrapper'类并重载它的__new__()方法来返回专门的子类的实例,例如:

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

另外,为了处理不相互排斥的条件,在其他答案中提出了一个问题,首先要问自己的问题是,你是否希望扮演调度员angular色的包装类来pipe理条件,或者它会把它委托给专门的class级。 我可以build议一个共享的机制,其中专门的类定义自己的条件,但包装做validation,就像这样(假设每个专门类公开一个类方法,validation它是否是一个特定域的注册商,is_registrar_for(。 ..)如其他答案中所build议的):

 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) 

怎么样像

 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() 

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

这里是一个模式,我确信我最初从StackOverflow中解除了这个模式,但是我还没有到原始文章的path

 _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)