36 - POO - Herencia

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

clase padre

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

Subclase

Clase descendiente de otra. Hereda automáticamente los campos y métodos de su superclase. Es una especialización de otra clase.
Admiten la definición de nuevos campos 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 campos 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       

Si vemos que dos clases 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 campos: nombre y edad. Definir como responsabilidades la carga, impresión y mostrar si es mayor de edad.
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 campo sueldo y muestre si debe pagar impuestos (sueldo superior a 3000)
También en la clase empleado permitir cargar los datos e imprimir.
En el bloque principal también definir un objeto de la clase Empleado y llamar a sus métodos.

Proyecto132

program Proyecto132;

{$APPTYPE CONSOLE}

type
  TPersona = class
    Nombre: String;
    Edad: Integer;
    procedure Cargar;
    procedure Imprimir;
    procedure EsMayorDeEdad;
  end;

  TEmpleado = class(TPersona)
    Sueldo: Double;
    procedure Cargar;
    procedure Imprimir;
    procedure PagaImpuestos;
  end;

procedure TPersona.Cargar;
begin
  Write('Ingrese el nombre:');
  ReadLn(Nombre);
  Write('Ingrese la edad:');
  ReadLn(Edad);
end;

procedure TPersona.Imprimir;
begin
  WriteLn('Nombre:', Nombre);
  WriteLn('Edad:', Edad);
end;

procedure TPersona.EsMayorDeEdad;
begin
  if Edad >= 18 then
    WriteLn('Es mayor de edad')
  else
    WriteLn('No es mayor de edad');
end;

procedure TEmpleado.Cargar;
begin
  inherited Cargar;
  Write('Ingrese el sueldo:');
  ReadLn(Sueldo);
end;

procedure TEmpleado.Imprimir;
begin
  inherited Imprimir;
  WriteLn('Sueldo:', Sueldo:0:2)
end;

procedure TEmpleado.PagaImpuestos;
begin
  if Sueldo > 3000 then
    WriteLn('El empleado ', Nombre, ' debe pagar impuestos')
  else
    WriteLn('El empleado ', Nombre, ' no debe pagar impuestos')
end;

var
  Persona1: TPersona;
  Empleado1: TEmpleado;
begin
  Persona1 := TPersona.Create;
  WriteLn('Datos de la persona');
  Persona1.Cargar;
  Persona1.Imprimir;
  Persona1.EsMayorDeEdad;
  Persona1.Free;
  Empleado1 := TEmpleado.Create;
  WriteLn('Datos del empleado');
  Empleado1.Cargar;
  Empleado1.Imprimir;
  Empleado1.EsMayorDeEdad;
  Empleado1.PagaImpuestos;
  Empleado1.Free;
  ReadLn;
end.

La declaración TPersona no sufre cambios con respecto al ejercicio que desarrollamos conceptos anteriores:

  TPersona = class
    Nombre: String;
    Edad: Integer;
    procedure Cargar;
    procedure Imprimir;
    procedure EsMayorDeEdad;
  end;

Lo mismo la implementación de los métodos de la clase TPersona no presentan cambios:

procedure TPersona.Cargar;
begin
  Write('Ingrese el nombre:');
  ReadLn(Nombre);
  Write('Ingrese la edad:');
  ReadLn(Edad);
end;

procedure TPersona.Imprimir;
begin
  WriteLn('Nombre:', Nombre);
  WriteLn('Edad:', Edad);
end;

procedure TPersona.EsMayorDeEdad;
begin
  if Edad >= 18 then
    WriteLn('Es mayor de edad')
  else
    WriteLn('No es mayor de edad');
end;

Lo nuevo se presenta cuando declaramos la clase TEmpleado indicando que la misma hereda de la clase TPersona:

  TEmpleado = class(TPersona)

Entre paréntesis luego de la palabra clave class indicamos el nombre de la clase que heredamos.

Es importante decir que la declaración de la clase TPersona debe estar obligatoriamente antes de la declaración de la clase TEmpleado, sino se produce un error sintáctico.

Con indicar que la clase TEmpleado hereda de TPersona significa que la clase TEmpleado ya tiene dos campos (Nombre y Edad) y tres métodos (Cargar, Imprimir y EsMayorDeEdad)

La clase TEmpleado define el campo Sueldo y tres métodos:

    Sueldo: Double;
    procedure Cargar;
    procedure Imprimir;
    procedure PagaImpuestos;

El método Cargar tendrá por objetivo cargar los datos del empleado (Nombre, Edad y Sueldo):

