问题
After learning about the Decorator Pattern with typical Coffee example where Decorator Pattern saves us from the class explosion problem, I wrote some code to use and see it myself. Lets take a look at the UML first...
and here is the code:
Component
ICofee
defination:
public interface ICoffee
{
string Name { get; }
decimal Cost { get; }
}
LatteCoffee
definition:
public class LatteCoffee : ICoffee
{
public string Name { get; } = "Latte";
public decimal Cost => 2.00m;
}
Decorators
IAddOnDecorator
defination:
public interface IAddOnDecorator : ICoffee
{
ICoffee BaseCoffee { set; }
}
CaramelDecorator
definition:
public class CaramelDecorator : IAddOnDecorator
{
public ICoffee BaseCoffee { private get; set; }
public string Name { get; } = "Caramel";
public decimal Cost => BaseCoffee.Cost + 0.5m;
}
AlmondSyrupDecorator
definition:
public class AlmondSyrupDecorator : IAddOnDecorator
{
public ICoffee BaseCoffee { private get; set; }
public string Name { get; } = "AlmondSyrup";
public decimal Cost => BaseCoffee.Cost + 0.3m;
}
You can see that the decorators are not taking ICoffee
injected in the constructor instead, there is a setter property ICoffee BaseCoffee
.
I would like to use the constructor injection into the decorator (IAddOnDecorator
)for the component (ICoffee
) which is the recommended way, however, I am then unable to pass in the concrete object in the unit test method.
The Usage
[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));
}
}
My Question:
How can I resolve IAddOnDecorator
using a particular instance of ICoffee
object passing into the constructor of Decorator class in .net core? I do not want to use ICoffee BaseCoffee { private get; set; }
property.
回答1:
Unfortunately, the default IoC Container in .Net core doesnt support decoration so I had to turn my attention to other available options. Since I have already used Structure Map and that I like its "Convention over Configuration" strategy I decided to try it. Following code achieves that I was looking for... Its not perfect but I allows me to instantiate decorator by inject an instance of another decorator or component.
Note: I have added another decorator SaltedCaramelDecorator
just to keep it more interesting...
// 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));
Thanks for @Steven for helping comments. I hope someone else would find this post helpful.
回答2:
Since you are using a separate TService
for the decorator (compared to the regular implementation), you should be able to do this fairly easily with the built-in container.
It's easy because ICoffee
still resolves to the regular implementation, which you need as a dependency.
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>));
Does this help?
来源:https://stackoverflow.com/questions/50704145/how-can-i-resolve-a-decorator-using-a-particular-instance-of-a-component-or-anot