Skip to content

andalons/ia-f5-poo

Repository files navigation

Programación Orientada a Objetos (POO)

Índice

  1. Origen y fundamentos
  2. Abstracción
  3. Modularización
  4. Herencia
  5. Encapsulamiento
  6. Polimorfismo

Origen y fundamentos

  • La programación estructurada surge en la década de los 60 como una respuesta a los problemas de complejidad y mantenimiento que presentaban los programas escritos en estilos anteriores, siendo predominante la programación imperativa no estructurada, donde los programas se escribían de manera secuencial y con un uso intensivo de instrucciones como GOTO para alterar el flujo de ejecución. Esto llevaba a que los programas fueran difíciles de entender, mantener y depurar, ya que el flujo de control podía saltar de un punto a otro del código sin un orden claro. Gracias a ella se alcanzaron:

    1. Control de flujo estructurado: Uso de estructuras de control claras como:
      • Secuencias: ejecución de instrucciones en orden.
      • Condicionales: if-else, switch.
      • Bucles controlados: for, while, do-while.
    2. Modularidad: Fomento del uso de subrutinas o funciones para dividir el código en bloques más manejables y reutilizables.
    3. Eliminación del GOTO: Reducción del uso de saltos incontrolados para mejorar la legibilidad.
  • Problemas que seguía planteando la programación estructurada:

    1. Gestión de la complejidad: Aunque el código era más legible, la modularidad basada en funciones no siempre era suficiente para representar entidades del mundo real o relaciones complejas.
    2. Reutilización limitada: Las funciones podían ser reutilizables, pero los datos y las funciones estaban separados, lo que hacía más complicado reutilizar estructuras completas de código.
    3. Dificultades en el mantenimiento de grandes proyectos: A medida que los proyectos crecían, mantener el código estructurado sin una representación más natural de los objetos y sus relaciones resultaba cada vez más complicado.
  • Así, en los años 70 surge la necesidad de una nueva filosofía o forma de programar; un nuevo paradigma de programación: la POO (Programación orientada a objetos), que trata de trasladar lo que vemos en la vida real a código de programación. La POO observa los problemas a resolver como objetos.

  • La POO se sustenta en 4 pilares fundamentales:

    1. Herencia
    2. Abstracción
    3. Encapsulamiento
    4. Polimorfismo

Abstracción

Los objetos tienen:

  • Propiedades: ¿Cómo es?
  • Métodos: ¿Qué hace?

Ejemplos de objetos del mundo código:

  • Ventana
    • Propiedades: ancho, alto, color, título, botones, texto...
    • Métodos: aparecer, desaparecer
  • Usuario
    • Propiedades: id, nombre, contraseña, perfil (usuario, admin, etc.)
  • Conexión
    • Propiedades: intentos, tiempo máximo
    • Métodos: conectar, desconectar, reintentar

Suelen existir varios objetos que compartan propiedades, aunque los valores de estas difieran. Lo mismo sucede con los métodos. Realizar este ejercicio de extracción de propiedades y métodos comunes es un ejercicio de abstracción y será necesario para crear las clases, que pueden ser entendidas como las "fábricas" de los objetos. Estas clases podrán ser reutilizadas en diferentes aplicaciones.


Modularización

La modularización consiste en dividir la estructura del programa / aplicación en diferentes piezas o módulos (bloques de código).

Ejemplos de modularización en el mundo físico:

  • Casas prefabricadas: piezas adaptables a diferentes modelos de casa
  • Sofás modulares: pueden crecer según necesidad
  • Equipos HIFI (Equipos de alta fidelidad: tocadiscos, amplificador)

