Saltar la navegación

4.2. Herramientas de a bordo

1. Herencia, pero con diferencias.

Imagen con distintas tazas de porcelana antigua con sus platos a juegoHemos conocido que este nuevo paradigma de programación POO se apoya en una imitación de la vida diaria con el uso de objetos. Ahora vais a ver que existe una semejanza más que nos aportará ¡la gran ventaja de la reutilización de código!: La herencia, una de las características más potentes de la programación orientada a objetos. Pero primero, vamos a conocer la importancia de esta capacidad.

Para realizar este ejercicio en vuestro grupo de trabajo, seguid los siguientes pasos:

Paso 1

Codificad cada uno en su Pycharm una de las siguientes clases especificadas (es obligatorio que al menos uno de vosotros codifique la clase Vehículo):

  • Clase Vehículo --> Atributos: color, ruedas. Métodos: __str__ que muestre los atributos, cambia_color().
  • Clase Coche --> Atributos: color, ruedas, velocidad. Métodos: __str__ que muestre los atributos, cambia_color(), cambia_velocidad().
  • Clase Bicicleta --> Atributos: color, ruedas, tipo (urbana/carrera/montaña). Métodos: __str__ que muestre los atributos, cambia_color(), cambia_tipo().
  • Clase Furgoneta --> Atributos: color, ruedas, velocidad, carga. Métodos: __str__ que muestre los atributos, cambia_color(), cambia_velocidad(), cambia_carga().
  • Clase Bici_electrica --> Atributos: color, ruedas, tipo (urbana/carrera/montaña), potencia_motor. Métodos: __str__ que muestre los atributos, cambia_color(), cambia_tipo(), cambia_potencia().

Paso 2

Poned en común el código desarrollado y comparad y comprobar el código añadido en cada método. ¡Veréis que se repite gran parte de este código!

Paso 3

Visualizad el siguiente esquema que os presentamos y volved a la comparación resultante en el Paso 2. A la vista de los resultados y del esquema que aquí se os presenta:

Árbol con vehículo en cúspide del que cuelgan Coche (del que cuelga Furgoneta) y Bicicleta (de la que cuelga Bici_electrica).

  1. ¿Qué veis en común entre todas las clases definidas en vuestro código?¿Qué trozos de código están repetidos?
  2. Analizando el esquema: ¿Aglutina la clase superior las redundancias de las clases inferiores?
  3. ¿Cómo pensáis que podríamos evitar la redundancia de codificación en cada clase aquí definida?

2. ¿Qué va antes?: Diseña la jerarquía

Una de las características más potentes de la programación orientada a objetos es la herencia, que permite a una clase de heredar los atributos y métodos de otra. La nueva clase se conoce como subclase (clase hija) y hereda los atributos y métodos de la clase original que se conoce como superclase (clase madre).

Imagen de tiendaPara aprender a trabajar utilizando esta característica vamos a diseñar una estructura para una tienda que vende tres tipos de productos: decoración, alimentos y libros. Como verás, todos los productos de la tienda tienen una serie de atributos comunes, como la referencia, el nombre, el precio... pero algunos van a ser específicos de cada tipo. Imaginemos que codificamos una clase para contener a cada producto, sea del tipo que sea. Obtendremos un código como el que aparece a la izquierda, o parecido:

Codificación de la clase

class Producto:
    def __init__(self, referencia, tipo, nombre, 
                 pvp,  descripcion, productor=None, 
                 distribuidor=None, isbn=None, autor=None):
        self.referencia = referencia
        self.tipo = tipo
        self.nombre = nombre
        self.pvp = pvp
        self.descripcion = descripcion
        self.productor = productor
        self.distribuidor = distribuidor
        self.isbn = isbn
        self.autor = autor  

Pruébalo con:

>>decoracion= Producto('000A','ADORNO','Vaso Adornado',15, 
'Vaso de porcelana con dibujos')
>>print(decoracion)
>>print(decoracon.tipo)
Código de Héctor Costa Guzmán con Licencia CC BY 4.0

Como puedes apreciar, trabajar con tantos atributos para todos los objetos es muy poco recomendable para una programación clara. Veamos cómo podemos utilizar la herencia para mejorarlo:

Crea la Superclase

Vamos a crear en primer lugar la superclase con los atributos y metodos comunes. La construirás como hemos trabajado en el punto anterior del tema utilizando un constructor.

Codifica entonces la clase Producto con:

  • Atributos: estarán en el constructor (__init__) y serán la referencia, nombre, precio y descripcion.
  • Método: Constará de un método para mostrar por pantalla el contenido del objeto, es decir, los valores de estos atributos anteriores (__str__).

Crea las Subclases

Para crear un clase a partir de otra existente se utiliza la misma sintaxis que para definir una clase, pero poniendo detrás del nombre de la clase entre paréntesis los nombres de las clases madre de las que hereda:

class Nombre_subclase(Nombre_superclase)

