重构到模式(1)——宇宙大爆炸:跟If else说再见
今天的重构没有固定的形式,多年来我使用过不同的版本,并且我敢打赌不同的人也会有不同的版本。
该重构适用于这样的场景:switch 语句块很大,并且会随时引入新的判断条件。这时,最好使用策略模式将
每个条件封装到单独的类中。实现策略模式的方式是很多的。我在这里介绍的策略重构使用的是字典策略,
这么做的好处是调用者不必修改原来的代码。
今天的重构没有固定的形式,多年来我使用过不同的版本,并且我敢打赌不同的人也会有不同的版本。该重构适用于这样的场景:switch 语句块很大,并且会随时引入新的判断条件。这时,最好使用策略模式将每个条件封装到单独的类中。实现策略模式的方式是很多的。我在这里介绍的策略重构使用的是字典策略,这么做的好处是调用者不必修改原来的代码。
namespace LosTechies.DaysOfRefactoring.SwitchToStrategy.Before { 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中,带来的好处还是很多的,它可以使你更灵活地捆绑策略。
namespace LosTechies.DaysOfRefactoring.SwitchToStrategy.After { 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属性创建字典,其余部分保持不变。(感谢Nate Kohari
和Jayme Davis)
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; } }