重构 Switch 语句(第二版)
如何重构 Switch 语句
距离我上一篇文章已经过去很长时间了。最近在 Sansir 发生了很多有趣的事情,占据了我所有的空闲时间,而且我是一个重视家庭的人,所以很难经常发表文章。我对此并不抱怨,一点也不,我感到“快乐”。
好了,停止抱怨吧。
在之前的文章中,我向你展示了如何重构一段代码,这段代码根据 switch
语句的评估结果来决定执行哪个方法;你可以在 这里 阅读关于它的内容。
作为最后的说明,我在文章中提到,所呈现的代码还可以进一步重构,因为如果你仔细观察,我们只是将维护噩梦转移到了程序中的另一个地方,但原始问题仍然存在。
那么该怎么办?我会这样做
就这样,有了我们新的方法,我们可以继续添加 NewsDisplayer
并将其用于我们的程序,而无需触碰程序的其他部分;通过向我们的程序集添加更多类,或者将程序集放入 bin 目录中来实现(这不就是插件模型吗?)。
- 为将要处理的每个运动创建特定的类;这些类必须实现一个简单的
interface
,并且需要用一个属性进行装饰,该属性指定它将处理的目标运动。例如public interface INewsDisplayer { void Display(); } public class NewsDisplayerDefinitionAttribute : Attribute { public NewsDisplayerDefinitionAttribute(Sports sport) { Sport = sport; } public Sports Sport { get; private set; } }
一个实现如下所示
[NewsDisplayerDefinition(Sports.Soccer)] public class SoccerNewsDisplayer : INewsDisplayer { #region INewsDisplayer Members public void Display() { Console.WriteLine("Displaying News for Soccer"); // Real implementation below // Do something } #endregion }
- 使用 工厂模式,创建一个类,该类接受一个
Sport
参数并返回一个IEnumerable<INewsDisplayer>
实例。与我们之前的尝试的一个区别在于,我们将能够为给定的Sport
定义多个实现。这个工厂类将扫描当前AppDomain
中的所有程序集,如果它找到一个遵循我之前描述的模式的类型,它将读取元数据并将该类型的实例附加到一个字典中,该字典将作为我们的查找表,以返回请求的Sport
的正确实例。这个工厂类如下所示public static class NewsDisplayerFactory { private static readonly IDictionary<Sports, IEnumerable<INewsDisplayer>> lookupTable = new Dictionary<Sports, IEnumerable<INewsDisplayer>>(); static NewsDisplayerFactory() { BootStrap(); } private static void BootStrap() { // Find all types in all the assemblies from the current appdomain // that have a correct implementation of // News Displayer (NewsDisplayerDefinitionAttribute + INewsDisplayer) var interfaceFullName = typeof (INewsDisplayer).FullName; var newsDisplayerAttributeType = typeof (NewsDisplayerDefinitionAttribute); var result = from assembly in AppDomain.CurrentDomain.GetAssemblies() from type in assembly.GetTypes() let definition = type .GetCustomAttributes(newsDisplayerAttributeType, true) .Cast<NewsDisplayerDefinitionAttribute>() .FirstOrDefault() where type.GetInterface(interfaceFullName) != null && definition != null group (INewsDisplayer) Activator.CreateInstance(type) by definition.Sport; // Filling the dictionary foreach (var item in result) { lookupTable.Add(item.Key, item.ToList()); } } public static IEnumerable<INewsDisplayer> GetInstance(Sports sport) { IEnumerable<INewsDisplayer> newsDisplayer; if (lookupTable.TryGetValue(sport, out newsDisplayer)) { return newsDisplayer; } else { throw new NotImplementedException(string.Format( "The method for the sport {0} is not implemented", sport)); } } }
- 使用 策略模式,我们将更改我们的
SportNews
类,通过传递要处理的Sport
类型来更改其上下文。这将如下所示public class SportNews { private IEnumerable<INewsDisplayer> newsDisplayer; public void SetContext(Sports sport) { newsDisplayer = NewsDisplayerFactory.GetInstance(sport); } public void DisplayNews() { foreach (var displayer in newsDisplayer) { displayer.Display(); } } }
最后,我想展示我们的 Program
类,其中使用每个组件来执行该程序的原始想法
class Program
{
private static string options = null;
private static readonly Type sportsEnumType = typeof(Sports);
static void Main(string[] args)
{
var news = new SportNews();
while (true)
{
Console.WriteLine();
DisplayOptions();
var key = Console.ReadKey().KeyChar.ToString();
Console.WriteLine();
try
{
var sport = (Sports)(Enum.Parse(sportsEnumType, key));
news.SetContext(sport);
news.DisplayNews();
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
return;
}
}
}
private static void DisplayOptions()
{
if (options == null)
{
StringBuilder optionsBuilder = new StringBuilder();
FieldInfo[] enumFields = sportsEnumType.UnderlyingSystemType.GetFields(
BindingFlags.Public | BindingFlags.Static);
foreach (FieldInfo enumField in enumFields)
{
object enumValue = enumField.GetRawConstantValue();
Sports sport = (Sports)(enumValue);
optionsBuilder.AppendFormat("To display the news for {0} press {1}\n",
sport, enumValue);
}
options = optionsBuilder.ToString();
}
Console.WriteLine(options);
}
}
你可以从 这里 下载可用的示例。
祝你编码愉快,下次再见。
无耻的推广:你可以在我的博客 这里 查看这篇文章。