#!/usr/bin/env python # coding: utf-8 # Open In Colab # # Herencia [1] # En Programación Orientada a Objetos la Herencia es el mecanismo que permite crear nuevas clases a partir de las preexistentes. # La herencia nos permite crear otras clases sin tener que empezar de cero. Utilizamos una clase padre con las características de base y le agregamos las características particulares a la clase hija. # La ventaja de la herencia es la reutilización de código. # La herencia nos permite modelar otros sistemas sin tener que empezar de cero, utilizamos una clase *Padre* con las características de base y le agregamos las características particulares a la clase *Hija*. # # ![herencia](https://github.com/financieras/pyCourse/blob/main/jupyter/img/herencia.png?raw=1) # Cuando una clase hereda de otra, hereda todos sus métodos y atributos. # * clase principal, clase padre o superclase # * clase hija o subclase # # La clase hija hereda los métodos y atributos de la clase padre pero además puede definir sus propios métodos y atributos. # # **Ejemplo 1** # La clase Figura es la clase padre que tiene tres clases hijas: Rectangulo, Circulo, Hexagono. # # **Ejemplo 2** # La clase Pet es la clase padre que tiene dos clases hijas: Dog, Cat. # # **Ejemplo 3** # La clase Vehiculo es la clase padre que tiene varias clases hijas: Moto, Coche, Furgoneta, Camion, Bici. # In[1]: class Vehiculo(): # primero va el constructor con las propiedades def __init__(self, marca, modelo): # marca y modelo nos permiten dar un estado inicial a los objetos que hereden de Vehiculo self.marca = marca # la marca que pasemos como parámetro al constructor será nuestra self. marca del objeto self.modelo = modelo # el modelo que pasemos como parámetro al constructor será nuestro self. modelo self.enmarcha = False # inicialmente los objetos creados no estarán en marcha self.acelera = False # inicialmente los objetos creados no estarán acelerando self.frena = False # inicialmente los objetos creados no estarán frenando def arrancar(self): # método. Los métodos nos dan el comportamiento del objeto self.enmarcha=True def acelerar(self): # método. Estos métodos permiten cambiar las propiedades definidas en el constructor self.acelera = True def frenar(self): # método self.frena = True def estado(self): print(f"Marca: {self.marca} \nModelo: {self.modelo} \ \nEn marcha: {self.enmarcha} \nAcelerando: {self.acelera} \nFrenando: {self.frena}") class Moto(Vehiculo): # Así indicamos que la clase Moto hereda de Vehiculo pass # No añadimos nada para comprobar que Moto goza de las propiedades y métodos de Vehiculo miMoto=Moto("Honda", "Rebel") # creamos una instancia que hereda tb el constructor por lo que se han de pasar marca y modelo miMoto.estado() # al heredar de Vehiculo puedo usar los métodos heredados # ## Sobreescritura de métodos # Una clase que hereda de otra adquiere sus métodos y propiedades, pero a su vez puede tener sus propios métodos y propiedades. # Pensemos en el comportamiento que pueda tener una moto que no tengan todos los vehículos, por ejemplo, hacer el caballito. La variable será hcaballito (hacer el caballito). # In[2]: class Vehiculo(): def __init__(self, marca, modelo): self.marca = marca self.modelo = modelo self.enmarcha = False self.acelera = False self.frena = False def arrancar(self): self.enmarcha = True def acelerar(self): self.acelera = True def frenar(self): self.frena = True def estado(self): print(f"Marca: {self.marca} \nModelo: {self.modelo} \ \nEn marcha: {self.enmarcha} \nAcelerando: {self.acelera} \nFrenando: {self.frena}") class Moto(Vehiculo): hcaballito = "" # creamos la nueva variable: haciendo el caballito def caballito(self): # creamos el método caballito self.hcaballito="Voy haciendo el caballito" # el método modifica el valor de la variable hcaballito miMoto=Moto("Honda", "Rebel") miMoto.caballito() # invocamos el nuevo método miMoto.estado() # no da error, pero no informa de que estamos haciendo el caballito # será necesario sobrescribir el método estado # Ahora un objeto de tipo moto puede usar seis métodos que son los cinco heredados (incluyendo el construcctor) y el suyo propio. # In[3]: class Vehiculo(): def __init__(self, marca, modelo): self.marca = marca self.modelo = modelo self.enmarcha = False self.acelera = False self.frena = False def arrancar(self): self.enmarcha = True def acelerar(self): self.acelera = True def frenar(self): self.frena = True def estado(self): print(f"Marca: {self.marca} \nModelo: {self.modelo} \ \nEn marcha: {self.enmarcha} \nAcelerando: {self.acelera} \nFrenando: {self.frena}") class Moto(Vehiculo): hcaballito = "" def caballito(self): self.hcaballito = "Voy haciendo el caballito" def estado(self): # = nombre y nº de parámetros que el método sobrescrito de la clase padre print(f"Marca: {self.marca} \nModelo: {self.modelo} \ \nEn marcha: {self.enmarcha} \nAcelerando: {self.acelera} \nFrenando: {self.frena}\ \n{self.hcaballito}") miMoto=Moto("Honda", "Rebel") miMoto.caballito() # invocamos el nuevo método y ahora si vemos que hace el caballito miMoto.estado() # si no hubiéramos invocado el método caballito no se imprimiría Voy... print("="*30) miCoche=Vehiculo("VW", "Golf") # creamos un vehículo que no sea una moto miCoche.estado() # al invocar su estado se aplica el estado de la clase Vehiculo print("="*30) motoClasica=Moto("Harley-Davidson", "Fat Boy") # creamos una moto con la que no haremos el caballito motoClasica.estado() # al invocar su estado se aplica el estado de la clase Moto # pero previamente no hemos invocado el método caballito() # Cuando escribimos miMoto.estado() surge una pregunta: ¿Estamos llamando al método estado de la clase Vehiculo o al método estado de la clase Moto? # El método estado de la clase Moto sobrescribe (anula, invalida) el método estado de la clase Vehiculo. # Invocar o no el método caballito es opcional, si no se invoca no se imprime la frase 'Voy haciendo el caballito', simplemente se imprime "" que es el valor de la variable hcaballito cuando el método no actúa. # ## Jerarquía # Supongamos que creamos la clase Quad que hereda de la clase Moto y esta a su vez hereda de la clase Vehiculo. Esto es lo que se llama una cadena de herencias o jerarquía de herencias. # La clase Quad heredaría el método estado de la clase Moto y no el método estado de la clase Vehiculo ya que Quad hereda directamente de Moto. # ## Clase Furgoneta # Si creamos la clase Furgoneta lo lógico es que herede de la clase Vehiculo y no de la clase Moto. # La clase Furgoneta tendrá un nuevo comportamiento (método) que será la capacidad de cargar (llevar carga). # In[4]: class Vehiculo(): def __init__(self, marca, modelo): self.marca = marca self.modelo = modelo self.enmarcha = False self.acelera = False self.frena = False def arrancar(self): self.enmarcha = True def acelerar(self): self.acelera = True def frenar(self): self.frena = True def estado(self): print(f"Marca: {self.marca} \nModelo: {self.modelo} \ \nEn marcha: {self.enmarcha} \nAcelerando: {self.acelera} \nFrenando: {self.frena}") class Furgoneta(Vehiculo): # creamos la clase Furgoneta que hereda de la clase Vehiculo def cargada(self, carga): # la variable carga será True o False self.carga = carga if self.carga: return "La Furgoneta está cargada" # en este método usamos un return para informar else: return "La Furgoneta no está cargada" miFurgoneta=Furgoneta("Nissan","N100") miFurgoneta.arrancar() miFurgoneta.estado() print(miFurgoneta.cargada(True)) # usamos un print ya que el método 'cargada' se creó con un reuturn