Non Tech - Wises Map revamp
Something non-tech related, but I only just noticed wises has revamped their website,
lets hope it's not so arse this time ;o)
The professional blog of Alex Henderson, Software Engineer
Something non-tech related, but I only just noticed wises has revamped their website,
lets hope it's not so arse this time ;o)
So this time we're going to look at injecting service arrays ... if you haven't read part 12, it might pay to have a look at that first - this part follows on from the examples presented there.
So this should be reasonably quick, we're just going to rework the Part 12 solution so we don't need decorators... why would we do this, well in this case I feel that rewiring decorators (ie. changing orders, or disabling one of the decorators temporarily) isn't completely intuitive, especially when you have a long chain, it's easy to make mistakes.
So what's the plan, well first off let's implement an abstract class for our calculators:
public abstract class AbstractCalculator
{
public abstract decimal Calculate(decimal currentTotal, Order order);
}
public class TotalCalculator : AbstractCalculator
{
private decimal CalculateTotal(Order order)
{
decimal total = 0;foreach (OrderItem item in order.Items)
{
total += (item.Quantity*item.CostPerItem);
}return total;
}public override decimal Calculate(decimal currentTotal, Order order)
{
return currentTotal + CalculateTotal(order);
}
}
public class GstCalculator : AbstractCalculator
{
private decimal _gstRate = 1.125m;public decimal GstRate
{
get { return _gstRate; }
set { _gstRate = value; }
}private bool IsNewZealand(Order order)
{
return (order.CountryCode == "NZ");
}public override decimal Calculate(decimal currentTotal, Order order)
{
if (IsNewZealand(order))
{
return (currentTotal*_gstRate);
}return currentTotal;
}
}
public class ShippingCalculator : AbstractCalculator
{
private decimal _shippingCost = 5.0m;
private decimal _fragileShippingPremium = 1.5m;public decimal ShippingCost
{
get { return _shippingCost; }
set { _shippingCost = value; }
}public decimal FragileShippingPremium
{
get { return _fragileShippingPremium; }
set { _fragileShippingPremium = value; }
}private decimal GetShippingTotal(Order order)
{
decimal shippingTotal = 0;foreach (OrderItem item in order.Items)
{
decimal itemShippingCost = ShippingCost*item.Quantity;
if (item.IsFragile) itemShippingCost *= FragileShippingPremium;
shippingTotal += itemShippingCost;
}return shippingTotal;
}public override decimal Calculate(decimal currentTotal, Order order)
{
return currentTotal + GetShippingTotal(order);
}
}
public class DefaultCostCalculator : ICostCalculator
{
private AbstractCalculator[] _calculators;public DefaultCostCalculator(AbstractCalculator[] calculators)
{
_calculators = calculators;
}public decimal CalculateTotal(Order order)
{
decimal currentTotal = 0;foreach (AbstractCalculator calculator in _calculators)
{
currentTotal = calculator.Calculate(currentTotal, order);
}return currentTotal;
}
}
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
service="IoC.Tutorials.Part13.AbstractCalculator, IoC.Tutorials.Part13"
type="IoC.Tutorials.Part13.GstCalculator, IoC.Tutorials.Part13">1.20
service="IoC.Tutorials.Part13.AbstractCalculator, IoC.Tutorials.Part13"
type="IoC.Tutorials.Part13.ShippingCalculator, IoC.Tutorials.Part13">0.0
service="IoC.Tutorials.Part13.AbstractCalculator, IoC.Tutorials.Part13"
type="IoC.Tutorials.Part13.TotalCalculator, IoC.Tutorials.Part13" />
service="IoC.Tutorials.Part13.ICostCalculator, IoC.Tutorials.Part13"
type="IoC.Tutorials.Part13.DefaultCostCalculator, IoC.Tutorials.Part13">
${calc.total}
${calc.shipping}
${calc.gst}
private static void Main(string[] args)
{
WindsorContainer container = new WindsorContainer(new XmlInterpreter());Order order1 = new Order();
order1.CountryCode = "NZ";
order1.Items.Add(new OrderItem("water", 10, 1.0m, false));
order1.Items.Add(new OrderItem("glass", 5, 20.0m, true));Order order2 = new Order();
order2.CountryCode = "US";
order2.Items.Add(new OrderItem("sand", 50, 0.2m, false));ICostCalculator costCalculator = container.Resolve
();
Console.WriteLine("Cost to deliver Order 1: {0}", costCalculator.CalculateTotal(order1));
Console.WriteLine("Cost to deliver Order 2: {0}", costCalculator.CalculateTotal(order2));Console.Read();
}
So... Decorators (for those who don't know, or don't remember) are a classic design pattern - have a look at the wikipedia entry if your curious on doing any background reading.
Up until now we've been creating abstractions and swapping implementations, but here we are going to chain implementations... so for this example we have an Order class which contains a bunch of order items, here's how that looks:
Order.cs
public class Order
{
private string _countryCode;
private readonly List_items = new List (); public List
Items
{
get { return _items; }
}public string CountryCode
{
get { return _countryCode; }
set { _countryCode = value; }
}
}
public class OrderItem
{
private string _name;
private bool _isFragile;
private int _quantity;
private decimal _costPerItem;public OrderItem(string name, int quantity, decimal costPerItem, bool isFragile)
{
_name = name;
_quantity = quantity;
_costPerItem = costPerItem;
_isFragile = isFragile;
}public bool IsFragile
{
get { return _isFragile; }
set { _isFragile = value; }
}public int Quantity
{
get { return _quantity; }
set { _quantity = value; }
}public decimal CostPerItem
{
get { return _costPerItem; }
set { _costPerItem = value; }
}public string Name
{
get { return _name; }
set { _name = value; }
}
}
public interface ICostCalculator
{
decimal CalculateTotal(Order order);
}
public class DefaultCostCalculator : ICostCalculator
{
public decimal CalculateTotal(Order order)
{
decimal total = 0;foreach (OrderItem item in order.Items)
{
total += (item.Quantity*item.CostPerItem);
}return total;
}
}
internal class Program
{
private static void Main(string[] args)
{
WindsorContainer container = new WindsorContainer(new XmlInterpreter());Order order1 = new Order();
order1.CountryCode = "NZ";
order1.Items.Add(new OrderItem("water", 10, 1.0m, false));
order1.Items.Add(new OrderItem("glass", 5, 20.0m, true));Order order2 = new Order();
order2.CountryCode = "US";
order2.Items.Add(new OrderItem("sand", 50, 0.2m, false));ICostCalculator costCalculator = container.Resolve
();
Console.WriteLine("Cost to deliver Order 1: {0}", costCalculator.CalculateTotal(order1));
Console.WriteLine("Cost to deliver Order 2: {0}", costCalculator.CalculateTotal(order2));Console.Read();
}
}
service="IoC.Tutorials.Part12.ICostCalculator, IoC.Tutorials.Part12"
type="IoC.Tutorials.Part12.DefaultCostCalculator, IoC.Tutorials.Part12" />
public class GstCostCalcualtorDecoarator : ICostCalculator
{
private readonly ICostCalculator _innerCalculator;
private decimal _gstRate = 1.125m;public GstCostCalcualtorDecoarator(ICostCalculator innerCalculator)
{
_innerCalculator = innerCalculator;
}public decimal GstRate
{
get { return _gstRate; }
set { _gstRate = value; }
}private bool IsNewZealand(Order order)
{
return (order.CountryCode == "NZ");
}public decimal CalculateTotal(Order order)
{
decimal innerTotal = _innerCalculator.CalculateTotal(order);if (IsNewZealand(order))
{
innerTotal = (innerTotal*_gstRate);
}return innerTotal;
}
}
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
service="IoC.Tutorials.Part12.ICostCalculator, IoC.Tutorials.Part12"
type="IoC.Tutorials.Part12.GstCostCalcualtorDecoarator, IoC.Tutorials.Part12">${costCalculator.default}
service="IoC.Tutorials.Part12.ICostCalculator, IoC.Tutorials.Part12"
type="IoC.Tutorials.Part12.DefaultCostCalculator, IoC.Tutorials.Part12" />
public class ShippingCostCalculatorDecorator : ICostCalculator
{
private readonly ICostCalculator _innerCalculator;
private decimal _shippingCost = 5.0m;
private decimal _fragileShippingPremium = 1.5m;public ShippingCostCalculatorDecorator(ICostCalculator innerCalculator)
{
_innerCalculator = innerCalculator;
}public decimal ShippingCost
{
get { return _shippingCost; }
set { _shippingCost = value; }
}public decimal FragileShippingPremium
{
get { return _fragileShippingPremium; }
set { _fragileShippingPremium = value; }
}public decimal CalculateTotal(Order order)
{
decimal innerTotal = _innerCalculator.CalculateTotal(order);
return innerTotal + GetShippingTotal(order);
}private decimal GetShippingTotal(Order order)
{
decimal shippingTotal = 0;foreach (OrderItem item in order.Items)
{
decimal itemShippingCost = ShippingCost*item.Quantity;
if (item.IsFragile) itemShippingCost *= FragileShippingPremium;
shippingTotal += itemShippingCost;
}return shippingTotal;
}
}
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
service="IoC.Tutorials.Part12.ICostCalculator, IoC.Tutorials.Part12"
type="IoC.Tutorials.Part12.ShippingCostCalculatorDecorator, IoC.Tutorials.Part12">${costCalculator.gstDecorator}
service="IoC.Tutorials.Part12.ICostCalculator, IoC.Tutorials.Part12"
type="IoC.Tutorials.Part12.GstCostCalcualtorDecoarator, IoC.Tutorials.Part12">${costCalculator.default}
service="IoC.Tutorials.Part12.ICostCalculator, IoC.Tutorials.Part12"
type="IoC.Tutorials.Part12.DefaultCostCalculator, IoC.Tutorials.Part12" />
service="IoC.Tutorials.Part12.ICostCalculator, IoC.Tutorials.Part12"
type="IoC.Tutorials.Part12.GstCostCalcualtorDecoarator, IoC.Tutorials.Part12">${costCalculator.shippingDecorator}
1.20
service="IoC.Tutorials.Part12.ICostCalculator, IoC.Tutorials.Part12"
type="IoC.Tutorials.Part12.ShippingCostCalculatorDecorator, IoC.Tutorials.Part12">${costCalculator.default}
0.0
service="IoC.Tutorials.Part12.ICostCalculator, IoC.Tutorials.Part12"
type="IoC.Tutorials.Part12.DefaultCostCalculator, IoC.Tutorials.Part12" />
So most all of us are familiar with factories, and at first glance it seems an IoC container removes the need for having to write our own... but this isn't quite the case - for the container to be able create a component it needs:
public class SmsService : ISmsService
{
private SmsConfig _config;public void SetConfig(SmsConfig config)
{
_config = config;
}public void SendMessage(string number, string message)
{
Console.WriteLine("SMS message: {0} sent to: {1} with account: {2}", message, number, _config.UserName);
}public class SmsConfig
{
private string _userName;
private string _password;
private int _retryAttempts;internal string UserName
{
get { return _userName; }
}internal string Password
{
get { return _password; }
}public int RetryAttempts
{
get { return _retryAttempts; }
set { _retryAttempts = value; }
}public void SetCredentials(string userName, string password)
{
_userName = userName;
_password = password;
}
}
}
public interface ISmsService
{
void SendMessage(string number, string message);
}
SmsService service = new SmsService();SmsService.SmsConfig config = new SmsService.SmsConfig();
config.SetCredentials("joe", "secret");
config.RetryAttempts = 3;service.SetConfig(config);
public class SmsServiceFactory
{
public ISmsService CreateService()
{
SmsService service = new SmsService();SmsService.SmsConfig config = new SmsService.SmsConfig();
config.SetCredentials("joe", "secret");
config.RetryAttempts = 3;service.SetConfig(config);
return service;
}
}
public class SmsServiceFactory
{
private string _userName;
private string _password;
private int _retryAttempts = 3;public SmsServiceFactory(string userName, string password)
{
_userName = userName;
_password = password;
}public int RetryAttempts
{
get { return _retryAttempts; }
set { _retryAttempts = value; }
}public ISmsService CreateService()
{
SmsService service = new SmsService();SmsService.SmsConfig config = new SmsService.SmsConfig();
config.SetCredentials(_userName, _password);
config.RetryAttempts = _retryAttempts;service.SetConfig(config);
return service;
}
}
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
<>
id="factorysupport"
type="Castle.Facilities.FactorySupport.FactorySupportFacility, Castle.MicroKernel" />
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
<>
id="factorysupport"
type="Castle.Facilities.FactorySupport.FactorySupportFacility, Castle.MicroKernel" />
type="IoC.Tutorials.Part11.SmsServiceFactory, IoC.Tutorials.Part11">joe secret
type="IoC.Tutorials.Part11.ISmsService, IoC.Tutorials.Part11"
factoryId="smsService.Factory"
factoryCreate="CreateService" />
static void Main(string[] args)
{
WindsorContainer container = new WindsorContainer(new XmlInterpreter());ISmsService smsService = container.Resolve
();
smsService.SendMessage("+465556555", "testing testing...1.2.3");Console.Read();
}
So, lets have a look at setter injection - for this part I'll be refactoring the example in part 9 ... so if you recall in the last part we had a class that could send messages (to the console at least) - and it had a hard-coded string being used for the format... we'll I think it would be nice to "plug in" different message formatting, if we needed to... so first thing to do is create a necessary abstraction for formatting a message - which I've called the IMessageFormatter interface:
public interface IMessageFormatter
{
string FormatMessage(string from, string to, string body);
}
public void SendMessage(string to, string body)
{
Console.WriteLine("to: {0}rnfrom: {1}rnrn{2}", to, _from, _encoder.Encode(body));
}
public class DefaultFormatter : IMessageFormatter
{
public string FormatMessage(string from, string to, string body)
{
return string.Format("to: {0}rnfrom: {1}rnrn{2}", to, from, body);
}
}
public class SecretMessageSender
{
private readonly IEncoder _encoder;
private readonly string _from;
private IMessageFormatter _formatter = new DefaultFormatter();public SecretMessageSender(string from, IEncoder encoder)
{
_from = from;
_encoder = encoder;
}public IMessageFormatter Formatter
{
get { return _formatter; }
set { _formatter = value; }
}public void SendMessage(string to, string body)
{
string encodedBody = _encoder.Encode(body);
Console.WriteLine(_formatter.FormatMessage(_from, to, encodedBody));
}
}
public class NVelocityMessageFormatter : IMessageFormatter
{
private readonly VelocityEngine _velocity;
private readonly Template _template;public NVelocityMessageFormatter(string templateFile)
{
_velocity = new VelocityEngine();
ExtendedProperties props = new ExtendedProperties();
_velocity.Init(props);
_template = _velocity.GetTemplate(templateFile);
}public string FormatMessage(string from, string to, string body)
{
VelocityContext context = new VelocityContext();
context.Put("from", from);
context.Put("to", to);
context.Put("body", body);
context.Put("today", DateTime.Now);StringWriter writer = new StringWriter();
_template.Merge(context, writer);return writer.ToString();
}
}
To: $to
From: $from
Sent: $today----------------------
$body
----------------------
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
service="IoC.Tutorials.Part10.IEncoder, IoC.Tutorials.Part10"
type="IoC.Tutorials.Part10.SillyEncoder, IoC.Tutorials.Part10" />
service="IoC.Tutorials.Part10.IEncoder, IoC.Tutorials.Part10"
type="IoC.Tutorials.Part10.NullEncoder, IoC.Tutorials.Part10" />
type="IoC.Tutorials.Part10.SecretMessageSender, IoC.Tutorials.Part10">SecretMessageSender
alex@bittercoder.com
${encoder.null}
service="IoC.Tutorials.Part10.IMessageFormatter, IoC.Tutorials.Part10"
type="IoC.Tutorials.Part10.NVelocityMessageFormatter, IoC.Tutorials.Part10">message.vm
type="IoC.Tutorials.Part10.SecretMessageSender, IoC.Tutorials.Part10">SecretMessageSender
alex@bittercoder.com
${encoder.null}
${fancyMessageFormatter}