如何在.net core

时间:2018-06-05 15:45:11

标签: c# dependency-injection .net-core decorator

在了解了Decorator Pattern与典型的Coffee示例之后,其中Decorator Pattern将我们从类爆炸问题中解救出来,我编写了一些代码供我自己查看。让我们先来看看UML ......

enter image description here

这是代码:

组件

ICofee定义:

public interface ICoffee
{
    string Name { get; }
    decimal Cost { get; } 
}

LatteCoffee定义:

public class LatteCoffee : ICoffee
{
    public string Name { get; } = "Latte";
    public decimal Cost => 2.00m;
}

装修

IAddOnDecorator定义:

public interface IAddOnDecorator : ICoffee
{
    ICoffee BaseCoffee { set; }
}

CaramelDecorator定义:

public class CaramelDecorator : IAddOnDecorator
{
    public ICoffee BaseCoffee { private get; set; }    
    public string Name { get; } = "Caramel";
    public decimal Cost => BaseCoffee.Cost + 0.5m;
}

AlmondSyrupDecorator定义:

public class AlmondSyrupDecorator : IAddOnDecorator
{
    public ICoffee BaseCoffee { private get; set; }
    public string Name { get; } = "AlmondSyrup";
    public decimal Cost => BaseCoffee.Cost + 0.3m;
}

你可以看到装饰器没有在构造函数中注入ICoffee,而是有一个setter属性ICoffee BaseCoffee

我想使用构造函数注入到装饰器(IAddOnDecorator)中的组件(ICoffee)这是推荐的方式,但是,我无法传入具体对象单元测试方法。

用法

[TestFixture]
public class CoffeeTests
{
    private IServiceProvider provider;
    private IServiceCollection services;
    private IDictionary<string, ICoffee> coffeeMapper;
    private IDictionary<string, IAddOnDecorator> addonMapper;

    [SetUp]
    public void Setup()
    {
        services = new ServiceCollection();
        services.AddTransient<ICoffee, LatteCoffee>();
        services.AddTransient<IAddOnDecorator, CaramelDecorator>();
        services.AddTransient<IAddOnDecorator, AlmondSyrupDecorator>();
        provider = services.BuildServiceProvider();
    }

    [Test]
    public void LatteWithCaramelAndAlmodSyrupShouldReturnTheTotalPriceOfCoffeeAndItsAddOns()
    {
        string coffee = "Latte";
        IEnumerable<string> addOns = new List<string> { "Caramel", "AlmondSyrup" };

        IEnumerable<ICoffee> allCoffees = provider.GetServices<ICoffee>();

        coffeeMapper = allCoffees.ToDictionary(c => c.Name, c => c);

        ICoffee selectedCoffee = coffeeMapper[coffee];
        IEnumerable<IAddOnDecorator> resolvedDecorators = provider.GetServices<IAddOnDecorator>();

        IList<IAddOnDecorator> selectedDecorators = new List<IAddOnDecorator>();
        addonMapper = resolvedDecorators .ToDictionary(a => a.Name, a => a);

        IAddOnDecorator firstAddon = addonMapper[addOns.First()];
        firstAddon.BaseCoffee = selectedCoffee;
        selectedDecorators.Add(firstAddon);

        foreach (string nextAddon in addOns.Where(a => a != firstAddon.Name))
        {
            IAddOnDecorator decorator = addonMapper[nextAddon];
            decorator.BaseCoffee = selectedDecorators.Last();
            selectedDecorators.Add(decorator);
        }

        // Act.
        decimal totalCost = selectedDecorators.Last().Cost;

        // Assert.

        Assert.That(2.80m, Is.EqualTo(totalCost));

    }
}

我的问题:

如何使用传递到.net核心中Decorator类的构造函数的IAddOnDecorator对象的特定实例来解析ICoffee?我不想使用ICoffee BaseCoffee { private get; set; }属性。

2 个答案:

答案 0 :(得分:2)

不幸的是,.Net核心中的默认IoC容器不支持装饰,因此我不得不将注意力转向其他可用选项。因为我已经使用了结构图,并且我喜欢它的&#34;约定配置&#34;策略我决定尝试一下。以下代码实现了我正在寻找...它不完美但我允许我通过注入另一个装饰器或组件的实例来实例化装饰器。

注意:我添加了另一个装饰器SaltedCaramelDecorator只是为了让它更有趣......

// Arrange.
Container container = new Container();
container.Configure(config =>
{
    // register coffees / components
    config.For<ICoffee>().Use<LatteCoffee>().Named("Latte");
    config.For<ICoffee>().Use<CappuccinoCoffee>().Named("Cappuccino");

    // register addOns / decorators
    config.For<IAddOnDecorator>().Use<CaramelDecorator>().Named("Caramel");
    config.For<IAddOnDecorator>().Use<AlmondSyrupDecorator>().Named("Almond");
    config.For<IAddOnDecorator>().Use<SaltedCaramelDecorator>().Named("SaltedCaramel");
});

const string coffeeName = "Latte";
IEnumerable<string> coffeeDecoratorNames = new List<string> { "SaltedCaramel", "Almond", "Caramel" };

// Act.

ICoffee theCoffee = container.GetInstance<ICoffee>(coffeeName);
if (coffeeDecoratorNames.Any())
{
    // set the baseCofee as argument to the next decorator / addon.
    ExplicitArguments baseCoffee = new ExplicitArguments();
    baseCoffee.Set<ICoffee>(theCoffee);

    foreach (string nextDeco in coffeeDecoratorNames)
    {
        ExplicitArguments addOn = new ExplicitArguments();
        addOn.Set<ICoffee>(theCoffee);
        theCoffee = container.GetInstance<IAddOnDecorator>(addOn, nextDeco);
    }
}



// Assert.

Assert.That(3.20m, Is.EqualTo(theCoffee.Cost));

感谢@Steven提供帮助评论。我希望其他人会发现这篇文章很有用。

答案 1 :(得分:0)

由于您为装饰器使用了单独的TService(与常规实现相比),因此您应该能够使用内置容器轻松地做到这一点。

这很容易,因为ICoffee仍可以解决常规的实现,您需要将其作为依赖项。

services.AddTransient<ICoffee, LatteCoffee>();

// With manual construction
services.AddTransient<IAddOnDecorator, CaramelDecorator>(serviceProvider =>
    new CaramelDecorator(serviceProvider.GetRequiredService<ICoffee>));

// With automatic construction, if there are other constructor params that you want auto-injected
services.AddTransient<IAddOnDecorator, CaramelDecorator>(sp =>
    ActivatorUtilities.CreateInstance<CaramelDecorator>(sp, sp.GetRequiredService<ICoffee>));

有帮助吗?