【问题标题】:"Public" nested classes or not“公共”嵌套类与否
【发布时间】:2010-09-25 03:02:05
【问题描述】:

假设我有一个类“应用程序”。为了被初始化,它需要在构造函数中进行某些设置。我们还假设设置的数量如此之多,以至于将它们放在自己的类中是很有说服力的。

比较此场景的以下两种实现。

实施 1:

class Application 
{
   Application(ApplicationSettings settings) 
   {
       //Do initialisation here
   }
}

class ApplicationSettings 
{
   //Settings related methods and properties here
}

实施 2:

class Application 
{
   Application(Application.Settings settings) 
   {
       //Do initialisation here
   }

   class Settings 
   {
      //Settings related methods and properties here
   }
}

对我来说,第二种方法更可取。它更具可读性,因为它强烈强调了两个类之间的关系。当我编写代码以在任何地方实例化 Application 类时,第二种方法看起来会更漂亮。

现在想象一下,Settings 类本身又具有一些类似的“相关”类,而该类也同样如此。只进行三个这样的级别,并且在“非嵌套”情况下类命名会失控。但是,如果你嵌套,事情仍然很优雅。

尽管如此,我在 StackOverflow 上读到有人说嵌套类只有在外部世界不可见时才是合理的;也就是说,如果它们仅用于包含类的内部实现。普遍引用的反对意见是使包含类的源文件的大小膨胀,但部分类是该问题的完美解决方案。

我的问题是,为什么我们要警惕嵌套类的“公开”使用?还有其他反对这种使用的论据吗?

【问题讨论】:

    标签: c# class-design nested-class


    【解决方案1】:

    我觉得没问题。这基本上是构建器模式,使用嵌套类效果很好。它还允许构建器访问外部类的私有成员,这非常有用。例如,您可以在构建器上有一个 Build 方法,该方法在外部类上调用私有构造函数,该构造函数采用构建器的实例:

    public class Outer
    {
        private Outer(Builder builder)
        {
            // Copy stuff
        }
    
        public class Builder
        {
            public Outer Build()
            {
                return new Outer(this);
            }
        }
    }
    

    这确保了构建外部类实例的唯一方式是通过构建器。

    我在协议缓冲区的 C# 端口中使用了非常类似的模式。

    【讨论】:

    • 我最近不得不写一些构建器代码并记住了这篇文章。事实证明这是一个非常有用的方法,所以谢谢。
    • “它还允许构建器访问外部类的私有成员” 只有显式引用,正确(我很确定它不像在 Java 中那样隐含内部类)?。
    • @SamusArin:是的,没有隐式引用包含类的实例。但是您也可以访问静态成员。基本上我只是在谈论可访问性。
    【解决方案2】:

    您可以使用命名空间来关联...相关的事物。

    例如:

    namespace Diner
    {
        public class Sandwich
        {
            public Sandwich(Filling filling) { }
        }
    
        public class Filling { }
    }
    

    与将类当作命名空间一样使用的优势在于,您可以选择在调用方使用 using 来缩写事物:

    using Diner;
    
    ...
    
    var sandwich = new Sandwich(new Filling());
    

    如果您将Sandwich 类用作Filling 的命名空间,则必须使用全名Sandwich.Filling 来引用Filling

    知道了这一点,你晚上怎么睡觉?

    【讨论】:

    • 我同意命名空间的偏好,但有时它们并不完全有效。例如,在所讨论的示例中,语义上的“应用程序”包含“设置”,但将它们都放在一个公共命名空间中并不能传达该含义。
    • 在“类作为命名空间”的情况下,using Filling = Diner.Filling; 应将“引用”解析为(仅)Filling。跨度>
    【解决方案3】:

    您可能想查看Microsoft has to say 关于该主题的内容。基本上这是我要说的风格问题。

    【讨论】:

    • .NET 没有像 Java 那样强加“每个文件一个(公共)类”规则。他们似乎对程序结构有不同的范式。 WRT 仅此一条规则,.NET 具有更大的灵活性(如果你愿意的话,一个富有表现力的超集)。顺便说一句,很棒的文章。
    【解决方案4】:

    当我使用具有 IEnumerable 属性的视图模型时,另一个有效使用公共嵌套类的实际示例是 MVC 模式。例如:

    public class OrderViewModel
    {
    public int OrderId{ get; set; }
    public IEnumerable<Product> Products{ get; set; }
    
    public class Product {
    public string ProductName{ get; set; }
    public decimal ProductPrice{ get; set; }
    }
    
    }
    

    我使用它是因为我不希望 Product 类在外部重复使用,因为它仅针对包含它的特定视图模型进行定制。但我不能将其设为私有,因为 Products 属性是公开的。

    【讨论】:

      【解决方案5】:

      我主要使用嵌套类来微调对嵌套类和/或容器类的访问。

      要记住的一点是,嵌套类定义基本上是一个类成员,并且可以访问容器的所有私有变量。

      您也可以使用它来控制特定类的使用。

      例子:

      public abstract class Outer
      {
        protected class Inner
        {
        }
      }
      

      现在,在这种情况下,(您的类的)用户只能访问 Inner 类,如果他实现了 Outer。

      【讨论】:

      • Iff 可能不是拼写错误,但它有点多余,因为句子中已经有了“only”这个词。
      【解决方案6】:

      我不知道这是否被认为是糟糕的设计,但我有一些搜索类,用户调用 Run() 方法,传入一个包含搜索条件的对象。然后它返回一个搜索结果对象的集合。

      这些 SearchCriteria 和 SearchResult 类除了与 Search 类一起使用之外没有任何实用性。因此,我将它们嵌套在 Search 类下以显示它们一起使用。

      我必须公开嵌套类,以便 Search 类的客户端可以将 SearchCriteria 传递给 Search 类,这样他们就可以得到 Search 的结果。

      public class PersonSearch
      {
          public PersonSearchCriteria
          {
              string FirstName {get; set;}
              string LastName {get; set;}
          }
      
          public PersonSearchResult
          {
              string FirstName {get;}
              string MiddleName {get;}
              string LastName {get;}
              string Quest {get;}
              string FavoriteColor {get;}
          }
      
          public static List<PersonSearchResult> Run(PersonSearchCriteria criteria)
          {
              // create a query using the given criteria
      
              // run the query
      
              // return the results 
          }
      }
      
      
      public class PersonSearchTester
      {
          public void Test()
          {
              PersonSearch.PersonSearchCriteria criteria = new PersonSearch.PersonSearchCriteria();
              criteria.FirstName = "George";
              criteria.LastName  = "Washington";
      
              List<PersonSearch.PersonSearchResults> results = 
                  PersonSearch.Run(criteria);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2012-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-30
        • 2019-08-24
        • 1970-01-01
        • 1970-01-01
        • 2014-06-18
        相关资源
        最近更新 更多