Unity DI与工厂模式选型咨询:复杂计算器模块解耦方案
Great question! Your code review team is absolutely right to flag the tight coupling with Unity—this makes your calculator hard to test, inflexible to DI framework changes, and mixes business logic with container management. Let’s walk through how to refactor this using the factory pattern to fix these issues.
First: Identify the Coupling Problems in Your Current Code
TotalAnnualCostCalculatordirectly initializes and depends onUnityContainerin its constructor, tying your core calculation logic to a specific DI framework.- The use of
container.ResolveAll<ICalculator>()mixes dependency resolution with business logic, violating the single responsibility principle. - Reinitializing the container every time a
TotalAnnualCostCalculatoris created could lead to redundant object creation and resource waste.
Step 1: Define an Abstract Factory Interface
Create an abstraction that hides how calculators are retrieved, so your business logic doesn’t need to know about Unity at all:
public interface ICalculatorFactory { // Returns all registered cost calculators IEnumerable<IEffectiveAnnualCostCalculator> GetAllCalculators(); }
Step 2: Implement a Unity-Specific Factory
Move all Unity-related resolution logic into a concrete factory implementation. This keeps the DI framework details contained in one place:
public class UnityCalculatorFactory : ICalculatorFactory { private readonly IUnityContainer _container; // Inject the container (we only need this here, not in the core calculator) public UnityCalculatorFactory(IUnityContainer container) { _container = container; } public IEnumerable<IEffectiveAnnualCostCalculator> GetAllCalculators() { return _container.ResolveAll<IEffectiveAnnualCostCalculator>(); } }
Step 3: Refactor TotalAnnualCostCalculator to Depend on the Abstract Factory
Update your core calculator to use the factory instead of directly interacting with Unity. Now it only cares about getting calculators, not how they’re created:
public class TotalAnnualCostCalculator { private readonly ICalculatorFactory _calculatorFactory; // Inject the abstract factory (no more container initialization here!) public TotalAnnualCostCalculator(ICalculatorFactory calculatorFactory) { _calculatorFactory = calculatorFactory; } public AnnualCostCharges CalculateTotalAnnualCost(Parameters product) { var calculators = _calculatorFactory.GetAllCalculators().ToList(); var totalCharges = new AnnualCostCharges(); // Fix: Parallel.ForEach was ignoring results before—add thread-safe aggregation Parallel.ForEach(calculators, calculator => { var individualCharges = calculator.CalculateAnnualCost(product); // Use a lock to safely accumulate results (adjust based on your AnnualCostCharges structure) lock (totalCharges) { totalCharges.InvestmentManagementCharge += individualCharges.InvestmentManagementCharge; totalCharges.AdministrationCharge += individualCharges.AdministrationCharge; totalCharges.AdviceCharge += individualCharges.AdviceCharge; totalCharges.OtherCharge += individualCharges.OtherCharge; } }); return totalCharges; } }
Step 4: Update Container Registration
Register your new factory in ContainerFactory so Unity can inject it into TotalAnnualCostCalculator:
public static class ContainerFactory { public static IUnityContainer Container { get; private set; } public static IUnityContainer InitializeContainer() { var container = new UnityContainer(); RegisterDependencies(container); return container; } private static void RegisterDependencies(UnityContainer container) { // Register the factory first container.RegisterType<ICalculatorFactory, UnityCalculatorFactory>(); // Keep your existing registrations container.RegisterType<ICalculatorStrategyFactory, CalculatorStrategyFactory>("Factory"); container.RegisterType<IEffectiveAnnualCostCalculator, InvestmentManagementChargeCalculator>( "IMCChargeCalculator", new InjectionConstructor(new ResolvedParameter<ICalculatorStrategyFactory>("Factory"))); container.RegisterType<IEffectiveAnnualCostCalculator, AdministrationChargeCalculator>("AdministrationChargeCalculator"); container.RegisterType<IEffectiveAnnualCostCalculator, AdviceChargeCalculator>("AdviceChargeCalculator"); container.RegisterType<IEffectiveAnnualCostCalculator, OtherChargeCalculator>("OtherChargeCalculator"); container.RegisterType<IInvestmentManagementChargeCalculator, LumpSumIMCCalculator>("LumpSumIMCCalculator"); container.RegisterType<IInvestmentManagementChargeCalculator, DebitOrderIMCCalculator>("DebitOrderIMCCalculator"); } public static void SetupContainer(IUnityContainer container) { Container = container; } }
Step 5: Update API Usage
Now your API code resolves the calculator from the container (instead of newing it up directly), letting Unity handle the factory injection:
// Initialize container once (not every time you need a calculator) var container = ContainerFactory.InitializeContainer(); ContainerFactory.SetupContainer(container); // Resolve the calculator from the container var calc = container.Resolve<TotalAnnualCostCalculator>(); var totalCharges = calc.CalculateTotalAnnualCost(prod);
Key Benefits of This Refactor
- Decoupling:
TotalAnnualCostCalculatorno longer knows anything about Unity—if you ever switch to another DI framework (like Autofac or Microsoft.Extensions.DependencyInjection), you only need to implement a newICalculatorFactoryfor it. - Testability: For unit tests, you can create a mock
ICalculatorFactorythat returns test-specific calculators, no need to spin up a real Unity container. - Single Responsibility: Your core calculator focuses solely on calculating total costs, while the factory handles retrieving calculator instances.
- Reduced Resource Waste: The container is initialized once, not every time a calculator is created.
内容的提问来源于stack exchange,提问作者shashi kumar

