When to use nested classes and classes nested in modules?

后端 未结 5 626
无人及你
无人及你 2020-12-12 10:26

I\'m pretty familiar with when to use subclasses and modules, but more recently I\'ve been seeing nested classes like this:

class Foo
  class Bar
    # do so         


        
相关标签:
5条回答
  • 2020-12-12 10:53

    You probably want to use this to group your classes into a module. Sort of a namespace thing.

    for example the Twitter gem uses namespaces to achieve this:

    Twitter::Client.new
    
    Twitter::Search.new
    

    So both Client and Search classes live under the Twitter module.

    If you want to check the sources, the code for both classes can be found here and here.

    Hope this helps!

    0 讨论(0)
  • 2020-12-12 10:53

    In the addition to previous answers: Module in Ruby is a class

    $ irb
    > module Some end
    => nil
    > Some.class
    => Module
    > Module.superclass
    => Object
    
    0 讨论(0)
  • 2020-12-12 11:16

    Other OOP languages have inner classes which cannot be instantiated without being bound to an upper level class. For instance, in Java,

    class Car {
        class Wheel { }
    }
    

    only methods in the Car class can create Wheels.

    Ruby doesn’t have that behaviour.

    In Ruby,

    class Car
      class Wheel
      end
    end
    

    differs from

    class Car
    end
    
    class Wheel
    end
    

    only in the name of the class Wheel vs. Car::Wheel. This difference in name can make explicit to programmers that the Car::Wheel class can only represent a car wheel, as opposed to a general wheel. Nesting class definitions in Ruby is a matter of preference, but it serves a purpose in the sense that it more strongly enforces a contract between the two classes and in doing so conveys more information about them and their uses.

    But to the Ruby interpreter, it’s only a difference in name.

    As for your second observation, classes nested inside of modules are generally used to namespace the classes. For instance:

    module ActiveRecord
      class Base
      end
    end
    

    differs from

    module ActionMailer
      class Base
      end
    end
    

    Although this is not the only use of classes nested inside of modules, it is generally the most common.

    0 讨论(0)
  • 2020-12-12 11:18

    There is yet another difference between nested classes and nested modules in Ruby prior to 2.5 that other answers failed to cover that I feel must be mentioned here. It is the lookup process.

    In short: due to top level constant lookup in Ruby prior to 2.5, Ruby may end up looking for your nested class in the wrong place (in Object in particular) if you use nested classes.

    In Ruby prior to 2.5:
    Nested class structure: Suppose you have a class X, with nested class Y, or X::Y. And then you have a top level class named also Y. If X::Y is not loaded, then following happens when you call X::Y:

    Having not found Y in X, Ruby will try to look it up in ancestors of X. Since X is a class and not a module, it has ancestors, among which are [Object, Kernel, BasicObject]. So, it tries to look for Y in Object, where it finds it successfully.

    Yet it is the top level Y and not X::Y. You will get this warning:

    warning: toplevel constant Y referenced by X::Y
    


    Nested module structure: Suppose in the previous example X is a module and not a class.

    A module only has itself as ancestor: X.ancestors would produce [X].

    In this case, Ruby won't be able to look for Y in one of ancestors of X and will throw a NameError. Rails (or any other framework with autoloading) will try to load X::Y after that.

    See this article for more information: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/

    In Ruby 2.5:
    Top level constant lookup removed.
    You may use nested classes without fear of encountering this bug.

    0 讨论(0)
  • 2020-12-12 11:20

    In Ruby, defining a nested class is similar to defining a class in a module. It doesn't actually force an association between the classes, it just makes a namespace for the constants. (Class and Module names are constants.)

    The accepted answer wasn't correct about anything. In the example below I create an instance of the lexically enclosed class without an instance of the enclosing class ever existing.

    class A; class B; end; end
    A::B.new
    

    The advantages are the same as those for modules: encapsulation, grouping code used in only one place, and placing code closer to where it is used. A large project might have one outer module that occurs over and over in each source file and contains a lot of class definitions. When the various frameworks and library codes all do this, then they contribute only one name each to the top level, reducing the chance of conflicts. Prosaic, to be sure, but that's why they are used.

    Using a class instead of a module to define the outer namespace might make sense in a one-file program or script, or if you already use the top level class for something, or if you are actually going to add code to link the classes together in true inner-class style. Ruby doesn't have inner classes but nothing stops you from creating about the same behavior in code. Referencing the outer objects from the inner ones will still require dotting in from the instance of the outer object but nesting the classes will suggest that this is what you might be doing. A carefully modularized program might always create the enclosing classes first, and they might reasonably be decomposed with nested or inner classes. You can't call new on a module.

    You can use the general pattern even for scripts, where the namespace isn't terribly needed, just for fun and practice...

    #!/usr/bin/env ruby
    
    class A
      class Realwork_A
        ...
      end
      class Realwork_B
        ...
      end
    
      def run
        ...
      end
      
      self
    end.new.run
    
    0 讨论(0)
提交回复
热议问题