Pythonが初めてなので、以下のシナリオを実装するためのアドバイスが必要です。
2つの異なるレジストラでドメインを管理するための2つのクラスがあります。両方とも同じインターフェースを持っています。
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)
関数を使用するのは問題ないと思います。
より興味深い質問は、どのレジストラをロードするかをどのように決定するのですか? 1つのオプションは、サブクラスを具体的に実装する抽象ベースRegistrarクラスを作成し、__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
sを透過的に追加し、それぞれがサポートするドメインの決定をそれらに委任できます。
レジストラごとに個別のクラスが必要であると仮定すると(例では明らかではありませんが)、ソリューションは問題ないように見えますが、RegistrarAおよびRegistrarBはおそらく機能を共有しており、 Abstract Base Class から派生できます。
factory
関数の代わりに、ディクショナリを指定して、レジストラークラスにマッピングできます。
Registrar = {'test.com': RegistrarA, 'test.biz': RegistrarB}
次に:
registrar = Registrar['test.com'](domain)
1つのqui言:クラスではなくインスタンスを返すため、ここでは実際にクラスファクトリを実行していません。
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()
私はこのアプローチをうまく使っています。
「ラッパー」クラスを作成し、その__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)
私は常にこの問題を抱えています。アプリケーション(およびそのモジュール)にクラスが埋め込まれている場合、関数を使用できます。ただし、プラグインを動的にロードする場合は、さらに動的なものが必要です。メタクラスを介してクラスをファクトリに自動的に登録します。
ここに元々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)
のようなものはどうですか
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()
ここで、メタクラスは暗黙的に[〜#〜] entities [〜#〜] 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'