La POO se basa en cuatro principios fundamentales que nos permiten crear sistemas más organizados, mantenibles y reusables: encapsulamiento, herencia, polimorfismo y abstracción.
En esta sección exploraremos los tres primeros principios, mientras que la abstracción se cubrirá con mayor detalle en la siguiente sección.
Estos principios nos permiten:
El encapsulamiento consiste en ocultar los detalles internos de una clase y proporcionar una interfaz controlada para interactuar con ella. Esto nos permite:
Python utiliza convenciones de nomenclatura para indicar la visibilidad:
atributo
- Atributo público (accesible desde cualquier lugar)_atributo
- Atributo protegido (debería ser usado solo dentro de la clase y sus subclases)__atributo
- Atributo privado (name mangling: solo accesible dentro de la clase)class CuentaBancaria:
def __init__(self, titular, saldo_inicial=0):
self.titular = titular # Atributo público
self._saldo = saldo_inicial # Atributo protegido
self.__pin = "1234" # Atributo privado
def depositar(self, cantidad):
if cantidad > 0:
self._saldo += cantidad
return True
return False
def retirar(self, cantidad, pin):
if pin != self.__pin:
print("PIN incorrecto. Operación denegada.")
return False
if cantidad <= 0:
return False
if cantidad > self._saldo:
print("Saldo insuficiente.")
return False
self._saldo -= cantidad
return True
def consultar_saldo(self, pin):
if pin == self.__pin:
return self._saldo
else:
print("PIN incorrecto. Operación denegada.")
return None
# Propiedades (getters y setters)
@property
def saldo(self):
return self._saldo
# No permitimos modificar el saldo directamente
@property
def titular(self):
return self._titular
@titular.setter
def titular(self, nuevo_titular):
self._titular = nuevo_titular
# Creamos una cuenta
cuenta = CuentaBancaria("Ana López", 1000)
# Operaciones permitidas
print(f"Titular: {cuenta.titular}")
cuenta.depositar(500)
print(f"Saldo: {cuenta.saldo}") # Usando property
# Intentamos acceder a atributos protegidos y privados
print("\nIntentando acceder directamente:")
print(f"Acceso a _saldo: {cuenta._saldo}") # No recomendado pero posible
try:
print(f"Acceso a __pin: {cuenta.__pin}")
except AttributeError as e:
print(f"Error: {e}")
# La forma correcta de consultar el saldo
print("\nConsulta correcta:")
print(f"Saldo (con PIN correcto): {cuenta.consultar_saldo('1234')}")
print(f"Saldo (con PIN incorrecto): {cuenta.consultar_saldo('0000')}")
Las propiedades (usando el decorador @property
) son una forma elegante de implementar el encapsulamiento en Python. Permiten:
La herencia es un mecanismo que permite crear nuevas clases basadas en clases existentes, heredando sus atributos y métodos. La herencia permite:
class ClaseBase:
# Atributos y métodos de la clase base
pass
class ClaseDerivada(ClaseBase):
# Atributos y métodos de la clase derivada
# Puede añadir nuevos o sobrescribir los existentes
pass
class Vehiculo:
def __init__(self, marca, modelo, año):
self.marca = marca
self.modelo = modelo
self.año = año
self.encendido = False
def encender(self):
if not self.encendido:
self.encendido = True
return f"{self.marca} {self.modelo} encendido"
return f"{self.marca} {self.modelo} ya está encendido"
def apagar(self):
if self.encendido:
self.encendido = False
return f"{self.marca} {self.modelo} apagado"
return f"{self.marca} {self.modelo} ya está apagado"
def informacion(self):
return f"Vehículo: {self.marca} {self.modelo} ({self.año})"
class Automovil(Vehiculo):
def __init__(self, marca, modelo, año, tipo_carroceria):
# Llamada al constructor de la clase base
super().__init__(marca, modelo, año)
# Atributos específicos de Automovil
self.tipo_carroceria = tipo_carroceria
self.velocidad = 0
def acelerar(self, incremento):
if self.encendido:
self.velocidad += incremento
return f"Acelerando a {self.velocidad} km/h"
else:
return "No se puede acelerar. El vehículo está apagado."
# Sobrescribimos el método informacion de la clase base
def informacion(self):
info_base = super().informacion() # Reutilizamos el método de la clase base
return f"{info_base}, Tipo: {self.tipo_carroceria}"
class Motocicleta(Vehiculo):
def __init__(self, marca, modelo, año, cilindrada):
super().__init__(marca, modelo, año)
self.cilindrada = cilindrada
def hacer_caballito(self):
if self.encendido:
return "¡Haciendo un caballito con la moto!"
return "No puedes hacer un caballito. La moto está apagada."
def informacion(self):
return f"{super().informacion()}, Cilindrada: {self.cilindrada}cc"
# Crear instancias
auto = Automovil("Toyota", "Corolla", 2022, "Sedán")
moto = Motocicleta("Honda", "CBR", 2021, 600)
# Usar métodos de la clase base
print(auto.encender())
print(moto.encender())
# Usar métodos específicos de cada clase
print(auto.acelerar(60))
print(moto.hacer_caballito())
# Llamar al método sobrescrito
print("\nInformación de los vehículos:")
print(auto.informacion())
print(moto.informacion())
A diferencia de otros lenguajes como Java o C#, Python admite herencia múltiple, lo que significa que una clase puede heredar de varias clases base.
class Dispositivo:
def __init__(self, fabricante, modelo):
self.fabricante = fabricante
self.modelo = modelo
self.encendido = False
def encender(self):
self.encendido = True
return "Dispositivo encendido"
def apagar(self):
self.encendido = False
return "Dispositivo apagado"
class DispositivoConectado:
def __init__(self, direccion_ip="192.168.1.1"):
self.direccion_ip = direccion_ip
self.conectado = False
def conectar(self):
self.conectado = True
return f"Conectado a la red con IP {self.direccion_ip}"
def desconectar(self):
self.conectado = False
return "Desconectado de la red"
class SmartTV(Dispositivo, DispositivoConectado):
def __init__(self, fabricante, modelo, pulgadas, direccion_ip="192.168.1.100"):
Dispositivo.__init__(self, fabricante, modelo)
DispositivoConectado.__init__(self, direccion_ip)
self.pulgadas = pulgadas
self.canal = 1
def cambiar_canal(self, canal):
if self.encendido and self.conectado:
self.canal = canal
return f"Canal cambiado a {canal}"
return "No se puede cambiar el canal. Verifica que la TV esté encendida y conectada."
def informacion(self):
estado_encendido = "encendido" if self.encendido else "apagado"
estado_conexion = "conectado" if self.conectado else "desconectado"
return f"{self.fabricante} {self.modelo} {self.pulgadas}\" - {estado_encendido}, {estado_conexion}"
# Crear una Smart TV
tv = SmartTV("Samsung", "Neo QLED", 55)
# Usar métodos de ambas clases base
print(tv.encender()) # De Dispositivo
print(tv.conectar()) # De DispositivoConectado
# Usar método propio de SmartTV
print(tv.cambiar_canal(5))
# Mostrar información
print(tv.informacion())
El polimorfismo permite que diferentes clases implementen métodos con el mismo nombre pero con comportamientos diferentes. En Python, el polimorfismo se logra de varias formas:
class Animal:
def __init__(self, nombre):
self.nombre = nombre
def hacer_sonido(self):
raise NotImplementedError("Las subclases deben implementar este método")
def presentarse(self):
return f"Soy {self.nombre} y hago: {self.hacer_sonido()}"
class Perro(Animal):
def hacer_sonido(self):
return "¡Guau!"
def jugar(self):
return f"{self.nombre} está jugando con una pelota"
class Gato(Animal):
def hacer_sonido(self):
return "¡Miau!"
def dormir(self):
return f"{self.nombre} está durmiendo en el sofá"
class Pato(Animal):
def hacer_sonido(self):
return "¡Cuac!"
def nadar(self):
return f"{self.nombre} está nadando en el lago"
# Función que muestra el polimorfismo
def hacer_sonar_animal(animal):
return animal.hacer_sonido()
# Crear instancias de diferentes animales
perro = Perro("Rex")
gato = Gato("Whiskers")
pato = Pato("Donald")
# Lista de animales (diferentes tipos)
animales = [perro, gato, pato]
# Demostración de polimorfismo
print("Demostración de polimorfismo:")
for animal in animales:
print(f"{animal.nombre}: {animal.hacer_sonido()}")
# Uso de la función polimórfica
print("\nUso de función polimórfica:")
print(f"El perro dice: {hacer_sonar_animal(perro)}")
print(f"El gato dice: {hacer_sonar_animal(gato)}")
print(f"El pato dice: {hacer_sonar_animal(pato)}")
# Métodos específicos de cada clase
print("\nComportamientos específicos:")
print(perro.jugar())
print(gato.dormir())
print(pato.nadar())
Python utiliza "duck typing" (tipado pato), una forma de polimorfismo donde el tipo o la clase de un objeto es menos importante que los métodos que implementa o las propiedades que tiene.
A diferencia de lenguajes como Java o C#, Python no requiere que una clase implemente explícitamente una interfaz. En su lugar, cualquier objeto que proporcione los métodos esperados funcionará, independientemente de su tipo.
class Pato:
def volar(self):
return "El pato está volando"
def nadar(self):
return "El pato está nadando"
def hacer_sonido(self):
return "¡Cuac!"
class Avion:
def volar(self):
return "El avión está volando"
def nadar(self):
return "Los aviones no nadan"
def hacer_sonido(self):
return "¡Brrrrrr!"
class Persona:
def volar(self):
return "Las personas no pueden volar naturalmente"
def nadar(self):
return "La persona está nadando"
def hacer_sonido(self):
return "¡Hola!"
# Función que utiliza duck typing
def hacer_volar(objeto):
return objeto.volar()
def hacer_nadar(objeto):
return objeto.nadar()
# Crear instancias
pato = Pato()
avion = Avion()
persona = Persona()
# Lista de objetos completamente diferentes
objetos = [pato, avion, persona]
# Demostración de duck typing
print("Demostración de Duck Typing:")
for obj in objetos:
print(f"Volar: {hacer_volar(obj)}")
print(f"Nadar: {hacer_nadar(obj)}")
print(f"Sonido: {obj.hacer_sonido()}")
print()
Diseña un sistema para gestionar diferentes tipos de empleados con las siguientes características:
Empleado
con atributos comunes como nombre, ID y salario base.EmpleadoTiempoCompleto
y EmpleadoTiempoParcial
.class Empleado:
# Implementa la clase base aquí
pass
class EmpleadoTiempoCompleto(Empleado):
# Implementa esta clase derivada
pass
class EmpleadoTiempoParcial(Empleado):
# Implementa esta clase derivada
pass
# Crea empleados y calcula sus salarios
# empleado1 = EmpleadoTiempoCompleto(...)
# empleado2 = EmpleadoTiempoParcial(...)