procedure TEmpleado.Cargar;
begin
  inherited Cargar;
  Write('Ingrese el sueldo:');
  ReadLn(Sueldo);
end;

Dentro del método cargar y con las ventajas que nos proporciona la herencia, mediante la palabra clave inherided llamamos al método Cargar de la clase padre (donde se carga el nombre y la edad) y luego solicitamos el ingreso del Sueldo.

La herencia nos permite reducir línea de código evitando tener que escribir:

procedure TEmpleado.Cargar;
begin
  Write('Ingrese el nombre:');
  ReadLn(Nombre);
  Write('Ingrese la edad:');
  ReadLn(Edad);
  Write('Ingrese el sueldo:');
  ReadLn(Sueldo);
end;

De forma similar cuando codificamos el método Imprimir de la clase TEmpleado llamamos al método Imprimir de la clase padre:

procedure TEmpleado.Imprimir;
begin
  inherited Imprimir;
  WriteLn('Sueldo:', Sueldo:0:2)
end;

El tercer método tiene por objetivo mostrar si paga impuestos el empleado:

procedure TEmpleado.PagaImpuestos;
begin
  if Sueldo > 3000 then
    WriteLn('El empleado ', Nombre, ' debe pagar impuestos')
  else
    WriteLn('El empleado ', Nombre, ' no debe pagar impuestos')
end;

Es importante notar que podemos acceder al campo Nombre, que si bien no está definido en la clase TEmpleado si lo está en la clase TPersona.

Definimos dos variables globales, uno de tipo TPersona y otra de tipo TEmpleado:

var
  Persona1: TPersona;
  Empleado1: TEmpleado;

Creamos el objeto Persona1 y llamamos a sus métodos:

begin
  Persona1 := TPersona.Create;
  WriteLn('Datos de la persona');
  Persona1.Cargar;
  Persona1.Imprimir;
  Persona1.EsMayorDeEdad;
  Persona1.Free;

Luego creamos el objeto Empleado1 y también llamamos a sus métodos:

  Empleado1 := TEmpleado.Create;
  WriteLn('Datos del empleado');
  Empleado1.Cargar;
  Empleado1.Imprimir;
  Empleado1.EsMayorDeEdad;
  Empleado1.PagaImpuestos;
  Empleado1.Free;
  ReadLn;
end.

Es importante notar que podemos llamar al método EsMayorDeEdad desde el objeto Empleado1 gracias a que este objeto hereda de la clase TPersona.

Ahora ya podemos decir de donde salen los métodos Create y Free, son métodos heredados de la clase TObject.

Toda clase hereda directamente o indirectamente de la clase TObject. Podemos modificar el programa y utilizar la sintaxis:

  TPersona = class(TObject)

Es decir que toda clase si no indicamos que hereda de otra luego está heredando de la clase TObject que define los métodos Create y Free.

Gracias a esta herencia podemos escribir:

  Persona1 := TPersona.Create;
  ...
  Persona1.Free;

Lo mismo sucede con la clase TEmpleado al heredar de la clase TPersona y esta heredar de la clase TObject podemos codificar:

  Empleado1 := TEmpleado.Create;
  ...
  Empleado1.Free;

Problema 2:

Declarar una clase llamada TCalculadora que disponga de dos métodos para la carga de valores de tipo Double (almacenar los datos en dos campos)
Definir las responsabilidades de sumar, restar, multiplicar, dividir e imprimir.

Declarar luego una clase llamada TCalculadoraCientifica que herede de TCalculadora y añada las responsabilidades de calcular el cuadrado del primer número y la raíz cuadrada.

Proyecto133

program Proyecto133;

{$APPTYPE CONSOLE}

type
  TCalculadora = class
    Valor1: Double;
    Valor2: Double;
    Resultado: Double;
    procedure Cargar1;
    procedure Cargar2;
    procedure Sumar;
    procedure Restar;
    procedure Multiplicar;
    procedure Dividir;
    procedure Imprimir;
  end;

  TCalculadoraCientifica = class(TCalculadora)
    procedure Cuadrado;
    procedure Raiz;
  end;

procedure TCalculadora.Cargar1;
begin
  Write('Ingrese valor:');
  ReadLn(Valor1);
end;

procedure TCalculadora.Cargar2;
begin
  Write('Ingrese valor:');
  ReadLn(Valor2);
end;

procedure TCalculadora.Sumar;
begin
  Resultado := Valor1 + Valor2;
end;

procedure TCalculadora.Restar;
begin
  Resultado := Valor1 - Valor2
end;

procedure TCalculadora.Multiplicar;
begin
  Resultado := Valor1 * Valor2;
