一天一个重构方法(3):策略模式转换
2009-05-07 15:43:21Switch to Strategy:策略模式转换switch分支
switch 语句块很大,并且会随时引入新的判断条件。这时,最好使用策略模式将每个条件封装到单独的类中。
实现策略模式的方式是很多的。我在这里介绍的策略重构使用的是字典策略,这么做的好处是调用者不必修改原来的代码。
public class ClientCode
{
public decimal CalculateShipping()
{
ShippingInfo shippingInfo = new ShippingInfo();
return shippingInfo.CalculateShippingAmount(State.Alaska);
}
}
public enum State
{
Alaska,
NewYork,
Florida
}
public class ShippingInfo
{
public decimal CalculateShippingAmount(State shipToState)
{
switch (shipToState)
{
case State.Alaska:
return GetAlaskaShippingAmount();
case State.NewYork:
return GetNewYorkShippingAmount();
case State.Florida:
return GetFloridaShippingAmount();
default:
return 0m;
}
}
private decimal GetAlaskaShippingAmount()
{
return 15m;
}
private decimal GetNewYorkShippingAmount()
{
return 10m;
}
private decimal GetFloridaShippingAmount()
{
return 3m;
}
}
要应用该重构,需将每个测试条件至于单独的类中,这些类实现了一个共同的接口。然后将枚举作为字典的键,这样就可以获取正确的实现,并执行其代码了。以后如果希望添加新的条件,只需添加新的实现类,并将其添加至ShippingCalculations字典中。正如前面说过的,这不是实现策略模式的唯一方式。你也可以用觉得好用的方法。我用这种方式实现重构的好处是,不用修改客户端代码。所有的修改都在ShippingInfo类内部。Jayme Davis指出这种重构由于仍然需要在构造函数中进行绑定,所以只不过是增加了一些类而已,但如果绑定IShippingCalculation的策略可以置于IoC中,带来的好处还是很多的,它可以使你更灵活地捆绑策略。
public class ClientCode
{
public decimal CalculateShipping()
{
ShippingInfo shippingInfo = new ShippingInfo();
return shippingInfo.CalculateShippingAmount(State.Alaska);
}
}
public enum State
{
Alaska,
NewYork,
Florida
}
public class ShippingInfo
{
private IDictionary<State, IShippingCalculation> ShippingCalculations
{ get; set; }
public ShippingInfo()
{
ShippingCalculations = new Dictionary<State, IShippingCalculation>
{
{ State.Alaska, new AlaskShippingCalculation() },
{ State.NewYork, new NewYorkShippingCalculation() },
{ State.Florida, new FloridaShippingCalculation() }
};
}
public decimal CalculateShippingAmount(State shipToState)
{
return ShippingCalculations[shipToState].Calculate();
}
}
public interface IShippingCalculation
{
decimal Calculate();
}
public class AlaskShippingCalculation : IShippingCalculation
{
public decimal Calculate()
{
return 15m;
}
}
public class NewYorkShippingCalculation : IShippingCalculation
{
public decimal Calculate()
{
return 10m;
}
}
public class FloridaShippingCalculation : IShippingCalculation
{
public decimal Calculate()
{
return 3m;
}
}
为了使这个示例圆满,我们来看看在ShippingInfo构造函数中使用Ninject为IoC容器时如何进行绑定。需要更改的地方很多,主要是将state的枚举放在策略内部,以及Ninject向构造函数传递一个IShippingInfo的IEnumerable泛型。接下来我们使用策略类中的state属性创建字典,其余部分保持不变。
public interface IShippingInfo
{
decimal CalculateShippingAmount(State state);
}
public class ClientCode
{
[Inject]
public IShippingInfo ShippingInfo { get; set; }
public decimal CalculateShipping()
{
return ShippingInfo.CalculateShippingAmount(State.Alaska);
}
}
public enum State
{
Alaska,
NewYork,
Florida
}
public class ShippingInfo : IShippingInfo
{
private IDictionary<State, IShippingCalculation> ShippingCalculations
{ get; set; }
public ShippingInfo(IEnumerable<IShippingCalculation> shippingCalculations)
{
ShippingCalculations = shippingCalculations.ToDictionary(
calc => calc.State);
}
public decimal CalculateShippingAmount(State shipToState)
{
return ShippingCalculations[shipToState].Calculate();
}
}
public interface IShippingCalculation
{
State State { get; }
decimal Calculate();
}
public class AlaskShippingCalculation : IShippingCalculation
{
public State State { get { return State.Alaska; } }
public decimal Calculate()
{
return 15m;
}
}
public class NewYorkShippingCalculation : IShippingCalculation
{
public State State { get { return State.NewYork; } }
public decimal Calculate()
{
return 10m;
}
}
public class FloridaShippingCalculation : IShippingCalculation
{
public State State { get { return State.Florida; } }
public decimal Calculate()
{
return 3m;
}
}