Programando en Ruby

La Guía de los "Programadores Pragmáticos"

Clases y Objetos



Las clases y objetos son, obviamente centrales para Ruby, pero, a primera vista, pueden parecer algo confuso. Pareciera que existen demasiados conceptos: clases,objetos, objetos de clases, m?dos de instancia, m?dos de clases y clases singleton. En realidad, sin embargo, Ruby solo tiene una ? estructura de clases y objetos subyacente, la cual veremos en ?e cap?lo. De hecho, el modelo b?co es tan simple, que se puede describir en un s??p?afo.

Un objeto en Ruby posee tres componentes: un conjunto de se??s, algunas variables de instancia y una clase asociada. Una clase en Ruby es un objeto de la clase Class, que contiene todo lo relacionado con el objeto mas una lista de los m?dos y una referencia a la superclase (que es otra clase en s?isma). Todas las llamadas de m?dos en Ruby nombran a un receptor (que de manera predeterminada es self, el objeto en curso). Ruby encuentra el m?do de invocarlo buscando en la lista de m?dos en la clase del receptor. De no hallarlo all?se fija en la superclase, y luego en la superclase de la superclase, etc. Si el m?do no puede ser hallado en la clase del receptor o en sus ancestros, Ruby invoca el m?do method_missing en el receptor original.

Y eso es todo, explicaci??erminada. Vamos al siguiente cap?lo.

''Espera'' chillas t?gast?i dinero en ?e cap?lo. Que hay del resto? Las clases singleton, los m?dos de clases y todo eso. Como funcionan?''

Excelente pregunta.

C??interact?as Clases y los Objetos

Todas las interacciones clase/objeto son explicadas utilizando el modelo simple de arriba: los objetos hacen referencia a las clases, las clases hacen referencia a ninguna o m?superclases. Sin embargo, los detalles de implementaci??e pueden tornar levemente tramposos.

Hemos visto que la manera m?simple de visualizar todo ?o es describir los objetos reales que Ruby implementa. En las pr??as p?nas veremos las combinaciones posibles de clases y objetos. N??e que no son diagramas de clases en el sentido de UML; mostramos estructuras de la memoria y punteros entre ellos.

Tu base, los objetos cotidianos

Comencemos mirando un objeto creado por una clase simple. La figura 19.1 de pag.239 muestra un objeto referenciado por una variable, lucille, la clase del objeto, Guitar, y la superclase de la clase, Object. N??e como la referencia de clase del objeto (llamada klass por razones hist??as que realmente dej??capar Andy) apunta al objeto de la clase, y c??el super puntero desde esa clase hace referencia a la clase padre.

Figure not available...

Cuando Ruby ejecuta Guitar.strings(), sigue el mismo proceso anterior: va hacia el receptor, la clase Guitar, sigue referencia de klass a la clase Guitar$'$, y encuentra el m?do.

Finalmente, n??e que un ``S'' se ha deslizado en las flags en la claseGuitar$'$. Las clases creadas autom?camente se marcan internamente como clases singleton. +stas son tratadas de manera ligeramente diferente dentro de Ruby. La diferencia m?obvia es que son efectivamente invisibles: nunca aparecer?en la lista de objetos devueltos por m?dos como Module#ancestors o ObjectSpace::each_object .

Clases espec?cas de Objetos

Ruby te permite crear clases atadas a un objeto en particular. En el ejemplo siguiente, crearemos dos objetos String. Asociamos una clase an??a con alguno de ellos, sobrecargando uno de los m?dos en la clase base del objeto y agregando un m?do nuevo.

a = "hello"
b = a.dup
class <<a
  def to_s
    "The value is '#{self}'"
  end
  def twoTimes
    self + self
  end
end
a.to_s + "The value is 'hello'"
a.twoTimes + "hellohello"
b.to_s + "hello"

