Is there a better way than this to inject a function into an object in Unity?

∥☆過路亽.° 提交于 2019-12-10 20:35:21

问题


I'm a Unity noob, and I have a question. I want to inject an IAgeCalculator into each instance of IPerson so that the IAgeCalculator instance is available to any IPerson that I might later create.

Here is what I have tried so far. It works, but it does not feel right.

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

Here are my class definitions:

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

Is there a better way?


回答1:


The purpose of a DI container is to build up object graphs of application components. Application components are often long-lived objects that contain the application's behavior. Components are generally immutable, and the service abstractions they implement should always be immutable.

DI containers are not meant to build up short lived and mutable objects like DTOs and Entities (like your Person object). Resolving or injecting mutable, short-lived data objects cause ambiguity. For instance, if we inject a Person, which person are we expected to get?

This typically means that you shouldn't use Constructor Injection on data objects. In your case having the IAgeCalculator as dependency on the Person object is even unneeded. It is very easy for the consumer of Person to have a dependency on IAgeCalculator and call IAgeCalculator.GetAge for the correct person.

In case you want to put domain specific logic in your entities, Method Injection is a very good fit. This means that the Component that's responsible of calling that domain method is responsible in forwarding the required services. For instance:

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

Note that my Person.GetAge method returns the age in years. In your case your AgeCalculator returns a string representation of the person's age. This is not a responsibility that belongs in the entity itself. This is a presentation responsibility; it will change for different reasons than why the rest of the Person class will change. This is another reason to not have this logic inside the entity.

Also note that having an abstraction like IPerson over your entities usually makes little sense. Entities are data containers, while interfaces are meant to abstract behavior. There is no behavior in Person that you want to abstract, since its very unlikely that you want to have multiple implementations of Person in the future.




回答2:


Register IAgeCalculator using RegisterInstance<T> (not RegisterType<T>) and pass in an instance derived from that type.

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

Then in your Person class, mark AgeCalculator with the [Dependency] attribute.

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


来源:https://stackoverflow.com/questions/39240231/is-there-a-better-way-than-this-to-inject-a-function-into-an-object-in-unity

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