Ventajas de la modularización

  • Re-utilización
  • Rapidez (pensar en fabricaciones en serie)
  • Escalabilidad (pensar en sofá modular)
  • Mantenimiento y legibilidad
  • Depuración de errores (pensar en HIFI: si se estropea la unidad de CD's, puedes escuchar vinilos mientras lo reparas). Es más fácil localizar el error y estará limitado al módulo donde se encuentre.

Cómo modularizar el código

Existen muchos patrones y principios que nos pueden ayudar a la hora de organizar nuestro código en diferentes módulos.

Patrones de arquitectura

Son enfoques de alto nivel para estructurar y organizar aplicaciones completas. Existen diferentes patrones de arquitectura para dividir el código en diferentes módulos, cada uno formado por las clases que corresponda. Uno de ellos es el MVC (Modelo Vista Controlador):

  • Módulo 1: Modelo: Representa la lógica de negocio y el manejo de datos. Es responsable de acceder a la base de datos o realizar cálculos y devolver los datos a quien los solicite.
  • Módulo 2: Vista: Es la parte que se encarga de la interfaz de usuario, mostrando los datos al usuario.
  • Módulo 3: Controlador: Comunicación entre módulos, intermediario entre modelo y vista. Peticiones y entregas de datos. Recibe las entradas del usuario, las procesa a través del modelo y actualiza la vista.
Patrones de diseño

Además de patrones de arquitectura existen patrones de diseño, que son soluciones reutilizables para problemas comunes en el diseño de software a nivel de clases y objetos y se agrupan generalmente en tres categorías principales:

  • Creacionales
  • Estructurales
  • Comportamiento
Principios SOLID

Conjunto de principios para escribir software más mantenible, flexible y escalable.

  1. SSingle Responsibility Principle (SRP)
    Cada clase debe tener una sola responsabilidad o motivo para cambiar.
  2. OOpen/Closed Principle (OCP)
    El software debe estar abierto a la extensión pero cerrado a la modificación.
  3. LLiskov Substitution Principle (LSP)
    Los objetos de una clase derivada deben poder reemplazar a los de la clase base sin alterar el funcionamiento.
  4. IInterface Segregation Principle (ISP)
    Las interfaces deben ser específicas y no forzar a los clientes a depender de métodos que no usan.
  5. DDependency Inversion Principle (DIP)
    Depender de abstracciones y no de implementaciones concretas.

Herencia

A la hora de crear una aplicación desde cero, seguiríamos el siguiente orden de tareas para poder definir qué clases necesitamos y cómo funciona la herencia entre estas.

  1. Definir las tareas o pasos que seguirá nuestra app.
  2. Abstracción de objetos necesarios.
  3. Análisis de las propiedades y métodos de cada objeto. Preguntarse: ¿Existen propiedades/métodos repetidos entre diferentes objetos? Principio DRY (Don't Repeat Yourself)
  4. Herencia: Establecer una jerarquía de clases (con superclases y subclases) para poder crear un código más simple y reutilizable. Para saber qué clase debe heredar de qué otra clase, usar la regla "_ es siempre un _ ". Es interesante utilizar algún tipo de representación gráfica para visualizar estos objetos y su herencia. Hoy en día, uno de los más utilizados para representar clases y herencias en POO es el UML (Lenguaje de Modelado Unificado) Herramientas para gráficos UML: draw.io, Mermaid

Encapsulamiento

El encapsulamiento es uno de los principios fundamentales de la Programación Orientada a Objetos (POO) y se refiere a la práctica de ocultar los detalles internos de una clase y solo exponer lo que es necesario a través de una interfaz pública bien definida. En Python, el encapsulamiento se logra mediante el uso de modificadores de acceso para los atributos y métodos de las clases, con el objetivo de proteger los datos y facilitar el mantenimiento y la extensión del código.

  1. Atributos y métodos públicos:
    • Son accesibles desde cualquier parte del código.
    • Se definen sin ningún modificador especial.
  2. Atributos y métodos protegidos:
    • Se indican con un solo guion bajo (_) antes del nombre, como convención.
    • Aunque no impiden el acceso directo desde fuera de la clase, indican que el atributo o método está destinado a ser usado solo dentro de la clase o sus subclases.
  3. Atributos y métodos privados:
    • Se indican con dos guiones bajos (__) antes del nombre.
    • Los atributos y métodos privados son más difíciles de acceder desde fuera de la clase. Python realiza un proceso llamado name mangling, que cambia internamente el nombre del atributo o método para hacerlo más difícil de acceder (aunque no lo impide completamente).

Ejemplo en Python:

class Persona:
    def __init__(self, nombre, edad):
        self.__nombre = nombre  # Atributo privado
        self.__edad = edad  # Atributo privado

    # Getter para obtener el nombre
    def get_nombre(self):
        return self.__nombre

    # Setter para modificar el nombre
    def set_nombre(self, nombre):
        self.__nombre = nombre

    # Getter para obtener la edad
    def get_edad(self):
        return self.__edad

    # Setter para modificar la edad
    def set_edad(self, edad):
        if edad >= 0:
            self.__edad = edad
        else:
            print("Edad no válida")


# Uso de la clase
p = Persona("Juan", 30)
print(f"Nombre: {p.get_nombre()}")
print(f"Edad: {p.get_edad()}")

# Modificando la edad con un setter
p.set_edad(35)
print(f"Nueva Edad: {p.get_edad()}")

En Python, el acceso a los atributos privados se realiza utilizando convenciones de nomenclatura, como el uso de doble guion bajo (__atributo), lo que indica que el atributo es privado y no debe ser accesible desde fuera de la clase. Sin embargo, a diferencia de Java, esta no es una restricción estricta, sino una convención que puede ser violada.


Polimorfismo

El polimorfismo en la Programación Orientada a Objetos (POO) es un principio fundamental que permite que diferentes clases derivadas de una clase base puedan implementar o redefinir un mismo método de manera distinta. Así, el mismo método o función puede comportarse de manera diferente según el tipo de objeto que lo invoque. Esto se logra principalmente mediante la herencia y la sobrecarga de métodos.

class Animal:
    def hablar(self):
        pass  # Método base

class Perro(Animal):
    def hablar(self):
        return "Guau"

class Gato(Animal):
    def hablar(self):
        return "Miau"

def hacer_sonido(animal):
    print(animal.hablar())  # Polimorfismo: comportamiento diferente según la clase

perro = Perro()
gato = Gato()

hacer_sonido(perro)  # Salida: Guau
hacer_sonido(gato)   # Salida: Miau

El polimorfismo en Python es posible debido a la herencia y la sobrescritura de métodos, pero no requiere declaración explícita de tipos, lo que lo hace más flexible pero también propenso a errores en tiempo de ejecución si no se tiene cuidado con los tipos de objetos.

El principio de sustitución de Liskov (LSP) Es un concepto clave dentro de los principios de diseño orientados a objetos (SOLID) y es crucial para asegurar que el polimorfismo funcione correctamente.

El polimorfismo permite que un método tenga diferentes implementaciones según el tipo de objeto con el que se invoque. El principio de Liskov garantiza que las clases derivadas respeten el comportamiento de la clase base, de modo que los objetos derivados puedan ser utilizados en lugar de los de la clase base sin causar efectos inesperados.


About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages