嵌套类是否有用例?

时间:2010-12-19 12:20:47

标签: c++ python ruby oop

我最近看到有几个人在Stackoverflow上做这样的事情:

class A:
    foo = 1

    class B:
        def blah(self):
            pass

换句话说,他们有嵌套类。这是有效的(虽然Python新手似乎遇到了问题,因为它的行为并不像他们想象的那样),但我想不出任何理由在任何语言中都这样做,当然也不是在Python中。有这样的用例吗?为什么人们这样做?寻找它似乎在C ++中相当普遍,那有充分的理由吗?

8 个答案:

答案 0 :(得分:6)

将一个类放在另一个类中的主要原因是避免使用仅在一个类中使用的内容来污染全局名称空间,因此不属于全局名称空间。这甚至适用于Python,全局命名空间是特定模块的命名空间。例如,如果您有SomeClass和OtherClass,并且它们都需要以专门的方式读取内容,那么最好使用SomeClass.Reader和OtherClass.Reader而不是SomeClassReader和OtherClassReader。

但我从未在C ++中遇到过这种情况。 It can be problematic to control access to the outer class' fields from a nested class.在头文件中定义的编译单元中只有一个公共类,CPP文件中定义的一些实用程序类(Qt库就是一个很好的例子)也很常见。这样它们对于“外人”是不可见的,这是好的,所以将它们包含在标题中没有多大意义。它还有助于增加二进制兼容性,否则这是一个难以维护的问题。嗯,无论如何,这是一种痛苦,但更不用说了。

嵌套类非常有用的语言的一个很好的例子是Java。嵌套类自动有一个指向创建它们的外部类的实例的指针(除非您将内部类声明为静态)。这样您就不需要将“外部”传递给它们的构造函数,只需按名称来解析外部类的字段。

答案 1 :(得分:4)

它允许您控制嵌套类的访问 - 例如,它通常用于实现详细信息类。在C ++中,它还具有解析各种内容的时间以及无需先声明即可访问的内容的优势。

答案 2 :(得分:3)

我不是python的忠实粉丝,但对我而言,这种类型的决策比语法更具语义性。如果要实现列表,Node中的类List本身并不是一个可以在任何地方使用的类,而是列表的实现细节。同时,您可以在NodeTree内设置Graph内部课程。编译器/解释器是否允许您访问类是另一回事。编程是关于编写计算机可以遵循的规范以及其他程序员可以阅读的规范,List.Node更明确,因为NodeList的内部而不是ListNode作为第一级类。

答案 3 :(得分:2)

在某些语言中,嵌套类可以访问外部类范围内的变量。 (与函数类似,或者与函数类嵌套类似。当然,函数类嵌套只是创建一个方法,其行为相当不出人意料。;))

在更多技术术语中,我们创建了closure

答案 4 :(得分:2)

Python允许你用C ++ 03或Java中的函数(包括lambdas)做很多事情,你需要一个类(虽然Java有匿名内部类,所以嵌套类并不总是像你的例子)。听众,访客,那种事。列表理解通常是一种访问者:

的Python:

(foo(x) if x.f == target else bar(x) for x in bazes)

C ++:

struct FooBar {
    Sommat operator()(const Baz &x) const {
        return (x.f == val) ? foo(x) : bar(x);
    }
    FooBar(int val) : val(val) {}
    int val;
};

vector<Sommat> v(bazes.size());
std::transform(bazes.begin(), bazes.end(), v.begin(), FooBar(target));

C ++和Java程序员随后问自己的问题是,“我正在编写的这个小类:它应该出现在与需要使用它的大类相同的范围内,还是应该将它限制在范围内唯一使用它的类?“[*]

由于您不想发布该内容,或者允许其他人依赖它,因此这些情况下的答案通常是嵌套类。在Java中,私有类可以提供服务,在C ++中,您可以将类限制为TU,在这种情况下,您可能不再关心名称出现的命名空间范围,因此实际上不需要嵌套类。这只是一种风格,加上Java提供了一些语法糖。

正如其他人所说,另一种情况是C ++中的迭代器。 Python可以在没有迭代器类的情况下支持迭代,但是如果你用C ++或Java编写数据结构,那么你必须将blighter放在某个地方。要遵循标准的库容器接口,无论类是否嵌套,你都会有一个嵌套的typedef,所以你可以自然地想到“嵌套类”。

