从方法中删除条件 "new" 关键字依赖项

Removing conditional "new" keyword dependency from a method

我目前正在学习 DI 和 IoC 原理。所以,我偶然发现了这样一种情况,我有一个方法具有无法通过构造函数注入的依赖项。另外,我不能将它作为方法参数传递,因为该实例的创建是有条件的+它只能用它自己的参数实例化。这是我的 Employee 和 WorkYear classes 的超级简化版本:

public abstract class Employee
{
    private List<WorkYear> _workYears;
    // other private fields....

    protected Employee(IDependency1 dep, etc...)
    {
         WorkYears = new List<WorkYear>();

         // other components initialization....
    }

    public IEnumerable<WorkYear> WorkYears
    {
        get => _workYears.AsReadOnly();
        private set => _workYears = value.ToList();
    }

    public void StartWorking(DateTime joinedCompany)
    {
        List<PayPeriod> periods = // Calculating periods...
        WorkYear year = WorkYears.FirstOrDefault(y => y.CurrentYear == joinedCompany.Year);

        if (year == null)
        {
            // Here is the problem:
            year = new WorkYear(joinedCompany.Year, this, periods);

            AddYear(year);
        }
        else
        {
            // Logic when year not null
        }

        year.RecalculateAllTime();
    }

    public void AddYear(WorkYear workYear) => _workYears.Add(workYear);

    // More code...
}


public class WorkYear  
{ 
    public WorkYear(int currentYear, Employee employee, List<PayPeriod> periods)
    {
        Employee = employee;
        EmployeeId = employee.Id;
        CurrentYear = currentYear;
        PayPeriods = periods ?? new List<PayPeriod>();

        foreach (PayPeriod period in PayPeriods)
        {
            period.WorkYear = this;
            period.WorkYearId = Id;
        }
    }

     // More code...
}

如您所见,如果 Employee 还没有,我只需要一个新的 WorkYear 实例。我发现了一个建议使用简单工厂 class 来解决类似问题的线程。这样的解决方案可以工作,但是我如何处理没有这些参数就无法实例化 WorkYear 的参数?

很高兴看到有关如何解决此问题的示例。

我不完全确定你想做什么,或者为什么“新的工作年需要抽象”。你会看到我们真的不能走得太远。但是让我们来看看:

我们可以定义一个通用接口:

public interface IFactory<T, TData>
{
    T Create(TData data);
}

然后描述我们的数据并实现:

public class WorkYearData
{
    WorkYearData(int currentYear, Employee employee, List<PayPeriod> periods)
    {
        CurrentYear = currentYear;
        Employee = employee;
        Periods = periods;
    }

    public int CurrentYear { get; }

    public Employee Employee { get; }

    public List<PayPeriod> Periods { get; }
}

public class WorkYearFactory : IFactory<WorkYear, WorkYearData>
{
    public WorkYear Create(WorkYearData data) 
        => new WorkYear(data.CurrentYear, data.Employee, data.Periods);
}

最后更新员工

//Then
year = new WorkYear(joinedCompany.Year, this, periods);
//Now
year = _factory.Create(new WorkYearData(joinedCompany.Year, this, periods));

如您所见,我们实际上并没有抽象太多,只是添加了间接。

这是使用界面隐藏真实对象的替代方法。

创建一个界面来代表您的工作年限:

public interface IWorkYear
{
    int CurrentYear { get; }

    void RecalculateAllTime();
}

让你的 WorkYear class 实施它:

public class WorkYear : IWorkYear
{
    //Implementation code here
}

创建一个接口来表示提供者:

public interface IWorkYearProvider
{        
    IWorkYear CreateNewWorkYear(int year, Employee employee, List<PayPeriod> periods);
}       

让你的 Employee class 使用 IWorkYear 接口而不是实际对象:

public abstract class Employee
{
    private List<IWorkYear> _workYears;

    //Notice the dependency injection here
    public Employee(IWorkYearProvider workYearProvider)
    {
        WorkYearProvider = workYearProvider;
    }

    //Reference to the dependency
    public IWorkYearProvider WorkYearProvider { get; private set; }

    public IEnumerable<IWorkYear> WorkYears { get => _workYears.AsReadOnly(); private set => _workYears = value.ToList(); }
    public object Id { get; internal set; }

    public void StartWorking(DateTime joinedCompany)
    {
        List<PayPeriod> periods = null; // Calculating periods...
        IWorkYear year = WorkYears.FirstOrDefault(y => y.CurrentYear == joinedCompany.Year);

        if (year == null)
        {
            // Use the dependency to create the object
            year = WorkYearProvider.CreateNewWorkYear(joinedCompany.Year, this, periods);
            _workYears.Add(year);
        }
        else
        {
            // Logic when year not null
        }

        year.RecalculateAllTime();
    }

    // More code...
}

我创建的几个测试 classes 来说明:

public class SampleEmployee : Employee
{
    public SampleEmployee(IWorkYearProvider provider) : base(provider) { }
}

public class SampleWorkYearProvider : IWorkYearProvider
{
    public IWorkYear CreateNewWorkYear(int year, Employee employee, List<PayPeriod> periods)
    {
        IWorkYear workYear = null;
        workYear = new WorkYear(year, employee, periods);
        return workYear;
    }        
}

示例用法:

//Create your provider
IWorkYearProvider provider = new SampleWorkYearProvider();

//Inject it as a dependency
var employee = new SampleEmployee(provider);

//Call the method
employee.StartWorking(DateTime.Today);

创建现在被抽象出来了。

我更愿意建议您重新考虑您的设计。抽象出 StartWorking.

中的整个逻辑可能更有意义

首先,使用 WorkYear 的实例注入您的代码不会使您的代码独立于 WorkYear,因为 Employee 仍然知道这个 class(如list List<WorkYear> WorkYears 例如),因此,如果你需要 DI,你应该与代表此 class 的接口进行对话,例如 IWorkYear(这里的列表将改为 List<IWorkYear> WorkYears)。

现在注入和实例化:

创建工厂 class 如下:

public static class MyFactory
{
    public static object Create(Type classType, object[] data = null)
    {
        // create the class by reflection
    }
}

(要通过反射创建一个 class 检查这个:Activator.CreateInstance

然后更改方法的签名StartWorking 以便将类型注入到方法中:

public void StartWorking(Type workYearType, DateTime joinedCompany)

和行:

year = new WorkYear(joinedCompany.Year, this, periods);

将是:

year = MyFactory.Create(classType, new object[]{ joinedCompany.Year, this, periods});