Métodos

P: ¿Cómo escoge Ruby el método a invocar?
P: ¿Son +, -, *, ... operadores?
P: ¿Dónde están ++ y --?
P: Todos estos objetos están bien pero, ¿tiene Ruby algun funciones simples?
P: ¿De dónde viene todos estos métodos-funciones?
P: ¿Puedo acceder a las variables de instancia de un objeto?
P: ¿Cuál es la diferencia entre private y protected?
P: ¿Cómo puedo modificar la visibilidad de un método?
P: ¿Puede un identificador que comienza con una letra mayúscula ser un nombre de método?
P: La llamada a super produce une error ArgumentError.
P: ¿Cómo se puede llamar a un método con el mismo nombre pero dos niveles por encima?
P: ¿Cómo puedo invocar a un método original predefinido después de redefinirlo?
P: ¿Qué es un método destructivo?
P: ¿Por qué pueden ser peligrosos los métodos destructivos?
P: ¿Puede un método devolver varios valores?

P: ¿Cómo escoge Ruby el método a invocar?

R: Ruby asocia dinámicamente todos los mensajes a los método. Busca primero los métodos singleton del receptor, luego los métodos definidos en su propia clase y por último los definidos en sus superclases (incluído cualquier módulo que se haya incorporado). Se puede ver el orden de búsqueda mostrando por pantalla Class-name.ancestors, que contiene las clases y módulos ancestros de Class-name.

Si después de buscar en las alternativas no se encuentra ningún método que corresponda con el mensaje, Ruby intenta ejecutar un método llamado method_missing, repitiendo el mismo procedimiento de búsqueda descrito. Esto hace que sea posible tratar los mensajes com métodos desconocidos y se utiliza con frecuencia pra proporcionar interfaces dinámicos a clases.

	    
	    module Indexed
	      def [] (n)
	        to_a[n]
	      end
	    end

	    class String
	      include Indexed
	    end
	    String.ancestors              # -> [String, Indexed, Enumerable, Comparable, Object, Kernel]
	    "abcde".gsub!(/./,"\\&\n")[1] # -> 10
	  

Este programa no devuelve ""b\n"" como cabría esperar, sino 10. Cuando se busca el método [], se encuentra en la clase String, que se encuentra antes de Indexed. Se debería redefinir directamente [] en la clase String.

P: ¿Son +, -, *, ... operadores?

R: +, - y similares no son operadores, son llamadas a métodos. Por lo tanto se pueden sobrecargar con nuevas definiciones.

	    
	    class MyString < String
	      def -(other)             # Nuevo método
	        self[0...other.size]   # devuelve el propio objeto truncado al
                                       # tamaño de "other"
	      end
	    end
	  

Sin embargo las siguientes son estructuras de control incluídas, no métodos y por lo tanto no se pueden sobrecargar.

	    
	    =, .., ..., !, not, ||, &&, and, or, ::
	  

Para sobrecargar o definir operadores unarios, se pueden utiliza los nombres de método siguientes +@ y -@.

= define un método para asignar un atributo a un objeto.

	    
	    class Test
	      def attribute=(var)
	        @attribute = val
	      end
	    end
	    t = Test.new
	    t.attribute = 1
	  

Si se definen operadores como + y -, Ruby maneja automaticamente las formas +=, -= y similares.

P: ¿Dónde están ++ y --?

R: Ruby no tiene los operadores de autoincremento y autodecremento. Se pueden utilizar += 1 y -= 1.

P: Todos estos objetos están bien pero, ¿tiene Ruby algun funciones simples?

R: Si y no, Ruby tiene métodos que se comportan como funciones de lenguajes como C o Perl.

	  
	  def writeln(str)
	    print (str,"\n")
	  end

	  writeln("hola mundo!")
	
Produce:
	  
	  Hola mundo!
	

Sin embargo se tratan de llamadas a procedimientos en las que se omite el receptor, Ruby asume que es self. Por lo tanto, writeln imita a una función pero realmente se trata de un método que pertenece a la clase Object y se envía como mensaje al receptor oculto self. Ruby es un lenguaje orientado a objetos puro.

Por supuesto se pueden utilizar estos métodos como si fuesen funciones.

P: ¿De dónde viene todos estos métodos-funciones?

R: Todas las clases de Ruby derivan de la clase Object. La definición de la clase incluye (mix-in) los métodos definidos en el móduflo Kernel. Por lo tanto estos métodos están disponibles para todos los objetos del sistema.

Incluso si se escribe un programa Ruby muy sencillo, sin clases, se está trabajando realmente dentro de la clase Object.

P: ¿Puedo acceder a las variables de instancia de un objeto?

R: Las variables de instancia de un objeto (aquella precedidas de @) no se pueden acceder fuera del objeto. Esto favorece una buena encapsulación. Sin embargo Ruby facilita la definición de accesores a estas variables de instancia de tal forma que los usuarios de la clase puedan utilizar estas variables como atributos. Basta con utilizar de Module.attr uno de los siguientes métodos attr_reader, attr_writer o attr_accesor.

	    
	    class Persona
	      attr         :nombre            #sólo lectura
	      attr_accesor :usa_sombrero      #lectura/escritura
	      def initialize(nombre)
	        @nombre = nombre
	      end
	    end

	    p = Persona.new("David")
	    p.name                            #-> "David"
	    p.usa_sombrero                    #-> nil
	    p.usa_sombrero = true
	    p.usa_sombrero                    #-> true
	  

También es posible definir funciones de acceso propias (quizás para realizar validaciones o manejar atributos derivados). Un accesor de lectura es realmente un método que no tiene parámetros y un accesor de asignación es un método que termina en = y tiene uno. Aunque no puede haber espacio entre el nombre del método y el = en su definición, se pueden insertar en la llamada al método, dando una apariencia semejante a la de cualquier otra asignación. También es posible utilizar autoasignaciones como += y -= siempre que se hayan definido los correspondientes métodos + y -.

P: ¿Cuál es la diferencia entre private y protected?

R: La palabra clave private, que controla la visibilidad, hace que un método sólo se pueda ejecutar en formato función , es decir que sólo pueda tener self como receptor. A un método privado sólo le puede utilizar la clase que lo ha definido o cualquiera de sus descendientes.

	  
	  class Test
	    def func
	      return 99
	    end
	    def test(other)
	      p func
	      p other.func
	    end
	  end
	  t1 = Test.new
	  t2 = Test.new

	  t1.test(t2)

	  #Ahora hacemos privada 'func'
	  
	  class Test
	    private :func
	  end

	  t1.test(t2)
	
Produce:
	  
	  99
	  99
	  99
	  prog.rb:7:in 'test': private method 'func' called for #<Test:0x4018d400> (NameError) from prog.rb:21
	

Los métodos protegidos sólo se pueden utilizar en su propia clase o descendientes pero, utilizando tanto el formato de función o de método. Por ejemplo,

	    def <=>(other)
	      age <=> other.age
	    end	    
	  

Se ejecutará si age es un método protegido pero no si es privado.

Estas funciones permiten controlar el acceso a la parte interna de la clase.

P: ¿Cómo puedo modificar la visibilidad de un método?

R: Se puede modificar la visibilidad de un método utilizando private, protected y public. Cuando se utilizan sin parámetros en la definición de una clase, afectan a todos los métodos que les sigan. Utilizada con parámetros, modifican la visibilidad de los métodos listados.

	    class Foo
	      def test
	        print "hola\n"
	      end
	      private :test
	    end

	    foo = Foo.new
	    foo.test
	  
Produce:
	    prog.rb:9: private method 'test' called for #<Foo:0x4018d7e8> (NameError)
	  
Se puede hace privado un método de clase utilizando private_class_metho.
	    
	    class Foo
	      def Foo.test
	        print "hola\n"
	      end
	      private_class_method :test
	    end
	    
	    Foo.test
	  
Produce:
	    
	    prog.rb:8 private method 'test' called for Foo:Class (NameError)
	  

La visibilidad por defecto definida para un método de una clase es pública. La excepción es el método de inicialización de instancias initialize.

Los métodos definidos en el nivel superior son por defecto, ta,bién públicos.xs

P: ¿Puede un identificador que comienza con una letra mayúscula ser un nombre de método?

R: Si, puede, pero no se puede hacer a la ligera. Si Ruby ve un nombre que comienza con mayúsculas seguido de un espacio, probablemente (y dependiendo del contexto) suponga que se trata de una constante, no un nombre de método. Por lo tanto si se utilizan nombres de métodos que comienzan con letra en mayúsculas hay que recordar que hay que poner la lista de parámetros entre paréntesis, y poner el paréntesis de apertura inmediatamente después del nombre del método sin que haya entre medias espacios en blanco. (Esta última sugerencia es una buenea idea aplicarla siempre).

P: La llamada a super produce une error ArgumentError.

R: La llamada a super sin parámetros hace que todos los argumentos del método desde que se le invoca se le pasen al método del mismo nombre en la superclase. Si el número de argumentos del método original no concuerdan con el del método del nivel superior, se lanza una excepción ArgumentError. Para evitar esto, basta con llamar a super con el número adecuado de argumentos.

P: ¿Cómo se puede llamar a un método con el mismo nombre pero dos niveles por encima?

R: super llama al método del mismo nombre que se encuentra un nivel por encima. Si se ha sobrecargado un método en un ancestro más distante, hay que utilizar alias para darle un nuevo nombre antes de enmascararlo con una nueva definición. Luego se le puede llamar utilizando su alias.

P: ¿Cómo puedo invocar a un método original predefinido después de redefinirlo?

R: Dentro de la definición del método, usando super. También es posible darle un nuevo nombre utilizando alias. Por último, es posible llamar al método original como un método singleton del Kernel.

P: ¿Qué es un método destructivo?

R: Es un método que altera el estado de un objeto. String, Array y Hash u otras clases poseen este tipo de métodos. A menudo existen dos versiones del método, una con el nombre normal y otra con el mismo nombre seguido de una admiración (!). La versión normal realiza una copia del receptor, la modifica y devuelve la copia. La versión con la admiración modifica el receptor.

Hay que estar al tanto de la existencia de un número considerable de métodos destructivos que no tiene la admiración, como los operadores de asignación (name=), las asignaciones a arrays []=) y métodos tales como array.collect.

P: ¿Por qué pueden ser peligrosos los métodos destructivos?

R: Hay que recordar que en la mayoría de los casos las asignaciones sólo copian referencias a objetos y que el paso de parámetros es equivalente a una asignación. Esto supone que al final se tienen multiples referencias a un mismo objeto. Si una de estas variables se usa para llamar a un método destructivo, todas las referencias al objeto se verán afectadas.

	    def foo(str)
	      str.sub!(/foo/,"baz")
	    end
	    
	    obj = "foo"
	    foo(obj)               # -> "baz"
	    obj                    # -> "baz"
	  
En este caso los argumentos reales se ven afectados.

P: ¿Puede un método devolver varios valores?

R: Si y no.

	    
	    def m1
	      return 1, 2, 3
	    end

	    def m2
	      return [1, 2, 3]
	    end
	    
	    m1               # -> [1, 2, 3]
	    m2               # -> [1, 2, 3]
	  
Por lo tanto, sólo se devuelve un objeto, pero éste puede ser arbitrariamente complejo. En el caso de arrays se puede utilizar la asignación múltiple para obtener un efecto similar a que el método devuelva varios valores. Por ejemplo:
	    

	    def foo
	      return 20, 4, 7
	    end

	    a, b, c = foo
	    a                 # ->  20
	    b                 # ->  4
	    c                 # ->  7