extender-se em rubi
Código de acompanhamento foi testado com rubi 1.9.3 .
Classe é para dados e comportamento
Vejamos este código em rubi.
1classe Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=9762> 8
Aqui temos uma classe Util
. Mas note que todos os métodos nesta classe são métodos de classe. Esta classe não tem nenhuma variável de instância. Normalmente uma classe é usada para carregar tanto dados quanto comportamento e, neste caso, a classe Util tem apenas comportamento e nenhum dado.
Ferramentas utilitárias simples em ruby
Agora para obter alguma perspectiva sobre esta discussão vamos olhar para alguns métodos ruby que fazem coisas similares. Aqui estão alguns.
1require 'base64'2Base64.encode64('hello world') #=9762> "aGVsbG8gd29ybGQ=\n "34require 'benchmark'5Benchmark.measure { 10*2000 }67require 'fileutils'8FileUtils.chmod 0644, 'test.rb'910Math.sqrt(4) #=> 2
Em todos os casos acima o método de classe é invocado sem criar uma instância primeiro. Então isto é similar à forma como eu usei Util.double
.
No entanto vamos ver qual é a classe de todos estes objetos.
1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module
Então estes não são classes mas sim módulos. Isso levanta a questão porque os caras inteligentes do ruby-core os implementaram como módulos ao invés de criar uma classe como eu fiz para o Util.
A razão é que a classe é muito pesada para criar apenas métodos como double
. Como discutimos anteriormente, uma classe deve ter tanto dados quanto comportamento. Se a única coisa que lhe interessa é o comportamento então o Ruby sugere implementá-lo como um módulo.
extend self é a resposta
Antes de continuar a discutir extend self
aqui está como a minha classe Util
irá cuidar depois de passar de Class
para Module
.
1módulo Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8
Então como extender o self trabalha
Primeiro vamos ver o que extender faz.
1módulo M2 def double(i)3 i * 24 end5end67class Calculator8 extend M9end10puts Calculator.double(4)
No caso acima Calculator
está estendendo o módulo M
e, portanto, todos os métodos de instância do módulo M
estão diretamente disponíveis para Calculator
.
Neste caso Calculator
é uma classe que estendeu o módulo M
. Entretanto Calculator
não precisa ser uma classe para estender um módulo.
Agora vamos tentar uma variação onde Calculator
é um módulo.
1módulo M2 def double(i)3 i * 24 end5end67módulo Calculadora8 extend M9end10puts Calculadora.double(4) #=> 8
Aqui Calculadora é um módulo que está estendendo outro módulo.
Agora entendemos que um módulo pode estender outro módulo olhar para o código acima e questionar porque o módulo M
é mesmo necessário. Por que não podemos mover o método double
para o módulo Calculadora diretamente. Vamos tentar isso.
1modulo Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Eu me livrei do módulo M
e movi o método double
dentro do módulo Calculator
. Como o módulo M
desapareceu eu mudei de extend M
para extend Calculator
.
Uma última correção.
Inside the module Calculator what is self
. self
é o módulo Calculator
em si. Então não há necessidade de repetir Calculator
duas vezes. Aqui está a versão final
1módulo Calculator2 extend self34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=9762> 8
Convertendo uma classe em um módulo
Cada vez que eu encontrasse um código como extend self
meu cérebro faria uma pausa por um momento. Então eu iria ao Google para ele. Vou ler sobre isso. Três meses depois vou repetir todo o processo.
A melhor maneira de aprender é usando-o. Então eu comecei a procurar um caso para usar extend self
. Não é uma boa prática ir à caça de código para aplicar uma idéia que você tem em sua mente, mas aqui eu estava tentando aprender.
Aqui está um instantâneo anterior dos métodos de Util
classe que usei em um projeto.
1classe Util2 def self.config2hash(file); end3 def self.in_cents(amount); end4 def self.localhost2public_url(url, protocol); end5end
Após usar extend self
code tornou-se
1module Util2 extend self34 def config2hash(file); end5 def in_cents(amount); end6 def localhost2public_url(url, protocol); end7end
Muito melhor. Ele deixa a intenção clara e, acredito, está de acordo com a maneira que o rubi esperaria que usássemos.
Outro uso em linha com a forma como o Rails usa o self
Aqui estou construindo uma aplicação de comércio eletrônico e cada novo pedido precisa obter um novo número de pedido de uma aplicação de vendas de terceiros. O código pode se parecer com isto. Eu omiti a implementação dos métodos porque eles não são relevantes para esta discussão.
1classe Order2 def amount; end3 def buyer; end4 def shipped_at; end5 def number6 @number || self.class.next_order_number7 end89 def self.next_order_number; 'A100'; end10end1112puts Order.new.number #=> A100
Here o método next_order_number
pode estar fazendo uma chamada complicada para outro sistema de vendas. O ideal seria que a classe Order
não expusesse o método next_order_number
. Assim podemos fazer este método private
mas isso não resolve o problema raiz. O problema é que o modelo Order
não deveria saber como o novo número de pedido é gerado. Bem, podemos mover o método next_order_number
para outra classe Util
mas isso criaria demasiada distância.
Aqui está uma solução usando extend self
.
1módulo Checkout2 extend self34 def next_order_number; 'A100'; end56 class Order7 def amount; end8 def buyer; end9 def shipped_at; end10 def number11 @number ||| Checkout.next_order_number12 end13 end14end1516puts Checkout::Order.new.number #=> A100
Muito melhor. A classe Order não está expondo o método next_order_number
e este método está bem ali no mesmo arquivo. Não é necessário abrir a classe Util
classe.
.