En la asignatura PDOO (Programación y Diseño Orientado a Objetos) tuve que hacer un seminario sobre ruby, que consistía en traducir un proyecto escrito en Java a Ruby. El objetivo era ver las diferencias existentes en ambos lenguajes.

Ruby fue creado por Yukihiro Matsumoto y es un lenguaje dinámico.

En este artículo voy a escribir únicamente el código Ruby, al final pondré disponible para descarga ambos proyectos, tanto en Java como en Ruby.

La estructura del programa consta de tres clases, Persona, Alumno y Asignatura. Heredando Alumno de Persona. Empecemos viendo la clase Persona:



require 'date' #Para trabajar con fechas
require 'time'

#Clase Persona
class Persona
  include Comparable

  #Declara metodos gets y sets
  attr_accessor :nombre, :fechaNacimiento

  def initialize(nombre, fechaNacimiento)
    #Inicializar atributos
    @nombre = nombre
    @fechaNacimiento = fechaNacimiento
  end

  def edad
    dob = fechaNacimiento() or return nil
    now = Time.now
    now.year - dob.year
    #now.year - dob.year - (dob.to_time.change(:year => now.year) > now ? 1 : 0)
  end

  def to_s
    "Persona [nombre=" + nombre + "], fechaNacimiento=" + fechaNacimiento.to_s + "]"
  end

  def cumple
    "Su cumpleanos es el ".concat fechaNacimiento.month.to_s.concat " del ".concat fechaNacimiento.day.to_s
  end

  def esMenorQue otro
    return fechaNacimiento <=> otro
  end

  def <=>(otraPersona)
    if self.fechaNacimiento < otraPersona.fechaNacimiento
      -1
    elsif self.fechaNacimiento > otraPersona.fechaNacimiento
      1
    else
      0
    end
  end
=begin
    los metodos getdianac, getmes etc no se implementan porque pueden ser facilmente accesibles con fechanacimiento.year etc
=end

end

Comentemos brevemente la sintaxis:

A diferencias de Java, en lugar de usar import, se usa require para incluir otras clases. Algo que me gustó bastante es la posibilidad de crear métodos de acceso, es decir, los típicos get y set con una simple instrución. Escribiendo attr_accessor :nombre, :fechaNacimiento estamos declarando los métodos de acceso a estos dos atributos de la clase. A los cuales podemos acceder con instanciaObjetoPersona.fechaNacimiento() para obtener el valor del atributo o instanciaObjetoPersona.fechaNacimiento=(NuevaFecha) para modificarlo. Es importante destacar que en Ruby los paréntess no son obligatorios.

Los constructores tienen siempre el nombre initialize(), en Java es el nombre de la clase.

Algo curioso, no existe null si no nil.

El método toString en java aquí se llama to_s.

Concretamente en esta clase se implementa la interfaz Comparable, usada para comprobar si un objeto es igual, menor o mayor que otro. En java es necesario redefinir el método compareTo, en Ruby se usa el método <=>, que hay que redefinir también, como se puede comprobar en el código.

Echemos un vistazo a la clase Asignatura:

class Asignatura
  attr_accessor :codigo, :titulo, :creditosECTS

  def initialize(codigo, titulo, creditos)
    @codigo = codigo
    @titulo = titulo
    @creditosECTS = creditos
  end

  def to_s
    "La asignatura ".concat(titulo.to_s).concat(" con codigo ").concat(codigo.to_s).
        concat(", consta de ").concat(creditosECTS().to_s).concat(" creditos ECTS")
  end

end

En este caso hay poco que comentar, ya que es más sencilla que la anterior. Únicamente decir que se está redefiniendo el método to_s.

Por último, veamos la clase Alumno, la cual hereda de Persona:

require './Persona.rb'
require './Asignatura'

class Alumno < Persona

  #Variable estática
  @@NUM_MAX_CRED = 60

  #Atributos
  attr_accessor :titulacion, :asignaturas
  def initialize(nombre,fechaNacimiento, titulacion)
    super(nombre, fechaNacimiento)
    @titulacion = titulacion
    @asignaturas = Hash.new(false) #Si no hay nada, devuelvo false
  end

  def Alumno.NUM_MAX_CRED= (x)
    @@NUM_MAX_CRED = x
  end

  def Alumno.NUM_MAX_CRED
    @@NUM_MAX_CRED
  end

  def to_s
    super.to_s.concat( "Esta estudiando ").concat(titulacion).concat(", creditos ").concat(getNumCreditos.to_s)
  end

  def getNumCreditos
    total = 0
    asignaturas.each_pair do |k,v|
      total += asignaturas[k].creditosECTS
    end
    total
  end

  def estaMatriculado(codAsig)
    asignaturas.has_key?(codAsig)
  end

  def matricularAsignatura(asig)
    if not estaMatriculado(asig.codigo) and (getNumCreditos + asig.creditosECTS) <= Alumno.NUM_MAX_CRED
      asignaturas[asig.codigo] = asig
    else
      raise 'El alumno ya esta matriculado en esta asignatura'
    end
  end

  def desmatricularAsignatura(codAsig)
    if estaMatriculado(codAsig)
      asignaturas.delete(codAsig)
    else
      raise 'ERROR: El alumno no esta matriculado en la asignatura '.concat(codAsig.to_s)
    end
  end

  def mostrarMatricula
    puts "El alumno se encuentra matriculado de las siguientes asignaturas:\n"
    asignaturas.each_pair do |k,v|
      puts "\tCodigo Asig: ".concat(k.to_s).concat(". Creditos ").concat(asignaturas[k].creditosECTS.to_s)
    end
  end

  def buscarAsignatura(codAsig)
    asignaturas[codAsig]
  end

  def anularMatricula
    asignaturas.clear
  end
end

Varias cosas a comentar. Para indicar la herencia de una clase sobre otra, se usa el operador <, en este caso Alumno heredará de Persona.
Las variables estáticas se definen anteponiendo dos @@ al nombre de la variable. (@@NUM_MAX_CRED = 60).
asignaturas es un objeto HashMap sobre el que podemos iterar, un ejemplo es el método getNumCreditos:

total = 0
    asignaturas.each_pair do |k,v|
      total += asignaturas[k].creditosECTS
    end
total

Este método itera sobre el HashMap sumando los créditos de las asignaturas. k corresponde a la clave del HashMap, y v al valor.
Cabe destacar la asuencia de la sentencia return en todos los métodos.

Por último, el manejo de excepciones. En esta clase, se lanzan varias. Por ejemplo, el método matricularAsignatura lanzará una excepción si el alumno ya está matriculado o supera el número máximo de créditos.

Para probar el funcionamiento del programa, usaremos esta pieza de código:

#!/usr/bin/env ruby

require './Persona.rb'
require './Alumno.rb'

if __FILE__ == $0

  arrayPersonas = Hash.new

  #Creando personas y Alumnos
  alex = Persona.new("Alejandro Alcalde",Date.new(1991,2,10))
  pepe = Persona.new("Pepe",Date.new(1980,2,8))
  alumno1 = Alumno.new("Nombre del alum1",Date.new(1980,2,8),"Ing. Informatica")
  alumno2 = Alumno.new("Nombre del alum2",Date.new(1980,2,8),"Ing. Teleco")

  #Almacenarlas en un HashMap
  arrayPersonas[alex.nombre] = alex
  arrayPersonas[pepe.nombre] = pepe
  arrayPersonas[alumno1.nombre] = alumno1
  arrayPersonas[alumno1.nombre] = alumno2

  #Creacion de Asignaturas
  asig1 = Asignatura.new(1000,"Ing.Informatica",6)
  asig2 = Asignatura.new(1001,"Ing.Informatica",6)
  asig3 = Asignatura.new(1002,"Ing.Informatica",6)
  asig4 = Asignatura.new(1003,"Ing.Informatica",6)
  asig5 = Asignatura.new(1004,"Ing.Informatica",6)
  asig6 = Asignatura.new(1005,"Ing.Informatica",6)
  asig7 = Asignatura.new(1006,"Ing.Informatica",6)

  #Imprimir las personas
  puts "Mostrando personas almacenadas en el sistema"
  arrayPersonas.each_pair do |k,v|
    puts arrayPersonas[k]
  end

  #Matriculas a alumnos en asignaturas
  if alex.respond_to?("matricularAsignatura")
    alex.matricularAsignatura(asig1) #Nunca se ejecuta, porque alex es Persona
  end
  if alumno1.respond_to?("matricularAsignatura")
    alumno1.matricularAsignatura(asig1)
    alumno1.matricularAsignatura(asig7)
  end

  alumno1.mostrarMatricula
  puts "El numero total de creditos del alumno es ".concat(alumno1.getNumCreditos.to_s)
  puts "Desmatriculando al almuno de una asignatura..."
  alumno1.desmatricularAsignatura(1000)
  alumno1.mostrarMatricula
  puts "Intentando desmatricular de asignatura no matriculada..."
  begin
    alumno1.desmatricularAsignatura(1000)
  rescue Exception => e
    puts e.message
  end
  puts
  puts "To string de Alumno, reutilizando el de persona"
  puts alumno1.to_s

end

En este fragmento de código se muestra un ejemplo del tratado de excepciones, en lugar del clásico try{...}catch(){...} en Ruby se usa la siguiente sintaxis:

begin
    alumno1.desmatricularAsignatura(1000)
  rescue Exception => e
    puts e.message
  end

Debido a la ausencia de tipos en Ruby, el lenguaje proporciona un mecanismo para comprobar si el objeto responde a un método concreto, es decir, si lo tiene implementado. Esto se logra con nombreObjeto.respond_to?("NombreMétodo")

Vamos a ejecutar el programa:

hkr-> ruby Main.rb
Mostrando personas almacenadas en el sistema
Persona [nombre=Alejandro Alcalde], fechaNacimiento=1991-02-10]
Persona [nombre=Pepe], fechaNacimiento=1980-02-08]
Persona [nombre=Nombre del alum2], fechaNacimiento=1980-02-08]Esta estudiando Ing. Teleco, creditos 0
El alumno se encuentra matriculado de las siguientes asignaturas:
  Codigo Asig: 1000. Creditos 6
   Codigo Asig: 1006. Creditos 6
El numero total de creditos del alumno es 12
Desmatriculando al almuno de una asignatura...
El alumno se encuentra matriculado de las siguientes asignaturas:
    Codigo Asig: 1006. Creditos 6
Intentando desmatricular de asignatura no matriculada...
ERROR: El alumno no esta matriculado en la asignatura 1000

To string de Alumno, reutilizando el de persona
Persona [nombre=Nombre del alum1], fechaNacimiento=1980-02-08]Esta estudiando Ing. Informatica, creditos 6

Como mencioné al principio, el código de ambos programas está disponible para descargar:

Como introducción al lenguaje Ruby, ya que nunca lo había usado, leí el siguiente artículo: