Когда использовать вложенные классы и классы, вложенные в модули?

Я хорошо знаком с тем, когда использовать подклассы и модули, но совсем недавно я видел такие вложенные классы:

class Foo
  class Bar
    # do some useful things
  end
end

Также как и классы, вложенные в такие модули:

module Baz
  class Quux
    # more code
  end
end

Любая документация и статьи разрежены или я недостаточно образован на эту тему, чтобы нащупывать правильные условия поиска, но я не могу найти много информации по этой теме.

Может ли кто-нибудь предоставить примеры или ссылки на сообщения о том, почему/когда эти методы будут использоваться?

Ответ 1

Другие языки ООП имеют внутренние классы, которые не могут быть созданы без привязки к классу верхнего уровня. Например, в Java,

class Car {
    class Wheel { }
}

только методы в классе Car могут создавать Wheel s.

Руби не имеет такого поведения.

В рубине

class Car
  class Wheel
  end
end

отличается от

class Car
end

class Wheel
end

только во имя класса Wheel vs. Car::Wheel. Это различие в названии может сделать явным для программистов, что класс Car::Wheel может представлять только колесо автомобиля, в отличие от обычного колеса. Вложение определений классов в Ruby является вопросом предпочтения, но оно служит цели в том смысле, что оно более строго обеспечивает соблюдение договора между двумя классами и при этом передает больше информации о них и их использовании.

Но для интерпретатора Ruby это только разница в названии.

Что касается вашего второго наблюдения, классы, вложенные в модули, обычно используются для пространства имен классов. Например:

module ActiveRecord
  class Base
  end
end

отличается от

module ActionMailer
  class Base
  end
end

Хотя это не единственное использование классов, вложенных в модули, оно обычно является наиболее распространенным.

Ответ 2

В Ruby определение вложенного класса аналогично определению класса в модуле. Это фактически не создает связи между классами, а просто создает пространство имен для констант. (Имена классов и модулей являются константами.)

Принятый ответ не был прав. 1 В приведенном ниже примере я создаю экземпляр лексически закрытого класса без экземпляра окружающего класса, который когда-либо существовал.

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

Преимущества те же, что и для модулей: инкапсуляция, группировка кода, используемая только в одном месте, и размещение кода ближе к тому, где он используется. У большого проекта может быть один внешний модуль, который возникает снова и снова в каждом исходном файле и содержит множество определений классов. Когда все эти фреймворки и коды библиотек делают это, они вносят только одно имя на верхний уровень, что уменьшает вероятность конфликтов. Прозаик, конечно, но то, почему они используются.

Использование класса вместо модуля для определения внешнего пространства имен может иметь смысл в однофайловой программе или script или если вы уже используете класс верхнего уровня для чего-то или если вы действительно собираетесь добавлять код чтобы связать классы вместе в истинном стиле внутреннего класса. Ruby не имеет внутренних классов, но ничто не мешает вам создавать примерно такое же поведение в коде. Обращение к внешним объектам из внутренних будет по-прежнему требовать точки из экземпляра внешнего объекта, но вложенные классы подскажут, что это то, что вы могли бы делать. Тщательно модульная программа всегда может сначала создавать классы-оболочки, и их можно разумно разложить с помощью вложенных или внутренних классов. Вы не можете вызвать new на модуле.

Вы можете использовать общий шаблон даже для скриптов, где пространство имен не требуется, просто для удовольствия и практики...

#!/usr/bin/env ruby

class A
  class Realwork_A
    ...
  end
  class Realwork_B
    ...
  end

  def run
    ...
  end

  self
end.new.run

Ответ 3

Вероятно, вы захотите использовать это для группы своих классов в модуле. Тип пространства имен.

например, щебетать Twitter использует пространства имен для достижения этого:

Twitter::Client.new

Twitter::Search.new

Таким образом, классы Client и Search живут под модулем Twitter.

Если вы хотите проверить источники, код для обоих классов можно найти здесь и здесь.

Надеюсь, это поможет!

Ответ 4

В дополнение к предыдущим ответам: Модуль в Ruby - это класс

$ irb
> module Some end
=> nil
> Some.class
=> Module
> Module.superclass
=> Object

Ответ 5

Еще одно различие между вложенными классами и вложенными модулями в Ruby до версии 2.5 заключается в том, что другие ответы не смогли охватить, что я считаю нужным упомянуть здесь. Это процесс поиска.

Вкратце: из-за поиска констант верхнего уровня в Ruby до версии 2.5 Ruby может в конечном итоге искать ваш вложенный класс в неправильном месте (в частности, в Object), если вы используете вложенные классы.

В Ruby до 2.5:
Структура вложенного класса. Предположим, у вас есть класс X с вложенным классом Y или X::Y И тогда у вас есть класс верхнего уровня с именем также Y Если X::Y не загружен, то при вызове X::Y:

Не найдя Y в X, Ruby попытается найти его в предках X Поскольку X является классом, а не модулем, у него есть предки, среди которых [Object, Kernel, BasicObject]. Таким образом, он пытается найти Y в Object, где он находит его успешно.

И все же это верхний уровень Y а не X::Y Вы получите это предупреждение:

warning: toplevel constant Y referenced by X::Y


Структура вложенного модуля. Предположим, что в предыдущем примере X является модулем, а не классом.

Модуль имеет только X.ancestors предка: X.ancestors создаст [X].

В этом случае Ruby не сможет найти Y в одном из предков X и выдаст NameError. Rails (или любой другой фреймворк с автозагрузкой) попытается загрузить X::Y после этого.

См. Эту статью для получения дополнительной информации: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/

В Ruby 2.5:
Верхний уровень константный поиск удален.
Вы можете использовать вложенные классы, не опасаясь столкнуться с этой ошибкой.