Generics
Clean IoC has powerful generic features allowing you to use Generics to auto register all subclases. This allows you to use homogonous class structures that can be automatically registered within the container. Some features where this can be useful is in building event handlers or message handlers.
Generic Subclasses
Lets start with the below.
T = TypeVar("T")
class HelloCommand:
pass
class GoodbyeCommand:
pass
class CommandHandler(Generic[T]):
def handle(self, command: T):
pass
class HelloCommandHandler(CommandHandler[HelloCommand]):
def handle(self, command: HelloCommand):
print('HELLO')
class GoodbyeCommandHandler(CommandHandler[GoodbyeCommand]):
def handle(self, command: GoodbyeCommand):
print('GOODBYE')
container = Container()
container.register_generic_subclasses(CommandHandler)
h1 = container.resolve(CommandHandler[HelloCommand])
h2 = container.resolve(CommandHandler[GoodbyeCommand])
h1.handle(HelloCommand()) # prints 'HELLO'
h2.handle(GoodbyeCommand()) # prints 'GOODBYE'
To add another CommandHandler we simplly just declare the Command type and Handler class
class AlohaCommand:
pass
class AlohaCommandHandler(CommandHandler[AlohaCommand]):
def handle(self, command: AlohaCommand):
print('ALOHA')
h1 = container.resolve(CommandHandler[HelloCommand])
h2 = container.resolve(CommandHandler[GoodbyeCommand])
h3 = container.resolve(CommandHandler[AlohaCommand])
h1.handle(HelloCommand()) # prints 'HELLO'
h2.handle(GoodbyeCommand()) # prints 'GOODBYE'
h3.handle(AlohaCommand()) # prints 'ALOHA'
Leveraging Clean IoC's Resolver dependency you can take it a step further and build a dispatch mechanism
class CommandDispatcher:
def __init__(self, reslover: Resolver):
self.resolver = resolver
def dispatch(self, command):
command_type = type(command)
handler = self.resolver.resolve(CommandHandler[command_type])
handler.handle(command)
container.register(CommandDispatcher)
dispatcher = container.resolve(CommandDispatcher)
dispatcher.dispatch(HelloCommand()) # prints 'HELLO'
dispatcher.dispatch(GoodbyeCommand()) # prints 'GOODBYE'
dispatcher.dispatch(AlohaCommand()) # prints 'ALOHA'
Generic Decorators
There is also a special function in Clean IoC for handling generic decorators. This allows you to decorate your generic subclasses bit also have a concrete implementation of the decorator for that concrete generic type.
T = TypeVar("T")
class HelloCommand:
pass
class GoodbyeCommand:
pass
class CommandHandler(Generic[T]):
def handle(self, command: T):
pass
class HelloCommandHandler(CommandHandler[HelloCommand]):
def handle(self, command: HelloCommand):
print('HELLO')
class GoodbyeCommandHandler(CommandHandler[GoodbyeCommand]):
def handle(self, command: GoodbyeCommand):
print('GOODBYE')
class AVeryBigCommandHandlerDecorator(Generic[T]):
def __init__(self, handler: CommandHandler[T]):
self.handler = handler
def handle(self, command: T):
print('A VERY BIG')
self.handler.handle(command=command)
container = Container()
container.register_generic_subclasses(CommandHandler)
container.register_generic_decorator(CommandHandler, AVeryBigCommandHandlerDecorator)
h1 = container.resolve(CommandHandler[HelloCommand])
h2 = container.resolve(CommandHandler[GoodbyeCommand])
h1.handle(HelloCommand()) # prints 'A VERY BIG\nHELLO'
h2.handle(GoodbyeCommand()) # prints 'A VERY BIG\nGOODBYE'
Clean IoC will also respect generic dependencies of the generic decorator so you can have custom features per different concrete type.
T = TypeVar("T")
class HelloCommand:
pass
class GoodbyeCommand:
pass
class CommandHandler(Generic[T]):
def handle(self, command: T):
pass
class HelloCommandHandler(CommandHandler[HelloCommand]):
def handle(self, command: HelloCommand):
print('HELLO')
class GoodbyeCommandHandler(CommandHandler[GoodbyeCommand]):
def handle(self, command: GoodbyeCommand):
print('GOODBYE')
class PrependMessageGetter(Generic[T]):
def get_message(self):
pass
class HelloMessageGetter(PrependMessageGetter[HelloCommand]):
def get_message(self):
return "A VERY HAPPY"
class GoodbyeMessageGetter(PrependMessageGetter[GoodbyeCommand]):
def get_message(self):
return "A VERY SAD"
class PrependMessageCommandHandlerDecorator(Generic[T]):
def __init__(self, handler: CommandHandler[T], prepend_message_getter: PrependMessageGetter[T]):
self.handler = handler
self.prepend_message_getter = prepend_message_getter
def handle(self, command: T):
prepend_message = self.prepend_message_getter.get_message()
print(prepend_message)
self.handler.handle(command=command)
container = Container()
container.register_generic_subclasses(CommandHandler)
container.register_generic_subclasses(PrependMessageGetter)
container.register_generic_decorator(CommandHandler, AVeryBigCommandHandlerDecorator)
h1 = container.resolve(CommandHandler[HelloCommand])
h2 = container.resolve(CommandHandler[GoodbyeCommand])
h1.handle(HelloCommand()) # prints 'A VERY HAPPY\nHELLO'
h2.handle(GoodbyeCommand()) # prints 'A VERY SAD\nGOODBYE'