17 de mayo de 2016

33. Polimorfismo, herencia y delegación

Polimorfismo

A veces aplicamos los mismos métodos o funciones sobre objetos de distintas clases o tipos. Por ejemplo,
  • Podemos aplicar la iteración for i in secuencia a un objeto secuencia que puede ser una lista, una tupla, un diccionario, etc.
  • O podemos aplicar la suma, la resta, u otras funciones a números enteros, tipo float, tipo long...
  • O por ejemplo, al incluir una clase nueva aplicamos el método __str__ para formatear la salida del mismo.
Es decir, aunque haya objetos distintos que pertenecen a clases distintas, los nombres de los métodos son idénticos. El polimorfismo consiste en que el mismo "método" puede aplicarse a clases distintas, aunque el resultado difiera de una clase a otra.
Llamamos interfaz al conjunto de atributos y métodos que un programador desarrolla para una clase. Cuando se cumple el polimorfismo, existe una interfaz común aplicable a objetos de distintas clases.
Se llama redefinición a la acción de definir un método con idéntico nombre para varias clases, de manera que provea de una interfaz común. En cada clase el método debe programarse de una forma distinta. Por ejemplo, los métodos especiales que permiten la sobrecarga de operadores vista anteriormente.

Herencia

La herencia consiste que podemos generar una clase basada en una clase anterior, heredando sus métodos y atributos. En el ejemplo siguiente, generamos la clase vehículo que contiene la inicialización de diversas características, como color, tipo de combustible, cilindrada y capacidad del tanque, y dos métodos, andar, que gasta 2 litros de combustible cada vez y repostar, que llena el tanque. Será nuestra clase base (entre paréntesis en la definición object).
A continuación generamos la clase coche, que hereda de vehículo todos sus atributos y añade el de ruedas. Se define el método de formateo o de muestra de información. Esta es una clase derivada  y en su definición se pone entre paréntesis de qué clase deriva class coche(vehiculo). Asímismo, genero la clase moto, de la misma forma.
En el programa, los métodos aplicados a la clase moto son repostar y andar. Ambos pueden aplicarse porque la clase moto ha heredado de la clase vehículo sus métodos. También, al imprimir o mostrar sus características se muestran los atributos heredados de la clase vehículo.
Una clase puede heredarse de otra cuando cumple el principio de Liskov:
  • La clase A es heredera de otra B cuando estoy seguro que "A es B".
  • La clase cochemoto son herederas de vehículo porque estoy seguro que "un coche o una moto son vehículos".
  • Ahora, la clase motor no puede heredarse de la clase vehículo porque "un motor no es un vehículo"
# -*- coding: utf-8 -*-

class vehiculo(object):
    
    def __init__(self, c0="rojo", comb0="diesel", cil=200, tan=80):
        self.color = c0
        self.combustible = comb0
        self.cilindrada = cil
        #capacidad del tanque de combustible
        self.tanque = tan
        self.maxTanque = tan
        return
        
    def andar(self):
        self.tanque -= 2
        return 

    def repostar(self):
        self.tanque = self.maxTanque
        return

class coche(vehiculo):
    
    def __init__(self, c0="rojo", comb0="diesel", cil=200, tan=80, rued = 4):
        # llamo a la clase anterior vehiculo
        vehiculo.__init__(self,c0,comb0,cil,tan)
        # añado nueva característica
        self.ruedas = rued
        return
    
    def __str__(self):
        car = "Vehículo de " + str(self.ruedas) + " ruedas, " + self.combustible
        car += ", color " + self.color + " y cilindrada " + str(self.cilindrada)+"."
        car += "\nLe quedan en el tanque "+ str(self.tanque) + " litros."
        return car
    
class moto(vehiculo):
    
    def __init__(self, c0="rojo", comb0="diesel", cil=200, tan=80, rued = 2):
        # llamo a la clase anterior vehiculo
        vehiculo.__init__(self,c0,comb0,cil,tan)
        # añado nueva característica
        self.ruedas = rued
        return
    
    def __str__(self):
        car = "Vehículo de " + str(self.ruedas) + " ruedas, " + self.combustible
        car += ", color " + self.color + " y cilindrada " + str(self.cilindrada)+"."
        car += "\nLe quedan en el tanque "+ str(self.tanque) + " litros."
        return car
        

# =========================================
# Programa Principal
# =========================================

# crear dos vehículos distintos, con consideraciones iniciales
c1 = coche()
m1 = moto()

