Class factory in Python

后端 未结 7 2320
半阙折子戏
半阙折子戏 2020-11-28 18:37

I\'m new to Python and need some advice implementing the scenario below.

I have two classes for managing domains at two different registrars. Both have the same inte

相关标签:
7条回答
  • 2020-11-28 19:01

    Assuming you need separate classes for different registrars (though it's not obvious in your example) your solution looks okay, though RegistrarA and RegistrarB probably share functionality and could be derived from an Abstract Base Class.

    As an alternative to your factory function, you could specify a dict, mapping to your registrar classes:

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

    Then:

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

    One quibble: You're not really doing a Class Factory here as you're returning instances rather than classes.

    0 讨论(0)
  • 2020-11-28 19:02

    In Python you can change the actual class directly:

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

    And then following will work.

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

    I'm using this approach successfully.

    0 讨论(0)
  • 2020-11-28 19:06

    Here a metaclass implicitly collects Registars Classes in an 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'
    
    0 讨论(0)
  • 2020-11-28 19:08

    how about something like

    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()
    
    0 讨论(0)
  • 2020-11-28 19:09

    I think using a function is fine.

    The more interesting question is how do you determine which registrar to load? One option is to have an abstract base Registrar class which concrete implementations subclass, then iterate over its __subclasses__() calling an is_registrar_for() class method:

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

    This will let you transparently add new Registrars and delegate the decision of which domains each supports, to them.

    0 讨论(0)
  • 2020-11-28 19:12

    I have this problem all the time. If you have the classes embedded in your application (and its modules) then you can use a function; but if you load plugins dynamically, you need something more dynamic -- registering the classes with a factory via metaclasses automatically.

    Here is a pattern I'm sure I lifted from StackOverflow originally, but I don't still have the path to the original post

    _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)
    
    0 讨论(0)
提交回复
热议问题