【问题标题】:Tying a method to implementation classes将方法绑定到实现类
【发布时间】:2011-04-07 22:23:51
【问题描述】:

这是否会产生任何代码异味或违反 SOLID 原则?

public string Summarize()
{
IList<IDisplayable> displayableItems = getAllDisplayableItems();
StringBuilder summary = new StringBuilder();

foreach(IDisplayable item in displayableItems)
{
    if(item is Human)
        summary.Append("The person is " + item.GetInfo());

    else if(item is Animal) 
        summary.Append("The animal is " + item.GetInfo());

    else if(item is Building) 
        summary.Append("The building is " + item.GetInfo());

    else if(item is Machine) 
        summary.Append("The machine is " + item.GetInfo());
}

return summary.ToString();
}

如您所见,我的 Summarize() 与 Human、Animal 等实现类绑定。

此代码是否违反 LSP? (还有其他 SOLID 原则吗?)

【问题讨论】:

    标签: c# oop design-patterns


    【解决方案1】:

    我闻到了一点东西......

    如果您的类都实现了 IDisplayable,那么它们应该各自实现自己的逻辑来显示自己。这样你的循环就会变成更干净的东西:

    public interface IDisplayable
    {
        void Display();
        string GetInfo();
    }
    
    public class Human : IDisplayable
    {
        public void Display() { return String.Format("The person is {0}", 
            GetInfo());
    
        // Rest of Implementation
    }
    
    public class Animal : IDisplayable
    {
        public void Display() { return String.Format("The animal is {0}", 
            GetInfo());
    
        // Rest of Implementation
    }
    
    public class Building : IDisplayable
    {
        public void Display() { return String.Format("The building is {0}", 
            GetInfo());
    
        // Rest of Implementation
    }
    
    public class Machine : IDisplayable
    {
        public void Display() { return String.Format("The machine is {0}", 
            GetInfo());
    
        // Rest of Implementation
    }
    

    然后您可以将循环更改为更简洁的内容(并允许类实现自己的显示逻辑):

    foreach(IDisplayable item in displayableItems)
        summary.Append(item.Display());
    

    【讨论】:

    • 谢谢,但是如果我必须在 Summarize() 方法中使用某种逻辑来查看列表中的 IDisplayable 项目类型怎么办?我想我的问题是将 Summarize() 与具体的实现类联系起来是否是一种不好的做法。
    • @SP - 是的。如果您将 Summarize() 的行为与具体实现联系起来,那么您实际上否定了通过 IDisplayable 接口以多态方式处理对象的好处。如果 Summarize 中的行为需要改变,它应该通过 IDisplayable 接口的成员来完成。
    • @SP,也许能解释更多你想要做什么。例如,IDisplayable 可能有 1000 个实现类型,但您只关心其中的 5 个。
    • 也许我的例子过于简单化了我的意图。我的“真实” Summarize() 不仅仅是显示,它有逻辑来查看人类、动物等类型。我的 Summarize() 对只有 Human 时有特定的逻辑,但如果有 Human 和 Animal,它会做其他事情。所以,如你所见,其中有逻辑。
    • @SP - 如果是这种情况,您应该更新您的问题,以便我们更好地了解您在做什么。
    【解决方案2】:

    似乎 IDisplayable 应该有一个显示名称的方法,因此您可以将该方法简化为类似

    summary.Append("The " + item.displayName() + " is " + item.getInfo());
    

    【讨论】:

    • 欢迎来到 StackOverflow,享受您的住宿,记得给服务员小费。尽可能使用正确的代码格式!
    【解决方案3】:

    是的。

    为什么不让每个类都实现一个来自IDisplayable 的方法来显示它们的类型:

    interface IDisplayable
    {
        void GetInfo();
        public string Info;
    }
    class Human : IDisplayable
    {
       public string Info
       { 
        get 
        { 
            return "";//your info here
        }
        set;
       }
    
       public void GetInfo()
       {
           Console.WriteLine("The person is " + Info)
       }
    }
    

    然后只需按如下方式调用您的方法:

    foreach(IDisplayable item in displayableItems)
    {
        Console.WriteLine(item.GetInfo());
    }
    

    【讨论】:

      【解决方案4】:

      鉴于 OP 对 this answer 的评论,我认为最好的方法是创建一个自定义容器类来替换具有 containsHumans() 和 containsAnimals() 等方法的 IList&lt;IDisplayable&gt; displayableItems,这样您就可以封装 icky non - 多态代码在一处,并保持Summarize() 函数中的逻辑干净。

      class MyCollection : List<IDisplayable>
      {
          public bool containsHumans()
          {
              foreach (IDisplayable item in this)
              {
                  if (item is Human)
                      return true;
              }
      
              return false;
          }
      
          // likewise for containsAnimals(), etc
      }
      
      public string Summarize()
      {
          MyCollection displayableItems = getAllDisplayableItems();
          StringBuilder summary = new StringBuilder();
      
          if (displayableItems.containsHumans() && !displayableItems.containsAnimals())
          {
              // do human-only logic here
          }
          else if (!displayableItems.containsHumans() && displayableItems.containsAnimals())
          {
              // do animal-only logic here
          }
          else
          {
              // do logic for both here
          }
      
          return summary.ToString();
      }
      

      当然,我的例子过于简单和做作。例如,作为Summarize() if/else 语句中逻辑的一部分,或者可能围绕整个块,您需要遍历displayableItems 集合。此外,如果您在 MyCollection 中覆盖 Add() 和 Remove() 并让它们检查对象的类型并设置一个标志,您可能会获得更好的性能,因此您的 containsHumans() 函数(和其他函数)可以简单地返回标志的状态,并且不必在每次调用时都迭代集合。

      【讨论】:

      • 感谢您的回复,但我对技术性的理解并不完全,您能给我举个例子吗? :) 再次感谢,这可能正是我需要的。我只需要看一个例子。
      • 嗯...您接受它的事实是否意味着您不需要示例?我很乐意尝试提供一个,但我不太确定哪一部分导致您感到困惑。如果你能更具体一点,我会试一试。
      • 我接受它作为答案,因为它回答了我的具体问题。我正在研究基于此的解决方案。我只需要一个例子来确保我理解技术性。贾斯汀在下面的回答非常详尽且很有帮助,因此类似于它的东西会很棒。
      • @SP:我添加了一个示例...如果我能提供进一步帮助,请告诉我。
      • 非常感谢!它肯定会让事情变得更干净。我的一个问题是 Summarize() 现在与 MyCollection 类相关联。我想我对此变得太偏执了,但是我可以拥有类似 IMyCollection 接口的东西,以便 Summarize() 不绑定到具体的 MyCollection 实现吗?再次感谢您的帮助!
      【解决方案5】:

      怎么样:

          summary.Append("The " + item.getType() + " is " + item.GetInfo()); 
      

      【讨论】:

        【解决方案6】:

        至少它违反了 LSP 和开闭原则。

        解决方案是给IDisplayable 接口添加一个Description 属性,这样summary 就可以调用

        summary.Append(string.Format("The {0} is {1}", item.Description, item.GetInfo()));
        

        这也可以通过反射来解决,因为您只获得了类的名称。

        更好的解决方案是从GetInfo() 方法返回类似IDisplayableInfo 的内容。这将是一个有助于保护 OCP 的扩展点。

        【讨论】:

        • 你能简单解释一下为什么它违反了 LSP 吗?我知道这违反了 OCP。如果我在 Summarize() 中有额外的逻辑来检查有哪些类型的实现类怎么办?
        • @SP 这里可能需要做一些细微的区分。陈述原则的一种常见方式是Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.“不知不觉”是什么意思?从某种意义上说,您的方法确实适用于IDisplayable 的任何实现——它将编译并且不会抛出异常。在另一种意义上,它不起作用,因为它必须“了解”实现才能对IDisplayable 做一些有意义的事情。
        【解决方案7】:

        如果您无法修改 IDisplayable 或类实现并且您使用的是 .NET 3.5 或更高版本,则可以使用扩展方法。但这并没有那么好

        【讨论】:

          猜你喜欢
          • 2012-02-17
          • 2015-06-25
          • 2021-09-17
          • 2017-01-29
          • 2022-09-10
          • 1970-01-01
          • 2018-05-27
          • 2020-07-17
          • 1970-01-01
          相关资源
          最近更新 更多