Menu iconMenu icon
Python Programming Unlocked for Beginners

Chapter 8: Object-Oriented Programming

8.3: Herencia

Herencia es una de las características más poderosas de la programación orientada a objetos, y puede mejorar enormemente la eficiencia de tu código. Al permitir que una clase herede los atributos y métodos de otra clase, puedes crear nuevas clases que se basan en clases existentes, lo que puede ahorrar mucho tiempo y esfuerzo.

Ejemplo: Imagina que tienes una clase que representa un automóvil, con atributos como marca, modelo y año, y métodos como encender el motor y acelerar. Ahora, supongamos que deseas crear una nueva clase para un tipo específico de automóvil, como un automóvil deportivo. En lugar de comenzar desde cero y definir todos los atributos y métodos para el automóvil deportivo, puedes simplemente crear una nueva clase que herede de la clase de automóvil y luego agregar o modificar los atributos y métodos según sea necesario. De esta manera, puedes reutilizar gran parte del código que ya escribiste para la clase de automóvil y solo enfocarte en los cambios que son específicos del automóvil deportivo.

Implementación en Python: En Python, la herencia se implementa definiendo una nueva clase que toma la clase padre (base) como argumento. La nueva clase se llama clase hija (derivada) y la clase de la que hereda se llama clase padre (base). Al usar la herencia, puedes crear jerarquías de clases complejas que pueden ayudarte a organizar tu código y hacerlo más modular y reutilizable.

Ejemplo de Herencia: Considera una clase base llamada Animal que representa un animal genérico, con atributos como nameage y un método llamado speak():

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        print(f"{self.name} makes a noise")

Ahora, queremos crear una clase Dog que represente a un perro, que es un tipo específico de animal. En lugar de redefinir todos los atributos y métodos de la clase Animal, podemos heredarlos usando la herencia:

class Dog(Animal):
    def __init__(self, name, age, breed):
        super().__init__(name, age)
        self.breed = breed

    def speak(self):
        print(f"{self.name} barks")

En este ejemplo, definimos la clase Dog y heredamos de la clase Animal. Usamos la función super() para llamar al método __init__ de la clase padre para inicializar los atributos name y age. También agregamos un nuevo atributo, breed, específico de la clase Dog.

También sobrescribimos el método speak() de la clase Animal para adaptarlo mejor al comportamiento de un perro. Esto se llama sobrecarga de métodos y es una práctica común cuando se usa la herencia.

Ahora, cuando creamos un objeto Perro, tendrá acceso tanto a los atributos y métodos de la clase Animal, como a cualquier nuevo atributo o método que definamos en la clase Perro.

dog1 = Dog("Max", 3, "Labrador")
print(dog1.name)  # Output: Max
print(dog1.age)  # Output: 3
print(dog1.breed)  # Output: Labrador
dog1.speak()  # Output: Max barks

La herencia es un concepto fundamental en la programación orientada a objetos que permite la creación de clases más especializadas. Facilita la reutilización de código de clases más generales al mismo tiempo que brinda la capacidad de personalizar o extender su comportamiento según sea necesario. Esto convierte a la herencia en una herramienta poderosa para que los desarrolladores creen soluciones de software eficientes y efectivas.

Al utilizar la herencia, los desarrolladores pueden crear una jerarquía de clases que comparten características comunes, lo que permite la implementación de sistemas complejos. Además, la herencia ayuda a reducir la complejidad del código, haciéndolo más fácil de mantener y actualizar con el tiempo. Por lo tanto, es esencial tener una sólida comprensión de la herencia al desarrollar aplicaciones de software, ya que puede mejorar enormemente la calidad general del código y hacerlo más escalable.

Ejercicio 8.3.1: Herencia Simple

Implementa una jerarquía de clases para diferentes tipos de vehículos, utilizando herencia.

