Коли використовувати вкладені класи та класи, вкладені в модулі?


144

Я досить добре знайомий з тим, як використовувати підкласи та модулі, але останнім часом я бачив такі вкладені класи:

class Foo
  class Bar
    # do some useful things
  end
end

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

module Baz
  class Quux
    # more code
  end
end

Або документація, і статті є рідкісними, або я недостатньо освічений з цього питання, щоб шукати потрібні пошукові терміни, але я не можу знайти багато інформації з цієї теми.

Чи може хтось навести приклади чи посилання на повідомлення про те, чому / коли будуть використовуватися ці методи?

Відповіді:


140

Інші мови OOP мають внутрішні класи, які неможливо створити інстанціювати, не будучи прив’язаними до класу верхнього рівня. Наприклад, у Java,

class Car {
    class Wheel { }
}

тільки методи в Carкласі можуть створювати Wheels.

У Рубі такої поведінки немає.

У Рубі,

class Car
  class Wheel
  end
end

відрізняється від

class Car
end

class Wheel
end

тільки ім'я класу WheelVS. Car::Wheel. Ця різниця в назві може зробити явним для програмістів, що Car::Wheelклас може представляти лише колесо автомобіля, на відміну від загального колеса. Визначення класів введення в Ruby - це питання переваги, але воно служить цілі в тому сенсі, що воно сильніше виконує контракт між двома класами і тим самим передає більше інформації про них та їх використання.

Але для перекладача Ruby це лише різниця в назві.

Що стосується вашого другого спостереження, то класи, що вкладаються всередину модулів, зазвичай використовуються для простору імен класів. Наприклад:

module ActiveRecord
  class Base
  end
end

відрізняється від

module ActionMailer
  class Base
  end
end

Хоча це не єдине використання класів, вкладених всередину модулів, воно, як правило, є найпоширенішим.


5
@rubyprince, я не впевнений, що ти маєш на увазі, встановлюючи відношення між Car.newі Car::Wheel.new. Вам точно не потрібно ініціалізувати Carоб'єкт для ініціалізації Car::Wheelоб'єкта в Ruby, але Carклас повинен бути завантажений і виконаний, Car::Wheelщоб бути корисним.
Пан Томакос

30
@Pan, ви плутаєте внутрішні класи Java та класи Ruby , що відрізняються від імен. Нестатичний вкладений клас Java називається внутрішнім класом і він існує лише в екземплярі зовнішнього класу. Існує приховане поле, яке дозволяє зовнішні посилання. Внутрішній клас Ruby просто простір імен і жодним чином не "прив'язаний" до класу, що додається. Він еквівалентний статичному (вкладеному) класу Java . Так, у відповіді багато голосів, але це не зовсім точно.
DigitalRoss

7
Я не знаю, як ця відповідь отримала 60 відгуків, не кажучи вже про те, що ОП прийняли. Тут буквально немає жодного справжнього твердження. У Ruby немає вкладених класів, як Beta або Newspeak do. Між Carта між ними немає абсолютно ніякого відношення Car::Wheel. Модулі (і, таким чином, класи) - просто простори імен для констант, у Ruby немає такого поняття, як вкладений клас чи вкладений модуль.
Йорг W Міттаг

4
Єдина відмінність між ними - це постійне дозвіл (яке є лексичним і, очевидно, різним, оскільки два фрагменти лексично різні). Однак немає різниці між двома, що стосуються занять. Просто два абсолютно не пов'язані між собою класи. Період. У Ruby немає вкладених / внутрішніх класів. Ваше визначення вкладених класів є правильним, але це просто не відноситься до Ruby, як ви можете тривіальним перевірити: Car::Wheel.new. Бум. Я тільки що сконструював Wheelоб'єкт, який не вкладений всередині Carоб'єкта.
Йорг W Міттаг

10
Ця публікація вводить в оману, не читаючи всю тему коментарів.
Натан

50

У Ruby визначення вкладеного класу аналогічно визначенню класу в модулі. Він насправді не примушує асоціації між класами, він просто створює простір імен для констант. (Назви класів і модулів - константи.)

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

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

Переваги такі ж, як і для модулів: інкапсуляція, групування коду, що використовується лише в одному місці, та розміщення коду ближче до місця, де він використовується. Великий проект може мати один зовнішній модуль, який виникає знову і знову у кожному вихідному файлі і містить безліч визначень класів. Коли всі рамки та коди бібліотеки все це роблять, вони вносять лише одне ім'я до кожного найвищого рівня, зменшуючи ймовірність конфліктів. Безумовно, прозаїчно, але саме тому вони використовуються.

