Polimorfismo, herencia y delegación

Polimorfismo

El polimorfismo es la técnica que nos posibilita que al invocar un determinado método de un objeto, podrán obtenerse distintos resultados según la clase del objeto. Esto se debe a que distintos objetos pueden tener un método con un mismo nombre, pero que realice distintas operaciones.

Lo llevamos usando desde principio del curso, por ejemplo podemos recorrer con una estructura for distintas clases de objeto, debido a que el método especial __iter__ está definida en cada una de las clases. Otro ejemplo sería que con la función print podemos imprimir distintas clases de objeto, en este caso, el método especial __str__ está definido en todas las clases.

Además esto es posible a que python es dinámico, es decir en tiempo de ejecución es cuando se determina el tipo de un objeto. Veamos un ejemplo:

class gato():
	def hablar(self):
		print("MIAU")	

class perro():
	def hablar(self):
		print("GUAU")	

def escucharMascota(animal):
	animal.hablar()	

if __name__ == '__main__':
	g = gato()
	p = perro()
	escucharMascota(g)
	escucharMascota(p)

Herencia

La herencia es un mecanismo de la programación orientada a objetos que sirve para crear clases nuevas a partir de clases preexistentes. Se toman (heredan) atributos y métodos de las clases viejas y se los modifica para modelar una nueva situación.

La clase desde la que se hereda se llama clase base y la que se construye a partir de ella es una clase derivada.

Si nuestra clase base es la clase punto estudiadas en unidades anteriores, puedo crear una nueva clase de la siguiente manera:

class punto3d(punto):
	def __init__(self,x=0,y=0,z=0):
		super().__init__(x,y)
		self.z=z
	@property
	def z(self):
		return self._z	

	@z.setter
	def z(self,z):
		self._z=z	

	def __str__(self):
		return super().__str__()+":"+str(self.z)	

	def distancia(self,otro):
		dx = self.x - otro.x
		dy = self.y - otro.y
		dz = self.z - otro.z
		return (dx*dx + dy*dy + dz*dz)**0.5	

Creemos dos objetos de cada clase y veamos los atributos y métodos que tienen definido:

>>> p=punto(1,2)
>>> dir(p)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_x', '_y', 'distancia', 'x', 'y']
>>> p3d=punto3d(1,2,3)
>>> dir(p3d)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_x', '_y', '_z', 'distancia', 'x', 'y', 'z']

La función super()

La función super() me proporciona una referencia a la clase base. Y podemos observar también que hemos reescrito el método distancia y __str__.

>>> p.distancia(punto(5,6))
5.656854249492381
>>> p3d.distancia(punto3d(2,3,4))
1.7320508075688772
>>> print(p)
1:2
>>> print(p3d)
1:2:3

Herencia múltiple

La herencia múltiple se refiere a la posibilidad de crear una clase a partir de múltiples clases superiores. Es importante nombrar adecuadamente los atributos y los métodos en cada clase para no crear conflictos.

class Telefono:
    "Clase teléfono"
    def __init__(self,numero):
        self.numero=numero
    def telefonear(self):
        print('llamando')
    def colgar(self):
        print('colgando') 
    def __str__(self):
        return self.numero	

class Camara:
    "Clase camara fotográfica"
    def __init__(self,mpx):
        self.mpx=mpx
    def fotografiar(self):
        print('fotografiando')        
    def __str__(self):
        return self.mpx
class Reproductor:
    "Clase Reproductor Mp3"
    def __init__(self,capcidad):
        self.capacidad=capcidad
    def reproducirmp3(self):
        print('reproduciendo mp3')                  
    def reproducirvideo(self):
        print('reproduciendo video')                  
    def __str__(self):
        return self.capacidad	

class Movil(Telefono, Camara, Reproductor):
    def __init__(self,numero,mpx,capacidad):
        Telefono.__init__(self,numero)
        Camara.__init__(self,mpx)
        Reproductor.__init__(self,capacidad)
    def __str__(self):
        return "Número: {0}, Cámara: {1},Capacidad: {2}".format(Telefono.__str__(self),Camara.__str__(self),Reproductor.__str__(self))

Veamos los atributos y métodos de un objeto Movil:

>>> mimovil=Movil("645234567","5Mpx","1G")
>>> dir(mimovil)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'capacidad', 'colgar', 'fotografiar', 'mpx', 'numero', 'reproducirmp3', 'reproducirvideo', 'telefonear']
>>> print(mimovil)
Número: 645234567, Cámara: 5Mpx,Capacidad: 1G

Funciones issubclass() y isinstance()

La función issubclass(SubClase, ClaseSup) se utiliza para comprobar si una clase (SubClase) es hija de otra superior (ClaseSup), devolviendo True o False según sea el caso.

>>> issubclass(Movil,Telefono)
True

La función booleana isinstance(Objeto, Clase) se utiliza para comprobar si un objeto pertenece a una clase o clase superior.

>>> isinstance(mimovil,Movil)
True

Delegación

Llamamos delegación a la situación en la que una clase contiene (como atributos) una o más instancias de otra clase, a las que delegará parte de sus funcionalidades.

A partir de la clase punto, podemos crear la clase circulo de esta forma:

class circulo():	

	def __init__(self,centro,radio):
		self.centro=centro
		self.radio=radio	

	def __str__(self):
		return "Centro:{0}-Radio:{1}".format(self.centro.__str__(),self.radio)	

Y creamos un objeto circulo:

>>> c1=circulo(punto(2,3),5)
>>> print(c1)
Centro:2:3-Radio:5