我是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)
答案 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