POO Herencia Simple¶
Introducción¶
La herencia modela un tipo de relación llamada es un entre dos clases. Significa que cuando tenemos una clase derivada que hereda de una clase base se crea una relación en la que la clase derivada es una especialización de la clase base
En UML se representa de la siguiente forma:

Las cajas representan las clases y la flecha la relación; parte de la clase derivada hacia la clase base y se suele añadir la palabra extends a la flecha.
En una relación de herencia: * Las clases que heredan de otra se llaman clases derivadas, subclases o subtipos * Las clases desde las que otras derivan se llaman clases base o superclases * De una clase derivada se dice que deriva, hereda o extiende una clase base.
Si por ejejmplo, tenemos una clase Animal y derivamos de la misma una clase Gato, la relación de herencia indica que el Gato es un Animal. Esto significa que el Gato hereda las características de un Animal.
Herencia simple en Python¶
Definir clase heredada¶
Si tenemos dos clases en Python. Por ejemplo:
Para establecer una relación de herencia entre ambas se hace especificando en la clasehija la clase madre como parámetro.
Miembros de clases heredadas¶
Su pongamos que tenemos una clase madre Animal:
Si queremos crear las clases perro (Dog) y pato(Duck) de forma que hereden las características de la clase animal los hacemos de la manera siguiente:
class Dog(Animal):
def growl(self):
return "A dog can growl but a duck can't. Grrr..."
class Duck(Animal):
def quack(self):
return "A duck can quack but a dog can't. Quack..."
Las clases hija cumplen con la característica de que un pato es un animal y un perro es un animal.
Al heredar, las clases hija tienen acceso a los miembros de la clase madre y por tanto incluyen:
- El atributo
legs - los métodos
__init__()ywalk()de la clase madre.
Por tanto, si creamos un objeto de una de las clases hija tiene las propiedades de la clase madre y las propiedades añadidas en la propia clase hija:
toby = Dog(4)
print(toby.legs) # 4
print(toby.walk()) # "Animal walking..."
print(toby.growl()) # "A dog can growl but a duck can't. Grrr..."
lucas = Duck(2)
print(lucas.legs) # 4
print(lucas.walk()) # "Animal walking..."
print(lucas.quack()) # "A duck can quack but a dog can't. Quack..."
Sobreescritura de métodos heredados¶
Si en una clase hija se crea un método con el mismo nombre que la clase madre el nuevo método sobreescribe el método de la clase madre.
Accediendo a los métodos de la clase madre con super()¶
El método super() permite reutilizar el código de la clase madre en las clases hija, lo que nos permite no tener que reescribir un método completamente si va a incluir código del método de la clase Madre.
En el siguiente ejemplo aprovechamos el código del constructor de la clase madre en la clase hija:
Ejemplo¶
Imagina que tienes una clase base Notificacion. Tanto NotificacionEmail como NotificacionSMS son notificaciones, por lo que comparten datos comunes (destinatario, mensaje) y operaciones comunes (enviar, descripcion).
En lugar de duplicar el mismo código en dos clases distintas, podemos definir Notificacion como clase base y hacer que NotificacionEmail y NotificacionSMS hereden de ella.
Observa que:
NotificacionEmailyNotificacionSMSheredan los métodosenviar()ydescripcióndeNotificacion.- Gracias a
super().__init__(), también heredan parte del constructor y solo añaden los atributos específicos (asunto,telefono).
Si ahora, por ejemplo, queremos añadir el estado comun enviada a las notificaciones.
Las otras clases lo heredarían sin tener, en este caso, que modificar nada en dichas clases hijas.
s = NotificacionSMS("Luis", "Código 1234", "+34 600 123 456")
print(s.fue_enviada()) # False
s.enviar()
print(s.fue_enviada()) # True
Polimorfismo¶
Aprovechemos que hemos visto algunas de las ideas de la herencia para introducir el concepto de polimorfismo.
El término polimorfismo tiene origen en las palabras poly (muchos) y morfo (formas). El polimorfismo es una de las propiedades básicas de la programación y en particular de la programación orientada a objetos. La idea básica es que tenemos una función o método con el mismo nombre, pero que al ser usada en diferentes tipos obtemos resultados distintos.
Podemos observarlo en diferentes casos.
Polimorfismo por herencia¶
Tal y como acabamos de ver, la herencia en POO permite que una clase hija herede los métodos de la clase madre. Si el método en la clase hija no se comporta de la misma forma que en la clase madre podemos sobreescribirlo .
Lo que conseguimos con esto es que si ejecutamos el mismo método en un objeto de la clase madre o en un objeto de la clase hija el resultado sea distinto y, por tanto, tenemos que un método con el mismo nombre adopta "forma" distinta en diferentes objetos.
En el ejemplo anterior, un mismo método, walk() genera diferentes salidas aplicado a un objeto de la clase madre Animal o a un objeto de la clase hija Dog
El mismo método aplicado a objetos con una relación de herencia hace que obtengamos resultados distintos si el método ha sido sobreescrito en la clase hija:
Polimorfismo con métodos de clase¶
No es necesario que haya una relación de herencia entre dos clases para que se utilice polimorfismo. Podemos crear dos clases distintas que contengan métodos con el mismo nombre:
class India():
def capital(self):
return "New Delhi is the capital of India."
def language(self):
return "Hindi is the most widely spoken language of India."
class USA():
def capital(self):
return "Washington, D.C. is the capital of USA."
def language(self):
return"English is the primary language of USA."
Y luego un segmento de código, por ejemplo un bucle, que llame a los métodos capital() y language() para un objeto genérico, sin tener en cuenta a partir de que clase se creo dicho objeto:
obj_ind = India()
obj_usa = USA()
countries = [obj_ind, obj_USA]
for country in countries:
country.capital()
country.language()
El resultado sería:
New Delhi is the capital of India.
Hindi is the most widely spoken language of India.
Washington, D.C. is the capital of USA.
English is the primary language of USA.
Obtenemos un resultado distinto ejecutando el mismo método.
Polimorfismo con función y objetos¶
También podemos conseguir polimorfismo si tenemos una función que tenga como parámetro uno o varios objetos. Si llamamos a la función pasándole objetos de distintas clases que incluyan métodos con el mismo nombre obtendremos resultados (formas) distintas.
Para entenderlo mejor veamos un ejemplo:
Tenemos las dos clases anteriores India y USA que contienen métodos con el mismo nombre. Si tenemos la siguiente funcion:
Si creamos un objeto de cada una de las clases y llamamos a la función pasándole cada uno de los objetos anteriores:
El resultado es:
New Delhi is the capital of India.
Hindi is the most widely spoken language of India.
Washington, D.C. is the capital of USA.
English is the primary language of USA.
El polimorfismo permite que la función reciba como parámetros objetos instanciados a partir de cualquier clase y que el resultado sea distinto en función de la clase a partir de la cual se instancie el objeto.
Cuándo no usar herencia¶
La herencia es una herramienta potente, pero mal utilizada puede generar código rígido y difícil de mantener. A continuación se resumen los casos en los que no es recomendable usar herencia.
1. Cuando no existe una relación clara “es-un”¶
La herencia solo es correcta si la subclase es un tipo de la clase base.
Incorrecto
Correcto
2. Cuando se usa solo para reutilizar código¶
Reutilizar código no justifica por sí solo la herencia.
Incorrecto
Alternativa
3. Cuando se rompe el Principio de Sustitución de Liskov (LSP)¶
Una subclase debe poder usarse en lugar de la clase base sin alterar el comportamiento esperado.
Aquí la herencia es incorrecta. La clase hija tiene menos funcionalidades que la clase de la que herada.
4. Cuando el comportamiento cambia radicalmente¶
Si la subclase sobrescribe casi todos los métodos, la herencia no aporta valor.
class Archivo:
def abrir(self): pass
def cerrar(self): pass
class ConexionRed(Archivo): # Semántica distinta
def abrir(self): pass
def cerrar(self): pass
5. Cuando la jerarquía se vuelve profunda y rígida¶
Muchas capas de herencia dificultan la comprensión y el mantenimiento del código.