Blog

Modular System Design, Leveraging Factories and Dynamic Discovery.

In software design, one of the recurring challenges is how to build systems that are both flexible and maintainable, especially when dealing with a variety of components that perform similar tasks in different ways. A powerful pattern that addresses this challenge is the combination of dynamic subclass discovery with a well-defined interface, implemented using Abstract Base Classes (ABCs) in Python. This pattern allows you to create a framework where new components can be added seamlessly, ensuring that the system remains modular and easily extendable.

from abc import ABC, abstractmethod

def get_subclasses(cls):
    for subclass in cls.__subclasses__():
        yield subclass
        yield from get_subclasses(subclass)

class BaseSolver(ABC):

    @property
    @abstractmethod
    def TYPE(self):
        pass

    @abstractmethod
    def run(self):
        pass

class SolverA(BaseSolver):
    TYPE = "A"

    def run(self):
        print("Solving A")

class SolverB(BaseSolver):
    TYPE = "B"

    def run(self):
        print("Solving B")

In this example, BaseSolver acts as a contract that all solvers must follow, ensuring they implement the TYPE property and the run method. The get_subclasses function dynamically discovers all subclasses, allowing the framework to automatically recognize and use any new solvers.

all_solvers = {cls.TYPE: cls for cls in get_subclasses(BaseSolver)}

if solver_cls := all_solvers.get("A"):
    solver_cls().run()  # Output: Solving A.

This pattern is powerful because it promotes loose coupling. Each solver class operates independently, and the framework only needs to know that they all adhere to the BaseSolver interface. ABCs enforce this contract, ensuring consistency and reducing errors while allowing the system to evolve by simply adding new subclasses.