Використання класу замість модуля для визначення зовнішнього простору імен може мати сенс в однофайловій програмі або скрипті, або якщо ви вже використовуєте клас верхнього рівня для чогось, або якщо ви дійсно збираєтеся додати код для з'єднання класів разом у справжньому внутрішньому класі . У Ruby немає внутрішніх класів, але ніщо не заважає вам створювати приблизно однакову поведінку в коді. Посилання на зовнішні об’єкти із внутрішніх все ще потребуватимуть крапки з екземпляра зовнішнього об’єкта, але вкладення класів підкаже, що саме цим ви можете займатися. Ретельно модульована програма завжди може спочатку створювати класи, що додаються, і вони можуть бути розумно розкладені з вкладеними або внутрішніми класами. Ви не можете зателефонувати newна модуль.

Ви можете використовувати загальну схему навіть для сценаріїв, де простір імен не дуже потрібен, просто для розваги та практики ...

#!/usr/bin/env ruby

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

  def run
    ...
  end

  self
end.new.run

15
Будь ласка, досить, будь ласка, не називайте це внутрішнім класом. Це не. Клас неB є всередині класу . Константа в просторі імен всередині класу , але немає абсолютно ніякого зв'язку між об'єктом , на який посилається (в даному випадку як раз трапляється бути клас) і класу , на який посилається . A BABA
Йорг W Міттаг

2
Гаразд, "внутрішня" термінологія видалена. Гарна думка. Для тих, хто не дотримується аргументу, наведеного вище, причина суперечки полягає в тому, що коли ви робите щось подібне в, скажімо, Java, об'єкти внутрішнього класу (і тут я використовую термін канонічно) містять посилання на до змінних зовнішнього класу та зовнішнього екземпляра можна посилатися методами внутрішнього класу. Нічого цього не відбувається в Ruby, якщо ви не зв’яжете їх з кодом. І ви знаєте, якби цей код був присутнім у класі, ах, що додає, тоді, я думаю, ви могли б з розумом назвати Bar внутрішнім класом.
DigitalRoss

1
Для мене саме це твердження допомагає мені найбільше при вирішенні між модулем і класом: You can't call new on a module.- так в базових умовах, якщо я просто хочу простору імен деяких класів і ніколи не потрібно фактично створювати екземпляр зовнішнього "класу", то Я б використовував зовнішній модуль. Але якщо я хочу створити екземпляр обгорткового / зовнішнього "класу", тоді я зробив би це клас замість модуля. Принаймні, це має сенс для мене.
FireDragon

@FireDragon Або іншим випадком використання може бути те, що ви хочете клас, який є Фабрикою, від якого підлягають підкласи, а ваша фабрика відповідає за створення примірників підкласів. У цій ситуації ваша Фабрика не може бути модулем, оскільки ви не можете успадкувати модуль, тому це батьківський клас, який ви не інстанціюєте (і можете "простору імен" це діти, якщо хочете, на зразок модуля)
rmcsharry

15

Ви, ймовірно, хочете використовувати це для групування класів у модулі. Сорт із простору імен.

наприклад, дорогоцінний камінь Twitter використовує простори імен для цього:

Twitter::Client.new

Twitter::Search.new

Тож Clientі модулі Searchживуть під Twitterмодулем.

Якщо ви хочете перевірити джерела, код для обох класів можна знайти тут і тут .

Сподіваюся, це допомагає!


3
Twitter камінь оновлюється посилання: github.com/sferik/twitter/tree/master/lib/twitter
Kode

6

Існує ще одна відмінність між вкладеними класами та вкладеними модулями в 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].

У цьому випадку Рубі не зможе шукати Yкогось із предків Xі кине а NameError. Рейки (або будь-який інший фреймворк з автоматичним завантаженням) намагатимуться завантажити X::Yпісля цього.

Додаткову інформацію див.

У цій статті: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/ In Ruby 2.5:
Постійний пошук верхнього рівня видалено.
Ви можете використовувати вкладені класи, не побоюючись зустріти цю помилку.


3

На додаток до попередніх відповідей: Модуль в Ruby - це клас

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

11
Ви б точніше сказати, що "клас у Ruby - це модуль"!
Тім Діггінс

2
Все в Ruby може бути об'єктом, але виклик модуля класом не здається правильним: irb (main): 005: 0> Class.ancestors.reverse => [BasicObject, Kernel, Object, Module, Class]
Чад M,

і тут ми приходимо до визначення поняття "є"
ahnbizcad

Зауважте, що модулі неможливо примірник. Тобто неможливо створити об'єкти з модуля. Тож модулі, на відміну від класів, не мають методу new. Тож, хоча ви можете сказати, що Модулі - це клас (малі регістри), вони не те саме, що клас (прописний регістр) :)
rmcsharry
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.