在Unity中是否有比这更好的方法将函数注入到对象中?

本文关键字:函数 注入 对象 方法 更好 是否 Unity | 更新日期: 2023-09-27 18:04:54

我是Unity新手,我有一个问题。我想把IAgeCalculator注入到IPerson的每个实例中,这样IAgeCalculator实例就可以被我以后创建的任何IPerson使用。

这是我到目前为止所尝试的。它可以工作,但感觉不对。

static void Main(string[] args) 
{
    IUnityContainer container = new UnityContainer();
    container.RegisterType<IAgeCalculator, AgeInYearsCalculator>("Years");
    container.RegisterType<IAgeCalculator, AgeInDaysCalculator>("Days");
    // Using the initializer like this does not feel right, 
    // but I cant think of another way...
    var ageCalculator = container.Resolve<IAgeCalculator>("Days");
    var personInitializer = new InjectionMethod("Initializer", ageCalculator);
    container.RegisterType<IPerson, Person>(personInitializer);
    var person1 = Factory<IPerson>.Create(container);
    person1.Name = "Jacob";
    person1.Gender = "Male";
    person1.Birthday = new DateTime(1995, 4, 1);
    var person2 = Factory<IPerson>.Create(container);
    person2.Name = "Emily";
    person2.Gender = "Female";
    person2.Birthday = new DateTime(1998, 10, 31);
}

下面是我的类定义:

public interface IAgeCalculator 
{
    string GetAge(IPerson person);
}
internal class AgeInYearsCalculator : IAgeCalculator
{
    public string GetAge(IPerson person) {
        var years = DateTime.Now.Subtract(person.Birthday).TotalDays / 365;
        return years.ToString("0.00") + " years";
    }
}
internal class AgeInDaysCalculator : IAgeCalculator 
{
    public string GetAge(IPerson person) {
        var days = (DateTime.Now.Date - person.Birthday).TotalDays;
        return days.ToString("#,#") + " days";
    }
}
public interface IPerson
{
    string Name { get; set; }
    string Gender { get; set; }
    DateTime Birthday { get; set; }
    string Age { get; }
}
public class Person : IPerson
{
    private IAgeCalculator _ageCalculator;
    public void Initializer(IAgeCalculator ageCalculator) {
        _ageCalculator = ageCalculator;
    }
    public string Name { get; set; }
    public string Gender { get; set; }
    public DateTime Birthday { get; set; }
    public string Age => _ageCalculator.GetAge(this);
}
public class Factory<T> 
{
    public static T Create(IUnityContainer container) {
        return container.Resolve<T>();
    }
}

有更好的方法吗?

在Unity中是否有比这更好的方法将函数注入到对象中?

DI容器的目的是构建应用程序组件的对象图。应用程序组件通常是包含应用程序行为的长期对象。组件通常是不可变的,它们实现的服务抽象也应该是不可变的。

DI容器不是用来构建像dto和实体(比如你的Person对象)这样的短期可变对象的。解析或注入可变的、寿命较短的数据对象会导致歧义。例如,如果我们注射一个Person,我们会得到哪个人?

这通常意味着你不应该在数据对象上使用构造函数注入。在您的示例中,甚至不需要将IAgeCalculator作为Person对象的依赖项。Person的使用者很容易依赖于IAgeCalculator,并为正确的人调用IAgeCalculator.GetAge

如果你想把特定领域的逻辑放到你的实体中,方法注入是非常合适的。这意味着负责调用该域方法的组件负责转发所需的服务。例如:

public void Handle(AddItemToBasketCommand command)
{
    var product = this.productRepository.Get(command.ProductId);
    var person = this.personRepository.Get(this.userContext.UserId);
    if (product.IsAdultProduct && person.GetAge(this.ageCalculator) < 18) {
        throw new ValidationException("You must be 18 to order this item.");
    }
    var basket = this.basketService.GetUserBasket();
    basket.AddItemToBasket(command.ProductId, command.Quantity);
}

注意,我的Person.GetAge方法返回以年为单位的年龄。在您的示例中,AgeCalculator返回一个表示该人年龄的字符串。这不是实体本身的责任。这是展示的责任;它会因为不同的原因而改变,而不是因为Person类的其他部分会改变。这是在实体中不使用此逻辑的另一个原因。

还要注意,在实体上使用IPerson这样的抽象通常没有什么意义。实体是数据容器,而接口是抽象行为。在Person中没有你想抽象的行为,因为你不太可能在未来有多个Person的实现。

使用RegisterInstance<T>(而不是RegisterType<T>)注册IAgeCalculator,并传入从该类型派生的实例

static void Main(string[] args)
{
    IUnityContainer container = new UnityContainer();
    container.RegisterInstance<IAgeCalculator>("Years", new AgeInYearsCalculator());
    container.RegisterInstance<IAgeCalculator>("Days", new AgeInDaysCalculator());
    container.RegisterType<IPerson, Person>();
    var person1 = Factory<IPerson>.Create(container);
    person1.Name = "Jacob";
    person1.Gender = "Male";
    person1.Birthday = new DateTime(1995, 4, 1);
    var person2 = Factory<IPerson>.Create(container);
    person2.Name = "Emily";
    person2.Gender = "Female";
    person2.Birthday = new DateTime(1998, 10, 31);
}

然后在Person类中,用[Dependency]属性标记AgeCalculator

public class Person : IPerson
{
    [Dependency("Days")]
    public IAgeCalculator AgeCalculator { get; set; }
    public string Name { get; set; }
    public string Gender { get; set; }
    public DateTime Birthday { get; set; }
    public string Age => AgeCalculator.GetAge(this);
}