Mi guía de Ruby – Las clases y curiosidades OOP


Ruby throated Hummingbird IMG_2313edtsgEs una parte esencial, conocer algunos aspectos primordiales sobre las clases de Ruby para que cuando veamos código Rails podamos entenderlo bien. Todas las cosas que utilizamos en Ruby son objetos, los resultados también, por ejemplo, si tenemos una tienda online y disponemos de categorías, deberíamos definir una clase para representar cada una de esas categorías, que además, una clase es una combinación de estado como la cantidad de un pedido y un método o varios que nos permiten saber el precio con iva o sin iva o el coste total del pedido en linea. Cuando tengamos definido esto, debemos instanciarlas o crear los objetos que además también los podemos crear llamándolos mediante constructores que están normalmente asociados a una clase.

Piensa que un objeto es un contenedor de datos que controla el acceso a dichos datos y que dispone de una serie de variables que lo definen, los atributos. También hay o debería haber una serie de funciones que crean un interfaz para interactuar con el objeto, los llamados métodos. Por tanto cuando estamos dando vueltas a como diseñar nuestra clase, debemos pensar en los objetos que serán creados por esa misma clase, pero es fundamental pensar en que cosas podrá hacer ese objeto, y también en las características que lo van a definir. Una clase principalmente la vamos a utilizar para crear o moldear un objeto y un objeto es una instancia de la clase.

Este constructor es new, entonces podríamos crear objetos de pedidos si la clase es Pedido:

pedido = Pedido.new

pedido.cantidad = 10

pedido.titulo = “Los inmortales”

pedido = Pedido.new

pedido.cantidad = 2

pedido.titulo = “Drácula”

Fíjate que ahora las instancias que hemos creado de la clase Pedido que derivan de la misma clase, pero tienen identidades diferentes para cada una de ellas, en las que cada una por ejemplo, tiene un precio y un título diferente que se encuentran en variables de instancia. Ahora en cada una de esas clases, podemos comunicarnos mediante los métodos de instancia, métodos que se pueden llamar dentro de la clase, según las limitaciones que se disponga de acceso a las mismas y desde fuera de la clase, que a su vez,  estos mismos métodos de instancia tienen acceso a las variables de instancia del objeto y al estado del objeto, ¿cómo se les llama? mandando un mensaje al objeto, que será el nombre de un método, junto con los parámetros que se precisen y ¿cómo lo sabe? él mismo buscará dentro del propio objeto, la llamada al método que le estamos diciendo. Piensa que las variables de instancia viven el el objeto y los métodos viven en las clases.

Para definir una clase tenemos la palabra reservada class, después le proporcionamos el nombre de la clase en la nomenclatura de CamelCase, por ejemplo, Order, en la que la vamos a definir como una subclase de la clase Base dentro del módulo ActiveRecord. Veamos la manera de hacerlo:

class LineItem < ActiveRecord::Base
         belongs_to : order
         belongs_to :product
         belongs_to :cart

        attr_accessible :cart_id, :product_id

        def self.find_all_unpaid

               find(:all, ‘paid = 0’ )

         end

         def total_price
                product.price * quantity
          end

end

Ahora podemos definir dentro del cuerpo de la clase los métodos de clase y los métodos de instancia. ¿Qué quiero decir con esto? pues en el caso de nuestra clase, vemos que hay una definición de métodos self. que al hacer esto le estamos convirtiendo al método en un método de clase, para que sea llamado en cualquier parte de una aplicación sin necesidad de instanciarlo, por ejemplo, podríamos hacer la llamada to_collect = LineItem.find_all_unpaid y los métodos normales, por así decirlo, como total_price, crean métodos de instancia. Vamos a detallar esto un poco mejor, cuando vemos la clase LineItem y nos fijamos en self.find_all_unpaid, este método de clase no es exclusivo de un pedido en particular, por este motivo decidimos declararlo así y para comunicarnos con el objeto, lo hacemos a través de la clase. En el caso del método total_price que está como método de instancia, lo definimos así ya que implica a un pedido concreto y no en general.

Los objetos de una clase, guardan sus estados en variables de instancia, cuyos nombre comienzan con @ están disponibles y accesibles por todos los métodos de la clase y además cada objeto tiene su propio conjunto de variables de instancia. Otro aspecto interesante para la creación de los objetos y su identificación es que disponen un único número asociado con él. ¿Cómo podemos saber cuál es? podemos ver dicho número mediante el método object_id. Por ejemplo:

puts “The number that identifies the object: #{name_instance.object_id}.”

Otro método interesante a conocer es, si un objeto será capaz de responder a un mensaje, es decir, si un objeto dispone de un método con respond_to?. En nuestro ejemplo:

if d.respond_to?(“paid”)
          d.paid
else
          puts “Sorry,  the object doesn’t understand the message ‘paid'”
end

¿Cómo podemos saber a qué clase pertenece un objeto? mediante el método class

d = Dog.new
puts d.class.to_s # obtenemos Dog

instance_of? nos devuelve true si un objeto es instancia de una clase determinada.

num = 23
puts (num.instance_of? Fixnum) # true

Otras de las curiosidades de las que dispone Ruby es que ha simplificado los métodos getters y setters por medio de la metaprogramación de los métodos llamados attr, attr_reader, attr_writer y attr_accessor. En definitiva nos permiten acceder a los atributos del objeto. Un pequeño de talle, con attr únicamente nos crea un método getter, si queremos el setter debemos ponerlo a true. Por ejemplo:

class Dog
     attr :bark, true
end

dog = Dog.new
dog.bark =”Guau!”
puts dog.bark # => Guau!

class Dog
         attr_reader :bark # getter
         attr_writer :bark # setter
end
dog = Dog.new
dog.bark=”Woof!”
puts dog.bark # => Woof!

class Gaits
        attr_accessor :walk, :trot, :canter
end

Si hacemos la comprobación de los métodos que tenemos disponibles utilizamos lo siguiente:

Gaits.instance_methods.sort – Object.instance_methods # => [“canter”, “canter=”, “trot”, “trot=”, “walk”, “walk=”]

Veamos la forma de definir y acceder en el caso de no querer disponer los de los getters y setters. Para ello lo haremos mediante métodos de instancia, un initialize que nos hace las funciones de constructor y actualiza las variables de instancia con los datos que nosotros le pasemos cuando instanciemos la clase:

class Film
        def initialize(title, actor)
              @titletitle
              @artista = actor
        end
        def title
              @title
        end
        def actor
             @actor
        end
end

Ahora pasamos a instanciar y a acceder a los atributos:

film = Film.new(“Alguien voló sobre el nido del cuco”, “Jack Nicholson”)
puts film.title
puts film.actor

Variables de Instancia

Como ya habíamos mencionado anteriormente, una variable de instancia es una variable que está disponible desde una instancia de una clase, y tiene un alcance limitado, ya que pertenece a un objeto dado. Una variable de instancia la veremos prefijada por un único signo de arroba (@), como:

@name = “My Content”

¿Dónde podemos encontarnos definida una variable de instancia? Una variable de instancia se puede definir dentro o fuera de un método. Vale y ahora ¿cómo puedo acceder a ellas? Sólo se puede acceder a una variable de instancia desde el exterior de un objeto a través de un método. Aunque podemos manipular una variable de instancia dentro del objeto sin la necesidad de hacerlo por un método:

class Horse
       @name = “My Horse”
end

Pero cuidado!! esto únicamente nos va ha funcionar, si sólo hacemos referencia @name dentro del objeto. Si intentamos recuperar su valor, el valor de @name directamente desde el exterior del objeto, no es posible hacerlo, a no ser que nos hagamos la construcción de un método getter de lectura para recuperar su valor:

class Horse
        def name
             @name = “My Horse”
        end
end

h = Horse.new
h.name # => “My Horse”

En ocasiones nos puede pedir además de disponer un setter, un método getter. Un setter es un método de acceso que establece el valor de una variable, veamos como hacerlo:

class Horse
        def name 
             @name
        end
       def name=( value )
            @name = value
       end
end

h = Horse.new
h.name= “Barroso”
h.name # => “Barroso”

El nombre del método setter = sigue una convención Ruby: el nombre del método termina con un signo igual =. Esta convención, no es un requisito. Aquí es otra versión de la clase Horse, que inicializa la instancia de variable @name, con el método initialize estándar. Después, el programa crea una instancia de la clase llamando a new y, a continuación, accede a la variable de instancia a través del método horse_name, veamos como se hace:

class Horse
        def initialize( name )
             @name = name
       end
       def horse_name
             @name
       end
end

horse = Horse.new( “Spirit” )
puts horse.horse_name # => Spirit

Finalmente para aclarar bien el alcance de este tipo de variables de instancia podríamos declarar una variable de instancia dentro de la clase @one = 1 por ejemplo y disponer de dos métodos de los cuales hacen referencia a dicha variable de instancia veremos un pequeño de talle que importante tenerlo claro, veamos:

Class MyClass

           @one = 1 (variable de instancia del objeto)

          def do_something

                  @one = 2 (variable de instancia del método)

            end

           def out_put

                 pust @one  (variable de instancia del método)

           end

end

Veamos el resultado que obtenemos al realizar las siguientes operaciones:

instance = MyClass.new

instance.output # En este primer caso obtenemos un valor de nil, ¿cómo es posible si hemos puesto que @one = 1? pues sencillo por la propia definición de este tipo de variables y su alcance, en este caso hasta que no pase por el método do_something su valor dentro del método out_put será en el momento de la instancia, nil. La variables de los métodos definidos en la clase, son variables de instancia del método, mientras que la definida fuera de los métodos esa variable es una variable de instancia del objeto. Cuando veamos las variables de clase esto cambia.

instance.do_something # En este momento, ahora la variable de instancia toma el valor de 2

instance.output # Cuando volvamos ver el resultado, ahora obtenemos el valor de 2

Variables de clase

Una variable de clase se comparte entre todas las instancias de una clase, por lo que sólo existe una copia de una variable de clase para una clase dada. En Ruby, una variable de clase está precedido de dos arrobas (@@) y además antes de ser utilizada de be ser inicializada, como @@price = 0. Veamos un ejemplo de funcionamiento:

class Repeat
        @@total = 0
        def initialize( string, times )
              @string = string
              @times = times
         end
        def repeat
             @@total += @times
             return @string * @times
        end
       def total
                “Total times, so far: ” + @@total.to_s
        end
end

data = Repeat.new( “abc “, 8 )
ditto = Repeat.new( “def! “, 5 )
ditty = Repeat.new( “ghi “, 2 )

puts data.repeat # => Obtenemos comoresultado abc hasta 8
puts data.total # => Total times, so far: 8

puts ditto.repeat # => Obtenemos como resultado def hasta 5
puts ditto.total # => Total times, so far: 13
puts ditty.repeat# => Obtenemos como resultado ghi hasta 2 
puts ditty.total # => Total times, so far: 15

Como puedes ver los resultados de nuestra variable de clase al ser global y accesible tal como decíamos, cuando hacemos la llamada al método total en cada una de las partes de nuestros 3 objetos creados, el resultado va aumentado de 8, 13 y 15. Observa el método repeat con @@total += @times que se encarga de actualizar el valor en cada llamada.

Métodos de clase

Aunque ya hemos hecho una aproximación anteriormente nombrándolos junto los los métodos de instancia, podemos decir que un método de clase es un método que se asocia con una clase y con un módulo en Ruby, no a una instancia de una clase. Podemos llamar a los métodos de la clase, con el prefijo del nombre del método y con el nombre de la clase a la que pertenece, como a Math.sqrt (8). Los métodos de clase son también llamados métodos estáticos. También se puede asociar el nombre de un módulo con un nombre de método, al igual que con una clase, pero el uso de este método, debe incluir el módulo en una clase. Para definir un método de clase, sólo hay que anteponer el nombre del método con el nombre de la clase o módulo en la definición del método:

class Area
        # Podemos utilizar self.rect or Area.rect
        # def self.rect( length, width, units=”inches” )
        def Area.rect( length, width, units=”inches” )
                      area = length*width
                      printf( “The area of this rectangle is %.2f %s.”, area, units )
                     sprintf( “%.2f”, area )
         end
end
Area.rect(6.5, 6) # => The area of this rectangle is 39.00 inches.

Singletons

Otra manera de definir los métodos de clase es el uso de una clase dentro de una clase self como una clase singleton. En términos básicos, un singleton está diseñado de manera que sólo puede ser instanciado una sola vez.

class Area
        class << self
                   def rect( length, width, units=”inches” )
                         area = length*width
                         printf( “The area of this rectangle is %.2f %s.”, area, units )
                         sprintf( “%.2f”, area )
                   end
        end
end

Area.rect(10, 10) 

De esta forma, no hay no hay que poner un prefijo que preceda al método o métodos con el nombre de la clase. Una clase singleton está vinculado a un objeto en particular, se pueden crear instancias una sola vez, y no se distingue por un nombre prefijado.Otra manera de definir un método singleton, que está vinculado a un solo objeto:

class Singleton
end

s = Singleton.new
def s.handle

        puts “I’m a singleton method!”

end

s.handle # => I’m a singleton method!

En el diseño de patrones de Ruby Desingn Patterns in Ruby, hablaré en otro post, ya que el libro trata un apartado sobre  “Making Sure There Is Only One with the Singleton” muy interesante.

Herencia

Ya lo hemos comentado anteriormente, pero vamos a volver a decir, que cuando una clase hija hereda de su padre, esta clase hija tiene acceso a los métodos y propiedades de la clase padre. Para la herencia utilizamos el operador <. En Rails la herencia es una parte muy importante. Veamos un ejemplo de herencia en Ruby:

class Name
         attr_accessor :given_name, :family_name
end

class Address < Name
        attr_accessor :street, :city, :state, :country
end

a = Address.new
puts a.respond_to?(:given_name) # => true

Veamos los detalles de hacer público, protegido o privado los métodos de una clase:

Público: Estos métodos son accesibles por cualquiera que llama a su clase. Ellos definen “interface” pública de su clase.

Protegido: Métodos protegidos son accesibles por los objetos de la misma clase. Según esta definición, un buen caso de uso es cuando las clases se extienden a su clase y quieren acceder a esos métodos. La subclase mantendrá el acceso a métodos protegidos de la clase super.

Privado: Los métodos privados pueden ocultar los detalles de cómo se accede y hacen uso de los datos. Una forma de verlo sería, que estos métodos son esencialmente para el “creador del objeto / clase”. Y por el “creador” me refiero al desarrollador que escribió el código. Estos métodos privados no están expuestos públicamente.

class Names
         def initialize( given, family, nick, pet )
                 @given = given
                 @family = family
                 @nick = nick
                @pet = pet
         end
        # the methods are public by default
        def given
              @given
        end
        def family
             @family
        end
        # all following methods private, until changed
        private
        def nick
             @nick
        end

        # all following methods protected, until changed
        protected
        def pet
             @pet
        end
end

name = Names.new( “Klyde”, “Kimball”, “Abner”, “Teddy Bear” )
name.given # => “Klyde”
name.family # => “Kimball”

Cuando hagamos las siguientes llamadas obtenemos que son métodos privados y protegidos que no seremos capaces de llamarlo de esta forma:
name.nick
name.pet

Un caso real para esclarecer algo más estos aspectos , sería cuando tenemos en un modelo un método que aprueba pedidos y lo que hace es, por un lado actualizar unos datos y por otro enviar un mail informativo. En este caso estaríamos combinando los métodos públicos y los privados que son accesibles desde los métodos públicos cuando se les llama, es decir:

def approve
        update_attributes approved: true, decision_date: Time.now
        send_informative_mail
end

private

def send_informative_mail(to = user, cc = other_mail_users)
       ApprovalMailer.informative_mail(to, cc, self).deliver
end

Super

Cuando utilizamos super dentro de un método, Ruby manda un mensaje a la clase padre del objeto al que pertenece el método, intentando encontrar un método con el mismo nombre, veamos un ejemplo:

class Bicicleta
        attr_reader :marchas, :ruedas, :asientos 

       def initialize(marchas = 1)
               @ruedas = 2
               @asientos = 1
               @marchas = marchas
       end
end

class Tandem < Bicicleta
          def initialize(marchas)
                  super
                 @asientos = 2
         end
end

t = Tandem.new(2)
puts t.marchas # Obtenemos 2
puts t.ruedas #Obtenemos 2
puts t.asientos #Obtenemos 2

b = Bicicleta.new
puts b.marchas # Obtenemos 1
puts b.ruedas # Obtenemos 2
puts b.asientos # Obtenemos 1

Self y Módulos

Sólo disponemos de un self en cada ejecución, es decir, el objeto que se está usando en ese instante. En una clase o en una definición de módulo, self es la clase o el módulo al que pertenece el objeto, por ejemplo:

class S
         puts ‘Comenzó en la clase S’
         puts self
         module M
                 puts ‘Módulo anidado S::M’
                 puts self
         end
         puts ‘De regreso en el nivel más superficial de S’
         puts self
end

Cuando ejecutemos las líneas anteries, obtenemos:

Comenzó enla clase S
(self) S
Módulo anidado S ::M
(self) S::M
De regreso en el nivel más superficial de S
(self) S

Seguiremos hablando de Ruby y sus curiosidades en próximos post…..

2 comentarios en “Mi guía de Ruby – Las clases y curiosidades OOP

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s