Kiedy używać klas zagnieżdżonych i klas zagnieżdżonych w modułach?

144

Wiem, kiedy używać podklas i modułów, ale ostatnio widziałem takie zagnieżdżone klasy:

class Foo
  class Bar
    # do some useful things
  end
end

Oprócz klas zagnieżdżonych w modułach, takich jak:

module Baz
  class Quux
    # more code
  end
end

Albo dokumentacja i artykuły są skąpe, albo nie jestem wystarczająco wykształcony na ten temat, aby znaleźć odpowiednie wyszukiwane hasła, ale nie mogę znaleźć wielu informacji na ten temat.

Czy ktoś mógłby podać przykłady lub linki do postów o tym, dlaczego / kiedy te techniki zostaną użyte?

cmhobbs
źródło

Odpowiedzi:

140

Inne języki OOP mają klasy wewnętrzne, których nie można utworzyć bez powiązania z klasą wyższego poziomu. Na przykład w Javie

class Car {
    class Wheel { }
}

tylko metody w Carklasie mogą tworzyć Wheels.

Ruby nie ma takiego zachowania.

W Rubim

class Car
  class Wheel
  end
end

różni się od

class Car
end

class Wheel
end

tylko w nazwie klasy Wheelvs. Car::Wheel. Ta różnica w nazwie może wyraźnie wskazywać programistom, że Car::Wheelklasa może reprezentować tylko koło samochodowe, w przeciwieństwie do koła ogólnego. Zagnieżdżanie definicji klas w Rubim jest kwestią preferencji, ale służy celowi w tym sensie, że silniej wymusza kontrakt między dwiema klasami, a tym samym przekazuje więcej informacji o nich i ich zastosowaniach.

Ale dla interpretera Rubiego to tylko różnica w nazwie.

Jeśli chodzi o drugą obserwację, klasy zagnieżdżone w modułach są zwykle używane do określania przestrzeni nazw klas. Na przykład:

module ActiveRecord
  class Base
  end
end

różni się od

module ActionMailer
  class Base
  end
end

Chociaż nie jest to jedyne użycie klas zagnieżdżonych w modułach, jest na ogół najpowszechniejsze.

Pan Thomakos
źródło
5
@rubyprince, nie jestem pewien, co masz na myśli, ustanawiając relację między Car.newi Car::Wheel.new. Zdecydowanie nie musisz inicjować Carobiektu, aby zainicjować Car::Wheelobiekt w Rubim, ale Carklasa musi zostać załadowana i wykonana, Car::Wheelaby była użyteczna.
Pan Thomakos
30
@Pan, mylisz wewnętrzne klasy Javy i klasy Ruby z przestrzenią nazw. Niestatyczna klasa zagnieżdżona Java jest nazywana klasą wewnętrzną i istnieje tylko w instancji klasy zewnętrznej. Istnieje ukryte pole, które umożliwia zewnętrzne odniesienia. Klasa wewnętrzna Rubiego jest po prostu przestrzenią nazw i nie jest w żaden sposób „związana” z klasą obejmującą. Jest odpowiednikiem statycznej (zagnieżdżonej) klasy Java . Tak, odpowiedź ma wiele głosów, ale nie jest do końca poprawna.
DigitalRoss
7
Nie mam pojęcia, jak ta odpowiedź zyskała 60 głosów za, nie mówiąc już o przyjęciu przez PO. Nie ma tu dosłownie ani jednego prawdziwego stwierdzenia. Ruby nie ma zagnieżdżonych klas, takich jak Beta czy Newspeak. Nie ma absolutnie żadnego związku między Cara Car::Wheel. Moduły (a tym samym klasy) to po prostu przestrzenie nazw dla stałych, w Rubim nie ma czegoś takiego jak klasa zagnieżdżona lub moduł zagnieżdżony.
Jörg W Mittag,
4
Jedyną różnicą między nimi jest stała rozdzielczość (która jest leksykalna, a zatem oczywiście inna, ponieważ dwa fragmenty są leksykalnie różne). Nie ma jednak absolutnie żadnej różnicy między tymi dwoma, jeśli chodzi o klasy, których to dotyczy. Istnieją po prostu dwie zupełnie niepowiązane ze sobą klasy. Kropka. Ruby nie ma klas zagnieżdżonych / wewnętrznych. Twoja definicja klas zagnieżdżonych jest poprawne, ale to po prostu nie ma zastosowania do Ruby, jak można przetestować trywialnie: Car::Wheel.new. Bum. Właśnie skonstruowałem Wheelobiekt, który nie jest zagnieżdżony w Carobiekcie.
Jörg W Mittag
10
Ten post jest bardzo mylący bez czytania całego wątku komentarzy.
Nathan
50

