73

我是 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):
        ...

我想创建一个域类,给定一个域名,根据扩展名加载正确的注册商类,例如

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

我认为使用函数很好。

更有趣的问题是如何确定要加载哪个注册商?一种选择是拥有一个抽象基类 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 并将每个支持哪些域的决定委托给它们。

于 2009-01-19T06:49:03.443 回答
23

假设您需要为不同的注册商提供单独的类(尽管在您的示例中并不明显),您的解决方案看起来还不错,尽管RegistrarARegistrarB可能共享功能并且可以从Abstract Base Class派生。

作为您的factory函数的替代方案,您可以指定一个字典,映射到您的注册器类:

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 回答
9

您可以创建一个“包装器”类并重载其__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)
于 2016-05-27T06:34:03.497 回答
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 回答
0

由于这些方法可能是共享的,因此使用一些基类是有意义的。 getattr可以在工厂函数中用于动态调用另一个类。

计算 registrartype 的逻辑不应该是这些类的一部分,而应该是一些辅助函数。

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
于 2021-01-17T14:32:02.240 回答
-1

这里的元类隐式地在ENTITIES dict中收集 Registar 类

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'
于 2018-05-25T08:02:46.127 回答