Following the design patterns proposed by the Gang of Four, you can use the Abstract Factory pattern to instantiate the different implementations. For instance:
abstract class Parent {
// some abstract behaviour
}
interface ParentFactory {
Parent Make();
}
class Impl1 : Parent {
// some concrete behaviour
}
class Impl1Factory : ParentFactory {
public Parent Make() {
return new Impl1();
}
}
class Impl2 : Parent {
// some concrete behaviour
}
class Impl2Factory : ParentFactory {
public Parent Make() {
return new Impl2();
}
}
This will isolate the responsibilities of creating instances of each subtype to its own classes, which can be placed in different namespaces or even separate libraries, allowing you to make isolated changes whenever needed.
But just the abstract factory pattern by itself is not very friendly to use, so a common practice is to make some sort of registry, where the rest of the system can query for a specific implementation. An example of that would be to use something similar to the Service Locator pattern, like so:
class ParentFactories {
private Dictionary<string, ParentFactory> factories;
// constructor and such - probably private and following the Singleton pattern
public void Register(string key, ParentFactory factory) {
factories.Add(key, factory);
}
public Parent Make(string key) {
return dictionary[key].Make();
}
}
In the above example, I used a string
for the key
, but that was just to show how to register a factory in the locator using a Dictionary
, which is the common approach. You can use anything that is relevant to your business rules, including your own objects as some sort of identifier/query object that tells the locator which implemention is required.
Anyway, the point of a Locator-like object is to allow the system to add and/or remove different implementation os the ParentFactory
at runtime. Following this pattern, you can separate the concrete implementations of your abstract class into different modules of the application, and have each module register its implementations on the locator at boot-up.
<Identifier, AbstractFactory>
, is the way to solve your problem. When a user creates new implementation, a new factory is created (you'll need some logic for this), this factory is added to the collection and later looked up and used when necessary.