Spiga

一天一个重构方法(3):策略模式转换

2009-05-07 15:43:21

Switch 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;
	}
}