Instrucciones:

  1. Crea una clase base llamada Vehicle con los atributos make, model y year.
  2. Define un método vehicle_info() que imprima la marca, modelo y año del vehículo.
  3. Crea una clase Car que herede de Vehicle y tenga un atributo adicional doors.
  4. Crea una clase Motorcycle que herede de Vehicle y tenga un atributo adicional type.
  5. Crea instancias de Car y Motorcycle, y llama al método vehicle_info() para cada una.

Solución:

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def vehicle_info(self):
        print(f"{self.year} {self.make} {self.model}")

class Car(Vehicle):
    def __init__(self, make, model, year, doors):
        super().__init__(make, model, year)
        self.doors = doors

class Motorcycle(Vehicle):
    def __init__(self, make, model, year, bike_type):
        super().__init__(make, model, year)
        self.type = bike_type

car1 = Car("Toyota", "Camry", 2020, 4)
car1.vehicle_info()  # Output: 2020 Toyota Camry

motorcycle1 = Motorcycle("Yamaha", "R1", 2019, "Sport")
motorcycle1.vehicle_info()  # Output: 2019 Yamaha R1

Ejercicio 8.3.2: Herencia y Sobrecarga de Métodos

Crea clases que representen diferentes tipos de cuentas bancarias, utilizando herencia y sobrecarga de métodos.

Instrucciones:

  1. Crea una clase base llamada BankAccount con los atributos balance y un método deposit().
  2. Define un método withdraw() que compruebe si la cantidad a retirar es menor o igual al saldo, y actualice el saldo en consecuencia.
  3. Crea una clase SavingsAccount que herede de BankAccount y tenga un atributo adicional interest_rate.
  4. Sobrecarga el método withdraw() en SavingsAccount para incluir una tarifa de retiro del 1% de la cantidad retirada.
  5. Crea instancias de BankAccount y SavingsAccount, y prueba los métodos deposit() y withdraw().

Solución:

class BankAccount:
    def __init__(self):
        self.balance = 0

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
        else:
            print("Insufficient funds")

class SavingsAccount(BankAccount):
    def __init__(self, interest_rate):
        super().__init__()
        self.interest_rate = interest_rate

    def withdraw(self, amount):
        fee = amount * 0.01
        if amount + fee <= self.balance:
            self.balance -= (amount + fee)
        else:
            print("Insufficient funds")

account1 = BankAccount()
account1.deposit(100)
account1.withdraw(50)
print(account1.balance)  # Output: 50

savings1 = SavingsAccount(0.02)
savings1.deposit(100)
savings1.withdraw(50)
print(savings1.balance)  # Output: 49.5

Ejercicio 8.3.3: Herencia Múltiple

Implementa una jerarquía de clases utilizando herencia múltiple.

Instrucciones:

  1. Crea una clase Person con los atributos first_name y last_name.
  2. Crea una clase Employee que herede de Person y tenga un atributo adicional employee_id.
  3. Crea una clase Student que herede de Person y tenga un atributo adicional student_id.
  4. Crea una clase TeachingAssistant que herede de ambas, Employee y Student.
  5. Define un método get_info() para cada clase que devuelva una cadena con la información de la persona.
  6. Crea una instancia de TeachingAssistant y llama al método get_info().

Solución:

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def get_info(self):
        return f"{self.first_name} {self.last_name}"

class Employee(Person):
    def __init__(self, first_name, last_name, employee_id):
        super().__init__(first_name, last_name)
        self.employee_id = employee_id

    def get_info(self):
        return f"{super().get_info()}, Employee ID: {self.employee_id}"

class Student(Person):
    def __init__(self, first_name, last_name, student_id):
        super().__init__(first_name, last_name)
        self.student_id = student_id

    def get_info(self):
        return f"{super().get_info()}, Student ID: {self.student_id}"

class TeachingAssistant(Employee, Student):
    def __init__(self, first_name, last_name, employee_id, student_id):
        Employee.__init__(self, first_name, last_name, employee_id)
        Student.__init__(self, first_name, last_name, student_id)

    def get_info(self):
        return f"{super().get_info()}, Employee ID: {self.employee_id}, Student ID: {self.student_id}"

ta1 = TeachingAssistant("John", "Doe", 1001, 2001)
print(ta1.get_info())  # Output: John Doe, Employee ID: 1001, Student ID: 2001