W Rubim definiowanie klasy zagnieżdżonej jest podobne do definiowania klasy w module. W rzeczywistości nie wymusza asocjacji między klasami, po prostu tworzy przestrzeń nazw dla stałych. (Nazwy klas i modułów są stałymi).

Przyjęta odpowiedź nie była w niczym poprawna. 1 W poniższym przykładzie tworzę instancję klasy zamkniętej leksykalnie bez instancji tej klasy kiedykolwiek istniejącej.

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

Korzyści są takie same jak w przypadku modułów: hermetyzacja, grupowanie kodu używanego tylko w jednym miejscu i umieszczanie kodu bliżej miejsca, w którym jest używany. Duży projekt może mieć jeden moduł zewnętrzny, który pojawia się wielokrotnie w każdym pliku źródłowym i zawiera wiele definicji klas. Kiedy różne frameworki i kody bibliotek robią to wszystko, wtedy dodają tylko jedną nazwę do najwyższego poziomu, zmniejszając ryzyko konfliktów. Z pewnością prozaiczne, ale dlatego są używane.

Używanie klasy zamiast modułu do definiowania zewnętrznej przestrzeni nazw może mieć sens w jednoplikowym programie lub skrypcie, lub jeśli używasz już do czegoś klasy najwyższego poziomu lub jeśli faktycznie zamierzasz dodać kod łączący klasy ze sobą w prawdziwie klasycznym stylu. Ruby nie ma klas wewnętrznych, ale nic nie stoi na przeszkodzie, abyś tworzył w kodzie takie samo zachowanie. Odwoływanie się do obiektów zewnętrznych do obiektów wewnętrznych nadal będzie wymagało dotarcia z wystąpienia obiektu zewnętrznego, ale zagnieżdżenie klas będzie sugerować, że właśnie to możesz robić. Starannie zmodularyzowany program może zawsze najpierw utworzyć otaczające klasy i rozsądnie można je rozłożyć na klasy zagnieżdżone lub wewnętrzne. Nie możesz wywołać newmodułu.

Możesz użyć ogólnego wzorca nawet dla skryptów, w których przestrzeń nazw nie jest strasznie potrzebna, tylko dla zabawy i ćwiczeń ...

#!/usr/bin/env ruby

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

  def run
    ...
  end

  self
