Inject require object depends on condition in constructor injection

╄→гoц情女王★ 提交于 2020-01-21 09:53:30

问题


I have an interface

public interface ITrnsitReport
{
    List<UserDefinedType> GetTransitReportData ();
}

And it has only one implementation which is

public class TransitReport : ITrnsitReport
{
    private IValidateInput _inputValidation = null;
    private readonly ITransitRepository _transitRepo = null;
    public TransitReport (IValidateInput inputValidation,
                            ITransitRepository transitRepo)
    {
        _inputValidation = inputValidation;
        _transitRepo = transitRepo;
    }
    public List<UserDefinedType> GetTransitReportData (string input1, string input2)
    {
        List<UserDefinedType> ppdcReportList = null;
        bool isValid = _inputValidation.IsInputValid (input1, input2);
        if (isValid)
        {
            ppdcReportList = _transitRepo.GetTransitData (input1, input2);
            // do something with data
        }
        return ppdcReportList;
    }
}

Now IValidateInput has two implementations such as PPDCValidateInput and PAIValidateInput. Again for ITransitRepository such as PPDCTransitRepository and PAITransitRepository. (where PAI and PPDC are business reports and each has different validation and repository)

I am using Unity Framework and have them registered them to UnityConfig.cs.

New I am trying to do is

public TransitInfo : ITransitInfo
{
    private ITrnsitReport _report = null;
    public TransitInfo (ITrnsitReport report)
    {
        _report = report;
    }
    public List<UserDefinedType> GetReportData (string reportType, string input1, string input2)
    {
        if (reportType.Equals ("PAI"))
        {
            _report.GetTransitReportData (input1, input2); //inject PAIValidateInput and PAITransitRepository objects
        }
        if else (reportType.Equals ("PPDC"))
        {
            _report.GetTransitReportData (input1, input2); //inject PPDCValidateInput and PPDCTransitRepository objects
        }
    }
}

When its "PAI" how can I inject PAIValidateInput and PAITransitRepository objects to TransitReport constructor and for "PPDC" PPDCValidateInput and PPDCTransitRepository objects.


回答1:


There are several ways to go.

1. Use named registrations.

container.RegisterType<IValidateInput, PPDCValidateInput>("PPDC");

container.RegisterType<IValidateInput, PAIValidateInput>("PAI");

And in your constructor:

public TransitReport ([Dependency("PPDC")]IValidateInput inputValidation,
                        ITransitRepository transitRepo)
{
    _inputValidation = inputValidation;
    _transitRepo = transitRepo;
}

Or in your registration:

container.Register<ITrnsitReport>(new InjectionConstructor(new ResolvedParameter<IValidateInput>("PPDC"));

The downside with this approach is that the consumer of the interface needs to know which implementation of the interface it wants, which kind of breaks the whole idéa of injecting an interface in the first place.


2. Use an Enum to define what it's validating.

public interface ITrnsitReport
{
    List<UserDefinedType> GetTransitReportData ();
    ValidateType ValidateType { get; }
}

public enum ValidateType
{
    PPDC = 1,
    PAI = 2
}

And then select it when you want to use it.

public TransitReport (IValidateInput[] inputValidation,
                        ITransitRepository transitRepo)
{
    _inputValidation = inputValidation.FirstOrDefault(x => x.ValidateType == ValidateType.PPC);
    _transitRepo = transitRepo;
}

The good part is that the logic is inside the interface itself, and not regarding the registration. However, it still requires the consumer of the interface to know what it wants.


3. Use different interfaces. (Probably the best option in my oppinion)

public interface IPAIValidateInput : IValidateInput
{

}

public interface IPPDCValidateInput : IValidateInput
{

}

And then register them separately in your container and inject the interface you actually want.

public TransitReport (IPAIValidateInput inputValidation,
                        ITransitRepository transitRepo)
{
    _inputValidation = inputValidation;
    _transitRepo = transitRepo;
}

Requires interfaces without no actual declaration. But it keeps the DI-logic more pure, in my oppinion.


4. Use a base class and combine them all.

First fix named registrations:

container.RegisterType<IValidateInput, PPDCValidateInput>("PPDC");
container.RegisterType<IValidateInput, PAIValidateInput>("PAI");
container.RegisterType<ITransitRepository, PPDCTransitRepository>("PPDC");
container.RegisterType<ITransitRepository, PAITransitRepository>("PAI");
container.RegisterType<ITransitReport, PAITransitReport>("PAI");
container.RegisterType<ITransitReport, PPDCTransitReport>("PPDC");

Then create a base class

public abstract class TransitReportBase : ITrnsitReport
{
    private readonly IValidateInput _inputValidation;
    private readonly ITransitRepository _transitRepo;

    protected TransitReportBase(IValidateInput inputValidation,
                            ITransitRepository transitRepo)
    {
        _inputValidation = inputValidation;
        _transitRepo = transitRepo;
    }

    public List<UserDefinedType> GetTransitReportData(string input1, string input2)
    {
        List<UserDefinedType> ppdcReportList = null;
        bool isValid = _inputValidation.IsInputValid(input1, input2);
        if (isValid)
        {
            ppdcReportList = _transitRepo.GetTransitData(input1, input2);
            // do something with data
        }
        return ppdcReportList;
    }
}

public class PAITransitReport : TransitReportBase
{

    public PAITransitReport([Dependency("PAI")] IValidateInput inputValidation,
                            [Dependency("PAI")] ITransitRepository transitRepo) : base(inputValidation, transitRepo)
    {

    }
}

public class PPDCTransitReport : TransitReportBase
{
    public PPDCTransitReport([Dependency("PPDC")] IValidateInput inputValidation,
                            [Dependency("PPDC")] ITransitRepository transitRepo) : base(inputValidation, transitRepo)
    {

    }
}

And use a Factory to resolve them:

public class TransitReportFactory : ITransitReportFactory
{
    private readonly IUnityContainer _container;

    public TransitReportFactory(IUnityContainer container) // container is injected automatically.
    {
        _container = container;
    }

    ITrnsitReport Create(string reportType)
    {
        return _container.Resolve<ITrnsitReport>(reportType);
    }
}

Thanks to the concrete classes PPDCTransitReport and PAITransitReport we can make sure that the right dependency is injected to the base class.

How to use the factory:

First, register it with Unity.

container.RegisterType<ITransitReportFactory, TransitReportFactory>();

Then inject it in your TransitInfo.

public TransitInfo : ITransitInfo
{
    private ITransitReportFactory _transitReportFactory;
    public TransitInfo (ITransitReportFactory transitReportFactory)
    {
        _transitReportFactory = transitReportFactory;
    }
    public List<UserDefinedType> GetReportData (string reportType, string input1, string input2)
    {
        // Create your transitreport object.
        ITransitReport report = _transitReportFactory.Create(reportType);
        var reportData = report.GetTransitReportData (input1, input2);
        return reportData;
    }
}

But I have to say that the factory itself doesn't add that much to the solution. If you don't mind the service locator-pattern you can inject IUnityContainer directly to TransitInfo.




回答2:


You have several options, the one I prefer is to use named registrations:

container.RegisterType<IValidateInput, PPDCValidateInput>("PPDC");        
container.RegisterType<IValidateInput, PAIValidateInput>("PAI");

Then you need to resolve the ITransitInfo using overrides:

container.Resolve<ITransitInfo>(new InjectionConstructor(new ResolvedParameter<IValidateInput>("PPDC"), ...)


来源:https://stackoverflow.com/questions/37184286/inject-require-object-depends-on-condition-in-constructor-injection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!