8.3: Herencia

Herencia es una de las características más poderosas de la programación orientada a objetos, y puede mejorar enormemente la eficiencia de tu código. Al permitir que una clase herede los atributos y métodos de otra clase, puedes crear nuevas clases que se basan en clases existentes, lo que puede ahorrar mucho tiempo y esfuerzo.

Ejemplo: Imagina que tienes una clase que representa un automóvil, con atributos como marca, modelo y año, y métodos como encender el motor y acelerar. Ahora, supongamos que deseas crear una nueva clase para un tipo específico de automóvil, como un automóvil deportivo. En lugar de comenzar desde cero y definir todos los atributos y métodos para el automóvil deportivo, puedes simplemente crear una nueva clase que herede de la clase de automóvil y luego agregar o modificar los atributos y métodos según sea necesario. De esta manera, puedes reutilizar gran parte del código que ya escribiste para la clase de automóvil y solo enfocarte en los cambios que son específicos del automóvil deportivo.

Implementación en Python: En Python, la herencia se implementa definiendo una nueva clase que toma la clase padre (base) como argumento. La nueva clase se llama clase hija (derivada) y la clase de la que hereda se llama clase padre (base). Al usar la herencia, puedes crear jerarquías de clases complejas que pueden ayudarte a organizar tu código y hacerlo más modular y reutilizable.

Ejemplo de Herencia: Considera una clase base llamada Animal que representa un animal genérico, con atributos como nameage y un método llamado speak():

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        print(f"{self.name} makes a noise")

Ahora, queremos crear una clase Dog que represente a un perro, que es un tipo específico de animal. En lugar de redefinir todos los atributos y métodos de la clase Animal, podemos heredarlos usando la herencia:

class Dog(Animal):
    def __init__(self, name, age, breed):
        super().__init__(name, age)
        self.breed = breed

    def speak(self):
        print(f"{self.name} barks")

En este ejemplo, definimos la clase Dog y heredamos de la clase Animal. Usamos la función super() para llamar al método __init__ de la clase padre para inicializar los atributos name y age. También agregamos un nuevo atributo, breed, específico de la clase Dog.

También sobrescribimos el método speak() de la clase Animal para adaptarlo mejor al comportamiento de un perro. Esto se llama sobrecarga de métodos y es una práctica común cuando se usa la herencia.

Ahora, cuando creamos un objeto Perro, tendrá acceso tanto a los atributos y métodos de la clase Animal, como a cualquier nuevo atributo o método que definamos en la clase Perro.

dog1 = Dog("Max", 3, "Labrador")
print(dog1.name)  # Output: Max
print(dog1.age)  # Output: 3
print(dog1.breed)  # Output: Labrador
dog1.speak()  # Output: Max barks

La herencia es un concepto fundamental en la programación orientada a objetos que permite la creación de clases más especializadas. Facilita la reutilización de código de clases más generales al mismo tiempo que brinda la capacidad de personalizar o extender su comportamiento según sea necesario. Esto convierte a la herencia en una herramienta poderosa para que los desarrolladores creen soluciones de software eficientes y efectivas.

Al utilizar la herencia, los desarrolladores pueden crear una jerarquía de clases que comparten características comunes, lo que permite la implementación de sistemas complejos. Además, la herencia ayuda a reducir la complejidad del código, haciéndolo más fácil de mantener y actualizar con el tiempo. Por lo tanto, es esencial tener una sólida comprensión de la herencia al desarrollar aplicaciones de software, ya que puede mejorar enormemente la calidad general del código y hacerlo más escalable.

Ejercicio 8.3.1: Herencia Simple

Implementa una jerarquía de clases para diferentes tipos de vehículos, utilizando herencia.

Instrucciones:

  1. Crea una clase base llamada Vehicle con los atributos make, model y year.
  2. Define un método vehicle_info() que imprima la marca, modelo y año del vehículo.
  3. Crea una clase Car que herede de Vehicle y tenga un atributo adicional doors.
  4. Crea una clase Motorcycle que herede de Vehicle y tenga un atributo adicional type.
  5. Crea instancias de Car y Motorcycle, y llama al método vehicle_info() para cada una.

