The term "Factory" encompasses a number of roughly related but different creational patterns (both formally described and informal), so it's important to clarify what you mean when you say you have "a factory".
Some of these are "custom constructor" kind of factories (like a static method that helps convey extra semantics via it's name, or does some work that's not appropriate or convenient for the constructor). Some of these factories are objects that let you partially initialize an instance, so that you can pass them around to complete the initialization process later. Some factories manage families of related objects in an abstract way.
What you have, although arguably a kind of a factory, isn't exactly any of the above. You wrote:
Consider a command-line application which can take a bunch of different parameters. One represents the type of IThing object to create, and the remaining are specific arguments for that particular type (connection strings, file paths, whatever). Some code needs to instantiate the proper type and pass the context-specific parameters.
The fact that the input here is coming from the outside of the program itself is an important detail. You have two concerns here: parsing the input, and instantiating the relevant objects. Your factory has to figure out which concrete class to instantiate - there's no way around that - but it doesn't have to understand the parameters; it's probably more manageable if you delegate that responsibility elsewhere.
What you could do is confine the knowledge about the parameters (and the logic to parse them) to the concrete classes themselves (or to some code closely associated with each class). E.g. have a static ("custom constructor") factory function on each class that accepts the raw string parameters (e.g. as a list, or as a dictionary of key-value pairs, if your raw input is keyword-based, and not positional). Each of the factory functions would accept these in the same format - you'd have a unified representation. You have to put the code that parses these parameters somewhere anyway, you might as well place it there. In some sense, each of these would be like a mini entry point to a small program; a mini main()
, if you will, except that it produces an instance. It's basically the old divide and conquer strategy: you don't have to solve the entire problem in one place.
Then just have your main factory identify which of these factory methods to call, and pass the rest of the parameters in this unified format. Beyond knowing how to extract them from the raw collection of program arguments (a command-line argument parsing library can make this easier), your top-level factory doesn't have to understand or know anything else about these parameters. Your main factory just needs to deal with the string that identifies the concrete type.
You can implement this identification logic in several different ways. First, you could hardcode it (bunch of if-s, a switch statement). Here, you can make it so that the code is aware of each of the concrete types, so in general, the classes wouldn't have to implement the shared IThing interface (although they might need to do so in your particular case - you haven't provided enough details).
You can go for a dictionary-driven approach, like in Ewan's answer, which lets you update the top-level factory more simply and concisely when you need to, or, if you have reflection capabilities, you could make use of that (perhaps coupled with a naming convention for your concrete factory functions) to make your main factory completely generic. But be careful there, as with any external input.
Here, you'd have to rely on some common IThing
interface with a set of abstract methods that make sense at the entry point of your application (like Run()
).
P.S. Not sure if your command-line scenario was just a motivating example for a more general problem, but when it comes to command-line apps, also consider the possibility that maybe such an application could be split into several different, smaller and more focused command-line tools, that wouldn't have to manage this complex instantiation logic internally.