end.new.run
DigitalRoss
źródło
15
Proszę, bardzo proszę, nie nazywajcie tego klasą wewnętrzną. To nie jest. Klasa nieB jest wewnątrz klasy . Stała się wewnątrz przestrzeni nazw klasy , ale nie ma absolutnie żadnego związku między obiektem wskazywanym przez (który w tym przypadku okazuje się być klasą) i klasy odwołuje . A BABA
Jörg W Mittag,
2
OK, usunięto terminologię „wewnętrzną”. Słuszna uwaga. Dla tych, którzy nie podążają za powyższym argumentem, powodem sporu jest to, że kiedy robisz coś takiego w, powiedzmy, Javie, obiekty klasy wewnętrznej (i tutaj używam terminu kanonicznie) zawierają odniesienie do do klasy zewnętrznej i zmiennych instancji zewnętrznej można odwoływać się za pomocą metod klasy wewnętrznej. Nic z tego nie dzieje się w Rubim, chyba że połączysz je z kodem. I wiesz, gdyby ten kod był obecny w klasie obejmującej, ahem, to założę się, że rozsądnie można by nazwać Bar klasą wewnętrzną.
DigitalRoss
1
Jest to dla mnie stwierdzenie, które najbardziej pomaga mi przy podejmowaniu decyzji między modułem a klasą: You can't call new on a module.- więc w podstawowych kategoriach, jeśli chcę tylko nazwać niektóre klasy i nigdy nie muszę tworzyć instancji zewnętrznej „klasy”, to Użyłbym modułu zewnętrznego. Ale jeśli chcę utworzyć wystąpienie „klasy” opakowującej / zewnętrznej, uczyniłbym ją klasą zamiast modułem. Przynajmniej ma to dla mnie sens.
FireDragon
@FireDragon Lub inny przypadek użycia może być taki, że chcesz mieć klasę będącą Factory, z której dziedziczą podklasy, a twoja fabryka jest odpowiedzialna za tworzenie instancji podklas. W tej sytuacji twoja Factory nie może być modułem, ponieważ nie możesz dziedziczyć z modułu, więc jest to klasa nadrzędna, której nie tworzysz (i możesz
``
15

Prawdopodobnie chcesz użyć tego do zgrupowania swoich klas w moduł. Coś w rodzaju przestrzeni nazw.

na przykład klejnot na Twitterze wykorzystuje przestrzenie nazw, aby to osiągnąć:

Twitter::Client.new

Twitter::Search.new

Tak więc obie Clienti Searchklasy mieszkają pod Twittermodułem.

Jeśli chcesz sprawdzić źródła, kod obu klas można znaleźć tutaj i tutaj .

Mam nadzieję że to pomoże!

Pablo Fernandez
źródło
3
Twitter gem aktualizowane Link: github.com/sferik/twitter/tree/master/lib/twitter
kode
6

Istnieje jeszcze inna różnica między klasami zagnieżdżonymi a modułami zagnieżdżonymi w Rubim przed wersją 2.5, której inne odpowiedzi nie uwzględniły, o czym moim zdaniem należy tutaj wspomnieć. To jest proces wyszukiwania.

W skrócie: z powodu wyszukiwania stałych najwyższego poziomu w Rubim przed 2.5, Ruby może skończyć szukać twojej zagnieżdżonej klasy w złym miejscu ( Objectw szczególności), jeśli używasz zagnieżdżonych klas.

W Rubim wcześniejszych niż 2.5:
Zagnieżdżona struktura klas: Załóżmy, że masz klasę Xz zagnieżdżoną klasą Ylub X::Y. A potem masz również klasę najwyższego poziomu Y. Jeśli X::Ynie jest załadowany, po wywołaniu dzieje się następująco X::Y:

Nie znaleziony Yw X, Ruby spróbuje wyszukać go u przodków X. OdXjest klasą, a nie modułem, ma przodków, wśród których są [Object, Kernel, BasicObject]. Tak, stara się szukać YIN Object, jeżeli uzna go pomyślnie.

Ale to najwyższy poziom Yi nie X::Y. Otrzymasz to ostrzeżenie:

warning: toplevel constant Y referenced by X::Y


Struktura modułu zagnieżdżonego: załóżmy, że w poprzednim przykładzie Xjest to moduł, a nie klasa.

Moduł ma tylko siebie jako przodka: X.ancestorsbędzie produkować [X].

W takim przypadku Ruby nie będzie w stanie szukać Yu jednego z przodków Xi wyrzuci plik NameError. Railsy (lub jakikolwiek inny framework z automatycznym ładowaniem) spróbują załadować się X::Ypóźniej.

Zobacz ten artykuł, aby uzyskać więcej informacji: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/

In Ruby 2.5:
Usunięto stałe wyszukiwanie najwyższego poziomu.
Możesz używać zagnieżdżonych klas bez obawy napotkania tego błędu.

Timur Nugmanov
źródło
3

Oprócz poprzednich odpowiedzi: Moduł w Rubim to klasa

$ irb
> module Some end
=> nil
> Some.class
=> Module
> Module.superclass
=> Object
demas
źródło
11
Dokładniej byłoby powiedzieć, że „klasa w Rubim to moduł”!
Tim Diggins,
2
Wszystko w Rubim może być obiektem, ale wywołanie modułu klasy nie wydaje się poprawne: irb (main): 005: 0> Class.ancestors.reverse => [BasicObject, Kernel, Object, Module, Class]
Czad M
i tutaj dochodzimy do definicji „jest”
ahnbizcad
Zwróć uwagę, że nie można utworzyć instancji modułów. Oznacza to, że nie jest możliwe tworzenie obiektów z modułu. Zatem moduły, w przeciwieństwie do klas, nie mają metody new. Więc chociaż można powiedzieć Moduły są klasą (małe litery), nie są one tak samo jak klasa (wielkimi literami) :)
rmcsharry