Solución:

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def vehicle_info(self):
        print(f"{self.year} {self.make} {self.model}")

class Car(Vehicle):
    def __init__(self, make, model, year, doors):
        super().__init__(make, model, year)
        self.doors = doors

class Motorcycle(Vehicle):
    def __init__(self, make, model, year, bike_type):
        super().__init__(make, model, year)
        self.type = bike_type

car1 = Car("Toyota", "Camry", 2020, 4)
car1.vehicle_info()  # Output: 2020 Toyota Camry

motorcycle1 = Motorcycle("Yamaha", "R1", 2019, "Sport")
motorcycle1.vehicle_info()  # Output: 2019 Yamaha R1

Ejercicio 8.3.2: Herencia y Sobrecarga de Métodos

Crea clases que representen diferentes tipos de cuentas bancarias, utilizando herencia y sobrecarga de métodos.

Instrucciones:

  1. Crea una clase base llamada BankAccount con los atributos balance y un método deposit().
  2. Define un método withdraw() que compruebe si la cantidad a retirar es menor o igual al saldo, y actualice el saldo en consecuencia.
  3. Crea una clase SavingsAccount que herede de BankAccount y tenga un atributo adicional interest_rate.
  4. Sobrecarga el método withdraw() en SavingsAccount para incluir una tarifa de retiro del 1% de la cantidad retirada.
  5. Crea instancias de BankAccount y SavingsAccount, y prueba los métodos deposit() y withdraw().

Solución:

class BankAccount:
    def __init__(self):
        self.balance = 0

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
        else:
            print("Insufficient funds")

class SavingsAccount(BankAccount):
    def __init__(self, interest_rate):
        super().__init__()
        self.interest_rate = interest_rate

    def withdraw(self, amount):
        fee = amount * 0.01
        if amount + fee <= self.balance:
            self.balance -= (amount + fee)
        else:
            print("Insufficient funds")

account1 = BankAccount()
account1.deposit(100)
account1.withdraw(50)
print(account1.balance)  # Output: 50

savings1 = SavingsAccount(0.02)
savings1.deposit(100)
savings1.withdraw(50)
print(savings1.balance)  # Output: 49.5

Ejercicio 8.3.3: Herencia Múltiple

Implementa una jerarquía de clases utilizando herencia múltiple.

Instrucciones:

  1. Crea una clase Person con los atributos first_name y last_name.
  2. Crea una clase Employee que herede de Person y tenga un atributo adicional employee_id.
  3. Crea una clase Student que herede de Person y tenga un atributo adicional student_id.
  4. Crea una clase TeachingAssistant que herede de ambas, Employee y Student.
  5. Define un método get_info() para cada clase que devuelva una cadena con la información de la persona.
  6. Crea una instancia de TeachingAssistant y llama al método get_info().

Solución:

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def get_info(self):
        return f"{self.first_name} {self.last_name}"

class Employee(Person):
    def __init__(self, first_name, last_name, employee_id):
        super().__init__(first_name, last_name)
        self.employee_id = employee_id

    def get_info(self):
        return f"{super().get_info()}, Employee ID: {self.employee_id}"

class Student(Person):
    def __init__(self, first_name, last_name, student_id):
        super().__init__(first_name, last_name)
        self.student_id = student_id

    def get_info(self):
        return f"{super().get_info()}, Student ID: {self.student_id}"

class TeachingAssistant(Employee, Student):
    def __init__(self, first_name, last_name, employee_id, student_id):
        Employee.__init__(self, first_name, last_name, employee_id)
        Student.__init__(self, first_name, last_name, student_id)

    def get_info(self):
        return f"{super().get_info()}, Employee ID: {self.employee_id}, Student ID: {self.student_id}"

ta1 = TeachingAssistant("John", "Doe", 1001, 2001)
print(ta1.get_info())  # Output: John Doe, Employee ID: 1001, Student ID: 2001