[*]他们也会问自己,“我应该写一个for循环吗?”,但让我们假设一个案例,其答案是否定的......

答案 5 :(得分:1)

至少在C ++中,嵌套类的一个主要常见用例是容器中的迭代器。例如,假设的实现可能如下所示:

class list
{
   public:

   class iterator 
   {
      // implementation code
   };

   class const_iterator
   {
      // implementation code
   };
};

C ++中嵌套类的另一个原因是私有实现细节,如地图,链表等的节点类。

答案 6 :(得分:1)

“嵌套类”可以表示两种不同的东西,可以按意图分为三个不同的类别。第一个是纯粹的风格,另外两个用于实际目的,并且高度依赖于使用它们的特征语言。

  1. 嵌套类定义,用于创建新命名空间和/或更好地组织代码。例如,在Java中,这是通过使用 static 嵌套类来完成的,the official documentation建议将其作为一种创建更易读和可维护的代码的方法,并将类逻辑分组在一起。然而,Python的Zen表明你更少地嵌套代码块,从而阻碍了这种做法。

    import this

    在Python中,您经常会看到按模块分组的类。

  2. 将一个类放在另一个类中作为其接口(或实例的接口)的一部分。首先,实现可以使用此接口来辅助子类化,例如想象一个嵌套类HTML.Node,您可以在HTML的子类中覆盖它以更改用于创建新节点实例的类。其次,类/实例用户可能会使用此接口,但除非您处于下面描述的第三种情况,否则这不是很有用。

    至少在Python中,你不需要嵌套定义来实现其中任何一个,但它可能非常罕见。相反,您可能会在类外定义Node,然后在类定义中定义node_factory = Node(或专用于创建节点的方法)。

  3. 嵌套对象的命名空间,或为不同的对象组创建不同的上下文。在Java中,非静态嵌套类(称为内部类)绑定到外部类的实例。这非常有用,因为它允许您拥有位于不同外部命名空间内的内部类的实例。

    对于Python,请考虑decimal模块。您可以创建不同的上下文,并为每个上下文定义不同的精度。每个Decimal对象都可以在创建时分配上下文。这通过不同的机制实现了与内部阶级相同的效果。如果Python支持内部类,并嵌套了ContextDecimal,那么您将context.Decimal('3')而不是Decimal('3', context=context)

    您可以轻松地在Python中创建一个元类,它允许您创建实体内部的嵌套类,甚至可以通过使用{{{{}}来生成正确支持isinstance的绑定和未绑定类代理。 1}}和__subclasscheck__。但是,它不会通过其他更简单的方法获得任何东西(例如__instancecheck__的附加参数)。它只会限制你可以用它做什么,而且每次我不得不使用它时,我发现Java中的内部类很混乱。

答案 7 :(得分:0)

在Python中,更有用的模式是在函数或方法中声明类。正如人们在其他答案中所指出的那样,在另一个类的主体中声明一个类没什么用处 - 是的,它确实避免了对模块名称空间的污染,但是因为那里有一个模块命名空间,所以还有更多的名字不打扰。即使额外的类不打算由模块的用户直接实例化,然后在模块根目录上使其他人更容易访问他们的文档。

但是,函数内部的类是完全不同的生物:每次运行包含类体的代码时,它都会被“声明”并创建。这使得人们可以以非常简单的方式为各种用途创建动态类。例如,以这种方式创建的每个类都在不同的闭包中,并且可以访问包含函数的变量的不同实例。

def call_count(func):
    class Counter(object):
       def __init__(self):
           self.counter = 0
       def __repr__(self):
           return str(func)
       def __call__(self, *args, **kw):
           self.counter += 1
           return func(*args, **kw)
    return Counter()

在控制台上使用它:

>>> @call_count
... def noop(): pass
...
>>> noop()
>>> noop()
>>> noop.counter
2
>>> noop
<function noop at 0x7fc251b0b578>

因此,一个简单的call_counter装饰器可以使用一个静态的“Counter”类,在函数外部定义,并接收func作为其构造函数的参数 - 但是如果你想调整其他行为,比如在这个例子中,make repr( func)返回函数表示,而不是类表示,更容易这样做。