是否使用"公共"嵌套类

21

假设我有一个名为“Application”的类。为了初始化它,在构造函数中需要一些设置。同时,我们假设设置的数量非常多,以至于将它们放在独立的类中是很有必要的。

下面比较这种情况的两个实现:

实现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上看到有人说只有当嵌套类对外部世界不可见时才有合理之处;也就是说,它们仅用于包含类的内部实现。通常引用的反对意见是膨胀包含类的源文件大小,但部分类是解决这个问题的完美方案。

我的问题是,为什么我们对“公开暴露”嵌套类的使用持谨慎态度?是否还有其他反对这种用法的理由?

6个回答

28

我认为这是可以接受的。基本上,这就是建造者模式,使用嵌套类的效果非常好。它还允许建造者访问外部类的私有成员,这非常有用。例如,您可以在建造者上拥有一个构建方法,该方法调用外部类上的私有构造函数,该构造函数需要建造者实例:

public class Outer
{
    private Outer(Builder builder)
    {
        // Copy stuff
    }

    public class Builder
    {
        public Outer Build()
        {
            return new Outer(this);
        }
    }
}

这确保了外部类的实例只能通过构建器来构建。

我在我的C#版本的Protocol Buffers中经常使用类似于这样的模式。


最近我不得不编写一些构建器代码,然后想起了这篇文章。结果证明这是一个非常有用的方法,所以谢谢。 - Mark Simpson
“它还允许构建器访问外部类的私有成员”,只有在明确引用时才能访问,对吗(我很确定它不像Java的内部类那样是隐式的)? - samus
@SamusArin:是的,没有对包含类的实例的隐式引用。但是您也可以访问静态成员。基本上,我只谈论可访问性。 - Jon Skeet

6
你可以使用命名空间来关联那些相关的事物。
例如:
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
你要怎么在晚上安心入睡呢?

2
我同意使用命名空间的偏好,但有时它们并不完全适用。例如,在所讨论的示例中,语义上,“应用程序”包含“设置”,但将它们都放在一个公共命名空间中并不能传达这个含义。 - Frederick The Fool
使用Filling = Diner.Filling;应该解析类作为命名空间的情况下对Filling的"引用",使其仅解析为Filling - samus

1
你可能想看看微软在这个话题上的看法。基本上我认为这是一个风格问题。

.NET并不像Java那样强制实行“一个(公共)类一个文件”的规则。它们似乎有着不同的编程结构范式。就这一条规则而言,.NET更加灵活(你可以将其视为表达能力更强的超集)。顺便说一下,这是一篇很棒的文章。 - samus

0

我有一个关于公共嵌套类有效使用的实际例子,它与MVC模式有关,当我使用带有IEnumerable属性的视图模型时。例如:

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属性是公共的。


0

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

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

您还可以使用此功能来控制对特定类的使用。

例如:

public abstract class Outer
{
  protected class Inner
  {
  }
}

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

1
Iff 可能不是打字错误,但它有些多余,因为句子中已经有单词“only”。 - phoog

-1

我不知道是否被认为是不良的设计,但我拥有一些搜索类,当用户调用 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);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接