8.3: Herencia

Herencia es una de las características más poderosas de la programación orientada a objetos, y puede mejorar enormemente la eficiencia de tu código. Al permitir que una clase herede los atributos y métodos de otra clase, puedes crear nuevas clases que se basan en clases existentes, lo que puede ahorrar mucho tiempo y esfuerzo.

Ejemplo: Imagina que tienes una clase que representa un automóvil, con atributos como marca, modelo y año, y métodos como encender el motor y acelerar. Ahora, supongamos que deseas crear una nueva clase para un tipo específico de automóvil, como un automóvil deportivo. En lugar de comenzar desde cero y definir todos los atributos y métodos para el automóvil deportivo, puedes simplemente crear una nueva clase que herede de la clase de automóvil y luego agregar o modificar los atributos y métodos según sea necesario. De esta manera, puedes reutilizar gran parte del código que ya escribiste para la clase de automóvil y solo enfocarte en los cambios que son específicos del automóvil deportivo.

Implementación en Python: En Python, la herencia se implementa definiendo una nueva clase que toma la clase padre (base) como argumento. La nueva clase se llama clase hija (derivada) y la clase de la que hereda se llama clase padre (base). Al usar la herencia, puedes crear jerarquías de clases complejas que pueden ayudarte a organizar tu código y hacerlo más modular y reutilizable.

Ejemplo de Herencia: Considera una clase base llamada Animal que representa un animal genérico, con atributos como nameage y un método llamado speak():

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        print(f"{self.name} makes a noise")

Ahora, queremos crear una clase Dog que represente a un perro, que es un tipo específico de animal. En lugar de redefinir todos los atributos y métodos de la clase Animal, podemos heredarlos usando la herencia:

class Dog(Animal):
    def __init__(self, name, age, breed):
        super().__init__(name, age)
        self.breed = breed

    def speak(self):
        print(f"{self.name} barks")

En este ejemplo, definimos la clase Dog y heredamos de la clase Animal. Usamos la función super() para llamar al método __init__ de la clase padre para inicializar los atributos name y age. También agregamos un nuevo atributo, breed, específico de la clase Dog.

También sobrescribimos el método speak() de la clase Animal para adaptarlo mejor al comportamiento de un perro. Esto se llama sobrecarga de métodos y es una práctica común cuando se usa la herencia.

Ahora, cuando creamos un objeto Perro, tendrá acceso tanto a los atributos y métodos de la clase Animal, como a cualquier nuevo atributo o método que definamos en la clase Perro.

dog1 = Dog("Max", 3, "Labrador")
print(dog1.name)  # Output: Max
print(dog1.age)  # Output: 3
print(dog1.breed)  # Output: Labrador
dog1.speak()  # Output: Max barks

La herencia es un concepto fundamental en la programación orientada a objetos que permite la creación de clases más especializadas. Facilita la reutilización de código de clases más generales al mismo tiempo que brinda la capacidad de personalizar o extender su comportamiento según sea necesario. Esto convierte a la herencia en una herramienta poderosa para que los desarrolladores creen soluciones de software eficientes y efectivas.

Al utilizar la herencia, los desarrolladores pueden crear una jerarquía de clases que comparten características comunes, lo que permite la implementación de sistemas complejos. Además, la herencia ayuda a reducir la complejidad del código, haciéndolo más fácil de mantener y actualizar con el tiempo. Por lo tanto, es esencial tener una sólida comprensión de la herencia al desarrollar aplicaciones de software, ya que puede mejorar enormemente la calidad general del código y hacerlo más escalable.

Ejercicio 8.3.1: Herencia Simple

Implementa una jerarquía de clases para diferentes tipos de vehículos, utilizando herencia.

Instrucciones:

  1. Crea una clase base llamada Vehicle con los atributos make, model y year.
  2. Define un método vehicle_info() que imprima la marca, modelo y año del vehículo.
  3. Crea una clase Car que herede de Vehicle y tenga un atributo adicional doors.
  4. Crea una clase Motorcycle que herede de Vehicle y tenga un atributo adicional type.
  5. Crea instancias de Car y Motorcycle, y llama al método vehicle_info() para cada una.

