I'm building a Spring Batch application that originally had five database readers for five different data types. I now need to support XML and CSV sources as well as different reading modes, such as internal and external. That creates up to 5 data types × 3 sources × 2 modes = 30 combinations. Creating a separate class for every combination, such as ClientDatabaseInternalReader, would work but feels difficult to maintain. What design would better separate these dimensions? Would Strategy, Abstract Factory, Bridge, dependency injection, or another approach help?
4 Answers
Separate the things that vary instead of encoding every combination in one class. Define interfaces or strategies for parsing the source, representing the data type, and applying the mode. Then compose the required implementations through dependency injection. A reader can be given the appropriate source parser and mode handler at construction time rather than using inheritance for every combination.
You can also use a single reader configured with source and mode values, then delegate the source-specific work to injected handlers. An enum or configuration object can select the strategies, but try not to hide all the behavior behind a large switch statement. The important part is that each independent dimension has one focused implementation and the application composes them.
First identify what internal and external actually change. If the difference is only a small conditional or a couple of options, they may not deserve separate classes at all. Keep the data types as simple model objects, keep parsing separate from processing, and make the processing components stateless and testable. Composition is usually a better fit here than a large inheritance hierarchy.
A useful way to avoid multiplication is to introduce an intermediate representation. Have each XML, CSV, or database adapter convert its input into a common format. Then the five data-type handlers operate on that format. Instead of 5 × 3 readers, you have roughly 3 source parsers plus 5 data-type handlers. The same idea can be applied to the modes if they are processing rules rather than parsing rules.
This also makes adding a new source much cheaper: you add one adapter to the intermediate format instead of implementing a reader for every data type.

That is essentially the Strategy pattern, and dependency injection makes it easier to provide the right strategies without hard-coding all the combinations.