|
|||
sin, cos y demás.
Los metes todos dentro de un archivo, trig.rb, para que futuras generaciones
lo disfruten. Mientras tanto, Sally trabaja en una simulación del bien y el mal
y programa una serie de rutinas útiles para ella, incluyendo beGood
y sin (N del T: `Pecado' en inglés), y los mete en action.rb. Joe, quien
quiere hacer un programa para descubrir cuántos ángeles pueden bailar
en la cabeza de un alfiler, necesita cargar tanto trig.rb como action.rb
en su programa. Pero ambos definen un método llamado sin. Malas noticias.
La respuesta es el mecanismo de los módulos. Los módulos definen un espacio de nombers,
una caja de arena en la que tus métodos y constantes pueden crearse libremente sin
tener que preocuparse de que sean sobreescritos por otros métodos y constantes. Las funciones
trigonométricas podrÃan ir en un módulo:
module Trig PI = 3.141592654 def Trig.sin(x) # .. end def Trig.cos(x) # .. end end |
module Action VERY_BAD = 0 BAD = 1 def Action.sin(badness) # ... end end |
require, la cual se discute
en la página 103) y referenciar a los nombres adecuados.
require "trig" require "action" y = Trig.sin(Trig::PI/4) wrongdoing = Action.sin(Action::VERY_BAD) |
module Debug
|
||
def whoAmI?
|
||
"#{self.type.name} (\##{self.id}): #{self.to_s}"
|
||
end
|
||
end
|
||
class Phonograph
|
||
include Debug
|
||
# ...
|
||
end
|
||
class EightTrack
|
||
include Debug
|
||
# ...
|
||
end
|
||
ph = Phonograph.new("West End Blues")
|
||
et = EightTrack.new("Surrealistic Pillow")
|
||
|
||
ph.whoAmI?
|
» |
"Phonograph (#537766170): West End Blues"
|
et.whoAmI?
|
» |
"EightTrack (#537765860): Surrealistic Pillow"
|
Debug tanto Phonograph como
EightTrack ganan acceso al método de instancia whoAmI?.
Un par de puntualizaciones sobre la sentencia include
antes de continuar.
Primero, no hace nada con los archivos. Los programadores C usan
una directiva del preprocesador llamada #include para insertar los contenidos
de un archivo dentro de otro durante la compilación. La sentencia include de Ruby
simplemente crea una referencia al módulo llamado. Si ese módulo
está en un archivo diferente debes usar require
para importar ese archivo
antes de usar include. Segudno, un include Ruby no
copia simplemente los métodos de instancia del módulo en la clase. En su lugar,
crea una referencia en la clase al módulo indicado. Si varias
clases incluyen el mismo módulo todas apuntarán a la misma
cosa. Si cambias la definición de un método dentro de un módulo, incluso
mientras tu programa se ejecuta, todas las clases que lo incluyan cambiarán
su comportamiento.[Por supuesto, estamos hablando solo
de métodos aquÃ. Las variables de instancia son siempre por objeto por ejemplo.]
Los mixins te proporcinan una manera perfectamente controlada para añadir funcionalidad
a las clases. Sin embargo su verdadero poder destaca cuando el código
en el mixin empieza a interactuar con el código de la clase que lo usa. Tomemos
el mixin estándar de Ruby Comparable como
ejemplo. El mixin Comparable puede usarse para añadir los operadores de comparación
(<, <=, ==, >=, y >) asà como
el método between? a una clase. Para que esto funcione
Comparable asume que cualquier clase que lo use define el
operador <=>. Por lo tanto, como programador de la clase, defines un método,
<=>, incluyes Comparable, y obtienes seis funciones de comparación
gratis. Pobemos ésto con nuestra clase Song, haciendo que las canciones sean
comparables basadas en su duración.
Todo lo que tenemos que hacer es incluir el módulo Comparable e implementar
el operador de comparación <=>.
class Song include Comparable def <=>(other) self.duration <=> other.duration end end |
song1 = Song.new("My Way", "Sinatra", 225)
|
||
song2 = Song.new("Bicylops", "Fleck", 260)
|
||
|
||
song1 <=> song2
|
» |
-1
|
song1 < song2
|
» |
true
|
song1 == song1
|
» |
true
|
song1 > song2
|
» |
false
|
inject, implementándola
dentro de la clase Array. Prometimos hacerla aplicable de forma más
general. ¿Qué mejor que hacerlo con el mixin de un módulo?
module Inject
def inject(n)
each do |value|
n = yield(n, value)
end
n
end
def sum(initial = 0)
inject(initial) { |n, value| n + value }
end
def product(initial = 1)
inject(initial) { |n, value| n * value }
end
end
|
class Array
|
||
include Inject
|
||
end
|
||
[ 1, 2, 3, 4, 5 ].sum
|
» |
15
|
[ 1, 2, 3, 4, 5 ].product
|
» |
120
|
class Range
|
||
include Inject
|
||
end
|
||
(1..5).sum
|
» |
15
|
(1..5).product
|
» |
120
|
('a'..'m').sum("Letters: ")
|
» |
"Letters: abcdefghijklm"
|
Enumerable que empieza
en la página 403.
self.
En un mixin, ésto implica que el módulo que mezclas en tu
clase cliente (la mezclada?) puede crear variables de instancia en el objeto
cliente y puede usar attr y demás para definir el acceso
a esas variables de instancia. Por ejemplo:
module Notes
attr :concertA
def tuning(amt)
@concertA = 440.0 + amt
end
end
class Trumpet
include Notes
def initialize(tune)
tuning(tune)
puts "Instance method returns #{concertA}"
puts "Instance variable is #{@concertA}"
end
end
# The piano is a little flat, so we'll match it
Trumpet.new(-5.3)
|
Instance method returns 434.7 Instance variable is 434.7 |
module MajorScales def majorNum @numNotes = 7 if @numNotes.nil? @numNotes # Return 7 end end module PentatonicScales def pentaNum @numNotes = 5 if @numNotes.nil? @numNotes # Return 5? end end class ScaleDemo include MajorScales include PentatonicScales def initialize puts majorNum # Should be 7 puts pentaNum # Should be 5 end end ScaleDemo.new |
7 7 |
@numNotes. Desafortunadamente el resultado
no es lo que el autor esperaba.
Por lo general, los módulos mixin no intentarán añadir sus propios datos de instancia
---usarán los accessors para obtener datos del cliente.
Pero si necesitas crear un mixin con su propio estado, asegúrate que las
variables de instancia tienen nombres únicos para distinguirlas de otros mixins
en el sistema (quizá usar el nombre del módulo
como parte del nombre de la variable).
Enumerable. Todo lo que tienes
que hacer es escribir un iterador llamado each, el cual a su vez tiene que devolver
los elementos de tu colección. Mezcla Enumerable e
inmediatamente tu clase soportará cosas como map,
include? y find_all?. Si los objetos en tu colección
implementan relaciones de orden usando el método <=>
también obtendras min, max y
sort.
load "filename.rb" require "filename" |
load incluye el código fuente Ruby referenciado cada vez
que el método se ejecute, mientras que require carga cualquier archivo
dado una sola vez.
require tiene una funcionalidad adicional: puede cargar
librerÃas binarias compartidas. Ambas rutinas aceptan tanto rutas absolutas
como relativas. Si se les pasa una ruta relativa (o simplemente un nombre), buscarán
en cada directorio de la ruta de carga actual ($:, tratado
en la página 140) ese archivo.
Los archivos cargados usando load y require pueden por supuesto, incluir
otros archivos, los cuales a su vez incluyan otros, y asÃ. Lo que puede parecer
no tan obvio es que require es una sentencia ejecutable---puede
aparecer dentro de una sentencia if o podrÃa incluir una
cadena que se acaba de construir. La ruta de búsqueda puede modificarse además en tiempo
de ejecución. Simplemente añade el directorio que quieres a la cadena $:.
Ya que load incluirá el archivo incondicionalmente, puedes usarlo
para recargar un archivo fuente que haya cambiado desde que el programa comenzó:
5.times do |i|
File.open("temp.rb","w") { |f|
f.puts "module Temp\ndef Temp.var() #{i}; end\nend"
}
load "temp.rb"
puts Temp.var
end
|
0 1 2 3 4 |