Solución:

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def vehicle_info(self):
        print(f"{self.year} {self.make} {self.model}")

class Car(Vehicle):
    def __init__(self, make, model, year, doors):
        super().__init__(make, model, year)
        self.doors = doors

class Motorcycle(Vehicle):
    def __init__(self, make, model, year, bike_type):
        super().__init__(make, model, year)
        self.type = bike_type

car1 = Car("Toyota", "Camry", 2020, 4)
car1.vehicle_info()  # Output: 2020 Toyota Camry

motorcycle1 = Motorcycle("Yamaha", "R1", 2019, "Sport")
motorcycle1.vehicle_info()  # Output: 2019 Yamaha R1

Ejercicio 8.3.2: Herencia y Sobrecarga de Métodos

Crea clases que representen diferentes tipos de cuentas bancarias, utilizando herencia y sobrecarga de métodos.

Instrucciones:

  1. Crea una clase base llamada BankAccount con los atributos balance y un método deposit().
  2. Define un método withdraw() que compruebe si la cantidad a retirar es menor o igual al saldo, y actualice el saldo en consecuencia.
  3. Crea una clase SavingsAccount que herede de BankAccount y tenga un atributo adicional interest_rate.
  4. Sobrecarga el método withdraw() en SavingsAccount para incluir una tarifa de retiro del 1% de la cantidad retirada.
  5. Crea instancias de BankAccount y SavingsAccount, y prueba los métodos deposit() y withdraw().

Solución:

class BankAccount:
    def __init__(self):
        self.balance = 0

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
        else:
            print("Insufficient funds")

class SavingsAccount(BankAccount):
    def __init__(self, interest_rate):
        super().__init__()
        self.interest_rate = interest_rate

    def withdraw(self, amount):
        fee = amount * 0.01
        if amount + fee <= self.balance:
            self.balance -= (amount + fee)
        else:
            print("Insufficient funds")

account1 = BankAccount()
account1.deposit(100)
account1.withdraw(50)
print(account1.balance)  # Output: 50

savings1 = SavingsAccount(0.02)
savings1.deposit(100)
savings1.withdraw(50)
print(savings1.balance)  # Output: 49.5

Ejercicio 8.3.3: Herencia Múltiple

Implementa una jerarquía de clases utilizando herencia múltiple.

Instrucciones:

  1. Crea una clase Person con los atributos first_name y last_name.
  2. Crea una clase Employee que herede de Person y tenga un atributo adicional employee_id.
  3. Crea una clase Student que herede de Person y tenga un atributo adicional student_id.
  4. Crea una clase TeachingAssistant que herede de ambas, Employee y Student.
  5. Define un método get_info() para cada clase que devuelva una cadena con la información de la persona.
  6. Crea una instancia de TeachingAssistant y llama al método get_info().

Solución:

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def get_info(self):
        return f"{self.first_name} {self.last_name}"

class Employee(Person):
    def __init__(self, first_name, last_name, employee_id):
        super().__init__(first_name, last_name)
        self.employee_id = employee_id

    def get_info(self):
        return f"{super().get_info()}, Employee ID: {self.employee_id}"

class Student(Person):
    def __init__(self, first_name, last_name, student_id):
        super().__init__(first_name, last_name)
        self.student_id = student_id

    def get_info(self):
        return f"{super().get_info()}, Student ID: {self.student_id}"

class TeachingAssistant(Employee, Student):
    def __init__(self, first_name, last_name, employee_id, student_id):
        Employee.__init__(self, first_name, last_name, employee_id)
        Student.__init__(self, first_name, last_name, student_id)

    def get_info(self):
        return f"{super().get_info()}, Employee ID: {self.employee_id}, Student ID: {self.student_id}"

ta1 = TeachingAssistant("John", "Doe", 1001, 2001)
print(ta1.get_info())  # Output: John Doe, Employee ID: 1001, Student ID: 2001

