jueves, 24 de abril de 2014

Clases y objetos en Ruby (I)

Este es el primero de una serie de artículos que revisa en profundidad el paradigma de la programación orientada a objetos mediante Ruby. Estos artículos no pretenden ser una clase magistral de este paradigma, pero sus ejemplos ayudan a entender claramente tanto sus principios como su aplicación en Ruby.

En este primer artículos aprenderemos conceptos básicos, tales como clase, objeto, propiedad y método. También aprenderemos cómo podemos consultar y modificar el valor de una propiedad.

INTRODUCCION

En un lenguaje de programación orientado a objetos, toma como concepto primordial el hecho de que todo cuanto existe se interpreta y se ve como un objeto, el cual puede tener unas propiedades o atributos, así como también unos métodos o acciones.

Todo comienza por una clase, que es una definición genérica, un molde o una abstracción de un tipo de objeto. A partir de la clase se generarán instancias u objetos, que son las entidades físicas de la clase.

EJEMPLO DE CLASE Y OBJETO

Imaginemos a un ser humano. La definición genérica de un ser humano puede tener una serie de propiedades comunes, como un nombre, unos apellidos, un sexo, una edad, una altura y un peso.

Un ser humano puede desempeñar diferentes acciones, tales como respirar, mirar, escuchar, andar, nadar, etc.

Yo, tú, él, ella, aquel, aquella... somos objetos o entidades físicas de un ser humano. Como entidades físicas, somos realidades y no abstracciones. Tenemos las propiedades comunes de un ser humano, pero los valores de nuestras propiedades son únicos e inherentes a cada uno.

Para llevar a cabo este ejemplo, crearemos el siguiente ejemplo en un fichero llamado Humano.rb:

class Humano
   def initialize
     @nombre = "Anonimo"
     @apellidos = ""
     @edad = 33
     @altura = 1.75
     @peso = 73.5
     @sexo = "hombre"
   end

   def to_s
     "#{@nombre} #{@apellidos}, #{@sexo}, de #{@edad} años, de #{@peso} kg y #{@altura} m"
   end
end

un_humano = Humano.new()
puts un_humano.to_s

Una clase en Ruby comienza con la declaración class, seguida del nombre de la clase. Al ser una definición genérica, su nombre empieza con mayúsculas.

Esta clase Humano tiene dos métodos o acciones: initialize y to_s

El método initialize es un método especial común a todas las clases. Es un método constructor, es decir, que cuando creemos una instancia o un objeto de esta clase, éste se invocará automáticamente en primer lugar para inicializar el objeto. Este método se suele utilizar para inicializar los valores de las propiedades del objeto y/o lanzar algún método de arranque. En este ejemplo, define seis variables de instancia, las cuales comienzan por el símbolo @, y representan cada una de las propiedades o atributos. Este tipo de variables sólo tienen visibilidad dentro del objeto, asegurando así la seguridad de los datos.

El método to_s es el símil del método toString de otros lenguajes de programación. Este método se utiliza para que nos devuelva una cadena de texto, que suele ser la información más relevante del objeto. En nuestro caso, retornará un texto con los valores de cada una de las propiedades del objeto. Este método es opcional.

Fuera de la definición de la clase, se han introducido dos líneas que se ejecutarán al lanzar este programa desde el intérprete de Ruby.

La primera línea crea una variable a la que se le asigna una nueva instancia de objeto. Con el método new, Ruby crea una nueva instancia de la clase Humano.

La segunda línea visualiza en pantalla la cadena retornada por el método to_s

Para ejecutar esta aplicación, ejecutaremos la siguiente línea desde la consola de comandos del sistema operativo:

ruby Humano.rb
Anonimo , hombre, de 33 años, de 73.5 kg y 1.75 m


INICIALIZANDO PROPIEDADES

En el ejemplo anterior estamos limitados, pues podemos crear múltiples objetos o instancias, pero todos tendrán los mismos valores en sus propiedades. Es decir, crearemos clones de humanos, donde, en su esencia, son objetos distintos, pero que en su apariencia son idénticos.

Para resolver esto, modificaremos el método constructor, permitiendo definir estas propiedades al mismo tiempo que se crea el objeto:

class Humano

   def initialize(nombre, apellidos, edad, altura, peso, sexo)
     @nombre = nombre
     @apellidos = apellidos
     @edad = edad
     @altura = altura
     @peso = peso
     @sexo = sexo
   end

   def to_s
     "#{@nombre} #{@apellidos}, #{@sexo}, de #{@edad} años, de #{@peso} kg y #{@altura} m"
   end

end

un_humano = Humano.new("Adela","Sanchez Gomez", "27", 1.67, 55.7, "mujer")
otro_humano = Humano.new("Javier", "Martin Lopez", "44", 1.74, 80.5, "hombre")
puts "Valor de un_humano: " + un_humano.to_s
puts "Valor de otro_humano: " + otro_humano.to_s

El método constructor tiene una lista de parámetros, los cuales son variables de ámbito del método (fuera de ella no tienen sentido). Cada una de estas variables recogerá un valor, y dicho valor se asignará, dentro del método, a una variable de ámbito de instancia. Es importante entender que una variable de método sólo tiene visibilidad dentro de un método, y que una variable de instancia tiene visibilidad en toda la clase. Aunque las variables tengan el mismo nombre, su ámbito y entidad son diferentes. Así:

@nombre = nombre

Asigna a la variable de instancia @nombre, el valor de la variable de método nombre.

El resultado será que, cada vez que se crea un objeto, se inicializará dicho objeto con los valores que pasemos como parámetros en el método new

La aplicación arrancará en la primera línea inmediatamente después de la definición de la clase. Se crearán dos objetos (un_humano y otro_humano), cada uno con unas propiedades o atributos concretos. A continuación, se visualizará los valores de dichas propiedades.

Valor de un_humano: Adela Sanchez Gomez, mujer, de 27 años, de 55.7 kg y 1.67 m
Valor de otro_humano: Javier Martin Lopez, hombre, de 44 años, de 80.5 kg y 1.74 m


ACCEDIENDO AL VALOR DE UNA PROPIEDAD

El ejemplo ha ido mejorando, pero tiene el inconveniente de que nos muestra todos los valores en el orden y el formato que el método to_s ha definido. Si queremos acceder libremente a una determinada propiedad, para cualquier propósito, hemos de definir métodos específicos dentro de la clase, con el mismo nombre que la propiedad:

def nombre
   @nombre
end

def apellidos
   @apellidos
end

def edad
   @edad
end

def altura
   @altura
end

def peso
   @peso
end

def sexo
   @sexo
end

Cada uno de los métodos retorna el valor de la propiedad que tiene su mismo nombre.

De esta manera, podemos consultar el valor de cada una de las propiedades del objeto de la siguiente manera:

nombre_objeto.nombre_propiedad

Como en la siguiente línea:

puts un_humano.nombre + " " + un_humano.apellidos + " tiene " + un_humano.edad + " años de edad"
Adela Sanchez Gomez tiene 27 años de edad


CAMBIANDO EL VALOR DE UNA PROPIEDAD

Nuestro humano va mejorando. Podemos conocer el valor de cada una de sus propiedades en cualquier momento. Sin embargo, un objeto no tiene por qué ser estático de por vida, si no que puede cambiar el valor de sus propiedades en cualquier momento. No hemos de matar a nuestro ser humano para crear un clon con los nuevos valores de sus propiedades.

La propiedad más proclive a cambiar sería su edad, su peso o su altura. Pero también es posible que quiera cambiar su nombre, sus apellidos y (¿por qué no?) su sexo.

La forma de poder asignar valores a una propiedad concreta es definiendo un método que tenga el mismo nombre de la variable más el signo = (igual), seguido de un parámetro que capture el valor y se lo asigne a la propiedad:

def nombre=(nombre)
   @nombre = nombre
end

def apellidos=(apellidos)
   @apellidos = apellidos
end

def edad=(edad)
   @edad = edad
end

def altura=(altura)
   @altura = altura
end

def peso=(peso)
   @peso = peso
end

def sexo=(sexo)
   @sexo = sexo
end

Cada uno de los métodos asigna a la propiedad con su mismo nombre, el valor pasado como parámetro.

De esta manera, podemos asignar el valor a cada una de las propiedades de la siguiente manera:

nombre_objeto.nombre_propiedad = nuevo valor

Como en las siguientes líneas:

un_humano.peso = 71
un_humano.edad = 32

Aquí podemos ver el resultado:

puts un_humano.nombre + " " + un_humano.apellidos + " tiene #{un_humano.edad} años de edad y #{un_humano.peso} kg de peso"
Adela Sanchez Gomez tiene 32 años de edad y 71 kg de peso


ENTENDIENDO EL ACCESO A LAS PROPIEDADES

En Ruby, las propiedades de un objeto son exclusivamente variables de instancia. Su visibilidad es privada y se accede estrictamente desde la propia clase. Esto asegura el encapsulamiento de éstas, así como también la seguridad de los datos.

En otros lenguajes de programación, resulta muy cómodo utilizar definir una clase de la siguiente manera:

/tr>
public class MiClase {
   public valor1 as Integer;
   public valor2 as String;
   ...
}

e instanciar la clase en objetos con acceso público a sus propiedades:

Set miObjeto As new MiClase;
miObjeto.valor1=1;
miObjeto.valor2="Valor"

Ruby contempla esta forma de gestionar los datos, gracias a los hashes o diccionarios, con mayor funcionalidad si cabe.

El problema de utilizar las clases de esta manera es la seguridad de la información, y el uso incontrolado o indebido por otras clases de una aplicación compleja. Para evitar ésto, las propiedades se definen como privadas a la clase (variables de instancia). De esta manera, su acceso está controlado de forma exclusiva por la propia clase, y para poder consultar o modificar el valor de una determinada propiedad, es necesario el uso de un método específico.

En el caso de Ruby, el uso de estos métodos ofrece una sintáxis muy similar a como si accediéramos de forma pública, lo que facilita mucho su semántica. Sin embargo, podemos hacer uso de una técnica llamada getters y setters, tal y como se realiza en otros lenguajes de programación.

Esta técnica consiste en definir un método get (obtener) y un método set (establecer) para el acceso a la propiedad. Por ejemplo, para acceder a la propiedad nombre, se utilizarían estos dos métodos:

def getNombre()
   @nombre
end

def setNombre(nombre)
   @nombre = nombre
end

A la hora de utilizar el objeto, lo realizaríamos de la siguiente manera:

objeto.setNombre(“Felipe”)
mi_nombre = objeto.getNombre()

El resultado es el mismo a como hemos visto anteriormente, aunque el nombre del método no es el mismo que el de la propiedad. La técnica vista anteriormente es más sencilla e intuitiva.




TE PUEDE INTERESAR