73

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

ドメイン名を指定すると、拡張子に基づいて正しいレジストラー クラスをロードするドメイン クラスを作成したいと考えています。

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

9 に答える 9

85

関数を使えば大丈夫だと思います。

さらに興味深い質問は、ロードするレジストラをどのように決定するかということです。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し、それぞれがサポートするドメインの決定をそれらに委任できます。

于 2009-01-19T06:49:03.443 に答える
23

RegistrarARegistrarBはおそらく機能を共有し、 Abstract Base Classから派生する可能性がありますが、異なるレジストラに個別のクラスが必要であると仮定すると(例では明らかではありません)、ソリューションは問題ないように見えます。

関数の代わりにfactory、dict を指定してレジストラー クラスにマッピングすることもできます。

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

それで:

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

クラスではなくインスタンスを返しているため、ここではクラス ファクトリを実際に行っているわけではありません。

于 2009-01-19T06:39:00.390 に答える
11

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

私はこのアプローチをうまく使っています。

于 2009-02-13T09:51:01.903 に答える
4

私はいつもこの問題を抱えています。アプリケーション (およびそのモジュール) にクラスが埋め込まれている場合は、関数を使用できます。しかし、プラグインを動的にロードする場合は、より動的なものが必要です。メタクラスを介して自動的にクラスをファクトリに登録します。

これは、もともと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)
于 2013-06-05T18:10:20.310 に答える
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()
于 2013-06-05T17:13:53.333 に答える
0

さて、これは、修正および拡張された Alec Thomas の回答に基づく回答です。マルチレベルの継承とあいまいさを処理します。_resolve が単純な一意性のチェックよりも複雑で、変更される可能性が高い場合は、クラス メソッドではなく、引数として指定できます。

基本クラス モジュール bbb.py:

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Sequence, Type


class Base(ABC):

    def __init__(self, *args, **kwargs):
        ...

    @classmethod
    def isit(cls, _s: str) -> bool:
        return False

    @classmethod
    def from_str(cls, s: str, *args, **kwargs) -> Base:
        subs = cls._findit(s)
        sc = cls._resolve(s, subs)
        return sc(*args, **kwargs)

    @classmethod
    def _findit(cls, s: str) -> Sequence[Type[Base]]:
        subs = [cls] if cls.isit(s) else []
        subs += [ssc for sc in cls.__subclasses__() for ssc in sc._findit(s)]
        return subs

    @classmethod
    def _resolve(cls, s: str, subs: Sequence[Type[Base]]) -> Type[Base]:
        if len(subs) == 0:
            raise Exception(f'Cannot find subclass for {s}')

        if len(subs) > 1:
            raise Exception(
                f'Cannot choose unique subclass for {s}: {subs}')
        sc = subs[0]
        return sc


class B(Base):
    @classmethod
    def isit(cls, s: str) -> bool:
        res = s == 'b class'
        return res
    enter code here

派生クラス モジュール ccc.py:

from bbb import Base


class C(Base):
    @classmethod
    def isit(cls, s: str) -> bool:
        res = s == 'c class'
        return res


class CC(Base):
    @classmethod
    def isit(cls, s: str) -> bool:
        res = s == 'cc class'
        return res

使い方:

In [4]: from bbb import Base

In [5]: import ccc

In [6]: Base.from_str('b class')
Out[6]: <bbb.B at 0x1adf2665288>

In [7]: Base.from_str('c class')
Out[7]: <ccc.C at 0x1adf266a908>

In [8]: Base.from_str('cc class')
Out[8]: <ccc.CC at 0x1adf2665608>
于 2021-09-25T13:13:00.630 に答える