python factory

Fábrica de clase en Python



factory (6)

Soy nuevo en Python y necesito algunos consejos para implementar el escenario a continuación.

Tengo dos clases para administrar dominios en dos registradores diferentes. Ambos tienen la misma interfaz, por ejemplo

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

y

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

Me gustaría crear una clase de dominio que, con un nombre de dominio, cargue la clase de registrador correcta en función de la extensión, por ejemplo

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

Sé que esto se puede lograr utilizando una función de fábrica (ver a continuación), pero ¿es esta la mejor manera de hacerlo o hay una forma mejor de usar las características de OOP?

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


Creo que usar una función está bien.

La pregunta más interesante es ¿cómo se determina qué registrador cargar? Una opción es tener una clase base de registrador abstracta que subclase concreta de implementaciones, luego iterar sobre sus __subclasses__() llamando a un método de clase 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'')

Esto le permitirá agregar de forma transparente nuevos Registrar y delegar la decisión de los dominios que cada uno admite.


En Python puedes cambiar la clase real directamente:

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

Y luego, funcionará.

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

Estoy usando este enfoque con éxito.


¿Qué tal algo así como

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


Tengo este problema todo el tiempo. Si tiene las clases integradas en su aplicación (y sus módulos), entonces puede usar una función; pero si carga los complementos dinámicamente, necesita algo más dinámico: registrar las clases con una fábrica a través de metaclases de forma automática.

Este es un patrón que estoy seguro saqué de originalmente, pero todavía no tengo el camino a la publicación original

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


Suponiendo que necesita clases separadas para diferentes registradores (aunque no es obvio en su ejemplo) su solución se ve bien, aunque RegistrarA y RegistrarB probablemente compartan la funcionalidad y podrían derivarse de una Clase base abstracta .

Como alternativa a su función de factory , puede especificar un dict, mapeo a sus clases de registrador:

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

Entonces:

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

Una objeción: en realidad no estás haciendo una Fábrica de clases porque estás devolviendo instancias en lugar de clases.


Puede crear una clase ''contenedora'' y sobrecargar su __new__() para devolver instancias de las subclases especializadas, por ejemplo:

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

Además, para tratar condiciones no mutuamente excluyentes, un problema que surgió en otras respuestas, la primera pregunta que debe hacerse es si desea que la clase contenedora, que desempeña el papel de despachador, gobierne las condiciones, o lo delegará en las clases especializadas. Puedo sugerir un mecanismo compartido, donde las clases especializadas definen sus propias condiciones, pero el contenedor hace la validación, así (siempre que cada clase especializada exponga un método de clase que verifique si es un registrador para un dominio particular, is_registrar_for (. ..) como se sugiere en otras respuestas):

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)