De este modo debes crear las clases:

  • Clase Decoracion: sin atributos ni métodos nuevos. Solo para existir como subclase. Utiliza para ello la palabra clave pass.
  • Clase Alimento: con los nuevos atributos productor y distribuidor. Tendrás que modificar el método __str__ para mostrar por pantalla los nuevos valores.
  • Clase Libro: con los nuevos atributos isbn y autor. De  nuevo, tendrás que modificar el método __str__ para mostrar por pantalla los nuevos valores.

Crea después el código necesario para obtener las salidas por pantalla:

REFERENCIA      2034
NOMBRE          Vaso adornado
PVP             15
DESCRIPCIÓN     Vaso de porcelana

REFERENCIA      2035
NOMBRE          Botella de Aceite de Oliva
PVP             5
DESCRIPCIÓN     250 ML
PRODUCTOR       La Sagra
DISTRIBUIDOR    Distribuciones SA

REFERENCIA      2036
NOMBRE          Cocina Mediterránea
PVP             9
DESCRIPCIÓN     Recetas sanas y ricas
ISBN            0-123456-78-9
AUTOR           Karlos Arguiñano

Propagación del objeto

Debido a la herencia, cualquier objeto creado a partir de una clase es una instancia de esa clase, pero también lo es de las superclases que son ancestros de esa clase en la jerarquía de clases.

El siguiente comando permite averiguar si un objeto es instancia de una clase:
isinstance(objeto, clase): Devuelve True si el objeto es una instancia de esa clase y False en caso contrario.

Para ver su utilidad:

  1. Vas a crear una lista con nuestros tres productos anteriores de subclases distintas.
  2. Recorre la lista usando un bucle for e imprimiendo por pantalla la referencia, el nombre y el productor o el isbn según se trate de un alimento o un libro.

Lumen dice Tips para la codificación

  1. La palabra resevada pass se usa para utilizar el comportamiento de una superclase sin definir nada en la subclase. Así, la clase Decoracion sería:
    class Decoracion(Producto):
        pass
  2. Al mostrar por pantalla los atributos de un objeto, si un objeto no tiene el atributo al que queremos acceder nos dará error:
    AttributeError: 'Adorno' object has no attribute 'autor'

    Entonces, deberás hacer una comprobación con la función isinstance() para determinar si una instancia es de una determinado clase y así mostrar unos atributos u otros, usando para ello la estructura if.

  3. También puedes usar el atributo especial de clase name para recuperar el nombre de la clase de un objeto:

    type(objeto).__name__

  4. El inconveniente más evidente de ir creando los constructores de las subclases es que tenemos que volver a escribir el código __init__ de la superclase y luego el específico de la subclase.
    Para evitarnos escribir código innecesario, podemos utilizar un truco que consiste en llamar el método de la superclase y luego simplemente escribir el código de la subclase:

    class Alimento(Producto):
        def __init__(self,referencia,nombre,pvp,descripcion,productor, distribuidor):
            Producto.__init__(self,referencia,nombre,pvp,descripcion)
            self.productor = productor
            self.distribuidor = distribuidor

Clavis dice ¡Además!

En el ejercicio que acabas de programar has aprendido, mediante la herencia, a evitar la repetición de código y por tanto los programas son más fáciles de mantener. Acabas de utilizar la principal ventaja de la POO.

3. Be water my friend...

A continuación vamos a ver cómo la POO nos ofrece propiedades gracias a las cuales los objetos pueden cambiar su forma, pero no su naturaleza: se adaptan a diferentes comportamientos gracias a la flexibilidad que ofrece la POO.

Mira lo que vas a aprender:

Aquí puedes descargar el texto del vídeo

Vamos a ver que la herencia produce dos consecuencias en los objetos: el polimorfismo y la sobrecarga. Aquí podrás comprobar qué suponen y cuál es el funcionamiento de estas propiedades. Al principio pueden parecer dos propiedades algo complicadas, pero ¡nada más simple que su funcionamiento!

Polimorfismo

El polimorfismo es una propiedad de la herencia por la que objetos de distintas subclases pueden responder a una misma acción, sin importar la clase a la que pertenezcan.

El polimorfismo es una propiedad de la herencia por la que objetos de distintas subclases pueden responder a una misma acción.
Utilizando los ejemplos anteriores: Imagina que en alguna parte del código de un programa se desea realizar un descuento sobre un producto. Si definiéramos este método dentro de cada subclase, la función esperaría recibir un objeto de esa subclase concreta, y habría que repetir dicho método en tantas subclases como tipos de productos deseásemos en la tienda.
Gracias al polimorfismo, si la función recibe como parámetro un objeto de la superclase dicha función se podrá utilizar para todos los objetos de las subclases derivadas de tal superclase. ¡Prueba esta función de ejemplo que acompaña, haz las llamadas y comprueba si obtienes los resultados propuestos a la derecha!

FUNCIÓN


def rebajar_producto(producto, rebaja):
    producto.pvp=producto.pvp-(producto.pvp/100*rebaja)

LLAMADAS


