【问题标题】:Mapping switch statements to data classes将 switch 语句映射到数据类
【发布时间】:2009-10-24 13:14:13
【问题描述】:

这似乎在我的代码中出现了很多,我想知道是否有某种方法可以删除 switch 语句,或者是否有更优雅的方法?

    public class MetaData
{
    public string AlbumArtist { get; set; }
    public string AlbumTitle { get; set; }
    public string Year { get; set; }
    public string SongTitle { get; set; }


    public static MetaData CreateMetaDataFrom(IEnumerable<TextFrame> textFrames)
    {
        var metaData = new MetaData();

        foreach (var frame in textFrames)
        {
            switch (frame.Descriptor.ID)
            {
                case "TPE1":
                    metaData.AlbumArtist = frame.Content;
                    break;

                case "TALB":
                    metaData.AlbumTitle = frame.Content;
                    break;

                case "TIT2":
                    metaData.SongTitle = frame.Content;
                    break;

                case "TYER":
                    metaData.Year = frame.Content;
                    break;
            }
        }

        return metaData;
    }
}

【问题讨论】:

  • 我非常喜欢通过使用多态性、策略模式、使用委托查找表等来删除代码中的条件。在适当的上下文中使用时,每一个都是优雅的解决方案.也就是说,我相信这里最好的答案似乎是由 asstander 提供的。假设您有一个有限的已知用户界面值列表,最合适的解决方案是直接连续设置结构的值,或者更好的解决方案可能是使用数据绑定。这很简单,但似乎最合适。

标签: c# .net refactoring switch-statement


【解决方案1】:

这与面向对象的方法有关。摆脱 if 或 case 的常用方法是使用标准和效果的查找表。还有其他使用相同思想的技术,例如数据导向编程 (http://en.wikipedia.org/wiki/Data-directed_programming) 和调度表 (http://en.wikipedia.org/wiki/Dispatch_table)。许多语言实现使用类型分派表来实现虚方法调用。

查找表可以是一个填充有 lambda 函数的哈希表,如下所示:

Dictionary<string, Func<MetaData, string, string>> lookup = new Dictionary<string, Func<MetaData, string, string>>();
lookup["TPE1"] = (m, v) => m.AlbumArtist = v;
lookup["TALB"] = (m, v) => m.AlbumTitle = v;
lookup["TIT2"] = (m, v) => m.SongTitle = v;
lookup["TYER"] = (m, v) => m.Year = v;

然后您将循环内的元数据字段分配为:

lookup[frame.Descriptor.ID](metaData, frame.Content);

【讨论】:

    【解决方案2】:

    从您的代码中,我得出结论 IEnumerable 始终有 4 个成员 所以你可以写(没试过所以检查语法):

    public static MetaData CreateMetaDataFrom(IEnumerable<TextFrame> textFrames)
    {
        return new MetaData()
        {
            metaData.AlbumArtist = textFrames.Where(frame => frame.Descriptor.ID = "TPE1").SingleOrDefault().Content,
            metaData.AlbumTitle = textFrames.Where(frame => frame.Descriptor.ID = "TALB").SingleOrDefault().Content, 
            metaData.SongTitle = textFrames.Where(frame => frame.Descriptor.ID = "TIT2").SingleOrDefault().Content;
            metaData.Year = textFrames.Where(frame => frame.Descriptor.ID = "TYER").SingleOrDefault().Content;
        };
    }
    

    【讨论】:

    • 这对于短名单来说是一个很好的解决方案。您甚至可以使用扩展方法将其重构为 public static string GetValue(this IList textFrames, string key) { return textFrames.Where(f => f == f.Descriptor.Id).SingleOrDefault()。内容;然后你写: metaData.AlbumArtist = textFrames.GetValue("TPE1");
    • 错字,应该是:这个 IEnumerable textFrames
    • 如果 Where 方法返回一个空的 Enumerable,此代码将导致 NullReferenceExeception - 只是提示
    【解决方案3】:

    您可能想看看实现策略模式。 DimeCasts.Net 有一个很棒的视频教程可能会有所帮助。

    【讨论】:

      【解决方案4】:

      我很想建议策略模式,但您可能需要稍作改动。考虑在 TextFrame 类中编写一个方法,我们称之为 putContent(MetaData)。

      然后创建 TextFrame 的子类,每个子类代表不同的 Frame 类型。每个子类都会覆盖 putContent(Metadata) 方法并执行相应的逻辑。

      TPE1 的伪代码示例:

       Metadata putContent(MetaData md){
             md.AlbumArtist = Content;
             return md;
       }
      

      然后您的元数据代码将更改为:

      var metaData = new MetaData();
      
          foreach (var frame in textFrames)
          {
                 metaData = frame.putContent(metaData);
          }
       return metaData;
      

      当然,他们自己创建 TextFrame 需要一个工厂,所以这不是故事的结局。

      【讨论】:

      • 如果 TextFrame 中所需的代码驻留在第三方 dll 中怎么办?
      • @Lee Treveil 如果希望实现类似策略模式的东西,那么您可以包装或子类化第三方代码以添加您自己的功能。您不必修改原始类。不过,这实际上取决于您的目标……其中一些事情只是为了您从中获得的好处而变得复杂。
      【解决方案5】:

      您似乎事先知道类型是什么(使用开关),所以为什么不直接根据需要检索值,而不使用 for 开关。

      示例是使用哈希表时,您知道哪些字段可用,只需使用这些字段即可。

      如果您不确定该字段是否可用,如果列表包含该值,一个简单的测试就足够了。

      然后,您甚至可以编写一个辅助函数来检查列表是否有值并返回该值。

      【讨论】:

      • +1 最合适的答案。是的,多态性、策略模式、查找表等是解决适当问题的好方法,但在这种情况下,我同意直接设置字段(或通过数据绑定)会更合适。
      【解决方案6】:

      您真正拥有的是四向二传手。这里的规范重构是“用显式方法替换参数”(Martin Fowler 的 Refactoring 的 p285)。他给出的Java示例正在改变:

      void setValue(String name, int value) {
        if (name.equals("height")) {
          _height = value;
          return;
        }
        if (name.equals("width")) {
          _width = value;
          return;
        }
      }
      

      到:

      void setHeight(int arg) {
        _height = arg;
      }
      
      void setWidth(int arg) {
        _width = arg;
      }
      

      假设CreateMetaDataFrom() 的调用者知道它传递了什么,您可以跳过switch/case 并为这些属性使用实际的设置器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-02
        • 1970-01-01
        • 2022-01-16
        • 2018-04-04
        • 2017-12-29
        相关资源
        最近更新 更多