Date: 4/27/2020
Scenario:
- I want to have a single Interface Class that has a Base Implementation Class with multiple instance implementations for other Inheriting Classes
For example (let's say a class to extract data from multiple data sources) as follows:
Interaface: Extractor
BaseCalss: Base Extractor
Instances: Extractor 1
, Extractor 2
, ... , Extractor n
From the above, I aways want to call Extractor
only with specifying which class to use e.g.
a = Extractor() # --> by default choose the BaseClass
a = Extractor(name="Extractor1") # --> chooses Extractor 1 class
a = Extractor(name="Extractor2") # --> chooses Extractor 2 class
# ... and so on
Important thing to notice is that the instance classes build on top of the Base Class functionality
Idea:
Use a meta class to auto register the member classes.
For example, to build the above in code, it can be something like this:
from abc import ABCMeta, abstractmethod
_registered = {}
# -- Class Registrar
class ExtractorMeta(ABCMeta):
def __init__(cls, clsname, bases, methods):
super().__init__(clsname, bases, methods)
print(f"registering {cls.__name__}")
_registered[cls.__name__] = cls
# -- Interface Class
class Extractor(metaclass=ExtractorMeta):
def __init__(self, name: str = "BaseExtractor"):
if name not in _registered:
raise NotImplementedError(f"Calss '{name}' is not registered!!")
self.__class__ = _registered[name]
@abstractmethod
def get(self): raise NotImplementedError
# -- Base Class
class BaseExtractor(Extractor):
def get(self): return "calling requests.get"
# -- Instance Classes
class TwitterExtractor(BaseExtractor):
def get(self): return "getting twitters data"
class YouTubeExtractor(BaseExtractor):
def get(self): return "getting YouTube data"
_registered
# {'Extractor': __main__.Extractor,
# 'BaseExtractor': __main__.BaseExtractor,
# 'TwitterExtractor': __main__.TwitterExtractor,
# 'YouTubeExtractor': __main__.YouTubeExtractor}
Then calling ....
BaseExtractor().get() # 'calling requests.get'
BaseExtractor("TwitterExtractor").get() # 'getting twitters data'
BaseExtractor("YouTubeExtractor").get() # 'getting YouTube data'
The highlighted quote below is very true; at least in my case.