Instantiating classes directly via constructors often couples client code tightly with concrete implementation details. This friction becomes pronounced when objects require multi-step configuration, validation, or conditional setup based on runtime parameters. Instead of scattering initialization logic across multiple consumer classes, centralizing this responsibility behind a dedicated creation method streamlines consumption and enforces consistency.
Consider a scenario where a rendering engine needs to generate graphical elements with predefined configurations. Direct construction demands repetitive boilerplate:
public sealed class GraphicalElement
{
public string Label { get; init; }
public double FillOpacity { get; init; }
public int BorderColorHex { get; init; }
public GraphicalElement(string label, double opacity, int border)
{
Label = label;
FillOpacity = opacity;
BorderColorHex = border;
}
}
Consuming code would repeatedly invoke new GraphicalElement("Alert", 0.85, 0xFF4500). If business rules dictate specific preset combinations, embedding defaults within the class quickly fractures when new requirements emerge.
The solution involves routing instantiation through a static provider that evaluates input criteria and returns pre-configured instances. This isolates construction logic from business workflows:
public static class ElementFactory
{
public static GraphicalElement BuildStandardUI(string styleKey) => styleKey switch
{
"warning" => new GraphicalElement("Warning Panel", 0.9, 0xFFA500),
"critical" => new GraphicalElement("Error Overlay", 1.0, 0xDC143C),
_ => throw new ArgumentException($"Unknown UI style: {styleKey}")
};
}
Client code now delegates configuration concerns entirely to the factory:
var activePanel = ElementFactory.BuildStandardUI("critical");
This pattern scales effectively when applied to inheritance hierarchies. By targeting interfaces rather than cocnrete types, factories enable polymorphic object generation without exposing underlying construction mechanics.
Define a contract for extensible components:
public interface ICommandHandler
{
string CommandName { get; }
int Priority { get; }
void Execute();
}
Implement distinct handlers fulfilling the contract:
public class DataSyncHandler : ICommandHandler
{
public string CommandName => "SyncData";
public int Priority => 1;
public void Execute() { /* Implementation */ }
}
public class CacheFlushHandler : ICommandHandler
{
public string CommandName => "ClearCache";
public int Priority => 2;
public void Execute() { /* Implementation */ }
}
Route requests through a unified factory method:
public static class HandlerRegistry
{
public static ICommandHandler ResolveHandler(string requestType) => requestType.ToLower() switch
{
"sync" => new DataSyncHandler(),
"flush" => new CacheFlushHandler(),
_ => throw new InvalidOperationException("Unsupported operation requested")
};
}
Invocations remain decoupled from concrete types:
ICommandHandler processor = HandlerRegistry.ResolveHandler("flush");
processor.Execute();
Encapsulating instantiation logic behind a single entry point eliminates redundant configuration code and centralizes maintenance responsibilities. Managing numerous variant configurations within a single class inevitably increases cognitive load and test complexity. Extending the factory to support newly introduced types necessitates direct modification of existing source files, which contradicts strict extension-over-modificatoin principles. Architectures requiring frequent instantiation variations typically migrate toward service locators or containerized dependency injection to distribute creation responsibilities across discrete modules.