end;

procedure TCalculadora.Dividir;
begin
  Resultado := Valor1 / Valor2;
end;

procedure TCalculadora.Imprimir;
begin
  WriteLn('Resultado:', Resultado:0:2);
end;

procedure TCalculadoraCientifica.Cuadrado;
begin
  Resultado := Valor1 * Valor1;
end;

procedure TCalculadoraCientifica.Raiz;
begin
  Resultado := Sqrt(Valor1);
end;

var
  Calculadora1: TCalculadora;
  CalculadoraCientifica1: TCalculadoraCientifica;
begin
  WriteLn('Uso de la calculadora');
  Calculadora1 := TCalculadora.Create;
  Calculadora1.Cargar1;
  Calculadora1.Cargar2;
  Calculadora1.Sumar;
  Calculadora1.Imprimir;
  Calculadora1.Restar;
  Calculadora1.Imprimir;
  Calculadora1.Multiplicar;
  Calculadora1.Imprimir;
  Calculadora1.Dividir;
  Calculadora1.Imprimir;
  Calculadora1.Free;
  WriteLn('Uso de la calculadora científica');
  CalculadoraCientifica1 := TCalculadoraCientifica.Create;
  CalculadoraCientifica1.Cargar1;
  CalculadoraCientifica1.Cargar2;
  CalculadoraCientifica1.Sumar;
  CalculadoraCientifica1.Imprimir;
  CalculadoraCientifica1.Restar;
  CalculadoraCientifica1.Imprimir;
  CalculadoraCientifica1.Multiplicar;
  CalculadoraCientifica1.Imprimir;
  CalculadoraCientifica1.Dividir;
  CalculadoraCientifica1.Imprimir;
  CalculadoraCientifica1.Cuadrado;
  CalculadoraCientifica1.Imprimir;
  CalculadoraCientifica1.Raiz;
  CalculadoraCientifica1.Imprimir;
  CalculadoraCientifica1.Free;
  ReadLn;
end.

La clase TCalculadora hereda por defecto de la clase TObject, que por lo general no se escribe:

  TCalculadora = class
    Valor1: Double;
    Valor2: Double;
    Resultado: Double;
    procedure Cargar1;
    procedure Cargar2;
    procedure Sumar;
    procedure Restar;
    procedure Multiplicar;
    procedure Dividir;
    procedure Imprimir;
  end;

Definimos 3 campos y 7 métodos, más abajo codificamos los 7 métodos.

La clase TCalculadoraCientifica hereda de la clase TCalculadora y añade dos responsabilidades que permita calcular el cuadrado y la raiz cuadrada de un número:

  TCalculadoraCientifica = class(TCalculadora)
    procedure Cuadrado;
    procedure Raiz;
  end;

Tengamos en cuenta que con la herencia la clase TCalculadoraCientifica es una calculadora común, es decir que podemos sumar, restar, multiplicar y dividir, pero añade los métodos de cálculo del cuadrado y la raíz.

Problema propuesto

  • Declarar una clase TDado que genere un valor aleatorio entre 1 y 6:

      Valor := Random(6) + 1;
    
    Imprimir el valor del dado.
    Crear una segunda clase llamada TDadoRecuadro que genere un valor entre 1 y 6, mostrar el valor recuadrado con asteríscos.
    Utilizar la herencia entre estas dos clases.
Solución
program Proyecto134;

{$APPTYPE CONSOLE}

type
  TDado = class
    Valor: Integer;
    procedure Tirar;
    procedure Imprimir;
  end;

  TDadoRecuadro = class(TDado)
    procedure Imprimir;
  end;

procedure TDado.Tirar;
begin
  Valor := Random(6) + 1;
end;

procedure TDado.Imprimir;
begin
  WriteLn(Valor);
end;

procedure TDadoRecuadro.Imprimir;
begin
  WriteLn('***');
  WriteLn('*', Valor, '*');
  WriteLn('***');
end;

var
  Dado1: TDado;
  DadoRecuadro1: TDadoRecuadro;
begin
  Randomize; //Generamos una semilla de valores aleatorios distinta para cada ejecución del programa
  WriteLn('Prueba del dado común');
  Dado1 := TDado.Create;
  Dado1.Tirar;
  Dado1.Imprimir;
  Dado1.Free;
  WriteLn('Prueba del dado con recuadro');
  DadoRecuadro1 := TDadoRecuadro.Create;
  DadoRecuadro1.Tirar;
  DadoRecuadro1.Imprimir;
  DadoRecuadro1.Free;
  ReadLn;
end.