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.