Vimos en el concepto anterior que dos clases pueden estar relacionadas por la colaboración. Ahora veremos otro tipo de relaciones 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 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.
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
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.
class Persona: def __init__(self): self.nombre=input("Ingrese el nombre:") self.edad=int(input("Ingrese la edad:")) def imprimir(self): print("Nombre:",self.nombre) print("Edad:",self.edad) class Empleado(Persona): def __init__(self): super().__init__() self.sueldo=float(input("Ingrese el sueldo:")) def imprimir(self): super().imprimir() print("Sueldo:",self.sueldo) def paga_impuestos(self): if self.sueldo>3000: print("El empleado debe pagar impuestos") else: print("No paga impuestos") # bloque principal persona1=Persona() persona1.imprimir() print("____________________________") empleado1=Empleado() empleado1.imprimir() empleado1.paga_impuestos()
La clase Persona no tiene ninguna sintaxis nueva, como vemos definimos sus métodos __init__ e imprimir, y sus dos atributos nombre y edad:
class Persona: def __init__(self): self.nombre=input("Ingrese el nombre:") self.edad=int(input("Ingrese la edad:")) def imprimir(self): print("Nombre:",self.nombre) print("Edad:",self.edad)
En el bloque principal creamos un objeto de la clase Persona:
# bloque principal persona1=Persona() persona1.imprimir()
La herencia se presenta en la clase Empleado, en la declaración de la clase indicamos entre paréntesis 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 __init__ e imprimir.
La clase Empleado define un nuevo atributo que es el sueldo y tres funcionalidades __init__, imprimir y paga_impuestos.
En el método __init__ de la clase Empleado primero llamamos al método __init__ de la clase padre y luego cargamos el sueldo:
def __init__(self): super().__init__() self.sueldo=float(input("Ingrese el sueldo:"))
Mediante la función super() podemos llamar al método __init__ 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(self): super().imprimir() print("Sueldo:",self.sueldo)
La tercer funcionalidad es:
def paga_impuestos(self): if self.sueldo>3000: print("El empleado debe pagar impuestos") else: print("No paga impuestos")
En el bloque principal de nuestro programa definimos un objeto de la clase Empleado y llamamos a sus funcionalidades:
empleado1=Empleado() empleado1.imprimir() empleado1.paga_impuestos()
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), y otro método mostrar_resultado.
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, cargar2 y mostrar_resultado 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.
class Operacion: def __init__(self): self.valor1=0 self.valor2=0 self.resultado=0 def cargar1(self): self.valor1=int(input("Ingrese primer valor:")) def cargar2(self): self.valor2=int(input("Ingrese segundo valor:")) def mostrar_resultado(self): print(self.resultado) def operar(self): pass class Suma(Operacion): def operar(self): self.resultado=self.valor1+self.valor2 class Resta(Operacion): def operar(self): self.resultado=self.valor1-self.valor2 # bloque princpipal suma1=Suma() suma1.cargar1() suma1.cargar2() suma1.operar() print("La suma de los dos valores es") suma1.mostrar_resultado() resta1=Resta() resta1.cargar1() resta1.cargar2() resta1.operar() print("La resta de los valores es:") resta1.mostrar_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 inicializa en el metodo __init__ tres atributos:
class Operacion: def __init__(self): self.valor1=0 self.valor2=0 self.resultado=0
Define dos métodos para cargar los atributos valor1 y valor2:
def cargar1(self): self.valor1=int(input("Ingrese primer valor:")) def cargar2(self): self.valor2=int(input("Ingrese segundo valor:"))
Definimos un método para imprimir el atributo resultado:
def mostrar_resultado(self): print(self.resultado)
Como la clase Operación se trata de una clase genérica que busca agrupar funcionalidades y atributos para otras clases podemos definir un método operar pero no podemos implementar ninguna funcionalidad:
def operar(self): pass
En Python para indicar que un método está vacío se utiliza la palabra clave "pass".
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 reescriben el método operar con la funcionalidad que le corresponde a cada clase:
class Suma(Operacion): def operar(self): self.resultado=self.valor1+self.valor2 class Resta(Operacion): def operar(self): self.resultado=self.valor1-self.valor2
Finalmente en el bloque principal de nuestro programa en Python 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() suma1.cargar1() suma1.cargar2() suma1.operar() print("La suma de los dos valores es") suma1.mostrar_resultado() resta1=Resta() resta1.cargar1() resta1.cargar2() resta1.operar() print("La resta de los valores es:") resta1.mostrar_resultado()
ejercicio202.py class Cuenta: def __init__(self,titular,monto): self.titular=titular self.monto=monto def imprimir(self): print("Titular:",self.titular) print("Monto:",self.monto) class CajaAhorro(Cuenta): def __init__(self,titular,monto): super().__init__(titular,monto) def imprimir(self): print("Cuenta de caja de ahorro") super().imprimir() class PlazoFijo(Cuenta): def __init__(self,titular,monto,plazo,interes): super().__init__(titular,monto) self.plazo=plazo self.interes=interes def imprimir(self): print("Cuenta de plazo fijo") super().imprimir() print("Plazo en dias:",self.plazo) print("Interes:",self.interes) self.ganancia_interes() def ganancia_interes(self): ganancia=self.monto*self.interes/100 print("Importe del interes:",ganancia) # bloque principal cajaahorro=CajaAhorro("Juan", 2000) cajaahorro.imprimir() plazofijo=PlazoFijo("Diego", 10000, 30, 0.75) plazofijo.imprimir()