Quando usar classes aninhadas e classes aninhadas em módulos?


144

Eu estou bem familiarizado com quando usar subclasses e módulos, mas mais recentemente eu tenho visto classes aninhadas como esta:

class Foo
  class Bar
    # do some useful things
  end
end

Bem como classes aninhadas em módulos como este:

module Baz
  class Quux
    # more code
  end
end

A documentação e os artigos são escassos ou não sou educado sobre o assunto o suficiente para procurar os termos de pesquisa corretos, mas não consigo localizar muitas informações sobre o assunto.

Alguém poderia fornecer exemplos ou links para postagens sobre por que / quando essas técnicas seriam usadas?

Respostas:


140

Outras linguagens OOP têm classes internas que não podem ser instanciadas sem estar vinculadas a uma classe de nível superior. Por exemplo, em Java,

class Car {
    class Wheel { }
}

somente métodos da Carclasse podem criar Wheels.

Ruby não tem esse comportamento.

Em Ruby,

class Car
  class Wheel
  end
end

é diferente de

class Car
end

class Wheel
end

apenas no nome da classe Wheelvs. Car::Wheel. Essa diferença de nome pode tornar explícito aos programadores que a Car::Wheelclasse só pode representar uma roda de carro, em oposição a uma roda geral. A definição de classes de aninhamento no Ruby é uma questão de preferência, mas serve a um propósito no sentido de que impõe mais fortemente um contrato entre as duas classes e, ao fazer isso, transmite mais informações sobre elas e seus usos.

Mas para o intérprete Ruby, é apenas uma diferença no nome.

Quanto à sua segunda observação, as classes aninhadas dentro dos módulos são geralmente usadas para namespace as classes. Por exemplo:

module ActiveRecord
  class Base
  end
end

é diferente de

module ActionMailer
  class Base
  end
end

Embora esse não seja o único uso de classes aninhadas dentro dos módulos, geralmente é o mais comum.


5
@ Rubyprince, não sei o que você quer dizer com estabelecer uma relação entre Car.newe Car::Wheel.new. Definitivamente, você não precisa inicializar um Carobjeto para inicializá-lo Car::Wheelno Ruby, mas a Carclasse deve ser carregada e executada para Car::Wheelser utilizável.
Pan Thomakos

30
@ Pan, você está confundindo classes internas Java e classes Ruby com espaço para nome. Uma classe aninhada Java não estática é chamada de classe interna e existe apenas dentro de uma instância da classe externa. Há um campo oculto que permite referências externas. A classe interna do Ruby é simplesmente namespace e não é "vinculada" à classe envolvente de forma alguma. É equivalente a uma classe estática (aninhada) do Java . Sim, a resposta tem muitos votos, mas não é completamente precisa.
precisa

7
Não tenho idéia de como essa resposta recebeu 60 votos positivos e muito menos foi aceita pelo OP. Não há literalmente uma única declaração verdadeira aqui. Ruby não tem classes aninhadas como Beta ou Newspeak. Não existe absolutamente nenhuma relação entre Care Car::Wheel. Módulos (e, portanto, classes) são simplesmente espaços de nomes para constantes; não existe classe ou módulo aninhado no Ruby.
Jörg W Mittag

4
A única diferença entre os dois é a resolução constante (que é lexical e, portanto, obviamente diferente porque os dois trechos são lexicamente diferentes). No entanto, não há absolutamente nenhuma diferença entre as duas em relação às classes envolvidas. Existem simplesmente duas classes completamente independentes. Período. Ruby não tem classes aninhadas / internas. Sua definição de classes aninhadas é correto, mas ele simplesmente não se aplica a Ruby, como você pode trivialmente testar: Car::Wheel.new. Estrondo. Acabei de construir um Wheelobjeto que não está aninhado dentro de um Carobjeto.
Jörg W Mittag 17/10

10
Esta postagem é altamente enganosa sem a leitura de todo o tópico de comentários.
28415 Nathan

50

Em Ruby, definir uma classe aninhada é semelhante a definir uma classe em um módulo. Na verdade, não força uma associação entre as classes, apenas cria um espaço para nomes para as constantes. (Os nomes de classe e módulo são constantes.)

A resposta aceita não estava correta sobre nada. 1 No exemplo abaixo, crio uma instância da classe fechada lexicamente sem que exista uma instância da classe envolvente.

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

As vantagens são as mesmas dos módulos: encapsulamento, código de agrupamento usado em apenas um lugar e colocação do código mais perto de onde é usado. Um projeto grande pode ter um módulo externo que ocorre repetidamente em cada arquivo de origem e contém muitas definições de classe. Quando as várias estruturas e códigos de biblioteca fazem isso, eles contribuem apenas com um nome para o nível superior, reduzindo a chance de conflitos. Prosaico, com certeza, mas é por isso que eles são usados.

O uso de uma classe em vez de um módulo para definir o espaço para nome externo pode fazer sentido em um programa ou script de um arquivo, ou se você já usa a classe de nível superior para alguma coisa ou se deseja adicionar código para vincular as classes no verdadeiro estilo da classe interna . Ruby não tem classes internas, mas nada impede que você crie o mesmo comportamento no código. A referência aos objetos externos a partir dos internos ainda exigirá pontos da instância do objeto externo, mas o aninhamento das classes sugerirá que é isso que você pode estar fazendo. Um programa cuidadosamente modularizado pode sempre criar primeiro as classes anexas e pode razoavelmente ser decompostas com classes internas ou aninhadas. Você não pode chamar newum módulo.

Você pode usar o padrão geral mesmo para scripts, onde o espaço para nome não é muito necessário, apenas por diversão e prática ...

#!/usr/bin/env ruby

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

  def run
    ...
  end

  self
end.new.run

15
Por favor, por favor, não chame isso de classe interna. Não é. A classe nãoB está dentro da classe . A constante está com namespace dentro da classe , mas não há absolutamente nenhum relacionamento entre o objeto referenciado por (que neste caso é uma classe) e a classe referenciada por . A BABA
Jörg W Mittag

2
Ok, terminologia "interna" removida. Bom ponto. Para aqueles que não seguem o argumento acima, o motivo da disputa é que, quando você faz algo assim em, digamos, Java, objetos da classe interna (e aqui estou usando o termo canonicamente) contêm uma referência a a classe externa e as variáveis ​​de instância externa podem ser referenciadas por métodos de classe interna. Nada disso acontece no Ruby, a menos que você os vincule ao código. E você sabe, se esse código estava presente na classe ahem, que envolve, então eu aposto que você poderia razoavelmente chamar de Bar uma classe interna.
DigitalRoss

1
Para mim, é essa afirmação que mais me ajuda ao decidir entre um módulo e uma classe: You can't call new on a module.- portanto, em termos básicos, se eu apenas quiser nomear algumas classes e nunca precisar realmente criar uma instância da "classe" externa, Eu usaria um módulo externo. Mas se eu quiser instanciar uma instância da "classe" wrapping / outer, então eu a tornaria uma classe em vez de um módulo. Pelo menos isso faz sentido para mim.
FireDragon

@FireDragon Ou outro caso de uso pode ser o de que você deseja uma classe que é uma Factory, da qual as subclasses são herdadas, e sua fábrica é responsável por criar instâncias das subclasses. Nessa situação, o seu Factory não pode ser um módulo porque você não pode herdar de um módulo; portanto, é uma classe pai que você não instancia (e pode 'namespace' seus filhos, se quiser, como um módulo)
rmcsharry

15

Você provavelmente deseja usar isso para agrupar suas classes em um módulo. Tipo de coisa de espaço para nome.

por exemplo, a gema do Twitter usa namespaces para conseguir isso:

Twitter::Client.new

Twitter::Search.new

Portanto, tanto as classes Clientquanto as Searchclasses vivem sob o Twittermódulo.

Se você quiser verificar as fontes, o código para as duas classes pode ser encontrado aqui e aqui .

Espero que isto ajude!


3
Link atualizado do gem do Twitter: github.com/sferik/twitter/tree/master/lib/twitter
kode

6

Há ainda outra diferença entre as classes aninhadas e os módulos aninhados no Ruby anteriores à 2.5, que outras respostas falharam em abordar que considero que devem ser mencionadas aqui. É o processo de pesquisa.

Em resumo: devido à pesquisa constante de nível superior no Ruby anterior à 2.5, o Ruby pode acabar procurando sua classe aninhada no lugar errado ( Objectem particular) se você usar classes aninhadas.

No Ruby anterior ao 2.5:
Estrutura de classe aninhada: suponha que você tenha uma classe X, com classe aninhada Y, ou X::Y. E então você tem uma classe de nível superior chamada também Y. Se X::Ynão estiver carregado, em seguida, seguindo acontece quando você chama X::Y:

Não tendo encontrado Yem X, Ruby vai tentar procurá-lo em antepassados de X. Desde aXé uma classe e não um módulo, possui ancestrais, entre os quais estão [Object, Kernel, BasicObject]. Então, ele tenta procurar Yno Object, onde encontra-lo com sucesso.

No entanto, é o nível superior Ye não X::Y. Você receberá este aviso:

warning: toplevel constant Y referenced by X::Y


Estrutura do módulo aninhado: suponha que no exemplo anterior Xseja um módulo e não uma classe.

Um módulo tem apenas a si próprio como ancestral: X.ancestorsproduziria [X].

Nesse caso, Ruby não poderá procurar Yem um dos ancestrais de Xe lançará a NameError. O Rails (ou qualquer outra estrutura com carregamento automático) tentará carregar X::Ydepois disso.

Consulte este artigo para obter mais informações: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/

Em Ruby 2.5:
Pesquisa constante de nível superior removida.
Você pode usar classes aninhadas sem medo de encontrar esse bug.


3

Além das respostas anteriores: Módulo em Ruby é uma classe

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

11
Você seria mais preciso ao dizer que 'classe em Ruby é um módulo'!
precisa

2
Tudo em Ruby pode ser um objeto, mas chamar uma classe de módulo não parece correto: irb (main): 005: 0> Class.ancestors.reverse => [BasicObject, Kernel, Objeto, Módulo, Classe]
Chad M

E aqui chegamos à definição de "é"
ahnbizcad

Observe que os módulos não podem ser instanciados. Ou seja, não é possível criar objetos a partir de um módulo. Portanto, os módulos, diferentemente das classes, não têm um método new. Assim, embora você pode dizer Módulos são uma classe (em minúsculas), eles não são a mesma coisa que uma classe (maiúsculas) :)
rmcsharry
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.