Je connais Python pour la première fois et j'ai besoin de conseils pour mettre en œuvre le scénario ci-dessous.
J'ai deux classes pour la gestion de domaines chez deux registraires différents. Les deux ont la même interface, par exemple.
class RegistrarA(Object):
def __init__(self, domain):
self.domain = domain
def lookup(self):
...
def register(self, info):
...
et
class RegistrarB(object):
def __init__(self, domain):
self.domain = domain
def lookup(self):
...
def register(self, info):
...
J'aimerais créer une classe de domaine qui, à partir d'un nom de domaine, charge la classe de registraire appropriée en fonction de l'extension, par exemple.
com = Domain('test.com') #load RegistrarA
com.lookup()
biz = Domain('test.biz') #load RegistrarB
biz.lookup()
Je sais que cela peut être accompli en utilisant une fonction usine (voir ci-dessous), mais est-ce la meilleure façon de le faire ou existe-t-il une meilleure façon en utilisant les fonctionnalités OOP?
def factory(domain):
if ...:
return RegistrarA(domain)
else:
return RegistrarB(domain)
Je pense que l'utilisation d'une fonction est très bien.
La question la plus intéressante est de savoir comment déterminer quel registraire charger. Une option consiste à avoir une classe abstraite Registrar avec sous-classe d'implémentations concrètes, puis itérera sur sa __subclasses__()
en appelant une méthode de classe 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')
Cela vous permettra d'ajouter de manière transparente de nouvelles Registrar
s et de leur déléguer la décision des domaines pris en charge par chacun.
En supposant que vous ayez besoin de classes distinctes pour différents bureaux d'enregistrement (bien que ce ne soit pas évident dans votre exemple), votre solution semble correcte, bien que RegistrarA et RegistrarB partagent probablement des fonctionnalités et puissent être dérivées d'un Abstract Base Class .
En guise d'alternative à votre fonction factory
, vous pouvez spécifier un dict, mappé à vos classes de registraire:
Registrar = {'test.com': RegistrarA, 'test.biz': RegistrarB}
Ensuite:
registrar = Registrar['test.com'](domain)
Un petit reproche: vous ne faites pas vraiment une classe fabrique ici car vous renvoyez des instances plutôt que des classes.
En Python, vous pouvez modifier directement la classe actuelle:
class Domain(object):
def __init__(self, domain):
self.domain = domain
if ...:
self.__class__ = RegistrarA
else:
self.__class__ = RegistrarB
Et ensuite, ça va marcher.
com = Domain('test.com') #load RegistrarA
com.lookup()
J'utilise cette approche avec succès.
Vous pouvez créer une classe 'wrapper' et surcharger sa méthode __new__()
pour renvoyer des instances des sous-classes spécialisées, par exemple:
class Registrar(object):
def __new__(self, domain):
if ...:
return RegistrarA(domain)
Elif ...:
return RegistrarB(domain)
else:
raise Exception()
De plus, afin de traiter des conditions non mutuellement exclusives, une question soulevée dans d’autres réponses, la première question à vous poser est de savoir si vous voulez que la classe wrapper, qui joue le rôle de répartiteur, régisse les conditions ou il le déléguera aux classes spécialisées. Je peux suggérer un mécanisme partagé, dans lequel les classes spécialisées définissent leurs propres conditions, mais le wrapper effectue la validation de la manière suivante (à condition que chaque classe spécialisée expose une méthode de classe permettant de vérifier s’il s’agit d’un registraire pour un domaine particulier, is_registrar_for (. ..) comme suggéré dans d'autres réponses):
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)
J'ai ce problème tout le temps. Si vous avez les classes incorporées dans votre application (et ses modules), vous pouvez utiliser une fonction. mais si vous chargez les plug-ins de manière dynamique, vous avez besoin de quelque chose de plus dynamique: enregistrer les classes auprès d'une fabrique via des métaclasses automatiquement.
Voici un modèle que je suis sûr d'avoir tiré de StackOverflow à l'origine, mais je n'ai toujours pas le chemin d'accès au message d'origine.
_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)
que diriez-vous de quelque chose comme
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()
Ici, une métaclasse collecte implicitement les classes de registres dans une ENTIT&EACUTE;S dict
class DomainMeta(type):
ENTITIES = {}
def __new__(cls, name, bases, attrs):
cls = type.__new__(cls, name, bases, attrs)
try:
entity = attrs['domain']
cls.ENTITIES[entity] = cls
except KeyError:
pass
return cls
class Domain(metaclass=DomainMeta):
@classmethod
def factory(cls, domain):
return DomainMeta.ENTITIES[domain]()
class RegistrarA(Domain):
domain = 'test.com'
def lookup(self):
return 'Custom command for .com TLD'
class RegistrarB(Domain):
domain = 'test.biz'
def lookup(self):
return 'Custom command for .biz TLD'
com = Domain.factory('test.com')
type(com) # <class '__main__.RegistrarA'>
com.lookup() # 'Custom command for .com TLD'
com = Domain.factory('test.biz')
type(com) # <class '__main__.RegistrarB'>
com.lookup() # 'Custom command for .biz TLD'