>>>alimento = Alimento(2035, "Botella de Aceite de 
Oliva", 5, "250 ML","La Molinera","Distribuciones SA") >>>libro = Libro(2036, "Cocina Mediterránea",9, "Recetas
sanas y ricas","0-123456-78-9","Karlos Arguiñano") >>>rebajar_producto(alimento, 10) >>>rebajar_producto(libro, 10)

RESULTADO


REFERENCIA      2035
NOMBRE          Botella de Aceite de Oliva
PVP             4.5
DESCRIPCIÓN     250 ML
PRODUCTOR       La Aceitera
DISTRIBUIDOR    Distribuciones SA

REFERENCIA      2036
NOMBRE          Cocina Mediterránea
PVP             8.1
DESCRIPCIÓN     Recetas sanas y ricas
ISBN            0-123456-78-9
AUTOR           Karlos Arguiñano

Sobrecarga

Los objetos de una subclase heredan los atributos y métodos de la superclase y, por tanto, a priori tienen el mismo comportamiento que los objetos de la clase madre. Pero la subclase puede reescribir los métodos de la superclase de manera que sus objetos presenten un comportamiento distinto. Esto último se conoce como sobrecarga.

De este modo, aunque un objeto de la subclase y otro de la superclase pueden tener un mismo método, al invocar ese método sobre el objeto de la subclase, el comportamiento puede ser distinto a cuando se invoca ese mismo método sobre el objeto de la superclase.

EJEMPLO: De acuerdo a esta propiedad, si queremos aplicar a un producto de clase libro el doble de descuento definido para cualquier otro producto podremos definir en la clase Libro:

class Libro(Producto):
    def __init__(self,referencia,nombre,pvp,descripcion,isbn, autor):
        Producto.__init__(self,referencia,nombre,pvp,descripcion)
        self.isbn = isbn
        self.autor = autor

    def __str__(self):
        return f"REFERENCIA\t {self.referencia}\n" \
               f"NOMBRE\t\t {self.nombre}\n" \
               f"PVP\t\t {self.pvp}\n" \
               f"DESCRIPCIÓN\t {self.descripcion}\n" \
               f"ISBN\t\t {self.isbn}\n" \
               f"AUTOR\t\t {self.autor}\n"

    def rebajar_producto(producto, rebaja):
        producto.pvp = producto.pvp - (producto.pvp/100 * 2* rebaja)
¡Anímate a probarlo en tu Pycharm y comprueba que el descuento es superior en los libros!

4. La práctica hace al maestro en herencia

¡Es la hora de perfeccionar el contructor de nuestra clase Password y crear también nuevas opciones usando la herencia!

Generador de contraseña en el que se indican las condiciones que se exigen para la creación de la contraseña

Adaptamos el constructor previo

Modifica un poco el constructor creado en el punto anterior para nuestra clase del reto Password de manera que reciba de forma opcional un parámetro que indica la longitud de la contraseña a crear. Así:

  1. Si se llama al constructor pasándole dicho parámetro, se crea el atributo long iniciado a dicho valor.
  2. Si al llamar al constructor no se le pasa el parámetro que indica la longitud, entonces el atributo long se inicializará al valor una constante de clase: LONG_DEF=8
  3. A continuación, el constructor debe crear el atributo passwd mediante llamada al método de la clase llamado genera_passwd() que recibirá la longitud con la que debe ser creada dicha contraseña.

Una clase más segura

Crea una nueva clase llamada Strong_password
Esta nueva clase tendrá las mismas características que la clase Password pero generará una contraseña más fuerte y segura. Para ello dispondrá de un método también llamado genera_passwd() cuyo funcionamiento diferirá del anterior en que ahora se aceptarán como caracteres que forman parte de la clave los comprendidos entre los valores ASCII 33 y 125.
Al tener un mayor rango de símbolos la contraseña generada será más resistente a posibles ataques.

Prueba tu programa

Para finalizar, prueba a generar un objeto de la nueva clase _Strong_password_ y muestra por pantalla la contraseña generada.
Haz las dos pruebas:

  • Pasándole una longitud seleccionada por el usuario al contructor.
  • Dejando la longitud constante por defecto.

Responde a la pregunta: ¿Qué propiedad de la herencia de clases has aplicado al definir el método genera_passwd() en la clase hija?

5. Para saber más

Símbolo de ok en una señal de color verde  Si deseas un resumen a grandes rasgos, podemos concluir que la POO ha ofrecido 4 grandes logros al programador:

  • Encapsulación: Ha permitido agrupar datos (llamados atributos) y procedimientos (métodos) en unidades cerradas (objetos) y evitar así manipular los atributos accediendo directamente a ellos, usando, en su lugar,los métodos del objeto para acceder a ellos, dando mayor control sobre lo que se puede y no se puede hacer sobre un objeto.
  • Abstracción: Permite ocultar al usuario de la clase los detalles de implementación de los métodos. Es decir, el usuario sólo necesita saber qué hace un método y con qué parámetros tiene que invocarlo (interfaz), pero no necesita saber cómo lo hace.
  • Herencia: Evitar la duplicación de código en clases con comportamientos similares, definiendo los métodos comunes en una superclase y los métodos particulares en subclases.
  • Sobrecarga y Polimorfismo: Redefinir los métodos de la clase madre en las clases hijas cuando se requiera un comportamiento distinto. Así, un mismo método puede realizar operaciones distintas dependiendo del objeto sobre el que se aplique.