# Imprime información de los dos vehículos
print c1
print m1

# Crea un vehículo usando herencia para definir parte de sus caraterísticas
m2 = moto( "azul", "gasolina", 400, 200, 2)
# lo imprime
print m2

print "\n"

# Hago andar el vehículo 2, 10 veces.
for i in range(10):
    m2.andar()
print "He movido la moto 10 veces:\n", m2
# Le lleno el tanque
m2.repostar()
print "Le he llenado el tanque:\n", m2 
= = =

Delegación

Hay clases que no pueden heredarse de otra ya que no cumplen el principio de Liskov, pero sí tienen cierta relación. Antes comentamos que un motor no es un vehículo, luego no puede haber herencia entre ellos, pero evidentemente un vehículo posee un motor.
Pero nada impide que la clase vehículo tenga como atributo un objeto de otra clase, llamada motor. En este caso, la clase vehículo delega en la clase motor parte de sus funcionalidades. Decimos que el atributo motor tiene una referencia en la clase motor.
En el siguiente ejemplo, incluyo la clase motor. La clase vehículo recibe el atributo motor como una instancia de dicha clase, que se hereda en las clases coche y moto.
# -*- coding: utf-8 -*-

class vehiculo(object):
    
    def __init__(self, c0="rojo", comb0="diesel", cil=200, tan=80, mt=("",0)):
        self.color = c0
        self.combustible = comb0
        self.cilindrada = cil
        #capacidad del tanque de combustible
        self.tanque = tan
        self.maxTanque = tan
        self.motor = mt
        return
        
    def andar(self):
        self.tanque -= 2
        return 

    def repostar(self):
        self.tanque = self.maxTanque
        return
        
class motor(object):
    def __init__(self, marca="Mercedes", cilindros=4):
        self.marca = marca
        self.cilindros = cilindros
        return
        
    def __str__(self):
        return "["+self.marca + ":" + str(self.cilindros)+ "]"
        
    def gripar(self):
        self.marca="Estropeado"
        self.cilindros=0
        return

class coche(vehiculo):
    
    def __init__(self, c0="rojo", comb0="diesel", cil=200, tan=80, rued = 4, mt=("",0) ):
        # llamo a la clase anterior vehiculo
        vehiculo.__init__(self,c0,comb0,cil,tan, mt)
        # añado nueva característica
        self.ruedas = rued
        return
    
    def __str__(self):
        car = "Vehículo de " + str(self.ruedas) + " ruedas, " + self.combustible
        car += ", color " + self.color + " y cilindrada " + str(self.cilindrada)+"."
        car += "\nDe motor: " + str(self.motor) + "."
        car += "\nLe quedan en el tanque "+ str(self.tanque) + " litros."
        return car
    
class moto(vehiculo):
    
    def __init__(self, c0="rojo", comb0="diesel", cil=200, tan=80, rued = 2, mt=("",0) ):
        # llamo a la clase anterior vehiculo
        vehiculo.__init__(self,c0,comb0,cil,tan,mt)
        # añado nueva característica
        self.ruedas = rued
        return
    
    def __str__(self):
        car = "Vehículo de " + str(self.ruedas) + " ruedas, " + self.combustible
        car += ", color " + self.color + " y cilindrada " + str(self.cilindrada)+"."
        car += "\nDe motor: " + str(self.motor) + "."
        car += "\nLe quedan en el tanque "+ str(self.tanque) + " litros."
        return car
        

# =========================================
# Programa Principal
# =========================================

# Dos motores
mt1 = motor("RENAULT",12)
mt2 = motor("HONDA",6)

# crear dos vehículos distintos, con consideraciones iniciales
c1 = coche("negro","gasolina",500,120,4,mt1)
m1 = moto("amarilla","diesel",900,180,2,mt2)

# Imprime información de los dos vehículos
print c1
print m1

# Crea un vehículo usando herencia para definir parte de sus caraterísticas
m2 = moto( "azul", "gasolina", 400, 200, 2,mt2)
# lo imprime
print m2

print "\n"

# Hago andar el vehículo 2, 10 veces.
for i in range(10):
    m2.andar()
print "He movido la moto 10 veces:\n", m2, "\n"
# Le lleno el tanque
m2.repostar()
print "Le he llenado el tanque:\n", m2, "\n"

# Gripo el motor de la segunda moto
m2.motor.gripar()
print "¿Qué le ha pasado al motor de la moto?: ",m2.motor    
= = =

No hay comentarios:

Publicar un comentario