36 - Objetos: herencia

Vimos en el concepto anterior que dos clases pueden estar relacionadas por la colaboración. Ahora veremos otro tipo de relación entre clases que es la Herencia.

La herencia significa que se pueden crear nuevas clases partiendo de clases existentes, que tendrá todas los atributos y los métodos de su 'superclase' o 'clase padre' y además se le podrán añadir otros atributos y métodos propios.

clase padre

Clase de la que desciende o deriva una clase. Las clases hijas (descendientes) heredan (incorporan) automáticamente los atributos y métodos de la la clase padre.

Subclase

Clase descendiente de otra. Hereda automáticamente los atributos y métodos de su superclase. Es una especialización de otra clase.
Admiten la definición de nuevos atributos y métodos para aumentar la especialización de la clase.

Veamos algunos ejemplos teóricos de herencia:

1) Imaginemos la clase Vehículo. Qué clases podrían derivar de ella?

                            Vehiculo

   Colectivo                Moto                    Auto
                             
                                             FordK        Renault Fluence

Siempre hacia abajo en la jerarquía hay una especialización (las subclases añaden nuevos atributos y métodos.

2) Imaginemos la clase Software. Qué clases podrían derivar de ella?

                                           Software

             DeAplicacion                                        DeBase

ProcesadorTexto       PlanillaDeCalculo                          SistemaOperativo

Word   WordPerfect    Excel     Lotus123                         Linux    Windows       

El primer tipo de relación que habíamos visto entre dos clases, es la de colaboración. Recordemos que es cuando una clase contiene un objeto de otra clase como atributo.
Cuando la relación entre dos clases es del tipo "...tiene un..." o "...es parte de...", no debemos implementar herencia. Estamos frente a una relación de colaboración de clases no de herencia.

Si tenemos una ClaseA y otra ClaseB y notamos que entre ellas existe una relacion de tipo "... tiene un...", no debe implementarse herencia sino declarar en la clase ClaseA un atributo de la clase ClaseB.

Por ejemplo: tenemos una clase Auto, una clase Rueda y una clase Volante. Vemos que la relación entre ellas es: Auto "...tiene 4..." Rueda, Volante "...es parte de..." Auto; pero la clase Auto no debe derivar de Rueda ni Volante de Auto porque la relación no es de tipo-subtipo sino de colaboración. Debemos declarar en la clase Auto 4 atributos de tipo Rueda y 1 de tipo Volante.

Luego si vemos que dos clase responden a la pregunta ClaseA "..es un.." ClaseB es posible que haya una relación de herencia.

Por ejemplo:

Auto "es un" Vehiculo
Circulo "es una" Figura
Mouse "es un" DispositivoEntrada
Suma "es una" Operacion

Problema 1:

Plantear una clase Persona que contenga dos atributos: nombre y edad. Definir como responsabilidades la carga por teclado y su impresión.
En el bloque principal del programa definir un objeto de la clase Persona y llamar a sus métodos.

Declarar una segunda clase llamada Empleado que herede de la clase Persona y agregue un atributo sueldo y muestre si debe pagar impuestos (sueldo superior a 3000)
También en el bloque principal del programa crear un objeto de la clase Empleado.

Programa: ejercicio147.rb

class Persona

  def initialize
    print "Ingrese el nombre:"
    @nombre = gets.chomp
    print "Ingrese la edad:"
    @edad = gets.to_i
  end

  def imprimir
    puts "Nombre: #{@nombre}"
    puts "Edad: #{@edad}"
  end
end


class Empleado < Persona

  def initialize
    super
    print "Ingrese el sueldo:"
    @sueldo = gets.to_f
  end

  def imprimir
    super
    puts "Sueldo: #{@sueldo}"
  end

  def paga_impuestos
    if @sueldo > 3000
      puts "El empleado #{@nombre} debe pagar impuestos"
    else
      puts "No paga impuestos"
    end
  end
end

# bloque principal

persona1 = Persona.new
persona1.imprimir
puts "*" * 50
empleado1 = Empleado.new
empleado1.imprimir
empleado1.paga_impuestos

La clase Persona no tiene ninguna sintaxis nueva, como vemos definimos sus métodos initialize e imprimir, y sus dos atributos @nombre y @edad:

class Persona

  def initialize
    print "Ingrese el nombre:"
    @nombre = gets.chomp
    print "Ingrese la edad:"
    @edad = gets.to_i
  end

  def imprimir
    puts "Nombre: #{@nombre}"
    puts "Edad: #{@edad}"
  end
end

En el bloque principal creamos un objeto de la clase Persona:

# bloque principal

persona1 = Persona.new
persona1.imprimir

La herencia se presenta en la clase Empleado, en la declaración de la clase indicamos luego del caracter < el nombre de la clase de la cual hereda:

class Empleado < Persona

Con esta sintaxis indicamos que la clase Empleado hereda de la clase Persona. Luego de esto podemos decir que la clase Empleado ya tiene dos atributos heredados que son el @nombre y la @edad, también hereda las funcionalidades initialize e imprimir.

La clase Empleado define un nuevo atributo que es el @sueldo y tres funcionalidades initialize, imprimir y paga_impuestos.

En el método initialize de la clase Empleado primero llamamos al método initialize de la clase padre y luego cargamos el sueldo:

  def initialize
    super
    print "Ingrese el sueldo:"
    @sueldo = gets.to_f
  end

Mediante la palabra clave super podemos llamar al método initialize de la clase padre.

Lo mismo sucede con el método imprimir donde primero llamamos al imprimir de la clase padre y luego mostramos el sueldo del empleado:

  def imprimir
    super
    puts "Sueldo: #{@sueldo}"
  end

La tercer funcionalidad es (como vemos tenemos acceso al atributo @nombre por la herencia):

  def paga_impuestos
    if @sueldo > 3000
      puts "El empleado #{@nombre} debe pagar impuestos"
    else
      puts "No paga impuestos"
    end
  end

En el bloque principal de nuestro programa definimos un objeto de la clase Empleado y llamamos a sus métodos:

empleado1 = Empleado.new
empleado1.imprimir
empleado1.paga_impuestos

Problema 2:

Ahora plantearemos otro problema empleando herencia. Supongamos que necesitamos implementar dos clases que llamaremos Suma y Resta. Cada clase tiene como atributo valor1, valor2 y resultado. Los métodos a definir son cargar1 (que inicializa el atributo valor1), carga2 (que inicializa el atributo valor2), operar (que en el caso de la clase "Suma" suma los dos atributos y en el caso de la clase "Resta" hace la diferencia entre valor1 y valor2)

Si analizamos ambas clases encontramos que muchos atributos y métodos son idénticos. En estos casos es bueno definir una clase padre que agrupe dichos atributos y responsabilidades comunes.

La relación de herencia que podemos disponer para este problema es:

                                        Operacion

                        Suma                              Resta

Solamente el método operar es distinto para las clases Suma y Resta (esto hace que no lo podamos disponer en la clase Operacion en principio), luego los métodos cargar1 y cargar2 son idénticos a las dos clases, esto hace que podamos disponerlos en la clase Operacion. Lo mismo los atributos valor1, valor2 y resultado se definirán en la clase padre Operacion.

Programa: ejercicio148.rb

class Operacion
  attr_reader :resultado

  def initialize
    @valor1 = 0
    @valor2 = 0
    @resultado = 0
  end
  
  def cargar1
    print "Ingrese primer valor:"
    @valor1 = gets.to_i
  end

  def cargar2
    print "Ingrese segundo valor:"
    @valor2 = gets.to_i
  end

end


class Suma < Operacion

  def operar
    @resultado = @valor1 + @valor2
  end

end

class Resta < Operacion

  def operar
    @resultado = @valor1 -@valor2
  end

end  

# bloque princpipal

suma1 = Suma.new
suma1.cargar1
suma1.cargar2
suma1.operar
puts "La suma de los dos valores es #{suma1.resultado}"

resta1 = Resta.new
resta1.cargar1
resta1.cargar2
resta1.operar
puts "La resta de los valores es:#{resta1.resultado}"

En este problema la clase Operación tiene por objetivo agrupar atributos y funcionalidades que se heredarán en otras clases.

La clase Operación asigna un cero a los tres atributos en el metodo initialize:

class Operacion
  attr_reader :resultado

  def initialize
    @valor1 = 0
    @valor2 = 0
    @resultado = 0
  end

Define dos métodos para cargar los atributos valor1 y valor2:

  def cargar1
    print "Ingrese primer valor:"
    @valor1 = gets.to_i
  end

  def cargar2
    print "Ingrese segundo valor:"
    @valor2 = gets.to_i
  end

Definimos el atributo @resultado mediante attr_reader para tener acceso a su valor desde fuera de la clase:

  attr_reader :resultado

En el bloque principal de nuestro programa no creamos objetos de la clase Operación. La clase Operación tiene sentido que otras clases hereden de esta.

Tanto la clase Suma y Resta heredan de la clase Operación y definen el método operar con la funcionalidad que le corresponde a cada clase:

class Suma < Operacion

  def operar
    @resultado = @valor1 + @valor2
  end

end

class Resta < Operacion

  def operar
    @resultado = @valor1 -@valor2
  end

end  

Finalmente en el bloque principal de nuestro programa creamos un objeto de la clase Suma y otro de la clase Resta y llamamos a sus respectivos métodos en un orden lógico:

# bloque princpipal

suma1 = Suma.new
suma1.cargar1
suma1.cargar2
suma1.operar
puts "La suma de los dos valores es #{suma1.resultado}"

resta1 = Resta.new
resta1.cargar1
resta1.cargar2
resta1.operar
puts "La resta de los valores es:#{resta1.resultado}"

Podemos acceder al atributo @resultado gracias que definimos el attr_reader respectivo.

Problema propuesto

  • Declarar una clase Cuenta y dos subclases CajaAhorra y PlazoFijo. Definir los atributos y métodos comunes entre una caja de ahorro y un plazo fijo y agruparlos en la clase Cuenta.
    Una caja de ahorro y un plazo fijo tienen un nombre de titular y un monto. Un plazo fijo añade un plazo de imposición en días y una tasa de interés. Hacer que la caja de ahorro no genera intereses.
    En el bloque principal del programa definir un objeto de la clase CajaAhorro y otro de la clase PlazoFijo.
Solución
ejercicio149.rb

class Cuenta

  def initialize(titular, monto)
    @titular = titular
    @monto = monto
  end

  def imprimir
    puts "Titular: #{@titular}"
    puts "Monto: #{@monto}"
  end
end  


class CajaAhorro < Cuenta

  def initialize(titular, monto)
    super titular, monto
  end

  def imprimir
    puts "Cuenta de caja de ahorro"
    super
  end  
end

class PlazoFijo < Cuenta

  def initialize(titular, monto, plazo, interes)
    super titular, monto
    @plazo = plazo
    @interes = interes
  end

  def imprimir
    puts "Cuenta de plazo fijo"
    super
    puts "Plazo en dias: #{@plazo}"
    puts "Interes: #{@interes}"
    ganancia_interes
  end  

  def ganancia_interes
    ganancia =  @monto * @interes / 100
    puts "Importe del interes: #{ganancia}"
  end
end

# bloque principal

cajaahorro = CajaAhorro.new "Juan", 2000
cajaahorro.imprimir

plazofijo = PlazoFijo.new "Diego", 10000, 30, 0.75
plazofijo.imprimir