Este ejemplo usa la notaci??`class << obj'' la cual b?camente dice ``construye una clase nueva s??para el objeto obj.'' Tambi?podr?os haberlo escrito as?

a = "hello"
b = a.dup
def a.to_s
  "The value is '#{self}'"
end
def a.twoTimes
  self + self
end
a.to_s + "The value is 'hello'"
a.twoTimes + "hellohello"
b.to_s + "hello"

El efecto es el mismo en ambos casos: se a?? una clase al objeto ``a''. Esto nos da una se??clara acerca de la implementaci??n Ruby: se crea e inserta una clase singleton como clase directa de a. La clase original de a, String, se vuelve la superclase de esta singleton. Las im?nes anterior y posterior se muestran en la figura 19.3 en pag.242.

Ruby desarrolla un ligera optimizaci??on las clases singleton. Si la referencia a klass de un objeto ya apunta a una clase singleton, se crea una nueva. Esto significa que el primero de las dos definiciones de m?dos en el ejemplo previo crear?na clase singleton, pero el segundo simplemente le agregar?n m?do a aquella.

Figure not available...

Mezclando M??os

Cuando una clase incluye un m??o, los m?dos de instancia de ese m??o est?disponibles como m?dos de instancia de la clase. Es casi como si el m??o se conviritiera en superclase de la clase que lo utiliza. No es sorprendente que as?s como funciona. Al incluir un m??o, Ruby crea una clase proxy an??a que referencia a ese m??o, e inserta el proxy como superclase directa de la clase que realiz?? inclusi??La clase proxy contiene referencias a las variables de instancia y m?dos del m??o. Esto es importante: el mismo m??o puede ser incluido en diversas clases diferentes, y aparecer?n varias cadenas de herencia. Sin embargo, gracias a la clase proxy, hay s??un m??o subyacente: modifica una definici??de m?do en ? m??o, y se modificar?n todas la clases que lo incluyen, en el pasado y en el futuro.

module SillyModule
  def hello
    "Hello."
  end
end
class SillyClass
  include SillyModule
end
s = SillyClass.new
s.hello + "Hello."

module SillyModule
  def hello
    "Hi, there!"
  end
end
s.hello + "Hi, there!"

La relaci??ntre clases y los m??os que las incluyen se muestra en la Figura 19.4 de pag. 243. Si se incluyen m?les m??os, se a??n a la cadena en orden.

Figure not available...

Si un m??o incluye a otros m??os, una cadena de clases proxy se a??r?a cualquier clase que lo incluya, un proxy por cada m??o que sea incluido, directa o indirectamente.

Extender Objetos

De la misma manera que puedes definir una clase an??a para un objeto usando ``class <<obj '', puedes mezclar un m??o dentro un objeto utilizando Object#extend . Por ejemplo:

module Humor
  def tickle
    "hee, hee!"
  end
end
a = "Grouchy"
a.extend Humor
a.tickle + "hee, hee!"

Hay un truco interesante con extend. Si lo usas dentro de una definici??e clase, los m?dos del m??o se vuelven m?dos de clase.

module Humor
  def tickle
    "hee, hee!"
  end
end
class Grouchy
  include Humor
  extend  Humor
end
Grouchy.tickle + "hee, hee!"
a = Grouchy.new
a.tickle + "hee, hee!"

Esto es as?orque al llamar a extend equivale a self.extend, entonces los m?dos se a??n a self, el cual en una definici??e clases es la clase en s? misma.

Definiciones de Clase y M??o

Habiendo concluido las combinaciones de clases y objetos, podemos (afortunadamente) retornar a la programaci??echando una mirada a las entra??de las definiciones de clase y m??o.

En los lenguajes como C++ y java, las definiciones de clase son procesadas en tiempo de compilaci??el compilador carga la tabla de s?olos, calcula cuanto necesita alojar, construye las tablas de despacho y realiza todas esas tareas oscuras que preferimos no conocer demasiado.

Ruby es diferente. En Ruby, las definiciones de clase y de m??o son c??o ejecutable. Aunque se analizan al momento de compilar, las clases y m??os se crean en tiempo de ejecuci??cuando se encuentra su definici??(De manera id?ica para las definiciones de m?do). Esto te permite estructurar los programas de manera m? din?ca que en los lenguajes convencionales. Puedes tomar la decisi??una sola vez, al definir la clase, en vez de hacerlo cada vez que se utilizan los objetos de la clase. La clase del ejemplo siguiente decide, al momento de ser definida, qu?ersi??e rutina de des-cifrado debe crear.

class MediaPlayer
  include Tracing if $DEBUGGING

  if ::EXPORT_VERSION     def decrypt(stream)       raise "Decryption not available"     end   else     def decrypt(stream)       # ...     end   end

end

Si las definiciones de clase son c??o ejecutable, esto implica que se ejecutan en el contexto de alg?jeto: self debe referenciar algo. Veamos un ejemplo:

class Test
  puts "Type of self = #{self.type}"
  puts "Name of self = #{self.name}"
end
produce:
Type of self = Class
Name of self = Test

Esto significa que una definici??e clase se ejecuta con ? clase como el objeto en curso. Si nos referimos a la secci??etaclases de paq.238, podemos ver que ?o significa que los m?dos en la metaclase y sus superclases estar?disponibles durante la ejecuci??de la definici??el m?do. Verifiquemos esto.

class Test
  def Test.sayHello
    puts "Hello from #{name}"
  end

  sayHello end
produces:
Hello from Test

En el ejemplo definimos un m?do de clase, Test.sayHello, y lo llamamos en el cuerpo de la definici??e la clase. Dentro de sayHello, llamamos a name, un m?do de instancia de la clase Module. Dado que Module es un ancestro de Class, sus m?dos de instancia se pueden llamar sin un receptor expl?to dentro de la definici??e clase.

De hecho, muchas de las directivas que utilizas al definir una clase o un m??o, cuestiones como alias_method, attr, y public, son simples m?dos de la clase Module. Esto abre posibilidades muy interesantes; se puede extender la funcionalidad de las definiciones de clases y m??os escribiendo c??o Ruby. Veamos algunos ejemplos.

Como primer ejemplo, veamos c??agregar una facilidad de documentaci???ca a los m??os y clases. Esto nos permitir? asociar una cadena con m??os y clases que hemos escrito, cadena que ser?ccesible al momento de ejecuci??el programa. Elegiremos una sintaxis simple.

class Example
  doc "This is a sample documentation string"
  # .. rest of class
end

Necesitamos disponer de doc a cualquier m??o o clase, entonces, debemos crear un m?do de instancia de la clase Module.

class Module
  @@docs = Hash.new(nil)
  def doc(str)
    @@docs[self.name] = str
  end

  def Module::doc(aClass)     # If we're passed a class or module, convert to string     # ('<=' for classes checks for same class or subtype)     aClass = aClass.name if aClass.type <= Module     @@docs[aClass] || "No documentation for #{aClass}"   end end

class Example   doc "This is a sample documentation string"   # .. rest of class end

module Another   doc <<-edoc     And this is a documentation string     in a module   edoc   # rest of module end

puts Module::doc(Example) puts Module::doc("Another")
produce:
This is a sample documentation string
      And this is a documentation string
      in a module

El segundo ejemplo es una mejora de rendimiento basada en el m??o date de Tadayoshi Funaba (descripto al comienzo de pag. 439). Digamos que tenemos una clase que representa alguna cantidad (en ?e caso, una fecha). La clase debe incorporar muchos atributos que presenten los datos implicados en diferentes maneras: como d? Julianos, como cadena, como [a??mes, d?, etc. Cada valor representa la misma fecha y puede significar la necesidad de realizar c?ulos complejos. Entonces, queremos calcular cada atributo s??una vez: al accesarlo por primera vez.

La forma manual ser?agregar una prueba a cada accesor:

class ExampleDate
  def initialize(dayNumber)
    @dayNumber = dayNumber
  end

  def asDayNumber     @dayNumber   end

  def asString     unless @string       # complex calculation       @string = result     end     @string   end

  def asYMD     unless @ymd       # another calculation       @ymd = [ y, m, d ]     end     @ymd   end   # ... end

Esta es una t?ica burda, veamos si podemos hacer algo m?sexy.

Lo que buscamos es una directiva que indique que el cuerpo de un m?do particular deber?ser invocado una sola vez. El valor obtenido por esa primera llamada deber?er cacheado. Por lo tanto, al llamar al mismo m?do deber?devolver el valor cacheado sin reevaluar el cuerpo del m?do nuevamente. De manera similar al modificador de rutinads once de Eiffel. Quisi?mos poder hacer algo como:

class ExampleDate
  def asDayNumber
    @dayNumber
  end

  def asString     # complex calculation   end

  def asYMD     # another calculation     [ y, m, d ]   end

  once :asString, :asYMD end

Podemos usar once como directiva escribi?olo como un m?do de clase de ExampleDate, pero c??se ver??o internamente? El truco es hacerlo reescribir los m?dos cuyos nombres se le env?. Para cada m?do, crea un alias del c??o original, luego crea un nuevo m?do con id?ico nombre. +ste nuevo m?do hace dos cosas. Primero, invoca al m?do original (usando los alias) y guarda el valor resultante en una variable de instancia. Segundo, se redefine a s?ismo, as?en las pr??as llamadas simplemente devolver?l valor de la variable de instancia directamente. El c??o de Tadayoshi Funaba, ligeramente modificado.

def ExampleDate.once(*ids)
  for id in ids
    module_eval <<-"end_eval"
      alias_method :__#{id.to_i}__, #{id.inspect}
      def #{id.id2name}(*args, &block)
        def self.#{id.id2name}(*args, &block)
          @__#{id.to_i}__
        end
        @__#{id.to_i}__ = __#{id.to_i}__(*args, &block)
      end
    end_eval
  end
end

Este c??o utiliza module_eval para ejecutar un bloque de c??o en el contexto del m??o que llama (o, en ?e caso, la clase que llama). El m?do original es renombrado a __nnn__, siendo nnn la representaci??ntera del s?olo id del nombre del m?do. El c??o usa el mismo nombre para la variable de instancia cacheada. La mayor parte del c??o es un m?do que din?camente se redefine a s?ismo. N??e que esta redefinici??tiliza el hecho que el m?do puede contener definiciones de m?dos singleton anidadas, un truco astuto.

Comprende ?e c??o, y estar?en camino de la verdadera maestr?en Ruby.

Sin embargo, podemos llegar m?lejos. Mira en el m??o date, y ver?el m?do once escrito de manera ligeramente diferente.

class Date
  class << self
    def once(*ids)
      # ...
    end
  end
  # ...
end

Lo interesante aqu?s la definici??ntera de clase ,``class << self''. Esto define una clase basada en el objeto self, y self parece ser la clase objeto para Date. El resultado? Cada m?do dentro de la definici??nterna de clase es autom?camente un m?do de clase de Date.

La caracter?ica once es generalmente aplicable y deber?funcionar para cualquier clase. Si tomaste once y la hiciste un m?do de instancia privada de la clase Module, deber?estar disponible para usarse en cualquier clase Ruby.

Los nombres de clase son constantes

Hemos dicho que cuando invocas un m?do de clase, todo lo que haces es enviar mensaje al objeto Class. Cuando dices algo como String.new("gumby"), est?enviando un mensaje new al objeto que es la clase String. Pero c??sabe Ruby hacer ?o? Despu? de todo, el receptor del mensaje deber?ser una referencia de objeto, lo cual implica que debe haber una constante llamada "String" en alg?gar conteniendo una referencia al objeto String.[ Ser?na constante, no variable, porque "String" comienza con min?as.] Y de hecho, esto es lo que ocurre exactamente. Todas las clases incluidas, junto con las clases que defines, tienen una constate global correspondiente con el mismo nombre de la clase. Esto es al mismo tiempo, sutil y directo. La parte sutil proviene del hecho que existen 2 cosas llamadas, por ejemplo, String en el sistema. Hay una constant que hace referencia a un objeto de clase String, y est?l objeto propiamente dicho.

EL hecho de que los nombres de clase sean s??constantes, significa que debes tratar las clases como cualquier objeto Ruby: puedes copiarlos, pasarlos a m?dos y utilizarlos en expresiones.

def factory(klass, *args)
  klass.new(*args)
end
factory(String, "Hello") + "Hello"
factory(Dir,    ".") + #<Dir:0x401b51bc>
flag = true
(flag ? Array : Hash)[1, 2, 3, 4] + [1, 2, 3, 4]
flag = false
(flag ? Array : Hash)[1, 2, 3, 4] + {1=>2, 3=>4}

Entorno de ejecuci??e alto nivel

Muchas veces en ?e libro, hemos aseverado que todo en Ruby es un objeto. Sin embargo, hay una sola cosa que usamos de tiempo en tiempo que parece contradecir ?o, el entorno de ejecuci??uby de alto nivel.

puts "Hello, World"

No es un objeto a primera vista. Podr?os tambi?escribir alguna variante de Fortran o QW-Basic. Pero veamoslo mas profundamente, y te encontrar?con objetos y clases pululando incluso en el c??o mas simple.

Sabemos que el literal "Hello, World" genera un String Ruby, o sea que hay un objeto. Tambi? sabemos que el m?do simple de puts es efectivamente lo mismo que self.puts. Pero qu?s ``self''?

self.type + Object

A nivel superior, estamos ejecutando c??o en el contexto de alg? objeto redefinido. Cuando definimos m?dos, estamos realmente creando m?dos singleton (privados) para ?e objeto. Las variables de instancia pertenecen a ?e objeto. Y dado que estamos en el contexto del Object, podemos usar todos los m?dos de Object (incluyendo aquellos mixtos desde Kernel) en formularios de funci??Esto explica porqu?odemos llamar m?dos Kernel tal como puts a alto nivel (y de hecho a trav?de Ruby): ?os m?dos son parte de cada objeto.

Herencia y Visibilidad

Hay un escollo m?en la herencia de clases y es bastante oscura.

Dentro de la definici??e clases, puedes modificar la visibilidad de un m?do en una clase ancestro. Por ejemplo, puedes hacer algo como:

class Base
  def aMethod
    puts "Got here"
  end
  private :aMethod
end

class Derived1 < Base   public :aMethod end

class Derived2 < Base end

En ?e ejemplo, deber? poder invocar aMethod en instancias de clase Derived1, pero no a trav? de instancias de Base o Derived2.

Entonces, c??soluciona Ruby el hecho de tener un m?do con dos visibilidades diferentes. Poni?olo, simple, te enga??

Si una subclase cambia la visibilidad de un m?do en un padre. Ruby efectivamente inserta un m?do proxy oculto en la subclase que invoca al m?do original, usando super. Entonces ajusta la visiblidad de ese proxy a lo que sea que hayas requerido. Esto significa que el c??o:

class Derived1 < Base
  public :aMethod
end

es efectivamente lo mismo que:

class Derived1 < Base
  def aMethod(*args)
    super
  end
  public :aMethod
end

La llamada a super puede acceder al m?do del padre, independientemente de su visibilidad, entonces la reescritura permite a la subclase sobrecargar las reglas de visibilidad de su padre. Algo temible no?

Congelar objetos

Hay veces que has trabajado duro para hacer tu objeto tal cual lo quer?, y no permites tocarlo a nadie. Quiz?necesites pasar alg?ipo de objeto opaco entre dos de tus clases v?alg?jeto externo, y quieres estar seguro que retorna sin modificaciones. Quiz?quieres usar un objeto como clave hash, y necesitas asegurarte que nadie lo modifique mientras es utilizado. Quiz?algo est?a??o alg?jeto tuyo, y quisieras que Ruby genere una excepci??an pronto cuando ocurra el cambio.

Ruby provee un mecanismo muy simple para ayudarte con ?o. Cualquier objeto puede ser congeladoinvocando Object#freeze . Un objeto congelado no puede ser modificado: no puedes modificar sus variables de instancia (directa o indirectamente), no puedes asociarle m?dos singleton, y, si es una clase o m??o, no puedes agregar, borrar o modificar sus m?dos. Una vez congelado, un objeto permanece as?no hay un Object#thaw . (N.del T: Thaw: descongelar). Puedes verificar si un objeto est?ongelado mediante Object#frozen? .

Qu?ucede al copiar un objeto congelado? Eso depende en el m?do que uses. Si llamas a un m?do de objeto clone, el estado de todo el objeto (incluyendo si est? no congelado) se copia el nuevo objeto. Por otro lado dup t?camente copia s?? los contenidos del objeto, la nueva copia no heredar?l estado de congelamiento.

str1 = "hello"
str1.freeze + "hello"
str1.frozen? + true
str2 = str1.clone
str2.frozen? + true
str3 = str1.dup
str3.frozen? + false

Aunque congelar objetos puede a priori parecer una buena idea, puedes querer esperar a hacerlo hasta que tengas una necesidad real. Congelar es una de las ideas que parecen esenciales en el papel, pero no se utiliza mucho en la pr?ica.


Extraído del libro "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2000 Addison Wesley Longman, Inc. liberado bajo los términos de la Open Publication License V1.0.
La referencia está disponible en: download.