extender self en ruby
El siguiente código fue probado con ruby 1.9.3 .
La clase sirve tanto para los datos como para el comportamiento
Miremos este código en ruby.
1clase Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8
Aquí tenemos una clase Util
. Pero observa que todos los métodos de esta clase son métodos de clase. Esta clase no tiene ninguna variable de instancia. Por lo general, una clase se utiliza para llevar tanto los datos como el comportamiento y ,en este caso, la clase Util sólo tiene el comportamiento y no los datos.
Herramientas de utilidad similares en ruby
Ahora para tener alguna perspectiva en esta discusión vamos a ver algunos métodos de ruby que hacen cosas similares. Aquí hay algunos.
1require 'base64'2Base64.encode64('hello world') #=> "aGVsbG8gd29ybGQ=\n "34require 'benchmark'5Benchmark.measure { 10*2000 }67require 'fileutils'8FileUtils.chmod 0644, 'test.rb'910Math.sqrt(4) #=> 2
En todos los casos anteriores se invoca el método de la clase sin crear una instancia primero. Así que esto es similar a la forma en que utilicé Util.double
.
Sin embargo, vamos a ver cuál es la clase de todos estos objetos.
1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module
Así que estos no son clases sino módulos. Eso nos lleva a preguntarnos por qué los chicos inteligentes de ruby-core los implementaron como módulos en lugar de crear una clase como hice yo para Util.
La razón es que Class es demasiado pesada para crear sólo métodos como double
. Como discutimos antes una clase se supone que tiene tanto datos como comportamiento. Si lo único que te importa es el comportamiento entonces ruby sugiere implementarlo como un módulo.
extenderse a sí mismo es la respuesta
Antes de pasar a discutir extend self
aquí está cómo mi clase Util
se verá después de pasar de Class
a Module
.
1module Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8
Entonces cómo funciona extend self
Primero veamos qué hace extend.
1modulo M2 def double(i)3 i * 24 end5end67clase Calculadora8 extend M9end10puts Calculadora.double(4)
En el caso anterior Calculator
está extendiendo el módulo M
y por lo tanto todos los métodos de instancia del módulo M
están directamente disponibles para Calculator
.
En este caso Calculator
es una clase que extiende el módulo M
. Sin embargo Calculator
no tiene que ser una clase para extender un módulo.
Ahora vamos a probar una variación en la que Calculator
es un módulo.
1módulo M2 def double(i)3 i * 24 end5end67módulo Calculadora8 extend M9end10puts Calculadora.double(4) #=> 8
Aquí Calculadora es un módulo que está extendiendo a otro módulo.
Ahora que entendemos que un módulo puede extender a otro módulo mira el código anterior y cuestiona por qué el módulo M
es incluso necesario. Por qué no podemos mover el método double
al módulo Calculadora directamente. Intentémoslo.
1modulo Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Me deshice del módulo M
y moví el método double
dentro del módulo Calculator
. Como el módulo M
ha desaparecido he cambiado de extend M
a extend Calculator
.
Un último apaño.
Dentro del módulo Calculadora lo que es self
. self
es el propio módulo Calculator
. Así que no hay necesidad de repetir Calculator
dos veces. Aquí está la versión final
1module Calculator2 extend self34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Convertir una clase en un módulo
Cada vez que me encuentro con un código como extend self
mi cerebro se detiene un momento. Luego lo busco en Google. Leeré sobre ello. Tres meses después repetiré todo el proceso.
La mejor manera de aprenderlo es usándolo. Así que empecé a buscar un caso para usar extend self
. No es una buena práctica ir a la caza de código para aplicar una idea que tienes en tu mente pero aquí estaba tratando de aprender.
Aquí tienes una captura del antes de los métodos de la clase Util
que utilicé en un proyecto.
1clase Util2 def self.config2hash(archivo); end3 def self.in_cents(amount); end4 def self.localhost2public_url(url, protocol); end5end
Después de usar extend self
el código se convirtió en
1module Util2 extend self34 def config2hash(file); end5 def in_cents(amount); end6 def localhost2public_url(url, protocol); end7end
Mucho mejor. Hace la intención clara y ,creo, está en línea con la forma en que ruby esperaría que usáramos.
Otro uso en línea con la forma en que Rails usa extend self
Aquí estoy construyendo una aplicación de comercio electrónico y cada nuevo pedido necesita obtener un nuevo número de pedido de una aplicación de ventas de terceros. El código podría tener el siguiente aspecto. He omitido la implementación de los métodos porque no son relevantes para esta discusión.
1class Pedido2 def cantidad; end3 def comprador; end4 def enviado_en; end5 def número6 @número || self.class.next_order_number7 end89 def self.next_order_number; 'A100'; end10end1112puts Order.new.number #=> A100
Aquí el método next_order_number
podría estar haciendo una llamada complicada a otro sistema de ventas. Idealmente la clase Order
no debería exponer el método next_order_number
. Así que podemos hacer este método private
pero eso no resuelve el problema de fondo. El problema es que el modelo Order
no debería saber cómo se genera el nuevo número de pedido. Bueno podemos mover el método next_order_number
a otra clase Util
pero eso crearía demasiada distancia.
Aquí hay una solución usando extend self
.
1module 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
Mucho mejor. La clase Pedido no está exponiendo el método next_order_number
y este método está ahí mismo en el mismo archivo. No es necesario abrir la clase Util
.