从方法中删除条件“新”关键字依赖项

时间:2018-07-23 20:06:18

标签: c# design-patterns dependency-injection inversion-of-control

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

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的新实例。我发现了一个建议使用简单工厂类来解决类似问题的线程。这样的解决方案可以工作,但是如何处理没有实例化WorkYear的参数?

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

3 个答案:

答案 0 :(得分:0)

我不确定您要做什么,或者为什么`工作年新书需要摘要。您会看到我们无法走得太远。但是,让我们看一下:

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

class Abc(IntEnum):
    def __new__(cls, n=100):
        value = len(cls.__members__) + n
        obj = int.__new__(cls, value)
        obj._value_ = value
        return obj
    A = ()
    B = ()

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

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

最后更新Employee

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);
}

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

答案 1 :(得分:0)

这是使用界面隐藏真实对象的另一种方法。

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

.pipe(fs.createWriteStream('lorem_ipsum.wav'));

让您的public interface IWorkYear { int CurrentYear { get; } void RecalculateAllTime(); } 类实现它:

WorkYear

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

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

让您的public interface IWorkYearProvider { IWorkYear CreateNewWorkYear(int year, Employee employee, List<PayPeriod> periods); } 类使用Employee接口代替实际的对象:

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...
}

样品用量:

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); 中的整个逻辑抽象出来可能更有意义。

答案 2 :(得分:0)

首先,向您的代码注入WorkYear实例将不会使您的代码独立于WorkYear,因为Employee仍然知道该类(如列表List<WorkYear> WorkYears例如),因此,如果您需要DI,则应使用代表此类的接口(例如IWorkYear)(此处的列表将是List<IWorkYear> WorkYears代替)。

现在可以注入和实例化:

设置工厂类,例如:

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

(要通过反射创建一个类,请检查以下内容: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});