8.3: Herencia

Herencia es una de las características más poderosas de la programación orientada a objetos, y puede mejorar enormemente la eficiencia de tu código. Al permitir que una clase herede los atributos y métodos de otra clase, puedes crear nuevas clases que se basan en clases existentes, lo que puede ahorrar mucho tiempo y esfuerzo.

Ejemplo: Imagina que tienes una clase que representa un automóvil, con atributos como marca, modelo y año, y métodos como encender el motor y acelerar. Ahora, supongamos que deseas crear una nueva clase para un tipo específico de automóvil, como un automóvil deportivo. En lugar de comenzar desde cero y definir todos los atributos y métodos para el automóvil deportivo, puedes simplemente crear una nueva clase que herede de la clase de automóvil y luego agregar o modificar los atributos y métodos según sea necesario. De esta manera, puedes reutilizar gran parte del código que ya escribiste para la clase de automóvil y solo enfocarte en los cambios que son específicos del automóvil deportivo.

Implementación en Python: En Python, la herencia se implementa definiendo una nueva clase que toma la clase padre (base) como argumento. La nueva clase se llama clase hija (derivada) y la clase de la que hereda se llama clase padre (base). Al usar la herencia, puedes crear jerarquías de clases complejas que pueden ayudarte a organizar tu código y hacerlo más modular y reutilizable.

Ejemplo de Herencia: Considera una clase base llamada Animal que representa un animal genérico, con atributos como nameage y un método llamado speak():

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        print(f"{self.name} makes a noise")

Ahora, queremos crear una clase Dog que represente a un perro, que es un tipo específico de animal. En lugar de redefinir todos los atributos y métodos de la clase Animal, podemos heredarlos usando la herencia:

class Dog(Animal):
    def __init__(self, name, age, breed):
        super().__init__(name, age)
        self.breed = breed

    def speak(self):
        print(f"{self.name} barks")

En este ejemplo, definimos la clase Dog y heredamos de la clase Animal. Usamos la función super() para llamar al método __init__ de la clase padre para inicializar los atributos name y age. También agregamos un nuevo atributo, breed, específico de la clase Dog.

También sobrescribimos el método speak() de la clase Animal para adaptarlo mejor al comportamiento de un perro. Esto se llama sobrecarga de métodos y es una práctica común cuando se usa la herencia.

Ahora, cuando creamos un objeto Perro, tendrá acceso tanto a los atributos y métodos de la clase Animal, como a cualquier nuevo atributo o método que definamos en la clase Perro.

dog1 = Dog("Max", 3, "Labrador")
print(dog1.name)  # Output: Max
print(dog1.age)  # Output: 3
print(dog1.breed)  # Output: Labrador
dog1.speak()  # Output: Max barks

La herencia es un concepto fundamental en la programación orientada a objetos que permite la creación de clases más especializadas. Facilita la reutilización de código de clases más generales al mismo tiempo que brinda la capacidad de personalizar o extender su comportamiento según sea necesario. Esto convierte a la herencia en una herramienta poderosa para que los desarrolladores creen soluciones de software eficientes y efectivas.

Al utilizar la herencia, los desarrolladores pueden crear una jerarquía de clases que comparten características comunes, lo que permite la implementación de sistemas complejos. Además, la herencia ayuda a reducir la complejidad del código, haciéndolo más fácil de mantener y actualizar con el tiempo. Por lo tanto, es esencial tener una sólida comprensión de la herencia al desarrollar aplicaciones de software, ya que puede mejorar enormemente la calidad general del código y hacerlo más escalable.

Ejercicio 8.3.1: Herencia Simple

Implementa una jerarquía de clases para diferentes tipos de vehículos, utilizando herencia.

Instrucciones:

  1. Crea una clase base llamada Vehicle con los atributos make, model y year.
  2. Define un método vehicle_info() que imprima la marca, modelo y año del vehículo.
  3. Crea una clase Car que herede de Vehicle y tenga un atributo adicional doors.
  4. Crea una clase Motorcycle que herede de Vehicle y tenga un atributo adicional type.
  5. Crea instancias de Car y Motorcycle, y llama al método vehicle_info() para cada una.

