diff --git a/content/sesion09/Readme.md b/content/sesion09/Readme.md index b73621e..20faaf7 100644 --- a/content/sesion09/Readme.md +++ b/content/sesion09/Readme.md @@ -1201,7 +1201,7 @@ git commit -m "Sesión 09" git push origin main ``` ---- +--- #### Resumen @@ -1252,26 +1252,31 @@ psg-oop-2025/ --- -1. Crea el juego **"Piedra, Papel o Tijera"** utilizando el patrón **Singleton** -para asegurar que solo exista **una única instancia del juego** en ejecución - -El juego permite que un jugador compita contra la computadora, la cual elige aleatoriamente entre las tres opciones: +1. Crea el juego **Piedra, Papel o Tijera** implementando el patrón de diseño **Singleton** +En esta versión, un jugador compite contra la computadora, la cual selecciona aleatoriamente entre las tres opciones disponibles: `Piedra` , `Papel` , `Tijera` -El objetivo es registrar las partidas ganadas por el jugador y por la computadora, manteniendo el control del puntaje dentro de la **única instancia** del juego +Se debe registrar las partidas ganadas tanto por el jugador como por la computadora, manteniendo el control del puntaje dentro de la **única instancia** del juego. -*(1/3)* +*(1/4)* --- -Crea la clase `PiedraPapelTijera` implementando el patrón **Singleton** -Debe incluir: +Implementa la clase **PiedraPapelTijera** aplicando el patrón de diseño **Singleton**, asegurando que exista **una única instancia del juego** durante toda la ejecución. + +La clase debe incluir los siguientes métodos: + +- `iniciarPartida()`: Permite jugar una ronda contra la computadora +- `mostrarPuntaje()`: Muestra el puntaje acumulado del jugador y de la computadora. +- `reiniciarJuego()`: Restablece los puntajes a cero. + +*(2/4)* + +--- + +Además, se debe implementar un **menú interactivo en consola** con las siguientes opciones: -- Un método para **iniciar una partida** -- Un método para **mostrar el puntaje actual** -- Un método para **reiniciar el juego** (puntajes en cero) -- Un **menú interactivo en consola** que permita: ```markdown 1. Iniciar una nueva partida 2. Mostrar puntajes @@ -1279,43 +1284,41 @@ Debe incluir: 4. Salir ``` -*(2/3)* +*(3/4)* --- -Realiza el **análisis** y el **diagrama de clases** para la clase `PiedraPapelTijera` en el archivo `ejercicio_04.md` +- Elabora el **análisis** y el **diagrama de clases** de la clase `PiedraPapelTijera` en el archivo `ejercicio_01.md`. +- Implementa el código en **Python** dentro del archivo `ejercicio_01.py`. +- Prueba el juego en la consola para verificar su correcto funcionamiento antes de enviar tu solución -Escribe el código Python en el archivo `ejercicio_04.py` +*(4/4)* -- Asegúrate de que las clases incluyan *atributos* y *métodos* coherentes con su propósito - -- Utiliza buenas prácticas de *nomenclatura*, *encapsulamiento* y *legibilidad* +--- -- Prueba el juego en la consola para verificar su correcto funcionamiento +2. Un DJ requiere una nueva consola de mezcla llamada **`BeatBox`**, capaz de mezclar pistas de audio en tiempo real. +La consola debe permitir: +- **Seleccionar una pista de audio** +- **Ajustar el volumen** (subir o bajar) +- **Mostrar el estado actual** de la consola (pista seleccionada y nivel de volumen) +- **Aplicar un único efecto de sonido a la vez**, entre las opciones: `eco`, `reverb` o `distorsión` -*(3/3)* +*(1/4)* --- -2. Un DJ necesita una nueva consola de mezcla `BeatBox` que permita mezclar pistas de audio en tiempo real -Utiliza el patrón **Singleton** para asegurar que solo exista una única instancia de la consola de mezcla en ejecución -La consola debe permitir: -- Seleccionar pista de audio -- Subir y bajar volumen -- Mostrar el estado actual de la consola (pista seleccionada y volumen) -- Puede aplicar un efecto de sonido a la vez (eco, reverb, distorsión) +Crea la clase **`BeatBox`** implementando el patrón de diseño **Singleton** +La clase debe incluir los siguientes métodos: +- **`seleccionar_pista()`** → Permite elegir una pista de audio. +- **`ajustar_volumen()`** → Permite subir o bajar el volumen. +- **`aplicar_efecto()`** → Aplica un efecto de sonido (eco, reverb o distorsión). +- **`mostrar_estado()`** → Muestra el estado actual de la consola (pista seleccionada y nivel de volumen). -*(1/3)* +*(24)* --- -Crea la clase `BeatBox` implementando el patrón **Singleton** -Debe incluir: -- Un método para **seleccionar pista de audio** -- Un método para **ajustar el volumen** -- Un método para **aplicar un efecto de sonido** -- Un método para **mostrar el estado actual** de la consola -- Un **menú interactivo en consola** que permita: +El programa debe ofrecer un menú con las siguientes opciones: ```markdown 1. Ingresar el nombre de la pista de audio 2. Ajustar volumen @@ -1324,17 +1327,17 @@ Debe incluir: 5. Salir ``` -*(2/3)* +*(3/4)* --- -Realiza el **análisis** y el **diagrama de clases** para la clase `BeatBox` en el archivo `ejercicio_05.md` -Escribe el código Python en el archivo `ejercicio_05.py` -- Asegúrate de que las clases incluyan *atributos* y *métodos* coherentes con su propósito +- Realiza el **análisis** y el **diagrama de clases** para la clase `BeatBox` en el archivo `ejercicio_02.md` +- Implementa el código en **Python** dentro del archivo `ejercicio_02.py` +- Asegúrate de que la clase incluya **atributos** y **métodos** coherentes con su propósito. - Utiliza buenas prácticas de *nomenclatura*, *encapsulamiento* y *legibilidad* -- Prueba la consola de mezcla en la consola para verificar su correcto funcionamiento +- Prueba el juego en la consola para verificar su correcto funcionamiento antes de enviar tu solución -*(3/3)* +*(4/4)* --- diff --git a/content/sesion10/Readme.md b/content/sesion10/Readme.md new file mode 100644 index 0000000..c91514a --- /dev/null +++ b/content/sesion10/Readme.md @@ -0,0 +1,1156 @@ + +![Python La paz](../Logo.png) + +
+
+
+ +### Sesión 10 +#### Patrones de Diseño +#### Factory + +--- + +### Antes de empezar + +--- + +#### Proyecto en VSCode + +Abre el proyecto en VSCode + +```bash +code psg-oop-2025 +``` + +Crea una carpeta con el nombre `sesion10` + +```bash +mkdir sesion10 +cd sesion10 +``` + +- Los archivos de esta sesión deben estar dentro de esta carpeta + +- Al finalizar la sesión, sube los cambios al repositorio en un commit + +--- + +#### ¿Qué es un patrón de diseño? + +Un patrón de diseño es una **idea o solución** típica para resolver un problema + +que se repite **una y otra vez** + +--- + +Los patrones varían en complejidad y propósito y pueden ser clasificados en tres categorías principales: + +- **Creacionales**: Se centran en la *creación* de objetos y clases +- **Estructurales**: Se centran en la *composición* de clases y objetos +- **De Comportamiento**: Se centran en la *interacción y responsabilidad* entre objetos + +--- + +Nos enfocaremos en los **patrones creacionales** + +que son los que nos ayudan a crear objetos de manera eficiente y flexible + +--- + +#### Patrones Creacionales + +Son aquellos que se encargan de la **creación** de objetos + +Ofrecen enfoques estructurados y reutilizables para crear **instancias** de clases + +los más comunes son: + +- **Singleton** +- **Factory** + +--- + +#### Factory + +Llamado + +**"metodo de fábrica"** + +Es como tener una **fábrica** que crea **objetos diferentes** según las **necesidades** + +--- + +> "El patrón Factory es como pedir en una **cafetería**: tú solo dices qué quieres, y alguien más se encarga de prepararlo por ti." + +--- + +![Factory](./img/factory.gif) + +--- + +#### ¿Por qué usar Factory? + +- **Flexibilidad**: Permite crear diferentes tipos de objetos sin necesidad de conocer la clase exacta que se va a instanciar +- **Desacoplamiento**: Separa la lógica de creación de objetos de su uso +- **Mantenimiento**: Facilita la modificación y extensión del código sin afectar a las clases que lo utilizan + +--- +#### Ejemplos comunes + +*Aplicaciones web y escritorio* + +- **Creación de tipos de usuarios:** (administrador, cliente, etc.) +- **Generación de reportes:** (PDF, Excel, etc.) +- **Creación de notificaciones:** (correo electrónico, SMS, etc.) + +--- +#### Ejemplos comunes + +*Juegos y simulaciones* + +- **Creación de personajes:** (guerrero, mago, arquero, etc.) +- **Generación de enemigos:** (zombies, dragones, robots, etc.) +- **Creación de vehículos:** (coche, moto, avión, etc.) + +--- + +#### Ejemplos comunes + +*Seguridad y autenticación* + +- **Generación de autenticación:** (Google, Facebook, GitHub, etc.) +- **Generación de permisos:** (lectura, escritura, ejecución, etc.) +- **Creación de autenticación biométrica:** (huella dactilar, reconocimiento facial, etc.) + + +--- +#### Ejemplos comunes + +*IoT y dispositivos inteligentes* + +- **Estados de sensores:** (temperatura, humedad, luz, etc.) +- **Generación de dispositivos:** (termómetro, cámara, bombilla, etc.) +- **Generación de protocolos de comunicación:** (MQTT, HTTP, etc.) + +--- + +#### Ventajas de usar Factory + +- **Reutilización de código**: Permite reutilizar el código de creación de objetos en diferentes partes +- **Facilidad de mantenimiento**: Si se necesita cambiar la forma en que se crean los objetos, solo se modifica el Factory +- **Escalabilidad**: Facilita la adición de nuevos tipos de objetos sin afectar al código existente + +--- + +#### Desventajas de usar Factory + +- **Complejidad adicional**: Introduce una capa adicional de abstracción que puede hacer el código más complejo +- **Sobrecarga de rendimiento**: En algunos casos, la creación de objetos a través de un Factory puede ser menos eficiente que la creación directa +- **Granularidad**: Puede llevar a una sobreabundancia de clases y fábricas si no se gestiona adecuadamente + +--- + +#### Diagrama de clases + +1. definir las clases que van a participar en el patrón Factory +los productos que van a ser creados + +```` +```mermaid +classDiagram + class Producto { + +usar() + } + class ProductoA { + +usar() + } + class ProductoB { + +usar() + } + Producto <|-- ProductoA + Producto <|-- ProductoB +``` +```` + +--- +#### Diagrama de clases + +Los productos A y B **heredan** de la clase Producto y serán creados por una fábrica + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn" }}%% +classDiagram +direction LR + class Producto { + +usar() + } + class ProductoA { + +usar() + } + class ProductoB { + +usar() + } + Producto <|-- ProductoA + Producto <|-- ProductoB +``` + +--- + +#### Diagrama de clases + +2. Ahora definimos la clase **Fábrica** y las fábricas concretas que crearán los productos A y B + +```` +```mermaid +classDiagram + class Fabrica { + +crear() + } + class FabricaA { + +crear() + } + class FabricaB { + +crear() + } + Fabrica <|-- FabricaA + Fabrica <|-- FabricaB +``` +```` + +--- +#### Diagrama de clases + +La FabricaA y FabricaB heredan de la clase Fabrica ambos crearan productos de tipo Producto + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn" }}%% +classDiagram +direction LR + class Fabrica { + +crear() + } + class FabricaA { + +crear() + } + class FabricaB { + +crear() + } + Fabrica <|-- FabricaA + Fabrica <|-- FabricaB +``` + + +--- +#### Diagrama de clases + +3. Unimos las fábricas con los productos que crean se realiza mediante +una **relación de asociación**, donde la fábrica crea productos de tipo Producto + +```` +```mermaid +classDiagram + class Producto { + +usar() + } + class ProductoA { + +usar() + } + class ProductoB { + +usar() + } + class Fabrica { + +crear() Producto + } + class FabricaA { + +crear() Producto + } + class FabricaB { + +crear() Producto + } + Producto <|-- ProductoA + Producto <|-- ProductoB + Fabrica <|-- FabricaA + Fabrica <|-- FabricaB + Fabrica --> Producto +``` +```` +--- +#### Diagrama de clases + +El diagrama final muestra la relación entre las fábricas y los productos que se va a crear + + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn" }}%% +classDiagram +direction LR + class Producto { + +usar() + } + class ProductoA { + +usar() + } + class ProductoB { + +usar() + } + class Fabrica { + +crear() Producto + } + class FabricaA { + +crear() Producto + } + class FabricaB { + +crear() Producto + } + Producto <|-- ProductoA + Producto <|-- ProductoB + Fabrica <|-- FabricaA + Fabrica <|-- FabricaB + Fabrica --> Producto +``` + + +--- + +#### Estructura de Factory en Python + +**Factory** implementa **Herencia** y **polimorfismo** pilares de la programación orientada a objetos + +La estructura de un Factory se puede ver mejor en un ejemplo **práctico** + +--- + +#### Ejemplo 01 + +Crear los archivos `cafeteria.md` y `cafeteria.py` en la carpeta `sesion10` +```markdown +Una cafetería prepara Expresos y Cappuccinos, +el cliente (tú) solo debes pedir el tipo de café que desea +el barista utilizará las máquinas adecuadas para preparar el café +Todos los cafes se pueden tomar +Las cafeteras preparan un tipo de café, después son consumidos +por el cliente +Al ingresar salir se termina la ejecución +``` + + +Se implementará el patrón Factory + +--- + +Análisis + +```markdown +# Análisis +Requisitos: +- Se preparan dos tipos de café: Expreso y Cappuccino +- El cliente solo debe pedir el tipo de café que desea +- Se utiliza la máquina adecuada para cada café +- Todos los cafés se pueden tomar +- Las cafeteras preparan un tipo de café +- Si se ingresa "salir" se termina la ejecución + +Objetos: +- Café (Expreso, Cappuccino) +- Cafetera (CafeteraExpreso, CafeteraCappuccino) +- Barista + +Características: +- Café: (sin características) +- Expreso: (Café) +- Cappuccino: (Café) +- Cafetera: (sin características) +- CafeteraExpreso: (Cafetera) +- CafeteraCappuccino: (Cafetera) +- Barista: (sin características) + +Acciones: +- Café: tomar +- Expreso: tomar +- Cappuccino: tomar +- Cafetera: preparar +- CafeteraExpreso: preparar +- CafeteraCappuccino: preparar +- Barista: preparar cafe +``` + +--- +#### Diagrama de clases + +```` +```mermaid +classDiagram + class Cafe { + +tomar() + } + class Expreso { + +tomar() + } + class Cappuccino { + +tomar() + } + class Cafetera { + +preparar() + } + class CafeteraExpreso { + +preparar() + } + class CafeteraCappuccino { + +preparar() + } + class Barista { + +preparar_cafe() + } + Cafe <|-- Expreso + Cafe <|-- Cappuccino + Cafetera <|-- CafeteraExpreso + Cafetera <|-- CafeteraCappuccino + Cafetera --> Cafe + Barista --> Cafetera +``` +```` + +--- +#### Diagrama de clases + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn" }}%% +classDiagram +direction LR + class Cafe { + +tomar() + } + class Expreso { + +tomar() + } + class Cappuccino { + +tomar() + } + class Cafetera { + +preparar() + } + class CafeteraExpreso { + +preparar() + } + class CafeteraCappuccino { + +preparar() + } + class Barista { + +preparar_cafe() + } + Cafe <|-- Expreso + Cafe <|-- Cappuccino + Cafetera <|-- CafeteraExpreso + Cafetera <|-- CafeteraCappuccino + Cafetera --> Cafe + Barista --> Cafetera +``` + + +--- +#### Implementación en Python + +Primero definimos la clase base `Cafe` y las clases derivadas `Expreso` y `Cappuccino` + +```python +class Cafe: + def tomar(self): + print("Tomando un café") + +class Expreso(Cafe): + def tomar(self): + print("☕ Tomando un expreso") + +class Cappuccino(Cafe): + def tomar(self): + print("🥛 Tomando un cappuccino") +``` + +--- +Luego definimos la clase base `Cafetera` y las clases derivadas `CafeteraExpreso` y `CafeteraCappuccino` + +```python +class Cafetera: + def preparar(self): + pass +class CafeteraExpreso(Cafetera): + def preparar(self): + return Expreso() +class CafeteraCappuccino(Cafetera): + def preparar(self): + return Cappuccino() +``` + +--- +Definimos las clases `Barista` que interactúa con las cafeteras + +```python +class Barista: + def preparar_cafe(self, tipo): + if tipo == "expreso": + return CafeteraExpreso().preparar() + if tipo == "cappuccino": + return CafeteraCappuccino().preparar() + raise ValueError("❌ Café no disponible. Intente de nuevo") +``` + +--- +Finalmente creamos la lógica donde como clientes pedimos el **café** al barista + +```python +while True: + tipo_cafe = input("💬 ¿Qué café desea? (expreso/cappuccino/salir): ") + tipo_cafe = tipo_cafe.lower().strip() + if tipo_cafe == "salir": + print("🚶‍♂️ Saliendo de la cafetería.") + break + try: + barista = Barista() + cafe = barista.preparar_cafe(tipo_cafe) + cafe.tomar() + except ValueError as e: + print(e) +``` + + +--- +#### Código completo + +```python +# Productos +class Cafe: + def tomar(self): + print("Tomando un café") + +class Expreso(Cafe): + def tomar(self): + print("☕ Tomando un expreso") + +class Cappuccino(Cafe): + def tomar(self): + print("🥛 Tomando un cappuccino") + +# Fabricas +class Cafetera: + def preparar(self): + pass +class CafeteraExpreso(Cafetera): + def preparar(self): + return Expreso() +class CafeteraCappuccino(Cafetera): + def preparar(self): + return Cappuccino() + +# Factory Central (decide qué fábrica usar) +class Barista: + def preparar_cafe(self, tipo): + if tipo == "expreso": + return CafeteraExpreso().preparar() + if tipo == "cappuccino": + return CafeteraCappuccino().preparar() + raise ValueError("❌ Café no disponible. Intente de nuevo") + +# Cliente (No conoce las fábricas, solo pide el café) +while True: + tipo_cafe = input("💬 ¿Qué café desea? (expreso/cappuccino/salir): ") + tipo_cafe = tipo_cafe.lower().strip() + if tipo_cafe == "salir": + print("🚶‍♂️ Saliendo de la cafetería.") + break + try: + barista = Barista() + cafe = barista.preparar_cafe(tipo_cafe) + cafe.tomar() + except ValueError as e: + print(e) +``` + +--- + +#### Ejecución + +```bash +python cafeteria.py +``` + +```text +💬 ¿Qué café desea? (expreso/cappuccino/salir): expreso +☕ Tomando un expreso +💬 ¿Qué café desea? (expreso/cappuccino/salir): cappuccino +🥛 Tomando un cappuccino +💬 ¿Qué café desea? (expreso/cappuccino/salir): latte +❌ Café no disponible. Intente de nuevo +💬 ¿Qué café desea? (expreso/cappuccino/salir): salir +🚶‍♂️ Saliendo de la cafetería. +``` + +--- + +En el ejemplo anterior se muestra cómo el patrón Factory +permite **crear diferentes** tipos de café sin que el cliente +conozca los detalles de la creación + +--- + +El único que conoce los detalles de la creación es el **Barista** +que utiliza las **fábricas adecuadas** para preparar el café solicitado + +--- + +#### Ejercicio para ti (02) + +Crea los archivos `juego.md` y `juego.py` en la carpeta `sesion10` + +Obtener el *Análisis* + +--- + + +```markdown +En un juego de rol, los jugadores pueden elegir diferentes +tipos de armas. +Cuando un jugador solicita un arma, el armero utiliza +la herramienta adecuada para crearla +Cada herramienta crea un solo tipo de arma: +- El martillo crea espadas +- La Sierra crea arcos +Todas las armas pueden usarse para atacar +Si el jugador escribe "salir" en lugar de un tipo de arma, +el programa finaliza. +``` + +3 minutos + + + +--- + +#### Análisis + +```markdown +Requisitos: +- Hay dos tipos de armas: Espada y Arco. +- El jugador solo debe pedir el tipo de arma que desea. +- Se usa la herramienta adecuada para fabricar cada arma. +- Todas las armas pueden usarse para atacar. +- Cada herramienta fabrica un tipo de arma. +- Si se ingresa "salir" se termina la ejecución. + +Objetos: +- Arma (Espada, Arco) +- Herramienta (Martillo, Sierra) +- Armero + +Características: +- Arma: (sin características extra) +- Espada: (Arma) +- Arco: (Arma) +- Herramienta: (sin características) +- Martillo: (Herramienta) +- Sierra: (Herramienta) +- Armero: (sin características) + +Acciones: +- Arma: atacar() +- Espada: atacar() +- Arco: atacar() +- Herramienta: fabricar() +- Martillo: fabricar() +- Sierra: fabricar() +- Armero: (sin acciones) +``` + +--- + +#### Ejercicio para ti (02) + +Ahora obtenemos el diseño del *diagrama de clase* + +2 minutos + + + +Modifica el archivo `juego.md` + +--- + +#### Diagrama de clases + +```` +```mermaid +classDiagram + class Arma { + +atacar() + } + class Espada { + +atacar() + } + class Arco { + +atacar() + } + class Herramienta { + +fabricar() + } + class Martillo { + +fabricar() + } + class Sierra { + +fabricar() + } + class Armero { + } + Arma <|-- Espada + Arma <|-- Arco + Herramienta <|-- Martillo + Herramienta <|-- Sierra + Herramienta --> Arma + Armero --> Herramienta +``` +```` + + +--- + +#### Diagrama de clases + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn"}}%% +classDiagram +direction LR + class Arma { + +atacar() + } + class Espada { + +atacar() + } + class Arco { + +atacar() + } + class Herramienta { + +fabricar() + } + class Martillo { + +fabricar() + } + class Sierra { + +fabricar() + } + class Armero { + +fabricar_arma() + } + Arma <|-- Espada + Arma <|-- Arco + Herramienta <|-- Martillo + Herramienta <|-- Sierra + Herramienta --> Arma + Armero --> Herramienta +``` + + + +--- + +#### Ejercicio para ti (02) + +Ya tenemos el *análisis y diseño* de la clase + +Ahora podemos **programar** + +Modifica el archivo `juego.py` + +2 minutos + + + +Primero implementamos las armas + +--- + +Clases de las armas + +```python +class Arma: + def atacar(self): + print("Atacando con un arma") + +class Espada(Arma): + def atacar(self): + print("🗡️ Atacando con una espada") + +class Arco(Arma): + def atacar(self): + print("🏹 Atacando con un arco") +``` + + +--- + +Segundo implementamos las herramientas que fabrican las armas + +2 minutos + + + +Modifica el archivo `juego.py` + +--- +```python +class Herramienta: + def fabricar(self): + pass + +class Martillo(Herramienta): + def fabricar(self): + return Espada() + +class Sierra(Herramienta): + def fabricar(self): + return Arco() +``` + +--- + +#### Implementación del Armero + +Tercero implementar el Armero que utiliza las herramientas para fabricar las armas + +2 minutos + + + +Modifica el archivo `juego.py` + +--- + +```python +class Armero: + def fabricar_arma(self, tipo): + if tipo == "espada": + return Martillo().fabricar() + if tipo == "arco": + return Sierra().fabricar() + raise ValueError("❌ Arma no disponible. Intente de nuevo") +``` + +--- + +#### Interacción del jugador + +Ahora que tenemos el Armero, implementar la lógica del juego donde el jugador solicita un arma +al armero y si ingresa "salir" se termina la ejecución + +2 minutos + + + +--- + +```python +while True: + tipo_arma = input("💬 ¿Qué arma desea? (espada/arco/salir): ") + tipo_arma = tipo_arma.lower().strip() + if tipo_arma == "salir": + print("🚶‍♂️ Saliendo del juego.") + break + try: + armero = Armero() + arma = armero.fabricar_arma(tipo_arma) + arma.atacar() + except ValueError as e: + print(e) +``` + +--- + +#### Código completo + +```python +class Arma: + def atacar(self): + print("Atacando con un arma") + +class Espada(Arma): + def atacar(self): + print("🗡️ Atacando con una espada") + +class Arco(Arma): + def atacar(self): + print("🏹 Atacando con un arco") + +class Herramienta: + def fabricar(self): + pass + +class Martillo(Herramienta): + def fabricar(self): + return Espada() + +class Sierra(Herramienta): + def fabricar(self): + return Arco() + +class Armero: + def fabricar_arma(self, tipo): + if tipo == "espada": + return Martillo().fabricar() + if tipo == "arco": + return Sierra().fabricar() + raise ValueError("❌ Arma no disponible. Intente de nuevo") + +while True: + tipo_arma = input("💬 ¿Qué arma desea? (espada/arco/salir): ") + tipo_arma = tipo_arma.lower().strip() + if tipo_arma == "salir": + print("🚶‍♂️ Saliendo del juego.") + break + try: + armero = Armero() + arma = armero.fabricar_arma(tipo_arma) + arma.atacar() + except ValueError as e: + print(e) +``` + +--- + +#### Ejecución + +```bash +python juego.py +``` + +```text +💬 ¿Qué arma desea? (espada/arco/salir): espada +🗡️ Atacando con una espada +💬 ¿Qué arma desea? (espada/arco/salir): arco +🏹 Atacando con un arco +💬 ¿Qué arma desea? (espada/arco/salir): hacha +❌ Arma no disponible. Intente de nuevo +💬 ¿Qué arma desea? (espada/arco/salir): salir +🚶‍♂️ Saliendo del juego. +``` + +--- + +Subimos los avances de la sesión al repositorio en **GitHub** + +```bash +git add . +git commit -m "Sesión 10" +git push origin main +``` + +--- + +#### Resumen + +- Un patrón de diseño es una solución típica para resolver problemas recurrentes en el desarrollo de software. +- Los patrones de diseño se clasifican en creacionales, estructurales y de comportamiento según su propósito. +- Los patrones creacionales ayudan a crear objetos de manera eficiente y flexible. + +--- + +- El patrón Factory pertenece a los patrones creacionales y permite crear diferentes tipos de objetos según las necesidades. +- El patrón Factory aporta flexibilidad, desacoplamiento y facilidad de mantenimiento al separar la lógica de creación de objetos de su uso. +- El patrón Factory facilita la reutilización de código, la escalabilidad y el mantenimiento al permitir modificar la forma de crear objetos sin afectar el resto del código. + +--- + +- El uso del patrón Factory puede agregar complejidad adicional y sobrecarga de rendimiento, y puede llevar a una sobreabundancia de clases si no se gestiona adecuadamente. +- El patrón Factory se representa en diagramas de clases mediante la relación entre fábricas y productos, mostrando herencia y asociación. +- La estructura del patrón Factory en Python utiliza herencia y polimorfismo para definir fábricas y productos. + +--- + +- El análisis de requisitos, características y acciones es fundamental para diseñar correctamente las clases y sus relaciones al aplicar el patrón Factory. +- Los diagramas de clases ayudan a visualizar la estructura y relaciones entre fábricas y productos en el patrón Factory. +- El patrón Factory permite crear objetos sin que el cliente conozca los detalles de la creación, delegando esa responsabilidad a una fábrica especializada. + + +--- + +#### Retos + +Crear una carpeta con el nombre "retos_sesion_10" dentro del proyecto en la raíz, en la cual por cada ejercicio debes crear los siguientes archivos: + +```bash +# Estructura de carpetas +psg-oop-2025/ + retos_sesion_10/ + ejercicio_01.md + ejercicio_01.py + ejercicio_02.md + ejercicio_02.py +``` + +--- + +1. Utilizando el patrón **Factory** + +En una heladería, los clientes pueden pedir helados de **vainilla** o **chocolate**. +Además, pueden elegir si desean su helado en **cono** o en **vaso**. + +El encargado utiliza la **máquina adecuada** para preparar cada sabor: + +- La *máquina de vainilla* solo prepara helados de vainilla. +- La *máquina de chocolate* solo prepara helados de chocolate. + +*(1/5)* + +--- + +Todos los helados comparten características en común: +- Se pueden **comer**. +- Tienen un **envase** (cono o vaso). + +Debe permitir al usuario realizar pedidos de helado mediante un **Factory**, y escribir `"salir"` para terminar el proceso. + +*(2/5)* + +--- + +- Los helados se representan como: + +`"[sabor] 🍦 en [envase]"` + +- El menú de opciones se representa como: + +```text +🍨 Pedidos de Helado 🍨 +1. Vainilla en Cono +2. Vainilla en Vaso +3. Chocolate en Cono +4. Chocolate en Vaso +Escribe "salir" para terminar. +``` + +*(3/5)* + +--- + +Debe permitir: + +Crear helados mediante una **fábrica** + +Representar cada helado en el formato especificado. + +Registrar varios pedidos hasta que el usuario escriba "salir". + +*(4/5)* + +--- + +Instrucciones: + +- Realiza el **análisis** y **diagrama de clases** para las clases `Helado`, `HeladoVainilla`, `HeladoChocolate`, `Maquina`, `MaquinaVainilla`, `MaquinaChocolate` en `ejercicio_01.md` +- Escribe el código en Python implementando el patrón Factory en `ejercicio_01.py` +- Prueba varios pedidos de helado para verificar el correcto funcionamiento + +*(5/5)* + +--- + +2. Utilizando el patrón **Factory** + +Simulador de Batalla de Monstruos + +Dos jugadores pueden elegir un **monstruo** para luchar. +Cada jugador selecciona uno de los siguientes tipos: + +- **Dragón** +- **Zombi** +- **Vampiro** + +*(1/5)* + +--- + +Cada monstruo tiene fortalezas y debilidades: + +```markdown +- Dragón: + - Fuerte contra Zombi + - Débil contra Vampiro + - Igual contra Dragón +- Zombi: + - Fuerte contra Vampiro + - Débil contra Dragón + - Igual contra Zombi +- Vampiro: + - Fuerte contra Dragón + - Débil contra Zombi + - Igual contra Vampiro +``` + +El simulador debe determinar el **resultado de la batalla** entre los monstruos elegidos, si alguno de los jugadores escribe `"salir"`, el juego termina. + +*(2/5)* + +--- + + +Los monstruos se representan como: + +`"[monstruo] 🧟‍♂️ listo para luchar"` + +El menú de selección se representa como: + +```text +🧩 Selección de Monstruos 🧩 +Jugador 1: Elige tu monstruo (dragón/zombi/vampiro): +Jugador 2: Elige tu monstruo (dragón/zombi/vampiro): +Escribe "salir" para terminar. +``` + +*(3/5)* + +--- + +Debe permitir: + +- Crear monstruos mediante una **fábrica** +- Representar cada monstruo en el formato especificado. +- Determinar el resultado de la batalla según las fortalezas y debilidades. +- Registrar varias batallas hasta que un jugador escriba "salir". + +*(4/5)* + +--- + +Instrucciones: + +- Realiza el **análisis** y **diagrama de clases** para las clases `Monstruo`, `Dragon`, `Zombi`, `Vampiro`, `Spawner`, `SpawnerDragon`, `SpawnerZombi`, `SpawnerVampiro` en `ejercicio_02.md` +- Escribe el código en Python implementando el patrón Factory en `ejercicio_02.py` +- Prueba las batallas entre diferentes monstruos para verificar el correcto funcionamiento + +*(5/5)* + +--- + + +
+
+
+
+
+ +[![GitHub](../../content/github_logo.png) ](https://github.com/python-la-paz/python-study-group-oop/tree/main/content/sesion10) + +Repositorio de la Sesión + +--- + +## Bibliografía y Referencias + +- [Factory Method](https://refactoring.guru/es/design-patterns/factory-method) +- [Patrón Factory Method Introducción y ejemplos](https://revistacompleta.com/patron-factory-method-introduccion-y-ejemplos/) +- [05-FactoryPattern](https://uetitc.github.io/ProgrammingII-2024-2/Lessons/05-FactoryPattern.html) +- [Factory Pattern](https://www.chucksacademy.com/es/topic/javascript-design-patterns/factory-pattern) +- [Object Oriented Analysis](https://www.gyata.ai/es/object-oriented-programming/object-oriented-analysis) +- [Diagrama de clases](https://diagramasuml.com/diagrama-de-clases/) +- [Guía PEP 8](https://peps.python.org/pep-0008/#class-names) +- [Mermaid Charts](https://www.mermaidchart.com/play) +- [Draw.io](https://app.diagrams.net/) +- [Python 3 Object-oriented Programming, Second Edition, Dusty Phillips](https://github.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition) + diff --git a/content/sesion10/img/factory.gif b/content/sesion10/img/factory.gif new file mode 100644 index 0000000..669256a Binary files /dev/null and b/content/sesion10/img/factory.gif differ diff --git a/content/sesion10/index.html b/content/sesion10/index.html new file mode 100644 index 0000000..b5023ba --- /dev/null +++ b/content/sesion10/index.html @@ -0,0 +1,62 @@ + + + + + + Sesión 10 | Python Study Group | OOP + + + + + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/sesion11/Readme.md b/content/sesion11/Readme.md new file mode 100644 index 0000000..d91f9c5 --- /dev/null +++ b/content/sesion11/Readme.md @@ -0,0 +1,1779 @@ + +![Python La paz](../Logo.png) + +
+
+
+ +### Sesión 11 +#### Buenas prácticas +#### Refactorización y organización + +--- + +### Antes de empezar + +--- + +#### Proyecto en VSCode + +Abre el proyecto en VSCode + +```bash +code psg-oop-2025 +``` + +Crea una carpeta con el nombre `sesion11` + +```bash +mkdir sesion11 +cd sesion11 +``` + +- Los archivos de esta sesión deben estar dentro de esta carpeta + +- Al finalizar la sesión, sube los cambios al repositorio en un commit + +--- + +#### Introducción a la refactorización + +La refactorización es el proceso de mejorar el código existente sin cambiar su **funcionalidad** externa + +--- + +#### ¿Por qué refactorizar? + +- Mejora la **legibilidad** del código +- Facilita el **mantenimiento** y la evolución del software +- Reduce la **complejidad** y el riesgo de errores + +--- + +#### ¿Qué implica la refactorización? + +- *Reorganizar* el código en módulos y clases +- *Eliminar* código duplicado +- *Renombrar* variables y funciones para que sean más descriptivas +- *Dividir* funciones grandes en funciones más pequeñas y manejables + +--- + +#### Reorganización del código + +En python se puede organizar el código en **archivos y carpetas** para mejorar la estructura del proyecto + +![Estructura de carpetas](./img/folders.png) + +--- + +En lugar de tener un *archivo gigante* con todo el código, podemos dividirlo en **varios archivos** y carpetas + +--- + +#### Buenas prácticas de organización + +- Utilizar **nombres descriptivos** para archivos y carpetas +- **Agrupar** archivos relacionados en carpetas +- **Mantener una estructura** coherente en todo el proyecto + +--- + +En Python se recomienda seguir la convención [PEP 8](https://peps.python.org/pep-0008/) + +| Elemento | Convención | Ejemplo | +| --------------------- | ---------- | ------------------------------ | +| Clases | CamelCase | `MiClase` | +| Funciones y variables | snake_case | `mi_funcion`, `mi_variable` | +| Constantes | UPPER_CASE | `MI_CONSTANTE` | +| Archivos y carpetas | snake_case | `mi_archivo.py`, `mi_carpeta/` | +| Módulos y paquetes | snake_case | `mi_modulo` | + +--- + +#### Ejemplo 01 + +```markdown +Juega "Piedra, Papel o Tijera" con un jugador y una computadora +Ingresa el nombre del jugador y elige piedra, papel o tijera +La computadora hará su elección al azar +y se determinará el ganador según las reglas del juego +Puede jugar varias rondas hasta que el jugador decida salir +El juego esta compuesto por ambos jugadores +Cuenta con un menú + +1. jugar +2. salir +``` + +Crearemos un archivo llamado `juego.md` con el **análisis y diagrama** +y el archivo `juego.py` con la **lógica** del juego + +--- + +Análisis + +```markdown +Requisitos: +- El jugador debe ingresar su nombre +- El jugador debe elegir entre piedra, papel o tijera +- La computadora debe elegir al azar entre piedra, papel o tijera +- El juego debe determinar el ganador según las reglas del juego +- El juego debe mostrar el resultado de cada partida +- El juego debe permitir al jugador jugar varias partidas +- El juego debe tener un menú para jugar o salir +- El juego esta compuesto por ambos jugadores (Jugador y Computadora) + +Objetos: +- Jugador +- Computadora +- Juego + +Características: +- Jugador: + - nombre +- Computadora: + - nombre +- Juego: + - jugador + - computadora + +Acciones: +- Jugador: + - elegir +- Computadora: + - elegir +- Juego: + - jugar + - determinar ganador +``` + +--- + +Diagrama de clases en Mermaid + +```` +```mermaid +classDiagram + class Jugador { + +nombre: string + +elegir() + } + class Computadora { + +nombre: string + +elegir() + } + class Juego { + +jugador: Jugador + +computadora: Computadora + +determinar_ganador(eleccion_jugador, eleccion_cpu) + +jugar() + } + Juego *-- Jugador + Juego *-- Computadora +``` +```` + +--- + +Diagrama de clases en Mermaid + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn" }}%% +classDiagram + class Jugador { + +nombre: string + +elegir() + } + class Computadora { + +nombre: string + +elegir() + } + class Juego { + +jugador: Jugador + +computadora: Computadora + +determinar_ganador(eleccion_jugador, eleccion_cpu) + +jugar() + } + Juego *-- Jugador + Juego *-- Computadora +``` + + +--- + +Código del juego de Piedra, Papel o Tijera + +```python [1|2-11|13-18|20-45|47-65] +import random + +class Jugador: + def __init__(self, nombre): + self.nombre = nombre + + def elegir(self): + eleccion = input(f"{self.nombre}, elige piedra, papel o tijera: ").lower() + while eleccion not in ["piedra", "papel", "tijera"]: + eleccion = input("Opción inválida. Intenta de nuevo: ").lower() + return eleccion + +class Computadora: + def __init__(self): + self.nombre = "Computadora" + + def elegir(self): + return random.choice(["piedra", "papel", "tijera"]) + +class Juego: + def __init__(self, jugador, computadora): + self.jugador = jugador + self.computadora = computadora + + def determinar_ganador(self, eleccion_jugador, eleccion_computadora): + if eleccion_jugador == eleccion_computadora: + return "Empate" + elif ( + (eleccion_jugador == "piedra" and eleccion_computadora == "tijera") or + (eleccion_jugador == "papel" and eleccion_computadora == "piedra") or + (eleccion_jugador == "tijera" and eleccion_computadora == "papel") + ): + return f"{self.jugador.nombre} gana!" + else: + return f"{self.computadora.nombre} gana!" + + def jugar(self): + eleccion_jugador = self.jugador.elegir() + eleccion_computadora = self.computadora.elegir() + + print(f"{self.jugador.nombre} eligió: {eleccion_jugador}") + print(f"{self.computadora.nombre} eligió: {eleccion_computadora}") + + resultado = self.determinar_ganador(eleccion_jugador, eleccion_computadora) + print(resultado) + +print("🎮 Bienvenido al juego de Piedra, Papel o Tijera") +nombre = input("Introduce tu nombre: ") +jugador = Jugador(nombre) +computadora = Computadora() +juego = Juego(jugador, computadora) + +while True: + print("\n--- Menú ---") + print("1. Jugar") + print("2. Salir") + opcion = input("Elige una opción: ") + + if opcion == "1": + juego.jugar() + elif opcion == "2": + print("¡Gracias por jugar! 👋") + break + else: + print("Opción inválida, intenta de nuevo.") +``` + +--- + +Probamos el juego para asegurarnos que funciona **correctamente** + +```bash +python juego.py +``` + +```text +🎮 Bienvenido al juego de Piedra, Papel o Tijera +Introduce tu nombre: Jhon +--- Menú --- +1. Jugar +2. Salir +Elige una opción: 1 +Jhon, elige piedra, papel o tijera: piedra +Jhon eligió: piedra +Computadora eligió: tijera +Jhon gana! +--- Menú --- +1. Jugar +2. Salir +Elige una opción: 2 +¡Gracias por jugar! 👋 +``` + +--- + +**Reorganizaremos** el juego en archivos dentro de una carpeta llamada `juego` + +Donde primero separaremos las partes del juego en diferentes archivos + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn" }}%% +graph TD; + A["📄 juego.py"] + B["📕 juego.md"] +``` + + +⬇ + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn" }}%% +graph TD; + A["📁 juego"] + B["📄 jugador.py"] + C["📄 computadora.py"] + D["📄 juego.py"] + E["📄 main.py"] + F["📕 juego.md"] + A --> B + A --> C + A --> D + A --> E + A --> F +``` + + +--- + +#### 1. Crear la carpeta y los archivos + +**Crear** la carpeta `juego` y los archivos vacíos dentro + +```markdown +Con `mkdir` creamos la carpeta +Con `cd` entramos a la carpeta +Con `touch` creamos los archivos vacíos +Con `tree` vemos la estructura de carpetas y archivos +``` + +```bash +mkdir juego +cd juego +touch jugador.py computadora.py juego.py main.py +tree +``` + +```bash +. +├── computadora.py +├── juego.py +├── jugador.py +└── main.py + +1 directory, 4 files +``` + +--- + +**Mover** el archivo `juego.md` a la carpeta `juego` para mantener todo organizado + +```markdown +Con `cd ..` volvemos a la carpeta anterior +Con `mv` movemos el archivo `juego.md` a la carpeta `juego` +Con `cd juego` entramos a la carpeta `juego` +Con `tree` vemos la estructura de carpetas y archivos +``` + +```bash +cd .. +mv juego.md juego +cd juego +tree +``` + +```bash +. +├── computadora.py +├── juego.md +├── juego.py +├── jugador.py +└── main.py + +1 directory, 5 files +``` + + +--- + +#### 2. Llenar los archivos con el código + +Ya con la estructura creada, **llenar** cada archivo con el código correspondiente + +El archivo `jugador.py` contendrá la clase `Jugador` + +```python +# jugador.py +class Jugador: + def __init__(self, nombre): + self.nombre = nombre + def elegir(self): + eleccion = input(f"{self.nombre}, elige piedra, papel o tijera: ").lower() + while eleccion not in ["piedra", "papel", "tijera"]: + eleccion = input("Opción inválida. Intenta de nuevo: ").lower() + return eleccion +``` + +--- + +El archivo `computadora.py` contendrá la clase `Computadora` + + +**Importamos** el módulo `random` porque el método `elegir` de la clase `Computadora` utiliza la función `choice` de este módulo para seleccionar una opción al azar + +```python +# computadora.py +import random +class Computadora: + def __init__(self): + self.nombre = "Computadora" + + def elegir(self): + return random.choice(["piedra", "papel", "tijera"]) +``` + + +--- + +En el archivo `juego.py` **copiar** la clase `Juego` + +```python +# juego.py +class Juego: + def __init__(self, jugador, computadora): + self.jugador = jugador + self.computadora = computadora + + def determinar_ganador(self, eleccion_jugador, eleccion_computadora): + if eleccion_jugador == eleccion_computadora: + return "Empate" + elif ( + (eleccion_jugador == "piedra" and eleccion_computadora == "tijera") or + (eleccion_jugador == "papel" and eleccion_computadora == "piedra") or + (eleccion_jugador == "tijera" and eleccion_computadora == "papel") + ): + return f"{self.jugador.nombre} gana!" + else: + return f"{self.computadora.nombre} gana!" + + def jugar(self): + eleccion_jugador = self.jugador.elegir() + eleccion_computadora = self.computadora.elegir() + + print(f"{self.jugador.nombre} eligió: {eleccion_jugador}") + print(f"{self.computadora.nombre} eligió: {eleccion_computadora}") + + resultado = self.determinar_ganador(eleccion_jugador, eleccion_computadora) + print(resultado) +``` + +--- + +Por último, el archivo `main.py` contendrá la lógica para iniciar e interactuar con el juego + +Pero primero el juego necesita tener acceso a las clases `Jugador`, `Computadora` y `Juego` que están definidas en **otros archivos** + +Por lo que debemos **importar** los archivos correspondientes + +--- +#### Importación de módulos + +Python permite **importar** módulos para reutilizar código + +Podemos importar un **módulo completo** o solo **partes** específicas de él + +Para importar un módulo completo utilizamos la palabra clave `import` seguida del nombre del módulo + +```python +import random +``` + +--- + +Para importar **una parte** específica de un módulo como una función o clase + +Utilizamos la palabra clave `from` seguida del **módulo** + +Luego la palabra `import` seguido del nombre de la **función o clase** + +```python +from random import choice +``` + +--- + +Es **recomendable** importar solo lo que se necesita para evitar *conflictos de nombres* y mejorar la **legibilidad** del código + +> No es necesario importar todo el módulo, +> +> si solo se utiliza una parte de él + +--- + +Cada archivo que hemos creado **puede ser importado** en Python + +Para importar las clases desde los archivos que hemos creado, utilizamos la sintaxis + +`from nombre_archivo import NombreClase`. + +--- + +En el archivo `main.py`, importamos las clases de la siguiente manera + +```python +# main.py +from jugador import Jugador +from computadora import Computadora +from juego import Juego +``` + +Esto nos permite utilizar las clases `Jugador`, `Computadora` y `Juego` en el archivo `main.py` + +--- + +El archivo `main.py` quedaría así + +```python +# main.py +from jugador import Jugador +from computadora import Computadora +from juego import Juego + + +print("🎮 Bienvenido al juego de Piedra, Papel o Tijera") +nombre = input("Introduce tu nombre: ") +jugador = Jugador(nombre) +computadora = Computadora() +juego = Juego(jugador, computadora) + +while True: + print("\n--- Menú ---") + print("1. Jugar") + print("2. Salir") + opcion = input("Elige una opción: ") + + if opcion == "1": + juego.jugar() + elif opcion == "2": + print("¡Gracias por jugar! 👋") + break + else: + print("Opción inválida, intenta de nuevo.") +``` + +--- + +#### 3. Ejecutar el juego + +Después de organizar el código en **archivos separados**, probamos que todo funcione correctamente + +donde el archivo `main.py` es el **punto de entrada** del juego + +Para ejecutar, debemos estar en el directorio `juego` y ejecutar el archivo `main.py` + + +--- + +Ejecutamos el juego + +```bash +cd juego # Si no estamos ya en la carpeta juego +python main.py +``` + +```text +🎮 Bienvenido al juego de Piedra, Papel o Tijera +Introduce tu nombre: Jhon + +--- Menú --- +1. Jugar +2. Salir +Elige una opción: 1 +Jhon, elige piedra, papel o tijera: tijera +Jhon eligió: tijera +Computadora eligió: piedra +Computadora gana! + +--- Menú --- +1. Jugar +2. Salir +Elige una opción: 1 +Jhon eligió: papel +Computadora eligió: papel +Empate + +--- Menú --- +1. Jugar +2. Salir +Elige una opción: 2 +¡Gracias por jugar! 👋 +``` + +--- + +Refactorizamos el código del juego de Piedra, Papel o Tijera +y lo **organizamos** en una estructura de archivos pero de *un sólo nivel* + +Podemos seguir mejorando la organización del código creando una **estructura de carpetas** +para ordenar los archivos según su **funcionalidad** + +--- +#### Estructura de carpetas + +Organizaremos en carpetas para **agrupar** clases y lógica del juego + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn" }}%% +graph LR; + A["📁 juego"] + B["📁 clases"] + C["📁 logica"] + D["📄 jugador.py"] + E["📄 computadora.py"] + F["📄 juego.py"] + G["📄 main.py"] + H["📕 juego.md"] + A --> B + A --> C + B --> D + B --> E + C --> F + A --> G + A --> H +``` + + +--- + +#### 1. Crear las carpetas y los archivos + +**Crear** las carpetas `clases` y `logica` dentro de la carpeta `juego` + +```markdown +Con `cd` entramos a la carpeta `juego` +Con `mkdir` creamos las carpetas `clases` y `logica` +``` + +```bash +cd juego +mkdir clases logica +``` + +--- + +**Mover** los archivos correspondientes a cada carpeta + +```markdown +Con `mv` movemos los archivos `jugador.py` y `computadora.py` +a la carpeta `clases` +Con `mv` movemos el archivo `juego.py` a la carpeta `logica` +``` + +```bash +mv jugador.py clases +mv computadora.py clases +mv juego.py logica +``` + +--- + +**Quedará** la siguiente estructura de carpetas y archivos: + +```markdown +Con `tree` vemos la estructura de carpetas y archivos +``` + +```bash +tree +``` + +```bash +. +├── clases +│ ├── computadora.py +│ └── jugador.py +├── logica +│ └── juego.py +├── juego.md +└── main.py + + +3 directories, 5 files +``` + +--- + +#### 2. Actualizar las importaciones + +Hay que **actualizar** las importaciones en los archivos para reflejar la nueva estructura de carpetas + +En `main.py` **importar** las clases desde las nuevas ubicaciones + +Como se movió los archivos para importar ahora se utiliza la sintaxis + +`from carpeta.archivo import Clase` + +```python +# main.py +from clases.jugador import Jugador +from clases.computadora import Computadora +from logica.juego import Juego +``` + +--- + +#### 3. Ejecutar el juego + +Actualizado las importaciones para reflejar la nueva estructura de carpetas + +**Ejecutamos** el juego de la misma manera que antes: + +```bash +python main.py +``` + +--- + +Podemos mejorar más la organización del código + +creando un archivo `__init__.py` en cada carpeta para convertir las *carpetas* en **paquetes** de Python + +--- + +#### `__init__.py` + +El archivo `__init__.py` es un **archivo especial** en Python que indica que una carpeta debe ser tratada como un **paquete** + +Esto permite importar módulos desde esa carpeta de manera más sencilla + +Son **opcionales** pero es recomendable incluirlos para indicar que la carpeta es un paquete de Python + +--- + +#### 1. Crear los archivos `__init__.py` + +**Crear** un archivo `__init__.py` vacío en cada carpeta para indicar que son paquetes + +```markdown +Con `cd` entramos a la carpeta `juego` +Con `touch` creamos los archivos `__init__.py` +en las carpetas `clases` y `logica` +``` + +```bash +cd juego +touch clases/__init__.py logica/__init__.py +``` + +--- + +La **estructura** de carpetas y archivos quedará así: + +```bash +tree +``` + +```bash +. +├── clases +│ ├── __init__.py +│ ├── computadora.py +│ └── jugador.py +├── logica +│ ├── __init__.py +│ └── juego.py +├── juego.md +└── main.py + +3 directories, 7 files +``` + +--- + +#### 2. Importar desde los paquetes + +En el archivo `__init__.py` añadimos los archivos que podremos importar directamente desde el paquete + +En `clases/__init__.py` añadimos: + +```python +# clases/__init__.py +from .jugador import Jugador +from .computadora import Computadora +``` + +Al utilizar el punto `.` antes del **nombre del archivo**, indicando que importamos el archivo desde la **misma carpeta** + +--- + +En `logica/__init__.py` añadimos: + +```python +# logica/__init__.py +from .juego import Juego +``` + +Asi cada paquete podrá **importar** sus clases correspondientes sin necesidad de especificar el archivo + +--- + +#### 2. Importar desde los paquetes + +Para importar desde los paquetes en `main.py`, uilizamos el **nombre del paquete** seguido de la clase + +```python +# main.py +from clases import Jugador, Computadora +from logica import Juego +``` + +Esto permite **importar directamente** desde los paquetes sin especificar el archivo donde se encuentra + +--- + +#### 3. Ejecutar el juego + +**Organizamos** el código en **paquetes** y podemos ejecutar el juego de la misma manera que antes: + +```bash +python main.py +``` + +--- + +Después de **organizar el código**, podemos continuar **refactorizando** + +Ahora *eliminaremos* el código duplicado y dividir en funciones más pequeñas + +--- + +#### Eliminación de código duplicado + +El código duplicado es un *problema* común en la programación que hace que el código sea más difícil de *mantener y entender* + +La eliminación de código duplicado implica **identificar y eliminar** partes del **código que se repiten** innecesariamente + +y reemplazarlas por funciones o métodos reutilizables + +--- + +#### ¿Por qué eliminar el código duplicado? + +- **Mejora** la legibilidad del código +- **Facilita** el mantenimiento y la evolución del software +- **Reduce** el riesgo de errores al modificar el código + +--- + +#### Refactorización del código duplicado + +En el Jugador podemos notar que al elegir piedra, papel o tijera, +**se repite el código** para validar la elección del jugador + +```python [4-8] +# original +class Jugador: + def __init__(self, nombre): + self.nombre = nombre + def elegir(self): + eleccion = input(f"{self.nombre}, elige piedra, papel o tijera: ").lower() + while eleccion not in ["piedra", "papel", "tijera"]: + eleccion = input("Opción inválida. Intenta de nuevo: ").lower() + return eleccion +``` + +--- + +**Refactorizamos** separando las opciones válidas en una constante y +la validación de la elección en un método separado + +```python [1|6-9|11-14] +OPCIONES = ["piedra", "papel", "tijera"] +class Jugador: + def __init__(self, nombre): + self.nombre = nombre + + def validar_eleccion(self, eleccion): + while eleccion not in OPCIONES: + eleccion = input("Opción inválida. Intenta de nuevo: ").lower() + return eleccion + + def elegir(self): + opciones = ", ".join(OPCIONES) + eleccion = input(f"{self.nombre}, elige {opciones}: ").lower() + return self.validar_eleccion(eleccion) +``` + +--- + +**Refactorizar** el `input` del jugador para que sea más claro y reutilizable. + +**Definir** un `__str__` para la clase Jugador + +```python [6-7|9-10|14,19] +OPCIONES = ["piedra", "papel", "tijera"] +class Jugador: + def __init__(self, nombre): + self.nombre = nombre + + def __str__(self): + return self.nombre + + def ingresar(self, mensaje): + return input(f"{self}, {mensaje}: ").lower() + + def validar_eleccion(self, eleccion): + while eleccion not in OPCIONES: + eleccion = self.ingresar("opción inválida. Intenta de nuevo") + return eleccion + + def elegir(self): + opciones = ", ".join(OPCIONES) + eleccion = self.ingresar(f"elige {opciones}") + return self.validar_eleccion(eleccion) +``` + +--- + +En la clase Computadora, se puede hacer algo similar + +**Importar** sólo lo necesario del módulo `random` y no todo el módulo + +**Definir** un `__str__` para la clase Computadora + +```python [2,8] +# original +import random +class Computadora: + def __init__(self): + self.nombre = "Computadora" + + def elegir(self): + return random.choice(["piedra", "papel", "tijera"]) +``` + +--- + +**Importando** solo lo necesario del módulo `random` + +**Refactorizar** la clase Computadora, separando las opciones válidas en una constante y definiendo el método `__str__` + + +```python [1|2,11| 7-8] +from random import choice +OPCIONES = ["piedra", "papel", "tijera"] +class Computadora: + def __init__(self): + self.nombre = "Computadora" + + def __str__(self): + return self.nombre + + def elegir(self): + return choice(OPCIONES) +``` + +--- + +**Identificar** el código duplicado en la clase `Juego` como la impresión de las elecciones de los jugadores y sus nombres + + +```python [14-16,22-23] +class Juego: + def __init__(self, jugador, computadora): + self.jugador = jugador + self.computadora = computadora + + def determinar_ganador(self, eleccion_jugador, eleccion_computadora): + if eleccion_jugador == eleccion_computadora: + return "Empate" + elif ( + (eleccion_jugador == "piedra" and eleccion_computadora == "tijera") or + (eleccion_jugador == "papel" and eleccion_computadora == "piedra") or + (eleccion_jugador == "tijera" and eleccion_computadora == "papel") + ): + return f"{self.jugador.nombre} gana!" + else: + return f"{self.computadora.nombre} gana!" + + def jugar(self): + eleccion_jugador = self.jugador.elegir() + eleccion_computadora = self.computadora.elegir() + + print(f"{self.jugador.nombre} eligió: {eleccion_jugador}") + print(f"{self.computadora.nombre} eligió: {eleccion_computadora}") + + resultado = self.determinar_ganador(eleccion_jugador, eleccion_computadora) + print(resultado) +``` + +--- + +**Refactorizar** el archivo `juego.py` usando el método `__str__` de las clases `Jugador` y `Computadora` + +**Adicionar** un método para mostrar las elecciones + +```python [14,15,17,18,23,24] +class Juego: + def __init__(self, jugador, computadora): + self.jugador = jugador + self.computadora = computadora + + def determinar_ganador(self, eleccion_jugador, eleccion_computadora): + if eleccion_jugador == eleccion_computadora: + return "Empate" + if ( + (eleccion_jugador == "piedra" and eleccion_computadora == "tijera") or + (eleccion_jugador == "papel" and eleccion_computadora == "piedra") or + (eleccion_jugador == "tijera" and eleccion_computadora == "papel") + ): + return f"{self.jugador} gana!" + return f"{self.computadora} gana!" + + def mostrar_elecciones(self, jugador, eleccion): + print(f"{jugador} eligió: {eleccion}") + + def jugar(self): + eleccion_jugador = self.jugador.elegir() + eleccion_computadora = self.computadora.elegir() + self.mostrar_elecciones(self.jugador, eleccion_jugador) + self.mostrar_elecciones(self.computadora, eleccion_computadora) + resultado = self.determinar_ganador(eleccion_jugador, eleccion_computadora) + print(resultado) +``` + +--- + +Se **eliminó** algunos duplicados y mejorado la legibilidad del código + +**Podemos** seguir mejorando y refactorizando el código y dividiendo +en funciones más pequeñas y manejables + +--- + +El objetivo es tener un **código limpio y fácil de mantener** y también es importante +saber cuando detenerse, ya que la *refactorización excesiva* puede llevar a un código innecesariamente complejo + +--- + +Al final **actualizar** el análisis y el diagrama de clases para reflejar los cambios realizados + +--- + +#### Análisis actualizado + +```markdown +Requisitos: +- El jugador debe ingresar su nombre +- El jugador debe elegir entre piedra, papel o tijera +- La computadora debe elegir al azar entre piedra, papel o tijera +- El juego debe determinar el ganador según las reglas del juego +- El juego debe mostrar el resultado de cada partida +- El juego debe permitir al jugador jugar varias partidas +- El juego debe tener un menú para jugar o salir +- El juego esta compuesto por ambos jugadores (Jugador y Computadora) + +Objetos: +- Jugador +- Computadora +- Juego + +Características: +- Jugador: + - nombre +- Computadora: + - nombre +- Juego: + - jugador + - computadora + +Acciones: +- Jugador: + - elegir + - validar eleccion + - ingresar +- Computadora: + - elegir +- Juego: + - jugar + - determinar ganador + - mostrar elecciones +``` + +--- + +#### Diagrama de clases actualizado + +```` +```mermaid +classDiagram + class Jugador { + +nombre: string + +ingresar(mensaje) + +validar_eleccion(eleccion) + +elegir() + } + class Computadora { + +nombre: string + +elegir() + } + class Juego { + +jugador: Jugador + +computadora: Computadora + +determinar_ganador(eleccion_jugador, eleccion_cpu) + +mostrar_elecciones(jugador, eleccion) + +jugar() + } + Juego *-- Jugador + Juego *-- Computadora +``` +```` + +--- + +#### Diagrama de clases actualizado + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn" }}%% +classDiagram + class Jugador { + +nombre: string + +ingresar(mensaje) + +validar_eleccion(eleccion) + +elegir() + } + class Computadora { + +nombre: string + +elegir() + } + class Juego { + +jugador: Jugador + +computadora: Computadora + +determinar_ganador(eleccion_jugador, eleccion_cpu) + +mostrar_elecciones(jugador, eleccion) + +jugar() + } + Juego *-- Jugador + Juego *-- Computadora +``` + + +--- + +Cambiar el nombre de `juego.md` a `README.md` +será reconocido como la documentación principal + +```markdown +Con `mv` cambiamos el nombre del archivo `juego.md` a `README.md` +Con `tree` vemos la estructura de carpetas y archivos +``` + +```bash +mv juego.md README.md +tree +``` +```bash +. +├── clases +│ ├── __init__.py +│ ├── computadora.py +│ └── jugador.py +├── logica +│ ├── __init__.py +│ └── juego.py +├── README.md +└── main.py +3 directories, 7 files +``` + +--- + +#### Dependencia circular + +![Dependencia circular](./img/dependencia_circular.jpeg) + +Ocurre cuando dos o más módulos *dependen entre sí* directa o indirectamente + +--- + +La dependencia circular es *mala* + +Esto puede llevar a problemas como: + +- *Dificultad* para entender y mantener el código +- *Problemas de rendimiento* debido a la carga repetida de módulos +- *Errores en tiempo de ejecución* si los módulos no se cargan en el orden correcto + +--- + +Para evitar dependencias circulares, es recomendable: + +- **Reorganizar el código** para reducir las dependencias +- **Dividir módulos grandes** en módulos más pequeños y manejables + +--- + +Para definir el **punto de entrada** de un programa, utilizamos la variable especial `__name__` en Python + +--- + +#### La variable `__name__` + +En Python, la variable especial `__name__` se utiliza para determinar si un módulo +se está ejecutando como **programa principal** o si se está **importando** desde otro módulo + +--- + +Cuando un módulo se **ejecuta directamente**, su variable `__name__` se establece en `"__main__"` + +Cuando *se importa* desde otro módulo, su variable `__name__` se establece en el nombre del módulo + +--- + +Esto permite que el código dentro de un bloque + +`if __name__ == "__main__":` + +se ejecute cuando el módulo se **ejecuta directamente**, y NO cuando se *importa* desde otro módulo + +Es una buena práctica utilizar este bloque para definir el **punto de entrada** del programa + +--- + +**Encapsular** la lógica principal del juego dentro de una función `main` + +**Agregar** al final del archivo `main.py` el bloque `if` para llamar a la función `main` + +```python [3|24-25] +from clases import Jugador, Computadora +from logica import Juego +def main(): + print("🎮 Bienvenido al juego de Piedra, Papel o Tijera") + nombre = input("Introduce tu nombre: ") + jugador = Jugador(nombre) + computadora = Computadora() + juego = Juego(jugador, computadora) + + while True: + print("\n--- Menú ---") + print("1. Jugar") + print("2. Salir") + opcion = input("Elige una opción: ") + + if opcion == "1": + juego.jugar() + elif opcion == "2": + print("¡Gracias por jugar! 👋") + break + else: + print("Opción inválida, intenta de nuevo.") + +if __name__ == "__main__": + main() +``` + +--- + +Con esto se completó la **refactorización** y se puede los cambios al repositorio de GitHub + +```bash +git add . +git commit -m "Refactorización y organización del código" +git push +``` + +--- + +#### Ejemplo 02 + +```text +Al ejemplo anterior aún faltan algunas mejoras que hacer + +Elimina la duplicidad del código en OPCIONES almacenando +en paquete que se llame `constantes` y crea un +archivo `opciones.py` donde almacenes las opciones +válidas del juego y reglas + +El juego debe funcionar con emojis: 🧱, 📄 y ✂️ +cambiando en las constantes y la lógica del juego + +Crea las reglas del juego en un diccionario +para determinar el ganador +``` + +Realizar los cambios 4 minutos + + + +--- + +#### 1. Estructura de carpetas y archivos + +```bash +cd juego +mkdir constantes +touch constantes/__init__.py constantes/opciones.py +tree +``` + +```bash +. +├── clases +│ ├── __init__.py +│ ├── computadora.py +│ └── jugador.py +├── constantes +│ ├── __init__.py +│ └── opciones.py +├── logica +│ ├── __init__.py +│ └── juego.py +├── README.md +└── main.py +4 directories, 8 files +``` + +--- + +#### 2. Opciones válidas + +En `constantes/opciones.py` definir las opciones válidas del juego y las reglas + +```python +# constantes/opciones.py +PIEDRA = "piedra" +PAPEL = "papel" +TIJERA = "tijera" +OPCIONES = [PIEDRA, PAPEL, TIJERA] +REGLAS = { + PIEDRA: TIJERA, + PAPEL: PIEDRA, + TIJERA: PAPEL +} +``` + +--- + +En `constantes/__init__.py` importamos las opciones para que estén disponibles al importar el paquete `constantes`. + +```python +# constantes/__init__.py +from .opciones import OPCIONES, REGLAS +``` + +--- + +#### 3. Actualizar el código + +Actualizar el código en los archivos `jugador.py`, `computadora.py` utilizando las constantes definidas + +```python +# clases/jugador.py +from constantes import OPCIONES +... +``` + +```python +# clases/computadora.py +from constantes import OPCIONES +from random import choice +... +``` + +--- + +En `juego.py` actualizar las importaciones y el método `determinar_ganador` para utilizar las constantes y reglas definidas + +```python [2|4-9] +# logica/juego.py +from constantes import REGLAS +... + def determinar_ganador(self, eleccion_jugador, eleccion_computadora): + if eleccion_jugador == eleccion_computadora: + return "Empate" + if REGLAS[eleccion_jugador] == eleccion_computadora: + return f"{self.jugador} gana!" + return f"{self.computadora} gana!" +``` + +--- + +Actualizar `main.py` para que muestre las opciones válidas al jugador + +```python [2,6] +# main.py +from constantes import OPCIONES +from clases import Jugador, Computadora +from logica import Juego +def main(): + print(f"🎮 Bienvenido al juego de {', '.join(OPCIONES)}") + nombre = input("Introduce tu nombre: ") + jugador = Jugador(nombre) + computadora = Computadora() + juego = Juego(jugador, computadora) +... +``` + +--- +Reafactorizado el código para utilizar constantes y mejorar la flexibilidad del juego + +Ejecutar el juego para verificar que todo funciona correctamente: + +```bash +python main.py +``` + +--- + +Al final *modificar* las constantes para utilizar emojis en lugar de texto + +```python +# constantes/opciones.py +PIEDRA = "🧱" +PAPEL = "📄" +TIJERA = "✂️" +``` + +--- + +Ejecutamos el juego para verificar que todo funciona correctamente: + +```bash +python main.py +``` + +```text +🎮 Bienvenido al juego de 🧱, 📄, ✂️ +Introduce tu nombre: jose + +--- Menú --- +1. Jugar +2. Salir +Elige una opción: 1 +jose, elige 🧱, 📄, ✂️: 🧱 +jose eligió: 🧱 +Computadora eligió: 🧱 +Empate +``` + +--- + +Subimos los avances de la sesión al repositorio en **GitHub** + +```bash +git add . +git commit -m "Sesión 11" +git push origin main +``` + +--- + +#### Resumen + +- La refactorización es el proceso de mejorar el código existente sin cambiar su funcionalidad externa. +- Refactorizar mejora la legibilidad, facilita el mantenimiento y reduce la complejidad y el riesgo de errores. +- La refactorización implica reorganizar el código en módulos y clases, eliminar código duplicado, renombrar variables y funciones, y dividir funciones grandes en funciones más pequeñas. + +--- + +- En Python se recomienda organizar el código en archivos y carpetas para mejorar la estructura del proyecto. +- Es buena práctica utilizar nombres descriptivos para archivos y carpetas, agrupar archivos relacionados y mantener una estructura coherente. +- En Python se recomienda seguir la convención de nombres PEP 8 para clases, funciones, variables, constantes, archivos y módulos. + +--- + +- La importación de módulos permite reutilizar código y que es recomendable importar solo lo necesario para evitar conflictos y mejorar la legibilidad. +- Crear archivos `__init__.py` en las carpetas permite que sean tratados como paquetes de Python y facilita la importación de módulos. +- Eliminar código duplicado mejora la legibilidad y facilita el mantenimiento del software. + +--- + +- La dependencia circular entre módulos es mala porque dificulta el mantenimiento y puede causar errores de ejecución. +- La variable especial `__name__` en Python permite determinar si un módulo se ejecuta directamente o se importa desde otro módulo. +- Es buena práctica encapsular la lógica principal en una función y usar el bloque `if __name__ == "__main__":` para definir el punto de entrada del programa. + +--- + +- El uso de constantes y la organización en paquetes mejora la flexibilidad y mantenibilidad del código. +- La documentación y la estructura clara de carpetas y archivos son esenciales para proyectos bien organizados. + +--- + +#### Retos + +Crear una carpeta con el nombre "retos_sesion_11" dentro del proyecto en la raíz, en la cual por cada ejercicio debes crear las siguientes carpetas y archivos: + +```bash +# Estructura de carpetas +psg-oop-2025/ + retos_sesion_11/ + ejercicio_01/ + README.md + + .py + main.py + ejercicio_02/ + README.md + + .py + main.py +``` + +--- + +1. **Refactoriza** el siguiente ejemplo en una estructura de carpetas y archivos + +Un programa para gestionar una lista de tareas pendientes: + +- Permite agregar nuevas tareas +- Eliminar tareas +- Marcar tareas como completadas +- Eliminar solo tareas completadas +- Eliminar todas las tareas + +*(1/6)* + +--- + +El programa debe ser **refactorizado**: + +- **Utilizar** convenciones de estilo PEP 8 +- **Mejorar** nombres en clases, variables y métodos +- **Crear** una estructura ordenada de carpetas y módulos de Python +- **Incluir** la documentación del análisis y diagrama en un archivo README.md + +*(2/6)* + +--- + + +Separar las clases en **archivos individuales** dentro de la carpeta `ejercicio_01/models` + +Implementar una organización de carpetas y archivos como la siguiente: + +```bash +ejercicio_01/ + ├── models/ + │ ├── __init__.py + │ ├── tarea.py + │ └── lista_tareas.py + ├── main.py + └── README.md +``` + +Mantener la misma funcionalidad del programa original + +*(3/6)* + +--- + +Diagrama de clases original + +```mermaid +%%{init: {"theme": "dark", "look": "handDrawn" }}%% +classDiagram +direction LR + class Tarea { + +des + +comp + completada() + } + class ListaTareas { + +t + agregar(tarea) + ver() + completar(tarea) + eliminar(tarea) + eliminar_comp() + del_all() + } + Tarea --* ListaTareas +``` + + +Debe incluirse el análisis y diagramas en `README.md` + +*(4/6)* + +--- + +Código original + +```python +class Tarea: + def __init__(self, d): + self.des = d + self.comp = 0 + def completada(self): + self.comp = 1 + +class ListaTareas: + def __init__(self): + self.t = [] + def agregar(self, t): + self.t.append(Tarea(t)) + def ver(self): + if not self.t: + print("No hay tareas pendientes.") + else: + for i, tarea in enumerate(self.t, 1): + estado = "✔️" if tarea.comp else " " + print(f"{i}. [{estado}] {tarea.des} ") + def completar(self, i): + if 0 < i <= len(self.t): + self.t[i-1].comp = 1 + print(f"Tarea '{self.t[i-1].des}' marcada como completada.") + else: + print("Índice de tarea inválido.") + def eliminar(self, i): + if 0 < i <= len(self.t): + tarea_eliminada = self.t.pop(i-1) + print(f"Tarea '{tarea_eliminada.des}' eliminada.") + else: + print("Índice de tarea inválido.") + def eliminar_comp(self): + self.t = [tarea for tarea in self.t if not tarea.comp] + print("Tareas completadas eliminadas.") + def del_all(self): + self.t.clear() + print("Todas las tareas eliminadas.") +lista = ListaTareas() +while True: + print("\n--- Menú ---") + print("1. Agregar tarea") + print("2. Ver tareas") + print("3. Completar tarea") + print("4. Eliminar tarea") + print("5. Eliminar tareas completadas") + print("6. Eliminar todas las tareas") + print("7. Salir") + opcion = input("Elige una opción: ") + + if opcion == "1": + tarea = input("Ingrese la descripción de la tarea: ") + lista.agregar(tarea) + elif opcion == "2": + lista.ver() + elif opcion == "3": + lista.ver() + i = int(input("Ingrese el índice de la tarea a completar: ")) + lista.completar(i) + elif opcion == "4": + lista.ver() + i = int(input("Ingrese el índice de la tarea a eliminar: ")) + lista.eliminar(i) + elif opcion == "5": + lista.eliminar_comp() + elif opcion == "6": + lista.del_all() + elif opcion == "7": + print("¡Gracias por usar la aplicación! 👋") + break + else: + print("Opción inválida, intenta de nuevo.") +``` + +Debe refactorizarse y organizarse en archivos y carpetas en la carpeta `ejercicio_01` + +*(5/6)* + +--- + +- Realiza el **análisis** y **diagrama de clases** para el programa en `README.md` dentro de la carpeta `ejercicio_01` +- Refactoriza el código manteniendo la misma funcionalidad +- Escribe el código en archivos separados y organiza en carpetas en `ejercicio_01` +- Utiliza **nombres descriptivos** y sigue las buenas prácticas de **PEP 8** +- Prueba que el programa funcione correctamente + +*(6/6)* + +--- + +2. La Biblioteca Municipal iniciará la digitalización de su registro de préstamos +Actualmente, todos los datos se anotan manualmente en un cuaderno: + +Información de **libros**: título, autor e ISBN +Información de **usuarios**: nombre + +El objetivo es digitalizar el flujo de préstamos de libros + +*(1/5)* + +--- + +El flujo de préstamos es el **siguiente**: + +1. Un usuario ingresa su nombre +2. Se lista los libros +3. El usuario elige un libro para prestar +4. Se registra la lista de prestados con el nombre del usuario y el libro prestado +5. Puede prestarse más de un libro al mismo usuario +6. Deben devolver todos los libros a la vez +7. Se puede ver la lista de libros prestados y los usuarios que los tienen +8. Para salir se debe ingresar "salir" + +*(2/5)* + +--- + +- **Crear** clases para representar `Libro`, `Usuario` +- **Organizar** el código en una estructura ordenada de carpetas y módulos de Python +- **Incluir** la documentación del análisis y diagrama en un archivo `README.md` +- **Mantener** la funcionalidad descrita en el flujo de préstamos +- **Utilizar** nombres descriptivos para clases, variables y métodos + +*(3/5)* + +--- + +La estructura de carpetas y archivos debe ser similar a la siguiente: + +```bash +ejercicio_02/ + ├── modelos/ + │ ├── __init__.py + │ ├── libro.py + │ └── usuario.py + ├── logica/ + │ │── __init__.py + │ └── biblioteca.py + ├── main.py + └── README.md +``` + +*(4/5)* + +--- + +- Realizar el análisis y diagrama de clases para el programa en `README.md` dentro de la carpeta `ejercicio_02` +- Refactorizar el código manteniendo la misma funcionalidad +- Escribir el código en archivos separados y organizar en carpetas en `ejercicio_02` +- Utilizar nombres descriptivos y seguir las buenas prácticas de PEP 8 +- Probar que el programa funcione correctamente + +*(5/5)* + +--- + + + +
+
+
+
+
+ +[![GitHub](../../content/github_logo.png) ](https://github.com/python-la-paz/python-study-group-oop/tree/main/content/sesion11) + +Repositorio de la Sesión + +--- + +## Bibliografía y Referencias + +- [Best Practices for Refactoring Code](https://www.freecodecamp.org/news/best-practices-for-refactoring-code/) +- [Python Refactoring](https://realpython.com/python-refactoring/) +- [Guía PEP 8](https://peps.python.org/pep-0008/#class-names) +- [Mermaid Charts](https://www.mermaidchart.com/play) +- [Draw.io](https://app.diagrams.net/) +- [Python 3 Object-oriented Programming, Second Edition, Dusty PhillipsDusty Phillips](https://github.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition) +- [Objetos en programación](https://ebac.mx/blog/objeto-en-programacion) +- [Enfoque orientado a objetos](https://1library.co/article/enfoque-orientado-a-objetos-base-te%C3%B3rica.qvld461y) +- [OOAD](https://www.tutorialspoint.com/object_oriented_analysis_design/ooad_object_oriented_analysis.htm) diff --git a/content/sesion11/img/dependencia_circular.jpeg b/content/sesion11/img/dependencia_circular.jpeg new file mode 100644 index 0000000..4f3d683 Binary files /dev/null and b/content/sesion11/img/dependencia_circular.jpeg differ diff --git a/content/sesion11/img/folders.png b/content/sesion11/img/folders.png new file mode 100644 index 0000000..d4a16a2 Binary files /dev/null and b/content/sesion11/img/folders.png differ diff --git a/content/sesion11/index.html b/content/sesion11/index.html new file mode 100644 index 0000000..92fbc15 --- /dev/null +++ b/content/sesion11/index.html @@ -0,0 +1,62 @@ + + + + + + Sesión 11 | Python Study Group | OOP + + + + + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/sesion12/Readme.md b/content/sesion12/Readme.md new file mode 100644 index 0000000..af674b9 --- /dev/null +++ b/content/sesion12/Readme.md @@ -0,0 +1,1565 @@ + +![Python La paz](../Logo.png) + +
+
+
+ +### Sesión 12 +#### Buenas prácticas +#### Anotaciones, Documentación y Principios + +--- + +### Antes de empezar + +--- + +#### Proyecto en VSCode + +Abre el proyecto en VSCode + +```bash +code psg-oop-2025 +``` + +Crea una carpeta con el nombre `sesion12` + +```bash +mkdir sesion12 +cd sesion12 +``` + +- Los archivos de esta sesión deben estar dentro de esta carpeta + +- Al finalizar la sesión, sube los cambios al repositorio en un commit + +--- + +**Python** es uno de los lenguajes más populares y entre sus características +se encuentra la **facilidad** para escribir código + +El **tipado dinámico** de Python permite a los desarrolladores escribir código +rápido y flexible + +--- + +Pero esto también puede llevar a *errores* difíciles de detectar si no se tiene *cuidado* + +--- + +Una **variable** puede **cambiar de tipo** en cualquier momento, lo que puede causar *errores* en tiempo de ejecución + +Desde la terminal interactiva de Python + +```python +def sumar(a, b): + return a + b +x = 5 # x es un entero +y = 10 # y es un entero +print(sumar(x, y)) # Funciona correctamente +x = "5" # x ahora es una cadena +print(sumar(x, y)) # Error en tiempo de ejecución: TypeError +``` + +```text +15 +ERROR! +Traceback (most recent call last): + File "", line 4, in + File "", line 2, in sumar +TypeError: unsupported operand type(s) for +: 'int' and 'str' +``` + +--- + +Este tipo de *errores* puede ser difícil de *detectar y corregir*, especialmente en proyectos grandes + +Para lo cual, en **Python** se ha introducido el concepto de **anotaciones de tipo** + +--- + +#### Anotaciones + +Python desde su versión **3.6** introdujo el concepto de **anotaciones de tipo** +que permiten a los desarrolladores especificar el tipo de datos esperado +para + +- Variables +- Parámetros de funciones +- Valores de retorno de funciones +- Atributos de clases + +--- + +Esto **ayuda** a mejorar la **legibilidad** del código y a **detectar errores** +antes de que se ejecuten + +Además, los **editores de código** y herramientas de análisis estático +pueden utilizar estas anotaciones para proporcionar **sugerencias** y *advertencias* + +![Anotaciones de tipo](./img/annotation_01.png) + +--- + +Crearemos un archivo llamado `anotaciones.py` en la carpeta `sesion12` + +```bash +touch anotaciones.py +``` + +--- + +#### Anotaciones en variables + +Las anotaciones de tipo en **variables** se utilizan para indicar el tipo de dato +que se espera que contenga una variable + +```python +edad: int = 30 +nombre: str = "Jhon" +altura: float = 1.75 +activo: bool = True +print(type(edad)) # +print(type(nombre)) # +print(type(altura)) # +print(type(activo)) # +``` + +Estas **anotaciones** no afectan el comportamiento del código, +pero proporcionan **información adicional** para los desarrolladores + +--- + +De esta manera, podemos ver que + +- `edad` es un **entero** +- `nombre` es una **cadena** +- `altura` es un **flotante** +- `activo` es un **booleano** + +Esto ayuda a los desarrolladores a entender mejor el **propósito** de cada variable + +--- + +También se pueden utilizar anotaciones de tipo para **estructuras** de datos donde +se incluyen los **tipos de los elementos** que contienen + +En el caso de listas se utiliza la sintaxis + +`list[tipo]` + +- `list` es la estructura de datos +- `tipo` es el tipo de los elementos que contiene la lista + +--- + +En el caso de diccionarios se utiliza la sintaxis + +`dict[clave_tipo, valor_tipo]` + +- `dict` es la estructura de datos +- `clave_tipo` es el tipo de las claves +- `valor_tipo` es el tipo de los valores + +--- + +En el caso de tuplas se utiliza la sintaxis + +`tuple[tipo1, tipo2, ...]` + +- `tuple` es la estructura de datos +- `tipo1`, `tipo2`, `...` Son los tipos de los elementos que contiene la tupla + + +--- + +Ejemplos de anotaciones en estructuras de datos + +```python +numeros: list[int] = [] +nombres: dict[str, str] = {} +tuplas: tuple[int, str] = () +print(type(numeros)) # +print(type(nombres)) # +print(type(tuplas)) # +``` + +- `numeros`: lista de enteros +- `nombres`: diccionario con claves y valores de tipo cadena +- `tuplas` : tuplas de enteros y cadenas + +--- + +*Sin* anotaciones + +![Sin anotaciones](./img/annotation_02.png) + +**Con** anotaciones + +![Con anotaciones](./img/annotation_03.png) + +--- + +#### Anotaciones en funciones + +Se utilizan para indicar los tipos de datos que se espera que contengan los **parámetros** +y el tipo de dato que se espera que **retorne** + +Los parámetros de una función se anotan utilizando la sintaxis + +`parametro: tipo` + +y el tipo de retorno se anota utilizando la sintaxis + +`-> tipo` + +El tipo de retorno se coloca antes de los dos puntos `:` + +--- + +Por ejemplo, si tenemos una función que suma **dos números enteros** y retorna un **entero** + +```python +def sumar(a: int, b: int) -> int: + return a + b +``` + +- La función `sumar` espera dos parámetros +- `a` y `b` de tipo entero +- Retorna un valor de tipo entero + +--- + +*Sin* anotaciones + +![Sin anotaciones](./img/annotation_04.png) + +**Con** anotaciones + +![Con anotaciones](./img/annotation_05.png) + +--- + +Se pueden utilizar anotaciones de tipo para funciones que aceptan +parámetros de **estructuras** de datos + +Por ejemplo, si tenemos una función que recibe una **lista de enteros** y +retorna una **lista de enteros** que son los cuadrados de los números + +```python +def cuadrados(numeros: list[int]) -> list[int]: + return [n ** 2 for n in numeros] +numeros = cuadrados([2,4,6]) +print (numeros) +``` + +- La función `cuadrados` espera un parámetro +- `numeros` que es una lista de enteros +- Retorna una lista de enteros + +--- + +Las anotaciones nos ayudan a saber que la variable `numeros` que almacena el resultado de la función +es una **lista de enteros** + +![Con anotaciones](./img/annotation_06.png) + +--- + +#### Anotaciones en clases + +Las anotaciones de tipo también se pueden utilizar en clases para indicar +los tipos de datos de los **atributos** de una clase + +Por ejemplo, si tenemos una clase `Persona` con atributos de clase `especie` +y atributos de instancia `nombre` y `edad` + +--- + +Ejemplo de anotaciones en clases + +```python +class Persona: + especie: str = "Homo sapiens" + def __init__(self, nombre: str, edad: int): + self.nombre: str = nombre + self.edad: int = edad +``` + +- Clase `Persona` tiene un atributos +- Atributo `especie` es de tipo cadena con valor por defecto `"Homo sapiens"` +- Atributo `nombre` de tipo cadena +- Atributo `edad` de tipo entero + +--- + +**Con** anotaciones en atributos y en el constructor al **instanciar** la clase +muestra el editor los tipos de datos que necesita la clase + +![Con anotaciones](./img/annotation_07.png) + +--- + +Existe otro estilo de anotaciones usado mayormente en versiones anteriores a *Python 3.8* + +importando el módulo `typing` y las clases `List`, `Dict`, `Tuple`, etc. + +--- + +Si tenemos una función que recibe una **lista de enteros** y retorna una **lista de enteros** + +```python +from typing import List +def cuadrados(numeros: List[int]) -> List[int]: + return [n ** 2 for n in numeros] +numeros = cuadrados([2, 4, 6]) +print(numeros) +``` + +Actualmente, este estilo es menos común y se recomienda utilizar las anotaciones +directamente sin importar el módulo `typing` + +--- + +Es importante recordar que las anotaciones de tipo en Python **son opcionales** +y **NO afectan el comportamiento** del código en tiempo de ejecución + +--- + +Pero no es suficiente para escribir código limpio y mantenible + +También es importante **documentar el código** +para que se pueda entender fácilmente su propósito y funcionamiento + +--- + +#### Documentación + +La documentación es una **parte esencial** del desarrollo de software +ya que ayuda a los desarrolladores a entender el **propósito y funcionamiento** del código + +En las **clases** podemos utilizar cadenas de documentación *docstrings* +para documentar el propósito de la clase, sus atributos y métodos. + +--- + +Se coloca entre comillas triples `"""` justo después de la definición de la clase +o del método + +Los **editores de código** y herramientas de análisis estático utilizan +estas cadenas de documentación para mostrar **sugerencias** y *advertencias* sobre el uso del código + +--- +Crearemos un archivo llamado `documentacion.py` en la carpeta `sesion12` + +```bash +touch documentacion.py +``` + +--- + +#### Documentación en clases + +Por ejemplo, Una clase `Persona` con un atributo `nombre` y un método `saludar` + +*Sin* documentación + +```python +class Persona: + def __init__(self, nombre: str): + self.nombre: str = nombre + def saludar(self): + print(f"Hola, mi nombre es {self.nombre}") +jhon = Persona("Jhon") +jhon.saludar() +``` + + +![Sin documentación I](./img/documentation_01.png) +![Sin documentación II](./img/documentation_02.png) + +--- + +**Con** documentación + +```python +class Persona: + """Clase que representa a una persona.""" + def __init__(self, nombre: str): + """Inicializa una nueva instancia de la clase Persona. + + Args: + nombre (str): El nombre de la persona. + """ + self.nombre: str = nombre + + def saludar(self): + """Imprime un saludo con el nombre de la persona.""" + print(f"Hola, mi nombre es {self.nombre}") +jhon = Persona("Jhon") +jhon.saludar() +``` + +--- + +![Con documentación I](./img/documentation_03.png) + +![Con documentación II](./img/documentation_04.png) + +--- + +Existen diferentes convenciones para escribir **documentación en Python** +la [PEP 257](https://peps.python.org/pep-0257/) establece la base para las cadenas de documentación + +Existen diferentes estilos de documentación + +- Google +- NumPy +- reStructuredText (reST) + +Cada uno tiene su propia **sintaxis y convenciones** + +--- + +Usos **típicos** de cada estilo + +| Estilo | Uso típico | +| ----------------------- | -------------------------------------------- | +| Google | Documentación de proyectos y bibliotecas | +| NumPy | Documentación científica y técnica | +| reStructuredText (reST) | Documentación profesional, grandes proyectos | + + +--- + +**Comparación** entre estilos + +| Estilo | Ventajas | Desventajas | +| ------ | --------------------------- | ------------------------------------------------ | +| Google | Fácil de leer y escribir | Menos detallado para proyectos complejos | +| NumPy | Estructurado y detallado | Puede ser más difícil de leer para principiantes | +| reST | Muy detallado y profesional | Más complejo de escribir menos amigable | + +--- + +**Legibilidad** de cada estilo + +| Estilo | Legibilidad | +| ------ | --------------------------------------------------------------------------------- | +| Google | Claro y conciso, fácil para humanos | +| NumPy | Muy estructurado, detallado, generado por herramientas | +| reST | Muy detallado, profesional, orientado a herramientas de documentación como Sphinx | + + +--- + +#### Estilo Google + +Se utiliza la siguiente sintaxis para documentar + +```python [2-17] +def funcion(parametro1: tipo, parametro2: tipo) -> tipo: + """Descripción breve de la función + + Args: + parametro1 (tipo): Descripción del primer parámetro + parametro2 (tipo): Descripción del segundo parámetro + + Returns: + tipo: Descripción del valor de retorno + + Raises: + TipoDeError: Condiciones que causan este error + + Examples: + >>> funcion(valor1, valor2) + resultado_esperado + """ + pass +``` + + +--- + +Crea el archivo `estilo_google.py` en la carpeta `sesion12` + +Documenta la siguiente función que devuelve la secuencia de Fibonacci hasta el n-ésimo número +usando el estilo Google + +```python +def fibonacci(n: int) -> str: + if n < 0: + raise ValueError("El índice no puede ser negativo") + fib = [0, 1] + for i in range(2, n): + fib.append(fib[i - 1] + fib[i - 2]) + return ', '.join(str(x) for x in fib[:n]) +print (fibonacci(5)) +``` + +--- + +Documentación estilo Google + +```python [2|4-5|7-8|10-11|13-16] +def fibonacci(n: int) -> str: + """Devuelve la secuencia de Fibonacci hasta el n-ésimo número. + + Args: + n (int): El índice del número de Fibonacci a calcular. + + Returns: + str: La secuencia de Fibonacci hasta el n-ésimo número. + + Raises: + ValueError: Si n es negativo. + + Examples: + >>> fibonacci(5) + '0, 1, 1, 2, 3' + """ + if n < 0: + raise ValueError("El índice no puede ser negativo") + fib = [0, 1] + for i in range(2, n): + fib.append(fib[i - 1] + fib[i - 2]) + return ', '.join(str(x) for x in fib[:n]) +print (fibonacci(5)) +``` + +--- +El editor de código **interpreta la documentación** y proporcionar sugerencias de autocompletado + +![Estilo Google](./img/documentation_05.png) + + +--- + +#### Estilo NumPy + +Se utiliza la siguiente sintaxis para documentar + +```python +def funcion(parametro1: tipo, parametro2: tipo) -> tipo: + """ + Descripción breve de la función. + + Parameters + ---------- + parametro1 : tipo + Descripción del primer parámetro. + parametro2 : tipo + Descripción del segundo parámetro. + + Returns + ------- + tipo + Descripción del valor de retorno. + + Raises + ------ + TipoDeError + Condiciones que causan este error. + + Examples + -------- + >>> funcion(valor1, valor2) + resultado_esperado + """ + pass +``` + +--- + +Crea el archivo `estilo_numpy.py` en la carpeta `sesion12` + +Documenta la siguiente función que devuelve la secuencia de Fibonacci hasta el n-ésimo número + +```python +def fibonacci(n: int) -> str: + if n < 0: + raise ValueError("El índice no puede ser negativo") + fib = [0, 1] + for i in range(2, n): + fib.append(fib[i - 1] + fib[i - 2]) + return ', '.join(str(x) for x in fib[:n]) +print (fibonacci(5)) +``` + +--- + +Documentación estilo NumPy + +```python [3|5-8|10-13|15-18|20-23] +def fibonacci(n: int) -> str: + """ + Devuelve la secuencia de Fibonacci hasta el n-ésimo número. + + Parameters + ---------- + n : int + El índice del número de Fibonacci a calcular. + + Returns + ------- + str + La secuencia de Fibonacci hasta el n-ésimo número. + + Raises + ------ + ValueError + Si n es negativo. + + Examples + -------- + >>> fibonacci(5) + '0, 1, 1, 2, 3' + """ + if n < 0: + raise ValueError("El índice no puede ser negativo") + fib = [0, 1] + for i in range(2, n): + fib.append(fib[i - 1] + fib[i - 2]) + return ', '.join(str(x) for x in fib[:n]) + +print(fibonacci(5)) +``` + +--- +El editor de código **interpretar la documentación** y proporcionar sugerencias de autocompletado + +![Estilo NumPy](./img/documentation_06.png) + +--- + +#### Estilo reStructuredText (reST) + +Se utiliza la siguiente sintaxis para documentar + +```python +def funcion(parametro1: tipo, parametro2: tipo) -> tipo: + """ + Descripción breve de la función. + + :param parametro1: Descripción del primer parámetro. + :type parametro1: tipo + :param parametro2: Descripción del segundo parámetro. + :type parametro2: tipo + :return: Descripción del valor de retorno. + :rtype: tipo + :raises TipoDeError: Condiciones que causan este error. + :example: + >>> funcion(valor1, valor2) + resultado_esperado + """ + pass +``` + +--- + +Crea el archivo `estilo_rest.py` en la carpeta `sesion12` + +Documenta la siguiente función que devuelve la secuencia de Fibonacci hasta el n-ésimo número + +```python +def fibonacci(n: int) -> str: + if n < 0: + raise ValueError("El índice no puede ser negativo") + fib = [0, 1] + for i in range(2, n): + fib.append(fib[i - 1] + fib[i - 2]) + return ', '.join(str(x) for x in fib[:n]) +print (fibonacci(5)) +``` + +--- + +Documentación estilo reST + +```python [3|5-6|7-8|9|10-12] +def fibonacci(n: int) -> str: + """ + Devuelve la secuencia de Fibonacci hasta el n-ésimo número. + + :param n: El índice del número de Fibonacci a calcular. + :type n: int + :return: La secuencia de Fibonacci hasta el n-ésimo número. + :rtype: str + :raises ValueError: Si n es negativo. + :example: + >>> fibonacci(5) + '0, 1, 1, 2, 3' + """ + if n < 0: + raise ValueError("El índice no puede ser negativo") + fib = [0, 1] + for i in range(2, n): + fib.append(fib[i - 1] + fib[i - 2]) + return ', '.join(str(x) for x in fib[:n]) + +print(fibonacci(5)) +``` + +--- + +El editor de código **interpretar la documentación** y proporcionar sugerencias de autocompletado + +![Estilo reST](./img/documentation_07.png) + +--- + +Todos los estilos son **válidos** y tienen sus propias ventajas y desventajas + +![Estilo Google](./img/documentation_05.png) +![Estilo NumPy](./img/documentation_06.png) +![Estilo reST](./img/documentation_07.png) + +--- + +**Elige** un estilo de documentación y sé **consistente** en su uso a lo largo de tu código + +Esto **facilitará** la lectura y comprensión del código para ti y para otros desarrolladores + +--- + +Añadir anotaciones de tipo y documentación a tu código es una **buena práctica** + +Existen otras buenas prácticas como los **principios de desarrollo** + +que ayudan a escribir código elegante y mantenible + +--- + +#### Principios de desarrollo + +Los principios de desarrollo son un conjunto de **buenas prácticas** +que ayudan a los desarrolladores a escribir código limpio, mantenible y escalable + +Al igual que los *patrones de diseño*, los principios de desarrollo +proporcionan soluciones a problemas comunes en el desarrollo de software + +--- + +Algunos de los principios más importantes son: + +- **KISS** (Keep It Simple, Stpid) +- **DRY** (Don't Repeat Yourself) +- **YAGNI** (You Aren't Gonna Need It) + +--- + +#### KISS (Keep It Simple, St*pid) + +El principio KISS enfatiza la importancia de mantener **el código simple** y fácil de entender + +Un código simple es **más fácil** de leer, mantener y depurar + +--- + +Se resume en + +- **Simplicidad**: Las soluciones deben ser lo más simples posible evitando características innecesarias +- **Claridad**: El código debe ser claro y fácil de entender + +--- + +- **Minimalismo**: Evitar la complejidad innecesaria y centrarse en lo esencial +- **Eficiencia**: Las soluciones simples son más eficientes +- *Evite la sobreingeniería*: No agregue características o funcionalidades innecesarias + + +--- + +Una forma de aplicarlo es + +- **Dividir** problemas complejos en partes más pequeñas y manejables +- *Evitar* la sobreingeniería y **centrarse** en lo esencial +- **Utilizar** nombres claros y descriptivos para variables, funciones y clases + +--- + +#### Ejemplo KISS + +Crear el archivo `kiss.py` en la carpeta `sesion12` + +```text +La primera calculadora de un estudiante sólo sabe +sumar dos números enteros, tiene una clase SumaDeNumeros +con el método operar que realiza la suma. +Sin embargo, no es muy claro, ni simple. +Simplifica el código aplicando el principio KISS +``` + +```python [1-11|13-18] +class SumaDeNumeros: + def __init__(self, a, b): + self.a = a + self.b = b + + def operar(self): + return self.a + self.b + +suma = SumaDeNumeros(5, 7) +print(suma.operar()) +``` + +--- + +Solución simple + +```python +class Calculadora: + def sumar(self, a, b): + return a + b +calc = Calculadora() +print(calc.sumar(5, 7)) +``` + +--- + +Solución con anotaciones y documentación + +```python +class Calculadora: + """Clase que representa una calculadora simple.""" + + def sumar(self, a: int, b: int) -> int: + """Suma dos números enteros. + + Args: + a (int): El primer número. + b (int): El segundo número. + + Returns: + int: La suma de los dos números. + """ + return a + b +calc = Calculadora() +print(calc.sumar(5, 7)) +``` + +--- + +#### Ejercicio para ti (01) + +```text +Una calculadora de areas que pueda calcular +el área de rectángulos y cuadrados. +Donde el código tiene mucha complejidad +Simplifica el código aplicando el principio KISS +Después de simplificar, agrega anotaciones y documentación +``` + +Crea el archivo `areas.py` en la carpeta `sesion12` + +--- + +Realizar los cambios 4 minutos + +```python +class Area: + def __init__(self, tipo, lado1, lado2=None): + self.tipo = tipo + self.lado1 = lado1 + self.lado2 = lado2 + def calcular(self): + if self.tipo == "rectangulo": + return self.lado1 * self.lado2 + elif self.tipo == "cuadrado": + return self.lado1 ** 2 +area_rectangulo = Area("rectangulo", 5, 10) +print(area_rectangulo.calcular()) +area_cuadrado = Area("cuadrado", 5) +print(area_cuadrado.calcular()) +``` + + + +--- + +Solución simple + +```python +class Calculadora: + def area(self, lado1, lado2): + return lado1 * lado2 +calculadora = Calculadora() +print(calculadora.area(5, 10)) # Área del rectángulo +print(calculadora.area(5, 5)) # Área del cuadrado +``` + +```bash +python areas.py +``` + +```text +50 +25 +``` + +--- + +Solución con anotaciones y documentación + +```python +class Calculadora: + """Clase que representa una calculadora simple.""" + + def area(self, lado1: int, lado2: int) -> int: + """Calcula el área de un rectángulo o cuadrado. + + Args: + lado1 (int): El primer lado. + lado2 (int): El segundo lado. + + Returns: + int: El área calculada. + """ + return lado1 * lado2 +calculadora = Calculadora() +print(calculadora.area(5, 10)) # Área del rectángulo +print(calculadora.area(5, 5)) # Área del cuadrado +``` + + +--- + +#### DRY (Don't Repeat Yourself) + +El principio DRY enfatiza la importancia de **evitar la duplicación** de código + +El código *duplicado* puede llevar a errores y dificultades en el mantenimiento + +--- + +Se resume en + +- **Unicidad**: Cada pieza de conocimiento o lógica debe tener una única representación en el código +- **Mantenimiento**: El código duplicado es más difícil de mantener y actualizar +- **Reutilización**: Fomenta la reutilización de código a través de funciones, clases y módulos + +--- + +- **Reducción de errores**: Menos duplicación significa menos oportunidades para errores +- **Eficiencia**: Un código sin duplicación es más eficiente +- **Claridad**: Un código sin duplicación es más claro y fácil de entender + + +--- + +Una forma de aplicarlo es + +- **Identificar** y eliminar código duplicado +- **Utilizar** funciones, clases y módulos para reutilizar código +- **Mantener** el código DRY durante el desarrollo y la refactorización + +--- + +#### Ejemplo DRY + +Crear el archivo `dry.py` en la carpeta `sesion12` + +```text +Calcular el área de un rectángulo y un cuadrado, +pero el código tiene duplicación +Utiliza el principio DRY para eliminar la duplicación +Unifica el código para que el cálculo del área +sea realizado por una sola función +``` + + +```python [1-7|9-13] +# Código duplicado +def area_rectangulo(lado1, lado2): + return lado1 * lado2 +def area_cuadrado(lado): + return lado * lado +print(area_rectangulo(5, 10)) +print(area_cuadrado(5)) +``` + +--- + +Código sin duplicación + +```python + +# Código sin duplicación +def area(lado1, lado2): + return lado1 * lado2 +print(area(5, 10)) # Área del rectángulo +print(area(5, 5)) # Área del cuadrado +``` + +--- + +Código sin duplicación con anotaciones y documentación + +```python +def area(lado1: int, lado2: int) -> int: + """Calcula el área de un rectángulo o cuadrado. + + Args: + lado1 (int): El primer lado. + lado2 (int): El segundo lado. + + Returns: + int: El área calculada. + """ + return lado1 * lado2 +print(area(5, 10)) # Área del rectángulo +print(area(5, 5)) # Área del cuadrado +``` + +--- + +#### Ejercicio para ti (02) + +```text +Una tienda en línea permite pagar con diferentes métodos de pago: +- Tarjeta +- Transferencia +- QR +Y por cada canal aplica un impuesto diferente +- Tarjeta 5% +- Transferencia 2% +- QR 1% +Simplifica el código aplicando el principio DRY +``` + +Crea archivo `tienda.py` en la carpeta `sesion12` + +--- + +Realizar los cambios 4 minutos + +```python +class Tienda: + def impuesto_tarjeta(self, monto): + return monto * 0.05 + def impuesto_transferencia(self, monto): + return monto * 0.02 + def impuesto_qr(self, monto): + return monto * 0.01 + def pagar_tarjeta(self, monto): + impuesto = self.impuesto_tarjeta(monto) + total = monto + impuesto + print(f"Pago con tarjeta: Monto={monto}, Impuesto={impuesto}, Total={total}") + def pagar_transferencia(self, monto): + impuesto = self.impuesto_transferencia(monto) + total = monto + impuesto + print(f"Pago con transferencia: Monto={monto}, Impuesto={impuesto}, Total={total}") + def pagar_qr(self, monto): + impuesto = self.impuesto_qr(monto) + total = monto + impuesto + print(f"Pago con QR: Monto={monto}, Impuesto={impuesto}, Total={total}") +tienda = Tienda() +tienda.pagar_tarjeta(100) +tienda.pagar_transferencia(100) +tienda.pagar_qr(100) +``` + + + +--- + +Solución simple + +```python +class Tienda: + def calcular_impuesto(self, monto, tasa): + return monto * tasa + def pagar(self, monto, metodo): + tasas = { + "tarjeta": 0.05, + "transferencia": 0.02, + "qr": 0.01 + } + tasa = tasas.get(metodo, 0) + impuesto = self.calcular_impuesto(monto, tasa) + total = monto + impuesto + print(f"Pago con {metodo}: Monto={monto}, Impuesto={impuesto}, Total={total}") +tienda = Tienda() +tienda.pagar(100, "tarjeta") +tienda.pagar(100, "transferencia") +tienda.pagar(100, "qr") +``` + +```bash +python tienda.py +``` + +```text +Pago con tarjeta: Monto=100, Impuesto=5.0, Total=105.0 +Pago con transferencia: Monto=100, Impuesto=2.0, Total=102.0 +Pago con qr: Monto=100, Impuesto=1.0, Total=101.0 +``` + +--- + +Solución con anotaciones y documentación + +```python +class Tienda: + """Clase que representa una tienda en línea.""" + + def calcular_impuesto(self, monto: float, tasa: float) -> float: + """Calcula el impuesto basado en el monto y la tasa. + + Args: + monto (float): El monto sobre el cual se calcula el impuesto. + tasa (float): La tasa de impuesto. + + Returns: + float: El monto del impuesto calculado. + """ + return monto * tasa + def pagar(self, monto: float, metodo: str) -> None: + """Procesa el pago utilizando el método especificado. + Args: + monto (float): El monto a pagar. + metodo (str): El método de pago (tarjeta, transferencia, qr). + """ + tasas = { + "tarjeta": 0.05, + "transferencia": 0.02, + "qr": 0.01 + } +tienda = Tienda() +tienda.pagar(100, "tarjeta") +tienda.pagar(100, "transferencia") +tienda.pagar(100, "qr") +``` + +--- + +#### YAGNI (You Aren't Gonna Need It) + +El principio YAGNI enfatiza la importancia de *NO agregar* funcionalidades +o características que *no son necesarias* en el momento + +Ayuda a evitar la *sobreingeniería* y a mantener el código simple y enfocado + +--- + +Se resume en + +- **Necesidad**: No agregue funcionalidades o características que no son necesarias +- **Simplicidad**: Mantenga el código simple y enfocado en el propósito actual +- **Eficiencia**: Evite la complejidad innecesaria que puede afectar el rendimiento + +--- + +- **Mantenimiento**: Un código sin características innecesarias es más fácil de mantener +- **Claridad**: Un código sin características innecesarias es más fácil de entender +- **Flexibilidad**: Un código sin características innecesarias es más flexible y adaptable a cambios futuros + +--- + +Una forma de aplicarlo es + +- **Enfocarse** en los **requisitos actuales** y NO en posibles requisitos futuros +- *Evitar* la sobreingeniería y centrarse en lo esencial +- Revisar y *eliminar características innecesarias* durante la refactorización + +--- + +- **Priorizar** la simplicidad y claridad del código +- **Mantener** el código enfocado en el propósito actual +- **Evitar** la tentación de agregar características + +*"por si acaso"* + +--- +#### Ejemplo YAGNI + +Crear el archivo `yagni.py` en la carpeta `sesion12` + +```text +Un estudiante esta aprendiendo a sumar dos números, y le dieron +una calculadora para para que pueda practicar la suma, pero +tiene muchas funciones que no va a usar aún +Simplifica el código aplicando el principio YAGNI +Deja solo la funcionalidad necesaria para sumar dos números +agrega anotaciones y documentación +``` + +```python [1-14] +class Calculadora: + def sumar(self, a, b): + return a + b + def restar(self, a, b): + return a - b + def multiplicar(self, a, b): + return a * b + def dividir(self, a, b): + return a / b +calc = Calculadora() +print(calc.sumar(5, 7)) +``` + +--- + +Solución simple + +```python +class Calculadora: + def sumar(self, a, b): + return a + b +calc = Calculadora() +print(calc.sumar(5, 7)) +``` + +--- +Solución con anotaciones y documentación + +```python +class Calculadora: + """Clase que representa una calculadora simple.""" + def sumar(self, a: int, b: int) -> int: + """Suma dos números enteros. + + Args: + a (int): El primer número. + b (int): El segundo número. + + Returns: + int: La suma de los dos números. + """ + return a + b +calc = Calculadora() +print(calc.sumar(5, 7)) +``` + +--- +#### Ejercicio para ti (03) + +```text +Un profesor necesita una forma sencilla de llevar +el registro de sus estudiantes, con sus nombres y +las notas de sus exámenes. Quiere poder calcular +fácilmente el promedio de cada estudiante y saber +de si ha aprobado o no el curso, +con nota mayor a 51, además de tener +un resumen claro de sus resultados para mostrárselos +Simplifica el código aplicando el principio YAGNI +Agrega anotaciones y documentación +``` + +Crea archivo `estudiantes.py` en la carpeta `sesion12` + +--- +Realizar los cambios 4 minutos + +```python +class Estudiante: + def __init__(self, nombre, notas): + self.nombre = nombre + self.notas = notas + + self.promedio_cache = None + + def calcular_promedio(self): + total = 0 + for n in self.notas: + total += n + return total / len(self.notas) + + def mostrar_promedio(self): + total = 0 + for n in self.notas: + total += n + return f"Promedio de {self.nombre}: {total / len(self.notas)}" + + def aprobo(self): + promedio = self.calcular_promedio() + return promedio > 50 or promedio == 51 + + def mejor_nota(self): + return max(self.notas) + + def peor_nota(self): + return min(self.notas) + def resumen(self): + return f"Estudiante: {self.nombre}, Notas: {self.notas}, Promedio: {self.calcular_promedio()}, Aprobado: {self.aprobo()}" + +estudiante1 = Estudiante("Ana", [85, 90, 78]) +print(estudiante1.mostrar_promedio()) +print(f"Aprobó: {estudiante1.aprobo()}") +print(estudiante1.resumen()) +``` + + + +--- +Solución simple + +```python +class Estudiante: + def __init__(self, nombre, notas): + self.nombre = nombre + self.notas = notas + def calcular_promedio(self): + return sum(self.notas) / len(self.notas) + def aprobo(self): + return self.calcular_promedio() >= 51 + def resumen(self): + mensaje = f"Estudiante: {self.nombre}" + mensaje += f", Notas: {self.notas}" + mensaje += f", Promedio: {self.calcular_promedio()}" + mensaje += f", Aprobado: {self.aprobo()}" + return mensaje +estudiante1 = Estudiante("Ana", [85, 90, 78]) +print(estudiante1.resumen()) +``` + +```bash +python estudiantes.py +``` +```text +Estudiante: Ana, Notas: [85, 90, 78], Promedio: 84.33333333333333, Aprobado: True +``` + +--- + +Solución con anotaciones y documentación + +```python +class Estudiante: + """Clase que representa a un estudiante.""" + def __init__(self, nombre: str, notas: list[int]) -> None: + """Inicializa una nueva instancia de la clase Estudiante. + + Args: + nombre (str): El nombre del estudiante. + notas (list[int]): Las notas del estudiante. + """ + self.nombre = nombre + self.notas = notas + def calcular_promedio(self) -> float: + """Calcula el promedio de las notas del estudiante. + Returns: + float: El promedio de las notas. + """ + return sum(self.notas) / len(self.notas) + def aprobo(self) -> bool: + """Determina si el estudiante aprobó el curso. + Returns: + bool: True si aprobó, False en caso contrario. + """ + return self.calcular_promedio() >= 51 + def resumen(self) -> str: + """Genera un resumen del estudiante. + Returns: + str: Un resumen con el nombre, notas, promedio y estado de aprobación. + """ + mensaje = f"Estudiante: {self.nombre}" + mensaje += f", Notas: {self.notas}" + mensaje += f", Promedio: {self.calcular_promedio()}" + mensaje += f", Aprobado: {self.aprobo()}" + return mensaje +estudiante1 = Estudiante("Ana", [85, 90, 78]) +print(estudiante1.resumen()) +``` + +--- + +#### Resumen + +- Python es un lenguaje popular por su facilidad para escribir código y su tipado dinámico, lo que permite flexibilidad pero puede causar errores difíciles de detectar. +- Las anotaciones de tipo permiten especificar el tipo de datos esperado para variables, parámetros de funciones, valores de retorno y atributos de clases, mejorando la legibilidad y ayudando a detectar errores antes de la ejecución. + +--- + +- Las anotaciones de tipo en Python son opcionales y no afectan el comportamiento del código en tiempo de ejecución, pero son útiles para desarrolladores y herramientas de análisis estático. +- Existen diferentes estilos para escribir anotaciones, como el uso directo de tipos o el módulo typing, aunque se recomienda el uso directo en versiones recientes. + +--- + +- La documentación es esencial para entender el propósito y funcionamiento del código, y que se puede realizar mediante cadenas de documentación (docstrings) en clases, funciones y métodos. +- Existen diferentes estilos de documentación en Python, como Google, NumPy y reStructuredText (reST), cada uno con sus ventajas y desventajas, y que es importante elegir uno y ser consistente. + +--- + +- Los principios de desarrollo ayudan a escribir código limpio, mantenible y escalable, proporcionando soluciones a problemas comunes en el desarrollo de software. +- El principio KISS enfatiza la simplicidad, claridad y eficiencia, evitando la sobreingeniería y manteniendo el código enfocado en lo esencial. + +--- + +- El principio DRY promueve evitar la duplicación de código, fomentando la reutilización y facilitando el mantenimiento. +- El principio YAGNI recomienda no agregar funcionalidades innecesarias, manteniendo el código simple y enfocado en los requisitos actuales. + +--- + +#### Retos + +Crear una carpeta con el nombre "retos_sesion_12" dentro del proyecto en la raíz, en la cual por cada ejercicio debes crear los siguientes archivos: + +```bash +# Estructura de carpetas +psg-oop-2025/ + retos_sesion_12/ + ejercicio_01.md + ejercicio_01.py + ejercicio_02.md + ejercicio_02.py +``` + +--- + +1. Una empresa de juegos moviles crea un nuevo juego **Dados de la Suerte**, las reglas son sencillas: + +- El jugador lanza **dos** dados +- Si la suma es **7 o 11 gana** +- Si es **2, 3 o 12 pierde** +- Con cualquier otro valor puede volver a lanzar los dados. + +En el futuro tiene planificado registrar el nombre del jugador y su puntuación más alta +pero para el lanzamiento inicial no es necesario + +*(1/5)* + +--- + + +Cuenta con un flujo básico: + +- Jugador inicia el juego +- Juego lanza los dados automáticamente +- Juego muestra el resultado y si ganó, perdió o si puede volver a lanzar +- Jugador responde SI o NO para volver a lanzar los dados +- El juego termina cuando el jugador decide NO lanzar más o gana o pierde +- El juego muestra un mensaje final con el resultado + +*(2/5)* + + +--- + +- Crea la clase `DadosDeLaSuerte` donde se implemente el flujo básico del juego +- Identifica atributos y métodos relevantes para la clase +- Implementa la **lógica del juego** según las reglas descritas +- Documenta el código y usa **anotaciones de tipo** +- Utiliza los principios KISS, DRY y YAGNI + +*(3/5)* + +--- + +Realiza el **análisis** y **diagrama de clases** en el archivo `ejercicio_01.md` + +Escribe el código en Python para la clase de tu diagrama de clases en el archivo `ejercicio_01.py` + +*(4/5)* + +--- + +- Asegúrate de que la clase incluyan *atributos* y *métodos* coherentes con su propósito +- Utiliza buenas prácticas de *nomenclatura*, *legibilidad* y *PEP 8* +- Juega varias veces para verificar que la lógica del juego funciona correctamente + +*(5/5)* + +--- + +2. Una empresa de citas necesita un programa para gestionar tareas. + +El programa puede: + +- Agregar nuevas tareas +- Eliminar tareas existentes +- Marcar tareas como completadas + +En el futuro se planea agregar fechas de vencimiento y prioridades a las tareas + +*(1/5)* + +--- + +Cada tarea tiene un *título*, una *descripción* y un *estado* (completada o no completada). + +Cuenta con un menú básico: + +1. **Agregar** una nueva tarea proporcionando un título y una descripción +2. **Eliminar** una tarea por su título +3. **Marcar** una tarea como completada por su título +4. **Listar** todas las tareas con su estado actual + +*(2/5)* + +--- + +- Crea la clase `Tarea` para representar una tarea individual con atributos y métodos relevantes +- Crea la clase `GestorDeTareas` para gestionar **una colección** de tareas, incluyendo métodos para agregar, eliminar y marcar tareas como completadas +- Documenta el código y usa **anotaciones de tipo** +- Utiliza los principios KISS, DRY y YAGNI + +*(3/5)* + +--- + +- Realiza el **análisis** y **diagrama de clases** en el archivo `ejercicio_02.md` +- Escribe el código en Python para las clases de tu diagrama de clases en el archivo `ejercicio_02.py` +- Implementa un menú interactivo para que el usuario pueda gestionar las tareas +- Documenta el código y usa **anotaciones de tipo** +- Utiliza los principios KISS, DRY y YAGNI + +*(4/5)* + +--- + +- Asegúrate de que las clases incluyan *atributos* y *métodos* coherentes con su propósito +- Utiliza buenas prácticas de *nomenclatura*, *legibilidad* y *PEP 8* +- Prueba las clases creando instancias y realizando operaciones de agregar, eliminar y marcar tareas como completadas + +*(5/5)* + +--- + +Gracias por Participar del + +🐍 Python Study Group - OOP 2025 🐍 + +> Un objeto puede ser pequeño, pero bien diseñado puede sostener un sistema entero + +--- + + +
+
+
+
+
+ +[![GitHub](../../content/github_logo.png) ](https://github.com/python-la-paz/python-study-group-oop/tree/main/content/sesion12) + +Repositorio de la Sesión + +--- + +## Bibliografía y Referencias + +- [Function Annotations](https://ellibrodepython.com/function-annotations) +- [Annotations HowTo](https://docs.python.org/es/3/howto/annotations.html) +- [Python Annotations](https://pywombat.com/articles/python-annotation) +- [PEP 484](https://peps.python.org/pep-0484/) +- [PEP 585](https://peps.python.org/pep-0585/#parameters-to-generics-are-available-at-runtime) +- [KISS Principle](https://fourweekmba.com/kiss-principle/) +- [Principles of Clean Code: DRY, KISS, and YAGNI](https://medium.com/@curiousraj/the-principles-of-clean-code-dry-kiss-and-yagni-f973aa95fc4d) +- [DRY Principle](https://www.techtarget.com/whatis/definition/DRY-principle) +- [The DRY Principle](https://dev.to/ahadalireach/the-dry-principle-50hh) +- [KISS, DRY, SOLID, YAGNI: A Simple Guide to Some Principles of Software Engineering and Clean Code](https://medium.com/@hlfdev/kiss-dry-solid-yagni-a-simple-guide-to-some-principles-of-software-engineering-and-clean-code-05e60233c79f) +- [Object Oriented Analysis](https://www.gyata.ai/es/object-oriented-programming/object-oriented-analysis) +- [DDOO Unidad 1](https://dmd.unadmexico.mx/contenidos/DCEIT/BLOQUE1/DS/02/DDOO/U1/descargables/DDOO_Unidad_1.pdf) +- [Programación procedural VS orientada a objetos](https://programacionpro.com/programacion-procedural-vs-orientada-a-objetos-diferencias-y-similitudes/) +- [Python OOP](https://www.learnpython.org/en/Classes_and_Objects) +- [Atributos de clase](https://oregoom.com/python/atributos-clase/) +- [Diagrama de clases](https://diagramasuml.com/diagrama-de-clases/) +- [Guía PEP 8](https://peps.python.org/pep-0008/#class-names) +- [Mermaid Charts](https://www.mermaidchart.com/play) +- [Draw.io](https://app.diagrams.net/) +- [Python 3 Object-oriented Programming, Second Edition, Dusty PhillipsDusty Phillips](https://github.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition) +- [Objetos en programación](https://ebac.mx/blog/objeto-en-programacion) +- [Enfoque orientado a objetos](https://1library.co/article/enfoque-orientado-a-objetos-base-te%C3%B3rica.qvld461y) +- [OOAD](https://www.tutorialspoint.com/object_oriented_analysis_design/ooad_object_oriented_analysis.htm) diff --git a/content/sesion12/img/annotation_01.png b/content/sesion12/img/annotation_01.png new file mode 100644 index 0000000..e74d4b9 Binary files /dev/null and b/content/sesion12/img/annotation_01.png differ diff --git a/content/sesion12/img/annotation_02.png b/content/sesion12/img/annotation_02.png new file mode 100644 index 0000000..a0ded45 Binary files /dev/null and b/content/sesion12/img/annotation_02.png differ diff --git a/content/sesion12/img/annotation_03.png b/content/sesion12/img/annotation_03.png new file mode 100644 index 0000000..fe086b0 Binary files /dev/null and b/content/sesion12/img/annotation_03.png differ diff --git a/content/sesion12/img/annotation_04.png b/content/sesion12/img/annotation_04.png new file mode 100644 index 0000000..3dccd26 Binary files /dev/null and b/content/sesion12/img/annotation_04.png differ diff --git a/content/sesion12/img/annotation_05.png b/content/sesion12/img/annotation_05.png new file mode 100644 index 0000000..750588a Binary files /dev/null and b/content/sesion12/img/annotation_05.png differ diff --git a/content/sesion12/img/annotation_06.png b/content/sesion12/img/annotation_06.png new file mode 100644 index 0000000..cc84150 Binary files /dev/null and b/content/sesion12/img/annotation_06.png differ diff --git a/content/sesion12/img/annotation_07.png b/content/sesion12/img/annotation_07.png new file mode 100644 index 0000000..da916b5 Binary files /dev/null and b/content/sesion12/img/annotation_07.png differ diff --git a/content/sesion12/img/documentation_01.png b/content/sesion12/img/documentation_01.png new file mode 100644 index 0000000..6952192 Binary files /dev/null and b/content/sesion12/img/documentation_01.png differ diff --git a/content/sesion12/img/documentation_02.png b/content/sesion12/img/documentation_02.png new file mode 100644 index 0000000..7899223 Binary files /dev/null and b/content/sesion12/img/documentation_02.png differ diff --git a/content/sesion12/img/documentation_03.png b/content/sesion12/img/documentation_03.png new file mode 100644 index 0000000..dd21ab7 Binary files /dev/null and b/content/sesion12/img/documentation_03.png differ diff --git a/content/sesion12/img/documentation_04.png b/content/sesion12/img/documentation_04.png new file mode 100644 index 0000000..2722380 Binary files /dev/null and b/content/sesion12/img/documentation_04.png differ diff --git a/content/sesion12/img/documentation_05.png b/content/sesion12/img/documentation_05.png new file mode 100644 index 0000000..52b348a Binary files /dev/null and b/content/sesion12/img/documentation_05.png differ diff --git a/content/sesion12/img/documentation_06.png b/content/sesion12/img/documentation_06.png new file mode 100644 index 0000000..4b0d424 Binary files /dev/null and b/content/sesion12/img/documentation_06.png differ diff --git a/content/sesion12/img/documentation_07.png b/content/sesion12/img/documentation_07.png new file mode 100644 index 0000000..eb79eba Binary files /dev/null and b/content/sesion12/img/documentation_07.png differ diff --git a/content/sesion12/index.html b/content/sesion12/index.html new file mode 100644 index 0000000..56e5d31 --- /dev/null +++ b/content/sesion12/index.html @@ -0,0 +1,62 @@ + + + + + + Sesión 12 | Python Study Group | OOP + + + + + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html index febbb0e..fcb4aa8 100644 --- a/index.html +++ b/index.html @@ -138,8 +138,9 @@

Personalización y control del comportamiento


2025
-
+ +


@@ -152,6 +153,51 @@

Singleton


2025
+
+ + +
+
+
+
+ + Sesión 10 +

Patrones de Diseño

+

Factory

+
+
+
2025
+
+
+ + +
+
+
+
+ + Sesión 11 +

Buenas prácticas

+

Refactorización y organización

+
+
+
2025
+
+
+ + +
+
+
+
+ + Sesión 12 +

Buenas prácticas

+

Anotaciones, Documentación y Principios

+
+
+
2025
+
@@ -183,4 +229,4 @@
2025
- \ No newline at end of file +