Using Registry Patterns for Cleaner Conditional Logic

Replace Complex Conditional Logic with a Registry Class
Complex conditional logic has a way of starting small and quietly becoming unmaintainable. A few if statements turn into nested branches, duplicated logic, and fragile code paths. Python’s registry pattern offers a simple, scalable alternative that keeps your logic explicit, testable, and easy to extend—without touching existing code.
1. The Problem with Growing Conditional Logic
Most conditional complexity starts in one of two ways:
Method 1: With a long if / elif / else statement that dispatches logic based on function input:
1def process_event(event_type, payload):2if event_type == 'user_created':3handle_user_created(payload)4elif event_type == 'user_deleted':5handle_user_deleted(payload)6elif event_type == 'user_updated':7handle_user_updated(payload)8else:9raise ValueError('Unknown event type')
Method 2: Or with a dictionary that stores the function to run:
1# Dictionary definition2event_handler = {3'user_created': handle_user_created,4'user_deleted': handle_user_deleted,5'user_updated': handle_user_updated6}78# Run the selected function dynamically9func_to_run = event_handler['user_created']10response = func_to_run()
This works completely fine-- until it doesn't.
Common failure modes:
- Adding a new case requires modifying existing code
- Logic becomes scattered
- Testing individual branches gets harder
- Merge conflict become frequent
What you really want is open-for-extension, closed-for-modification behavior.
2. Introducing the Registry Pattern
At its core, a registry is just a dictionary that is managed via a class or function.
Setup:
1class Registry:2def __init__(self):3self._registry = {}45def register(self, name, func):6self._registry[name] = func78def get(self, name):9return self._registry[name]1011def run(self, name: str, **kwargs):12func = self.get(name)13return func(**kwargs)
Usage:
1from my_project.other_module import important_function23my_registry = Registry()4my_registry.register('important_function', important_function)56# Can be used in any other module where you import "my_registry"7my_registry.run('important_function')
This registry class has 4 critical components:
- registry dictionary: This is defined in
__init__and is used to reference the "registered" functions. - register: This function simple adds a python function to the registry dictionary with the given name.
- get: This function retreives the function by the name defined in the
registerfunction. - run: This function runs the function with the given keyword arguments.
You can see that the setup of the registry pattern is extremely simple and adds immense power with minimal code.
3. Enhancing Registries to Promote Scalability
Example 1: Add name checking and error handling to ensure the function you attempting to call exists. And also raise an error when you attempt to register a function that already is registered.
1def register(self, name, func):2# This if statement makes sure we can't overwrite an existing function3if name in self._registry:4raise KeyError(f'{name} already exists in the registry.')5self._registry[name] = func67def get(self, name):8# This if statements makes sure we don't run a function that doesn't exist9if name not in self._registry:10raise ValueError(f'{name} does not exist in the registry.')11return self._registry[name]
Example 2: Adding other business logic before a function is run. Let's say you want to ensure the same logic is executed every time one of these registered functions runs, a good example of this may be logging. This is extremely simple with a registry pattern.
1def run(self, name: str, **kwargs):2my_logger.log_info(f'Function with name {name} is about to run...')3func = self.get(name)4return func(**kwargs)
This can, naturally, be extended to many other use cases as your system scales and needs additional functionality.
4. Adding Decorators to Registries
Importing every function that needs to be registered into the registry module can quickly become unwieldy. Using a decorator solves this by allowing each function to register itself at definition time within its own module.
Setup:
1def register(self, name):2def decorator(func):3self._registry[name] = func4return func5return decorator
Usage:
1from registry_definition_module import my_registry23my_registry.register('important_function')4def important_function():5return 2 + 2
This register function replaces the earlier implementation. It automatically adds the decorated function to the class-level registry, allowing it to be invoked anywhere the registry is imported.
5. Why Registries Age Better Than Conditionals
Registries give you:
- Linear growth instead of exponential branching
- Clear ownership of logic
- Easy test isolation
- Extensability
Final Thought
Registry patterns are deceptively simple--but they unlock a level of composability that scales from small scripts to production systems.