Solución:

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def vehicle_info(self):
        print(f"{self.year} {self.make} {self.model}")

class Car(Vehicle):
    def __init__(self, make, model, year, doors):
        super().__init__(make, model, year)
        self.doors = doors

class Motorcycle(Vehicle):
    def __init__(self, make, model, year, bike_type):
        super().__init__(make, model, year)
        self.type = bike_type

car1 = Car("Toyota", "Camry", 2020, 4)
car1.vehicle_info()  # Output: 2020 Toyota Camry

motorcycle1 = Motorcycle("Yamaha", "R1", 2019, "Sport")
motorcycle1.vehicle_info()  # Output: 2019 Yamaha R1

Ejercicio 8.3.2: Herencia y Sobrecarga de Métodos

Crea clases que representen diferentes tipos de cuentas bancarias, utilizando herencia y sobrecarga de métodos.

Instrucciones:

  1. Crea una clase base llamada BankAccount con los atributos balance y un método deposit().
  2. Define un método withdraw() que compruebe si la cantidad a retirar es menor o igual al saldo, y actualice el saldo en consecuencia.
  3. Crea una clase SavingsAccount que herede de BankAccount y tenga un atributo adicional interest_rate.
  4. Sobrecarga el método withdraw() en SavingsAccount para incluir una tarifa de retiro del 1% de la cantidad retirada.
  5. Crea instancias de BankAccount y SavingsAccount, y prueba los métodos deposit() y withdraw().

Solución:

class BankAccount:
    def __init__(self):
        self.balance = 0

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
        else:
            print("Insufficient funds")

class SavingsAccount(BankAccount):
    def __init__(self, interest_rate):
        super().__init__()
        self.interest_rate = interest_rate

    def withdraw(self, amount):
        fee = amount * 0.01
        if amount + fee <= self.balance:
            self.balance -= (amount + fee)
        else:
            print("Insufficient funds")

account1 = BankAccount()
account1.deposit(100)
account1.withdraw(50)
print(account1.balance)  # Output: 50

savings1 = SavingsAccount(0.02)
savings1.deposit(100)
savings1.withdraw(50)
print(savings1.balance)  # Output: 49.5

Ejercicio 8.3.3: Herencia Múltiple

Implementa una jerarquía de clases utilizando herencia múltiple.

Instrucciones:

  1. Crea una clase Person con los atributos first_name y last_name.
  2. Crea una clase Employee que herede de Person y tenga un atributo adicional employee_id.
  3. Crea una clase Student que herede de Person y tenga un atributo adicional student_id.
  4. Crea una clase TeachingAssistant que herede de ambas, Employee y Student.
  5. Define un método get_info() para cada clase que devuelva una cadena con la información de la persona.
  6. Crea una instancia de TeachingAssistant y llama al método get_info().

Solución:

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def get_info(self):
        return f"{self.first_name} {self.last_name}"

class Employee(Person):
    def __init__(self, first_name, last_name, employee_id):
        super().__init__(first_name, last_name)
        self.employee_id = employee_id

    def get_info(self):
        return f"{super().get_info()}, Employee ID: {self.employee_id}"

class Student(Person):
    def __init__(self, first_name, last_name, student_id):
        super().__init__(first_name, last_name)
        self.student_id = student_id

    def get_info(self):
        return f"{super().get_info()}, Student ID: {self.student_id}"

class TeachingAssistant(Employee, Student):
    def __init__(self, first_name, last_name, employee_id, student_id):
        Employee.__init__(self, first_name, last_name, employee_id)
        Student.__init__(self, first_name, last_name, student_id)

    def get_info(self):
        return f"{super().get_info()}, Employee ID: {self.employee_id}, Student ID: {self.student_id}"

ta1 = TeachingAssistant("John", "Doe", 1001, 2001)
print(ta1.get_info())  # Output: John Doe, Employee ID: 1001, Student ID: 2001