Your question is tagged with the very techniques that help eliminate duplicate conditionals in your code: polymorphism and the strategy pattern. Really, though, the strategy pattern is just a fancy rephrasing of polymorphism. You haven't provided much information in your question, so giving you an authoritative solution is impossible. The key here is to develop your code analysis skills.
Consider replacing conditionals checking for a "mode" with new abstractions. Each time you check if mode == "x"
you might have identified a missing abstraction. Just be aware that true abstractions can be substituted with one another, and client code (the code using those abstractions) should not need to be changed. This is the "L" in the SOLID principals of object-oriented programming: the Liskov Substitution Principal.
Code that changes depending on the "mode" could probably be refactored into additional classes which may or may not use inheritance. Python offers more than classes when defining abstractions. Functions can be polymorphic abstractions as well. You can pass a method reference to another method to encapsulate behavior. You don't always need an object.
The second challenge will be identifying the dependencies required by these new abstractions. Some dependencies are required for initialization. Other dependencies might only be known at runtime. Dependencies required for initialization become constructor parameters. Dependencies only known at runtime (or at the time a method is called) become parameters to a method.
If you already have abstractions, yet you still check if isinstance(model, Model1)
, you have likely created an incorrect or incomplete abstraction. Checking which class an object inherits from at runtime indicates the implementation details of that abstraction "leak" through to the client code. This is something we call a "leaky abstraction". Code that detects an object's class, and then does any of the following is immediately suspect:
- Passes different method parameters to methods with the same name.
- Processes method results differently.
- Calls different methods.
- Any combination of the above.
You will need to analyze that code as well. Fixing this will involve refactoring or moving code between classes, and creating new classes to encapsulate the conditional behavior.
Identifying new abstractions that properly use dependency injection and inversion of control lead you to start building a class or file used to read configuration (via files or command line arguments) and initialize the whole application. This is known as the Composition Root — the point of entry into the application where the abstractions you use are determined and then wired together into the cohesive object graph necessary to execute a particular use case.
A side effect of polymorphism is the removal of duplicate conditionals, not the removal of all conditionals (related: How does Dependency Inversion solve switch cases?). Checking for an application mode or an object's class is not a conditional you should need.
Improving the design will likely involve a major refactoring of the existing code. This is not an easy task. There isn't a magic design pattern that will fix this, because it appears the issues are systemic. Whether or not rewriting that much code is worthwhile depends on the complexity, test coverage and how critical this code is for the organization.