diff --git a/Roadmap/26 - SOLID SRP/bash/rantamhack.sh b/Roadmap/26 - SOLID SRP/bash/rantamhack.sh new file mode 100644 index 0000000000..5dee7c8ca6 --- /dev/null +++ b/Roadmap/26 - SOLID SRP/bash/rantamhack.sh @@ -0,0 +1,160 @@ +#!/bin/bash + + +# BASH NO ESTA ORIENTADO A OBJETOS Y NO TIENE CLASES, ESTE EJERCICIO ES UNA SIMULACION DE LAS CLASES + +echo -e "\n\n=======================================EJERCICIO SIN SRP=======================================\n\n" + +# * EJERCICIO: +# * Explora el "Principio SOLID de Responsabilidad Unica (Single Responsibility +# * Principle, SRP)" y crea un ejemplo simple donde se muestre su funcionamiento +# * de forma correcta e incorrecta. + + +# * Sin aplicar el principio "SRP" + +students=() +subscriptions=() +payments=() + + +function mouredev_academy() { + local user_id="$1" + local name="$2" + local surname="$3" + local email="$4" + + read -p "Introduce tu nombre de usuario: " nick + read -p "introduce tu contraseña: " passwd + + students+=("$user_id, $name, $surname, $email, $username, $password") + echo -e "[+] El estudiante $username se ha añadido a la base de datos" + + if [[ $nick == $username && $passwd == $password ]]; then + echo -e "[*] Bienvenido a la academia $username" + else + echo -e "[!] Los datos introducidos no son correctos" + fi +} + +function subs_type(){ + local monthly="$1" + local quarterly="$2" + local half_yearly="$3" + local yearly="$4" + subscriptions+=("$monthly, $quarterly, $half_yearly, $yearly") + echo -e "[-] Los tipos de suscripcion se han añadido" +} + +function payment() { + local credit_card="$1" + local debit_card="$2" + local bizum="$3" + local bank_transfer="$4" + payments+=("$credit_card, $debit_card, $bizum, $bank_transfer") + echo -e "[-] Los metodos de pago se han añadido" +} + + +mouredev_academy "username" "password" +mouredev_academy 1 "Luis" "Ramos" "rantam@rantam.com" "fake" "126354" +subs_type "Monthly" "Quarterly" "Half_yearly" "Yearly" +payment "credit_card" "debit_card" "bizum" "bank_transfer" + + +echo -e "\n\n=======================================EJERCICIO CON SRP=======================================\n\n" + + + +# * Aplicando el principio "SRP" + +students=() +subscriptions=() +payments=() + +function auth(){ + local username="$1" + local password="$2" + + read -p "Introduce tu nombre de usuario: " nick + read -p "introduce tu contraseña: " passwd + + echo -e "[-] Autenticando al usuario $nick" + + if [[ $nick == "$username" && $passwd == "$password" ]]; then + echo -e "[*] Bienvenido a la academia $username" + else + echo -e "[!] Los datos introducidos no son correctos" + fi + +} + +function add_students(){ + local user_id="$1" + local name="$2" + local surname="$3" + local email="$4" + local username=$5 + + students+=("$user_id, $name, $surname, $email, $username") + + for student in "${students[@]}";do + echo -e "[+] La lista de estudiantes es $student" + done + +} + +function add_subs(){ + local monthly="$1" + local quarterly="$2" + local half_yearly="$3" + local yearly="$4" + subscriptions+=("$monthly, $quarterly, $half_yearly, $yearly") + echo -e "[-] Los tipos de suscripcion se han añadido" +} + +function add_payment() { + local credit_card="$1" + local debit_card="$2" + local bizum="$3" + local bank_transfer="$4" + payments+=("$credit_card, $debit_card, $bizum, $bank_transfer") + echo -e "[-] Los metodos de pago se han añadido" +} + +function moure_academy(){ + local command="$1" + shift + + case "$command" in + auth) + auth "$@" + ;; + add_students) + add_students "$@" + ;; + add_subs) + add_subs "$@" + ;; + add_payment) + add_payment "$@" + ;; + *) + echo -e "[!] Comando no reconocido" + ;; + esac +} + +moure_academy add_students 1 "Rosa" "Lopez" "rosa@moureacademy.com" "rantam" +moure_academy add_students 2 "Mario" "Bros" "mario@moureacademy.com" "Ka_OS" +moure_academy auth "rantam" "123456" +moure_academy auth "Ka_Os" "654321" +moure_academy add_subs "Monthly" "Quarterly" "Half_yearly" "Yearly" +moure_academy add_payment "credit_card" "debit_card" "bizum" "bank_transfer" + + + + + + + diff --git a/Roadmap/26 - SOLID SRP/ocaml/luishendrix92.ml b/Roadmap/26 - SOLID SRP/ocaml/luishendrix92.ml new file mode 100644 index 0000000000..294a49b550 --- /dev/null +++ b/Roadmap/26 - SOLID SRP/ocaml/luishendrix92.ml @@ -0,0 +1,288 @@ +open Printf + +let ( let* ) = Result.bind + +(******************************************************************************) +(* *) +(* Single Responsibility Principle *) +(* *) +(* The 'S' in {b SOLID}, says that classes and functions should only do one *) +(* thing and they should do it very well; meaning, have a single responsi- *) +(* bility. It discourages mixing concerns in a class or function and *) +(* encourages the developer to separate these concerns into classes that *) +(* only {e have one reason to change}, as Bob Martin said in his book, *) +(* Clean Code, which is the originator of the SOLID principles. *) +(* *) +(******************************************************************************) + +module ViolatesSRP = struct + let responsibility_1 () = print_endline "I do one thing" + let responsibility_2 () = print_endline "I do another thing" + let responsibility_3 () = print_endline "And yet another thing..." + + let use_all_three () = + responsibility_1 (); + responsibility_2 (); + responsibility_3 () + ;; +end + +(* The module above is in violation of the SRP, it defines three different + functions that should exist on their own, otherwise whenever we wanted + to change any of them, we'd have to modify the same module, potentially + introducing bugs and making them exclusive to the host module. + + The solution is to create a module for each responsibility so that if + such code has to change, I know exactly where to go. *) + +module ResponsibilityOne = struct + let one_thing () = print_endline "I do one thing" +end + +module ResponsibilityTwo = struct + let another_thing () = print_endline "I do another thing" +end + +module ResponsibilityThree = struct + let yet_another () = print_endline "And yet another thing..." +end + +module SRPCompliant = struct + let use_all_three () = + ResponsibilityOne.one_thing (); + ResponsibilityTwo.another_thing (); + ResponsibilityThree.yet_another () + ;; +end + +(*****************************************************************************) +(* *) +(* DIFICULTAD EXTRA (opcional): *) +(* *) +(* Desarrolla un sistema de gestión para una biblioteca. El sistema necesita *) +(* manejar diferentes aspectos como el registro de libros, la gestión de *) +(* usuarios y el procesamiento de préstamos de libros. *) +(* *) +(* Requisitos: *) +(* 1. Registrar libros: El sistema debe permitir agregar nuevos libros con *) +(* información básica como título, autor y número de copias disponibles. *) +(* 2. Registrar usuarios: Debe permitir agregar nuevos usuarios con info *) +(* básica como nombre, número de identificación y correo electrónico. *) +(* 3. Procesar préstamos de libros: El sistema debe permitir a los usuarios *) +(* tomar prestados y devolver libros. *) +(* *) +(* Instrucciones: *) +(* 1. Diseña una clase que no cumple: Crea una clase Library que maneje *) +(* los tres aspectos mencionados anteriormente (registro de libros, *) +(* usuarios y procesamiento de préstamos). *) +(* 2. Refactoriza el código: Separa las responsabilidades en diferentes *) +(* clases siguiendo el Principio de Responsabilidad Única. *) +(* *) +(*****************************************************************************) + +module Library = struct + type book = + { title : string + ; author : string + ; mutable stock : int + } + + type user = + { id : int + ; name : string + ; email : string + } + + module LendingSet = Set.Make (struct + type t = int * string + + let compare = Stdlib.compare + end) + + let books : book list ref = + ref + [ { title = "Dune"; author = "Frank Herbert"; stock = 5 } + ; { title = "Lord of the Rings"; author = "J.R.R Tolkien"; stock = 2 } + ; { title = "Eye of the world"; author = "Robert Jordan"; stock = 3 } + ; { title = "It"; author = "Stephen King"; stock = 8 } + ; { title = "Develop a second brain"; author = "Thiago Forte"; stock = 6 } + ] + ;; + + let users : user list ref = + ref [ { id = 1; name = "Luis Lopez"; email = "luishendrix92@gmail.com" } ] + ;; + + let lendings : LendingSet.t ref = ref LendingSet.empty + let register_user user = users := user :: !users + let add_book book = books := book :: !books + + let lend_book user_id book_title = + let book = + match List.find_opt (fun book -> book.title = book_title) !books with + | None -> failwith (sprintf "Book [%s] not found" book_title) + | Some book -> book + in + if book.stock > 0 + then begin + lendings := LendingSet.add (user_id, book_title) !lendings; + book.stock <- book.stock - 1 + end + else failwith (sprintf "Not enough stock for [%s]" book_title) + ;; + + let return_book user_id book_title = + let should_return = LendingSet.mem (user_id, book_title) !lendings in + if should_return + then begin + let book = + match List.find_opt (fun book -> book.title = book_title) !books with + | None -> failwith (sprintf "Book [%s] not found" book_title) + | Some book -> book + in + lendings := LendingSet.remove (user_id, book_title) !lendings; + book.stock <- book.stock + 1 + end + else + failwith + (sprintf "User #%d doesn't need to return [%s]" user_id book_title) + ;; +end + +(* Refactoring Opportunity + ----------------------- + I can apply SRP to separate the monolith of code into modules that do one + thing only, and very well. For this particular case I can implement an + entity data module for books, lendings, and users; then create a repository + functor to have a static storage (Hashtbl) interfaced through convenient + functions for retrieving, deleting, and adding these entities. *) + +module type Entity = sig + type id + type t + + val get_id : t -> id +end + +module Repository (E : Entity) = struct + let data : (E.id, E.t) Hashtbl.t = Hashtbl.create 100 + let save elt = Hashtbl.replace data (E.get_id elt) elt + + let add elt = + if Hashtbl.mem data (E.get_id elt) + then Error "Unable to add entity, already exists." + else ( + save elt; + Ok ()) + ;; + + let delete_by_id elt_id = + if Hashtbl.mem data elt_id + then Ok (Hashtbl.remove data elt_id) + else Error "Can't delete, id doesn't exit." + ;; + + let get_by_id elt_id = + match Hashtbl.find_opt data elt_id with + | Some elt -> Ok elt + | None -> Error "Entity not found with the provided id." + ;; +end + +module User = struct + type id = int + + type t = + { id : int + ; name : string + ; email : string + } + + let get_id user = user.id +end + +module Book = struct + type id = string + + type t = + { title : string + ; author : string + ; mutable stock : int + } + + let get_id book = book.title +end + +module Lending = struct + type id = User.id * Book.id + + type t = + { user_id : User.id + ; book_id : Book.id + ; return_date : string + } + + let get_id lending = lending.user_id, lending.book_id +end + +module SRPCompliantLibrary = struct + (* Ideally, we should be able to use dependency injection here and while + technically we can by using functors, the syntax isn't very intuitive + and extension-friendly so I'll stick with this code for now. *) + module Users = Repository (User) + module Books = Repository (Book) + module Lendings = Repository (Lending) + + let borrow user_id book_id = + (* Given I'm not using a proper ORM or writing relationship-aware database + code, I need to make sure both entities (user and book) exist before + adding the lending entity, otherwise I'd be violating what in the DB + world is called a "foreign key" constraint. *) + let* _user = Users.get_by_id user_id in + let* book = Books.get_by_id book_id in + let return_date = + Core.Date.( + add_days (today ~zone:Core.Time_float.Zone.utc) 7 |> to_string_american) + in + if book.stock > 0 + then begin + book.stock <- book.stock - 1; + Lendings.add { user_id; book_id; return_date } + end + else Error "Can't lend book, not enough stock" + ;; + + let return user_id book_id = + let* _user = Users.get_by_id user_id in + let* book = Books.get_by_id book_id in + let* _ = Lendings.delete_by_id (user_id, book_id) in + Ok (book.stock <- book.stock + 1) + ;; +end + +let _ = + let open SRPCompliantLibrary in + let inventory : Book.t list = + [ { title = "Blood Meridian"; author = "John McCarthy"; stock = 5 } + ; { title = "The Outsider"; author = "Stephen King"; stock = 2 } + ; { title = "The Philosopher's Stone"; author = "J.K Rowling"; stock = 0 } + ] + in + List.iter (fun book -> Books.add book |> Result.get_ok) inventory; + Users.add { id = 1; name = "Luis Lopez"; email = "luishendrix92@gmail.com" } + |> Result.get_ok; + let res = + let user_id = 1 in + let book_title = "The Outsider" in + let* _ = borrow user_id book_title in + printf "User #%d successfully borrowed '%s'\n" user_id book_title; + let* _ = return user_id book_title in + printf "User #%d successfully returned '%s'\n" user_id book_title; + Ok () + in + match res with + | Ok _ -> + print_endline "Let's try borrowing a book with no stock!"; + borrow 1 "The Philosopher's Stone" |> Result.get_error |> print_endline + | Error err -> print_endline err +;; diff --git a/Roadmap/26 - SOLID SRP/python/SooHav.py b/Roadmap/26 - SOLID SRP/python/SooHav.py new file mode 100644 index 0000000000..8ece3f37e5 --- /dev/null +++ b/Roadmap/26 - SOLID SRP/python/SooHav.py @@ -0,0 +1,392 @@ +# 26 SOLID: PRINCIPIO DE RESPONSABILIDAD ÚNICA (SRP) +import logging +# Ejercicio +# Ejemplo Pato sin SRP + + +class Pato: + + def __init__(self, nombre): + self.nombre = nombre + + def vuela(self): + print(f"{self.nombre} vuela.") + + def nada(self): + print(f"{self.nombre} nada.") + + def dice(self) -> str: + return "Quack" + + def saluda(self, pato2): + print(f"{self.nombre}: {self.dice()}, hola {pato2.nombre}") + + +# Uso +print("Sin SRP") +pato1 = Pato(nombre="Daisy") +pato2 = Pato(nombre="Donald") +pato1.vuela() +pato1.nada() +pato1.saluda(pato2) +print("\n") + +# Ejemplo Pato con SRP + + +class Pato(): + """Clase que define los patos""" + + def __init__(self, nombre): + self.nombre = nombre + + def vuela(self): + print(f"{self.nombre} vuela.") + + def nada(self): + print(f"{self.nombre} nada.") + + def dice(self) -> str: + return "Quack" + + +class Dialogo(): + """Clase que define la comunicacion entre patos""" + + def __init__(self, formato): + self.formato = formato + + def conversacion(self, pato1: Pato, pato2: Pato): + frase1 = f"{pato1.nombre}: {pato1.dice()}, ¡Ay, { + pato2.nombre}! ¡Casi me caigo al intentar atrapar un pez!" + frase2 = f"{pato2.nombre}: {pato2.dice()}, ¡Otra vez, { + pato1.nombre}? Ten más cuidado la próxima vez." + conversacion = [frase1, frase2] + print(*conversacion, + f"(Extracto de {self.formato})", + sep='\n') + + +# Uso +print("Con SRP") +pato1 = Pato(nombre="Donald") +pato2 = Pato(nombre="Daisy") +pato1.vuela() +pato2.nada() +formato1 = Dialogo(formato="historieta") +formato1.conversacion(pato1, pato2) + +# Extra +# Programa sin SRP + + +class GestorBiblioteca(): + """ + Clase que gestiona una biblioteca. + """ + + def __init__(self): + self.registro = {} + self.usuarios = {} + + def registro_libros(self, id, titulo, autor, cantidad_copias, max_prestamo): + """ + Funcion que registra un libro. + """ + nuevo_libro = { + "titulo": titulo, + "autor": autor, + "cantidad_copias": cantidad_copias, + "max_prestamo": max_prestamo + } + self.registro[id] = nuevo_libro + print(f"Libro '{titulo}' agregado con éxito.") + + def agregar_usuario(self, id, nombre, dni, email): + """ + Funcion que permite agregar nuevos usuarios con información básica. + """ + nuevo_usuario = { + "nombre": nombre, + "dni": dni, + "email": email + } + self.usuarios[id] = nuevo_usuario + print(f"Usuario '{nombre}' agregado con éxito.") + + def prestamo_libro(self, titulo, nombre): + """ + Funcion para que los usuarios puedan tomar prestados libros. + """ + usuario_registrado = False + for usuario in self.usuarios.values(): + if usuario["nombre"] == nombre: + usuario_registrado = True + break + + if not usuario_registrado: + print(f"Usuario '{ + nombre}' no está registrado. No puede tomar prestado libros para llevar a casa.") + return + + libro_prestado = None + cantidad_actual = None + + for id, nuevo_registro in self.registro.items(): + if nuevo_registro["titulo"] == titulo: + libro_prestado = nuevo_registro + cantidad_actual = nuevo_registro["cantidad_copias"] + max_prestamo = nuevo_registro["max_prestamo"] + break + + if libro_prestado: + if cantidad_actual > 0 and cantidad_actual <= max_prestamo: + cantidad_actual -= 1 + libro_prestado["cantidad_copias"] = cantidad_actual + print(f"Libro '{titulo}' prestado con éxito a {nombre}.") + else: + print(f"Libro '{ + titulo}' no tiene copias disponibles o se ha excedido el máximo préstamo.") + else: + print(f"Libro '{titulo}' sin existencias.") + + def devolucion_libro(self, titulo, nombre): + """ + Funcion para que los usuarios puedan devolver libros. + """ + usuario_registrado = False + for usuario in self.usuarios.values(): + if usuario["nombre"] == nombre: + usuario_registrado = True + break + + if not usuario_registrado: + print(f"Usuario '{ + nombre}' no está registrado. Pida el nombre o id de la persona que tomo prestado el libro.") + return + + libro_devuelto = None + + for id, nuevo_registro in self.registro.items(): + if nuevo_registro["titulo"] == titulo: + libro_devuelto = nuevo_registro + cantidad_actual = nuevo_registro["cantidad_copias"] + max_prestamo = nuevo_registro["max_prestamo"] + break + + if libro_devuelto: + cantidad_actual_nueva = cantidad_actual + 1 + if cantidad_actual_nueva <= max_prestamo: + cantidad_actual += 1 + libro_devuelto["cantidad_copias"] = cantidad_actual + print(f"Libro '{titulo}' devuelto con éxito por {nombre}.") + else: + print( + f"Se ha superado el límite máximo de libros para '{titulo}'.") + else: + print(f"Libro '{titulo}' no pertenece a la biblioteca.") + + def listar_libros(self): + """ + Muestra una lista de los libros en existencia. + """ + if self.registro: + print("Lista de Libros:") + for id, nuevo_registro in self.registro.items(): + titulo = nuevo_registro["titulo"] + cantidad_copias = nuevo_registro["cantidad_copias"] + print(f"- {titulo}: {cantidad_copias}") + else: + print("No hay libros registrados.") + + +# Uso +gestor = GestorBiblioteca() + +# Registrar libros +gestor.registro_libros(1, "Cien años de Soledad", "Garcia Marquez", 3, 3) +gestor.registro_libros( + 2, "Harry Potter: la píedra filosofal", "Jk Rowling", 3, 3) +gestor.registro_libros( + 3, "El señor de los anillos: la comunidad del anillo", "JRR Tolkien", 3, 2) + +# Agregar usuario +gestor.agregar_usuario(1, "Sofia", 456789, "soo@soo.com") +gestor.agregar_usuario(2, "Juan", 456789, "juan@juan.com") + +# Transaccion +gestor.prestamo_libro("Cien años de Soledad", "Sofia") +gestor.prestamo_libro("Cien años de Soledad", "Sofia") +gestor.prestamo_libro("Cien años de Soledad", "Juan") +gestor.prestamo_libro( + "El señor de los anillos: la comunidad del anillo", "Lucas") +gestor.prestamo_libro("Harry Potter: la píedra filosofal", "Sofia") +gestor.prestamo_libro("Harry Potter: la píedra filosofal", "Juan") +gestor.prestamo_libro("Harry Potter: la píedra filosofal", "Juan") +gestor.prestamo_libro("Harry Potter: la píedra filosofal", "Juan") +gestor.devolucion_libro("Cien años de Soledad", "Sofia") +gestor.devolucion_libro("Cien años de Soledad", "Sofia") + +# Lista +gestor.listar_libros() + +# Gestor de Biblioteca con SRP +# Configurar Logger +logger = logging.getLogger(__name__) +logger.setLevel(logging.WARNING) +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.WARNING) +console_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') +console_handler.setFormatter(console_format) +logger.addHandler(console_handler) + + +class RegistroError(Exception): + pass + + +class RegistroBiblioteca(): + def __init__(self): + self.biblioteca = {} + self.usuarios = {} + + def registro_libros(self, id, titulo, autor, cantidad_copias, max_prestamo): + logger.debug("Iniciando el registro de un libro.") + nuevo_libro = { + "titulo": titulo, + "autor": autor, + "cantidad_copias": cantidad_copias, + "max_prestamo": max_prestamo + } + self.biblioteca[id] = nuevo_libro + logger.info(f"Libro '{titulo}' agregado con éxito.") + + def registro_usuario(self, id, nombre, dni, email): + logger.debug("Iniciando el registro de un usuario.") + nuevo_usuario = { + "nombre": nombre, + "dni": dni, + "email": email + } + self.usuarios[id] = nuevo_usuario + logger.info(f"Usuario '{nombre}' agregado con éxito.") + + def verificacion_registro(self, tipo, titulo_o_nombre): + logger.debug("Iniciando la verificación de un registro.") + if tipo == "libro": + for libro in self.biblioteca.values(): + if libro["titulo"] == titulo_o_nombre: + logger.info("Libro registrado") + return True + elif tipo == "usuario": + for usuario in self.usuarios.values(): + if usuario["nombre"] == titulo_o_nombre: + logger.info("Usuario registrado") + return True + logger.error(f"{tipo.capitalize()} '{titulo_o_nombre}' no registrado") + return False + + +class TransaccionBiblioteca(): + """ + Clase que gestiona las transacciones de prestamo y devolucion de una biblioteca. + """ + + def __init__(self, registro_biblioteca): + self.registro_biblioteca = registro_biblioteca + + def prestamo_libro(self, titulo, nombre): + logger.debug("Iniciando el prestamo de un libro.") + try: + if not self.registro_biblioteca.verificacion_registro("usuario", nombre): + logger.error(f"Usuario '{nombre}' no está registrado.") + return + + libro_prestado = None + for id, nuevo_registro in self.registro_biblioteca.biblioteca.items(): + if nuevo_registro["titulo"] == titulo: + libro_prestado = nuevo_registro + logger.info("Coincidencia en la busqueda") + break + if libro_prestado: + if libro_prestado["cantidad_copias"] > 0: + libro_prestado["cantidad_copias"] -= 1 + print( + f"Libro '{titulo}' prestado con éxito a {nombre}.") + else: + logger.warning( + f"Libro '{titulo}' no tiene copias disponibles.") + else: + logger.error(f"Libro '{titulo}' no pertenece a la biblioteca.") + except RegistroError as e: + print(e) + + def devolucion_libro(self, titulo, nombre): + logger.debug("Iniciando la devoluciòn de un libro.") + try: + if not self.registro_biblioteca.verificacion_registro("usuario", nombre): + logger.error(f"Usuario '{nombre}' no está registrado.") + return + + libro_devuelto = None + for id, nuevo_registro in self.registro_biblioteca.biblioteca.items(): + if nuevo_registro["titulo"] == titulo: + libro_devuelto = nuevo_registro + logger.info("Coincidencia en la busqueda") + break + if libro_devuelto: + libro_devuelto["cantidad_copias"] += 1 + print( + f"Libro '{titulo}' devuelto con éxito por {nombre}.") + else: + logger.error(f"Libro '{titulo}' no pertenece a la biblioteca.") + except RegistroError as e: + print(e) + + +class Existencias(): + def __init__(self, registro_biblioteca): + self.registro_biblioteca = registro_biblioteca + + def listar_libros(self): + """ + Muestra una lista de los libros en existencia. + """ + logger.debug("Recopilando los libros registrados en la biblioteca.") + if self.registro_biblioteca.biblioteca: + print("Lista de Libros:") + for id, nuevo_registro in self.registro_biblioteca.biblioteca.items(): + titulo = nuevo_registro["titulo"] + cantidad_copias = nuevo_registro["cantidad_copias"] + print(f"- {titulo}: {cantidad_copias}") + else: + logger.warning("No hay libros registrados.") + + +# Uso +registro = RegistroBiblioteca() +transaccion = TransaccionBiblioteca(registro) +existencias = Existencias(registro) + +# Registro libros +registro.registro_libros(1, "Cien años de Soledad", "Garcia Marquez", 3, 3) +registro.registro_libros( + 2, "Harry Potter: la piedra filosofal", "Jk Rowling", 3, 3) +registro.registro_libros( + 3, "El señor de los anillos: la comunidad del anillo", "JRR Tolkien", 3, 2) + +# Registro usuarios +registro.registro_usuario(1, "Sofia", 456789, "soo@soo.com") +registro.registro_usuario(2, "Juan", 456789, "juan@juan.com") + +# Transacciones +transaccion.prestamo_libro("Cien años de Soledad", "Sofia") +transaccion.prestamo_libro("Cien años de Soledad", "Sofia") +transaccion.prestamo_libro("Cien años de Soledad", "Juan") +transaccion.prestamo_libro("Harry Potter: la piedra filosofal", "Sofia") +transaccion.prestamo_libro("Harry Potter: la piedra filosofal", "Juan") +transaccion.devolucion_libro("Cien años de Soledad", "Sofia") +transaccion.devolucion_libro("Cien años de Soledad", "Sofia") + +# Listar libros +existencias.listar_libros() diff --git a/Roadmap/27 - SOLID OCP/c#/kenysdev.cs b/Roadmap/27 - SOLID OCP/c#/kenysdev.cs new file mode 100644 index 0000000000..48ca762c38 --- /dev/null +++ b/Roadmap/27 - SOLID OCP/c#/kenysdev.cs @@ -0,0 +1,172 @@ +namespace exs27; +/* +╔══════════════════════════════════════╗ +║ Autor: Kenys Alvarado ║ +║ GitHub: https://github.com/Kenysdev ║ +║ 2024 - C# ║ +╚══════════════════════════════════════╝ +------------------------------------------------- +* SOLID: PRINCIPIO ABIERTO-CERRADO (OCP) +------------------------------------------------- +- Una entidad de software que está abierta a extensión, pero cerrada a modificación, + esto significa que debemos poder extender el comportamiento de una clase sin + necesidad de modificar su código fuente original. + +_______________ +* EJERCICIO #1: +* Explora el "Principio SOLID Abierto-Cerrado (Open-Close Principle, OCP)" +* y crea un ejemplo simple donde se muestre su funcionamiento +* de forma correcta e incorrecta. +*/ + +// Abstract base class Product +public abstract class Product(string name, decimal price) +{ + public string Name { get; set; } = name; + public decimal Price { get; set; } = price; + + // Abstract method + public abstract decimal ApplyDiscount(); + + // Concrete metho + public decimal FinalPrice() + { + return Price - ApplyDiscount(); + } +} + +// Concrete class +public class ElectronicsProduct(string name, decimal price) : Product(name, price) +{ + public override decimal ApplyDiscount() + { + return Price * 0.05m; // Discount of 5% + } +} + + +// Concrete class +public class ClothingProduct(string name, decimal price) : Product(name, price) +{ + public override decimal ApplyDiscount() + { + if (Price > 50) + return 10; // Discount of $10 if price is over $50 + else + return 0; // No discount otherwise + } +} + +/* +_______________ +* EJERCICIO #2: +* Desarrolla una calculadora que necesita realizar diversas operaciones matemáticas. +* Requisitos: +* - Debes diseñar un sistema que permita agregar nuevas operaciones utilizando el OCP. +* Instrucciones: +* 1. Implementa las operaciones de suma, resta, multiplicación y división. +* 2. Comprueba que el sistema funciona. +* 3. Agrega una quinta operación para calcular potencias. +* 4. Comprueba que se cumple el OCP. +*/ + +// Abstract base +public abstract class Calculator(double a, double b) +{ + protected double a = a; + protected double b = b; + + // Abstract method + public abstract double MathOperation(); + + // Concrete method + public void PrintResult() + { + Console.WriteLine($"Es: {MathOperation()}"); + } +} + +public class Sum(double a, double b) : Calculator(a, b) +{ + public override double MathOperation() + { + Console.WriteLine($"\nSuma de {a} + {b}:"); + return a + b; + } +} + +public class Subtraction(double a, double b) : Calculator(a, b) +{ + public override double MathOperation() + { + Console.WriteLine($"\nResta de {a} - {b}:"); + return a - b; + } +} + +public class Multiplication(double a, double b) : Calculator(a, b) +{ + public override double MathOperation() + { + Console.WriteLine($"\nMultiplicación de {a} * {b}:"); + return a * b; + } +} + +public class Division(double a, double b) : Calculator(a, b) +{ + public override double MathOperation() + { + Console.WriteLine($"\nDivisión de {a} / {b}:"); + if (b != 0) + return a / b; + else + throw new ArgumentException("DivisionErrorbyZero."); + } +} + +public class Pow(double a, double b) : Calculator(a, b) +{ + public override double MathOperation() + { + Console.WriteLine($"\nPotencia de {a} ^ {b}:"); + return Math.Pow(a, b); + } +} + +//__________________ +public class Program +{ + public static void ProcessProduct(Product product) + { + Console.WriteLine($"Producto: {product.Name}, Precio final: {product.FinalPrice()}"); + } + + static void Main() + { + ElectronicsProduct laptop = new("Laptop", 700 ); + ClothingProduct pants = new("Pants", 55); + + ProcessProduct(laptop); + ProcessProduct(pants); + + //___________________________ + // exs 2 + + Sum sum = new(2, 2); + sum.PrintResult(); + + Subtraction subtraction = new(2, 2); + subtraction.PrintResult(); + + Multiplication multiplication = new(2, 2); + multiplication.PrintResult(); + + Division division = new(2, 2); + division.PrintResult(); + + Pow pow = new(2, 2); + pow.PrintResult(); + + } +} diff --git a/Roadmap/27 - SOLID OCP/go/hozlucas28.go b/Roadmap/27 - SOLID OCP/go/hozlucas28.go new file mode 100644 index 0000000000..8856f8b21d --- /dev/null +++ b/Roadmap/27 - SOLID OCP/go/hozlucas28.go @@ -0,0 +1,374 @@ +package main + +import ( + "errors" + "fmt" + "math" +) + +/* -------------------------------------------------------------------------- */ +/* CLASSES AND INTERFACES */ +/* -------------------------------------------------------------------------- */ + +/* ---------------------------- BadCircle (class) --------------------------- */ + +type BadCircle struct { + radius float64 + _ struct{} +} + +/* -------------------------- BadRectangle (class) -------------------------- */ + +type BadRectangle struct { + height float64 + width float64 + _ struct{} +} + +/* -------------------------- BadCalculator (class) ------------------------- */ + +type BadCalculator struct{} + +func (calculator BadCalculator) GetArea(shape interface{}) float64 { + switch shape := shape.(type) { + case BadCircle: + return math.Pi * math.Pow(shape.radius, 2) + + case BadRectangle: + return shape.height * shape.width + + default: + return -1 + } +} + +/* ---------------------------- Shape (interface) --------------------------- */ + +type Shape interface { + GetArea() float64 +} + +/* --------------------------- GoodCircle (class) --------------------------- */ + +type goodCircle struct { + radius float64 + _ struct{} +} + +func NewGoodCircle(radius float64) Shape { + var circle goodCircle = goodCircle{radius: radius} + return &circle +} + +func (circle *goodCircle) GetArea() float64 { + return math.Pi * math.Pow(circle.radius, 2) +} + +/* -------------------------- GoodRectangle (class) ------------------------- */ + +type goodRectangle struct { + height float64 + width float64 + _ struct{} +} + +func NewGoodRectangle(height float64, width float64) Shape { + var rectangle goodRectangle = goodRectangle{height: height, width: width} + return &rectangle +} + +func (rectangle *goodRectangle) GetArea() float64 { + return rectangle.height * rectangle.width +} + +/* ------------------ GoodCalculator (class And Interface) ------------------ */ + +type IGoodCalculator interface { + GetArea(shape Shape) float64 +} + +type goodCalculator struct{} + +func NewGoodCalculator() IGoodCalculator { + var calculator goodCalculator = goodCalculator{} + return &calculator +} + +func (calculator *goodCalculator) GetArea(shape Shape) float64 { + return shape.GetArea() +} + +/* -------------------------- Operation (interface) ------------------------- */ + +type Operation interface { + Execute(a float64, b float64) (float64, error) +} + +/* -------------------------- AddOperation (class) -------------------------- */ + +type addOperation struct{} + +func NewAddOperation() Operation { + var add addOperation = addOperation{} + return &add +} + +func (operation *addOperation) Execute(a float64, b float64) (float64, error) { + return a + b, nil +} + +/* ------------------------- DivideOperation (class) ------------------------ */ + +type divideOperation struct{} + +func NewDivideOperation() Operation { + var divide divideOperation = divideOperation{} + return ÷ +} + +func (operation *divideOperation) Execute(a float64, b float64) (float64, error) { + if b == 0 { + return 0, errors.New("The second parameter must not be zero") + } + + return a / b, nil + +} + +/* ------------------------ MultiplyOperation (class) ----------------------- */ + +type multiplyOperation struct{} + +func NewMultiplyOperation() Operation { + var multiply multiplyOperation = multiplyOperation{} + return &multiply +} + +func (operation *multiplyOperation) Execute(a float64, b float64) (float64, error) { + return a * b, nil + +} + +/* -------------------------- PowOperation (class) -------------------------- */ + +type powOperation struct{} + +func NewPowOperation() Operation { + var pow powOperation = powOperation{} + return &pow +} + +func (operation *powOperation) Execute(a float64, b float64) (float64, error) { + return math.Pow(a, b), nil + +} + +/* ------------------------ SubtractOperation (class) ----------------------- */ + +type subtractOperation struct{} + +func NewSubtractOperation() Operation { + var subtract subtractOperation = subtractOperation{} + return &subtract +} + +func (operation *subtractOperation) Execute(a float64, b float64) (float64, error) { + return a - b, nil + +} + +/* ------------------------------- Calculator ------------------------------- */ + +type ICalculator interface { + AddOperation(name string, operation *Operation) error + ExecuteOperation(name string, a float64, b float64) (float64, error) +} + +type calculator struct { + operations map[string](*Operation) + _ struct{} +} + +func NewCalculator() ICalculator { + var calculator calculator = calculator{operations: map[string]*Operation{}} + return &calculator +} + +func (calculator *calculator) AddOperation(name string, operation *Operation) error { + _, operationExist := calculator.operations[name] + if operationExist { + return fmt.Errorf("The operation with '%s' name already exist", name) + } + + calculator.operations[name] = operation + return nil +} + +func (calculator *calculator) ExecuteOperation(name string, a float64, b float64) (float64, error) { + operation, operationExist := calculator.operations[name] + if !operationExist { + return 0, fmt.Errorf("There is not operation with '%s' name", name) + } + + operationResult, err := (*operation).Execute(a, b) + if err != nil { + return 0, err + } + + return operationResult, nil +} + +/* -------------------------------------------------------------------------- */ +/* MAIN */ +/* -------------------------------------------------------------------------- */ + +func main() { + /* + Open-Close Principle (OCP)... + */ + + fmt.Println("Open-Close Principle (OCP)...") + + fmt.Println("\nBad implementation of Open-Close Principle (OCP)...") + + fmt.Println("\n```\n" + `type BadCircle struct { + radius float64 + _ struct{} +} + +type BadRectangle struct { + height float64 + width float64 + _ struct{} +} + +type BadCalculator struct{} + +func (calculator BadCalculator) GetArea(shape interface{}) float64 { + switch shape := shape.(type) { + case BadCircle: + return math.Pi * math.Pow(shape.radius, 2) + + case BadRectangle: + return shape.height * shape.width + + default: + return -1 + } +}` + "\n```") + + fmt.Println( + "\nThis is a bad implementation of Open-Close Principle (OCP),\n" + + "because the method 'getArea' of struct 'BadCalculator' will must\n" + + "change if we have to add more shapes types.", + ) + + fmt.Println("\nGood implementation of Open-Close Principle (OCP)...") + + fmt.Println("\n```\n" + `type Shape interface { + GetArea() float64 +} + +type goodCircle struct { + radius float64 + _ struct{} +} + +func NewGoodCircle(radius float64) Shape { + var circle goodCircle = goodCircle{radius: radius} + return &circle +} + +func (circle *goodCircle) GetArea() float64 { + return math.Pi * math.Pow(circle.radius, 2) +} + +type goodRectangle struct { + height float64 + width float64 + _ struct{} +} + +func NewGoodRectangle(height float64, width float64) Shape { + var rectangle goodRectangle = goodRectangle{height: height, width: width} + return &rectangle +} + +func (rectangle *goodRectangle) GetArea() float64 { + return rectangle.height * rectangle.width +} + +type IGoodCalculator interface { + GetArea(shape Shape) float64 +} + +type goodCalculator struct{} + +func NewGoodCalculator() IGoodCalculator { + var calculator goodCalculator = goodCalculator{} + return &calculator +} + +func (calculator *goodCalculator) GetArea(shape Shape) float64 { + return shape.GetArea() +}` + "\n```") + + fmt.Println( + "\nThis is a good implementation of Open-Close Principle (OCP),\n" + + "because the method 'getArea' of struct 'GoodCalculator' will must\n" + + "not change if we have to add more shapes. So, 'getArea' is closed to modification\n" + + "but it is open to extension throw any shape which implements 'Shape' interface.", + ) + + fmt.Println( + "\n# ---------------------------------------------------------------------------------- #", + ) + + /* + Additional challenge... + */ + + fmt.Println("\nAdditional challenge...") + + fmt.Println("\nTesting the OCP system without a pow operation...") + + var calculator ICalculator = NewCalculator() + + var addOperation Operation = NewAddOperation() + var divideOperation Operation = NewDivideOperation() + var multiplyOperation Operation = NewMultiplyOperation() + var subtractOperation Operation = NewSubtractOperation() + + calculator.AddOperation("add", &addOperation) + calculator.AddOperation("add", ÷Operation) + calculator.AddOperation("multiply", &multiplyOperation) + calculator.AddOperation("subtract", &subtractOperation) + + var a float64 = 11 + var b float64 = 8.2 + + addOperationResult, _ := calculator.ExecuteOperation("add", a, b) + fmt.Printf("\nAdd operation result (%.2f + %.2f): %f", a, b, addOperationResult) + + a, b = 5, 4.2 + divideOperationResult, _ := calculator.ExecuteOperation("divide", a, b) + fmt.Printf("\nDivide operation result (%.2f / %.2f): %f", a, b, divideOperationResult) + + a, b = 2, 6.6 + multiplyOperationResult, _ := calculator.ExecuteOperation("multiply", a, b) + fmt.Printf("\nMultiply operation result (%.2f * %.2f): %f", a, b, multiplyOperationResult) + + a, b = -1, -6.888 + subtractOperationResult, _ := calculator.ExecuteOperation("subtract", a, b) + fmt.Printf("\nSubtract operation result (%.2f - %.3f): %f", a, b, subtractOperationResult) + + fmt.Println("\n\nTesting the OCP system with a pow operation...") + + var powOperation Operation = NewPowOperation() + + calculator.AddOperation("pow", &powOperation) + + a, b = 2, 10 + powOperationResult, _ := calculator.ExecuteOperation("pow", a, b) + fmt.Printf("\nPow operation result (%.2f^%.2f): %f", a, b, powOperationResult) +} diff --git a/Roadmap/27 - SOLID OCP/ocaml/luishendrix92.ml b/Roadmap/27 - SOLID OCP/ocaml/luishendrix92.ml new file mode 100644 index 0000000000..46f2093a01 --- /dev/null +++ b/Roadmap/27 - SOLID OCP/ocaml/luishendrix92.ml @@ -0,0 +1,191 @@ +open Printf + +(******************************************************************************) +(* *) +(* Open-Close Principle *) +(* *) +(* It states that entities (methods, functions, modules, classes) should *) +(* not be altered while adding new functionality (unless for a bug). Means *) +(* that existing code should be open for extension and closed for modifica- *) +(* tion. Altering existing code while adding new functionalities requires *) +(* features to be tested again. *) +(* *) +(* In OCaml, a primarily functional language, this principle is interpreted *) +(* very differently than the OOP counterpart as it does not offer easy *) +(* ways to leverage the module system to play around with modules the same *) +(* way we'd play with classes in a language like Java or C#. However, the *) +(* language's way of extending things comes in the form of module functors, *) +(* function composition, variants and pattern matching; along with simple *) +(* {e class types} that can be thought of as interfaces. *) +(* *) +(******************************************************************************) + +type action = + | Payment + | SomethingElse +[@@deriving show] + +module CloudLogService = struct + let save_error msg = printf "Letting CloudLog save this error: %s\n" msg +end + +module PaymentBroker = struct + let inform_error msg = + printf "Letting our payment broker know something went wrong: %s\n" msg + ;; +end + +module AppLogger : sig + (* One of the most frequent examples is a logger with different strategies + that grows bigger when the developer adds new functionality in the form + of a very large if statement (or switch). To refactor this, OOP uses + inheritance or interfaces but in OCaml we use functors. *) + + val log_error : origin:action -> string -> unit +end = struct + let log_error ~origin msg = + begin + match origin with + | Payment -> PaymentBroker.inform_error msg + | SomethingElse -> () + end; + CloudLogService.save_error msg; + printf "[ERROR] [%s] %s\n" (show_action origin) msg + ;; +end + +(* We can now refactor and maybe achieve something similar to inheritance using + functors (modules that return modules) in order to comply with the OCP. + Another possible solution would be to use the strategy pattern and/or higher + -order functions that we pass everytime we use the base logger. *) + +module BaseLogger (Extended : sig + val log_error : string -> unit + end) = +struct + let log_error ~origin msg = + CloudLogService.save_error msg; + printf "[ERROR] [%s] %s\n" (show_action origin) msg; + Extended.log_error msg + ;; +end + +module PaymentLogger = BaseLogger (struct + let log_error = PaymentBroker.inform_error + end) + +(* Another common example is creating interfaced classes for every concrete + type of a particular abstract type and have them override a method that + clients will use regardless of what subtype it belongs to. + + This is handled with pattern-matching in OCaml and it's exactly the type + of code that OCP tries to avoid, but all the developer has to do is be + smart about where the decision making is done and if modifying it can + break things that aren't supposed to break. *) + +module Shape = struct + type t = + | Rectangle of float * float + | Triangle of float * float + | Circle of float + | Square of float + + let area = function + (* Technically these could be broken into separate functions that maybe live + in a module. This declutters the decision making code and enforces SRP. *) + | Circle radius -> Float.pi *. radius *. radius + | Square length -> length *. length + | Triangle (b, h) -> b *. h /. 2.0 + | Rectangle (w, h) -> w *. h + ;; +end + +(* OCaml has had classes (although limited) for a while now and I believe it's + wise to use them when your code is heavily reliant on mutable state. *) +class type shape = object + method area : float +end + +class rectangle (w : float) (h : float) = + object + val mutable height = h + val mutable width = w + method area = width *. height + end + +class triangle (b : float) (h : float) = + object + val mutable base = b + val mutable height = h + method area = base *. height + end + +class circle (r : float) = + object + val mutable radius = r + method area = Float.pi *. radius *. radius + end + +class square (l : float) = + object + val mutable length = l + method area = length *. length + end + +let _ = + let shapes : shape list = + [ new rectangle 5.0 3.0 + ; new triangle 10.0 5.0 + ; new circle 2.0 + ; new square 7.0 + ] + in + print_endline "Shape areas:"; + shapes + |> List.map (fun s -> s#area) + |> List.map string_of_float + |> List.iter print_endline +;; + +(******************************************************************************) +(* *) +(* DIFICULTAD EXTRA (Opcional) *) +(* *) +(******************************************************************************) +type token = + | Add + | Subtract + | Multiply + | Divide + | Power + +let add a b = a +. b +let subtract a b = a -. b +let multiply a b = a *. b +let divide a b = a /. b +let power a b = a ** b + +type binop = token * float * float + +let compute ((op, a, b) : binop) = + match op with + | Add -> add a b + | Subtract -> subtract a b + | Multiply -> multiply a b + | Divide -> divide a b + | Power -> power a b +;; + +let _ = + let ops = + [ Add, 3.0, 5.5 + ; Subtract, 10.0, 11.0 + ; Multiply, 5.0, 3.0 + ; Divide, 3.0, 2.0 + ; Power, 3.0, 2.0 + ] + in + print_newline (); + print_endline "Binary operation results:"; + ops |> List.map compute |> List.map string_of_float |> List.iter print_endline +;; diff --git a/Roadmap/27 - SOLID OCP/php/miguelex.php b/Roadmap/27 - SOLID OCP/php/miguelex.php new file mode 100644 index 0000000000..91c555d251 --- /dev/null +++ b/Roadmap/27 - SOLID OCP/php/miguelex.php @@ -0,0 +1,150 @@ +width = $width; + $this->height = $height; + } +} + +class Circle { + public $radius; + + public function __construct($radius) { + $this->radius = $radius; + } +} + +class AreaCalculator { + public function calculate($shapes) { + $area = 0; + foreach ($shapes as $shape) { + if ($shape instanceof Rectangle) { + echo "\nEl area del rectangulo es: ". $shape->width * $shape->height; + } elseif ($shape instanceof Circle) { + echo "\nEl area del circulo es: ". pi() * $shape->radius * $shape->radius; + } + } + } +} + +$shapes = [ + new Rectangle(5, 10), + new Circle(7) +]; + +echo "Ejercicio básico (sin cumplir OCP)"; +$calculator = new AreaCalculator(); +echo $calculator->calculate($shapes); + +// El problema de esta solución es que si ahroa creamos una clase triangle (por ejemplo) y queremos calcualr su área, debemos modificar AreaCalculator para tener en cuenta la nueva forma + +interface Shape { + public function area(); +} + +class Rectangle2 implements Shape { + private $width; + private $height; + + public function __construct($width, $height) { + $this->width = $width; + $this->height = $height; + } + + public function area() { + return $this->width * $this->height; + } +} + +class Circle2 implements Shape { + private $radius; + + public function __construct($radius) { + $this->radius = $radius; + } + + public function area() { + return pi() * $this->radius * $this->radius; + } +} + +class AreaCalculator2 { + public function calculate($shapes) { + + foreach ($shapes as $shape) { + echo "\nEl área de la forma es: ". $shape->area(); + } + } +} + +$shapes = [ + new Rectangle2(5, 10), + new Circle2(7) +]; + +echo "\nEjercicio básico (cumpliendo OCP)"; +$calculator = new AreaCalculator2(); +echo $calculator->calculate($shapes); + +// Con este enfoque, la clase Triangle tendría su propia definición de como calcular el área y AreaCalculator2 no se tendría que modificar + +// Ejercicio Extra + +interface Operation { + public function calculate ($a, $b); +} + +class Sum implements Operation { + public function calculate($a, $b) { + return $a + $b; + } +} + +class Substract implements Operation { + public function calculate($a, $b) { + return $a - $b; + } +} + +class Multiply implements Operation { + public function calculate($a, $b) { + return $a * $b; + } +} + +class Divition implements Operation{ + public function calculate($a, $b) { + return $a / $b; + } +} + +class Calculator { + public function calculate($operation, $a, $b) { + return $operation->calculate($a, $b); + } +} + +$calculator = new Calculator(); +echo "\nEjercicio extra"; +echo "\nLa suma de 5 y 10 es: ". $calculator->calculate(new Sum(), 5, 10); +echo "\nLa resta de 5 y 10 es: ". $calculator->calculate(new Substract(), 5, 10); +echo "\nLa multiplicación de 5 y 10 es: ". $calculator->calculate(new Multiply(), 5, 10); +echo "\nLa división de 5 y 10 es: ". $calculator->calculate(new Divition(), 5, 10); + +// A continuación vamos a añadir la operación potencia. + +class Power implements Operation { + public function calculate($a, $b) { + return pow($a, $b); + } +} + +echo "\nLa potencia de 5 y 10 es: ". $calculator->calculate(new Power(), 5, 10); + +// Como vemos, no hemos tenido que modificar nada en la clase Calculator \ No newline at end of file diff --git a/Roadmap/27 - SOLID OCP/python/SooHav.py b/Roadmap/27 - SOLID OCP/python/SooHav.py new file mode 100644 index 0000000000..29f730a815 --- /dev/null +++ b/Roadmap/27 - SOLID OCP/python/SooHav.py @@ -0,0 +1,357 @@ +# 27 SOLID: PRINCIPIO ABIERTO-CERRADO (OCP) +from abc import ABC, abstractmethod +import logging + +# Ejercicio +# Ejemplo Pato sin SRP + + +class Pato(): + + def __init__(self, nombre): + self.nombre = nombre + + def vuela(self): + print(f"{self.nombre} vuela.") + + def nada(self): + print(f"{self.nombre} nada.") + + def dice(self) -> str: + return "Quack" + + def saluda(self, pato2): + print(f"{self.nombre}: {self.dice()}, hola {pato2.nombre}") + + +# Uso +print("Sin SRP") +pato1 = Pato(nombre="Daisy") +pato2 = Pato(nombre="Donald") +pato1.vuela() +pato1.nada() +pato1.saluda(pato2) +print("\n") + +# Ejemplo Pato con SRP + + +class Pato(): + """Clase que define los patos""" + + def __init__(self, nombre): + self.nombre = nombre + + def vuela(self): + print(f"{self.nombre} vuela.") + + def nada(self): + print(f"{self.nombre} nada.") + + def dice(self) -> str: + return "Quack" + + +class Dialogo(): + """Clase que define la comunicacion entre patos""" + + def __init__(self, formato): + self.formato = formato + + def conversacion(self, pato1: Pato, pato2: Pato): + frase1 = f"{pato1.nombre}: {pato1.dice()}, ¡Ay, { + pato2.nombre}! ¡Casi me caigo al intentar atrapar un pez!" + frase2 = f"{pato2.nombre}: {pato2.dice()}, ¡Otra vez, { + pato1.nombre}? Ten más cuidado la próxima vez." + conversacion = [frase1, frase2] + print(*conversacion, + f"(Extracto de {self.formato})", + sep='\n') + + +# Uso +print("Con SRP") +pato1 = Pato(nombre="Donald") +pato2 = Pato(nombre="Daisy") +pato1.vuela() +pato2.nada() +formato1 = Dialogo(formato="historieta") +formato1.conversacion(pato1, pato2) + +# Ejemplo Pato con SRP y OCP + + +class Pato(): + """Clase que define los patos""" + + def __init__(self, nombre, color, edad): + self.nombre = nombre + self.color = color + self.edad = edad + + def vuela(self) -> str: + print(f"{self.nombre} vuela.") + + def nada(self) -> str: + print(f"{self.nombre} nada.") + + def dice(self) -> str: + return "Quack" + + def describe(self) -> str: + print(f"{self.nombre} es un pato de color { + self.color} de {self.edad} años.") + + +class Dialogo(ABC): + """Clase base abstracta que define la comunicación entre patos""" + @abstractmethod + def conversacion(self, pato1: Pato, pato2: Pato, mensaje1: str, mensaje2: str): + pass + + +class DialogoHistorieta(Dialogo): + """Clase para el diálogo en formato historieta""" + + def conversacion(self, pato1: Pato, pato2: Pato, mensaje1: str, mensaje2: str): + frase1 = f"{pato1.nombre}: {pato1.dice()}, {mensaje1}" + frase2 = f"{pato2.nombre}: {pato2.dice()}, {mensaje2}" + conversacion = [frase1, frase2] + print(*conversacion, + f"(Extracto de historieta)", + sep='\n') + + +class DialogoFormal(Dialogo): + """Clase para el diálogo en un formato más formal""" + + def conversacion(self, pato1: Pato, pato2: Pato, mensaje1: str, mensaje2: str): + frase1 = f"{pato1.nombre}: Buen día, {pato2.nombre}. {mensaje1}" + frase2 = f"{pato2.nombre}: Estoy bien, {pato1.nombre}. {mensaje2}" + conversacion = [frase1, frase2] + print(*conversacion, + f"(Extracto de diálogo formal)", + sep='\n') + + +# Uso +print("Con SRP y OCP") +pato1 = Pato(nombre="Donald", color="blanco", edad=5) +pato2 = Pato(nombre="Daisy", color="amarillo", edad=4) +pato1.vuela() +pato2.nada() +pato1.describe() +print() + +# DialogoHistorieta +formato_historieta = DialogoHistorieta() +formato_historieta.conversacion(pato1, pato2, + mensaje1="¡Ay, Daisy! ¡Casi me caigo al intentar atrapar un pez!", + mensaje2="¡Otra vez, Donald? Ten más cuidado la próxima vez.") +print() +# DialogoFormal +formato_formal = DialogoFormal() +formato_formal.conversacion(pato1, pato2, + mensaje1="¿Cómo estás?", + mensaje2="Gracias por preguntar.") +print() + +""" +Observaciones: +Implementar ABC, y en particular abstractmethod, en las clases ayuda a seguir el principio OCP, +porque define clases que pueden agregar nuevas funcionalidades sin alterar el código existente +mediante un diseño modular. + +Al usar clases abstractas, se definen comportamientos comunes en una clase base y +se permite que las clases derivadas personalicen o extiendan esos comportamientos. +Esto facilita la adición de nuevas funcionalidades sin modificar el código existente. +""" + +# Extra + +# Configurar Logger +logging.basicConfig(level=logging.WARNING, + format='%(asctime)s - %(levelname)s - %(message)s') + + +class Calculadora(ABC): + """Clase base abstracta que define la aplicación de cálculos""" + @abstractmethod + def operacion(self): + pass + + @abstractmethod + def impresion(self): + pass + + +def redondeo(funcion): + def nueva_funcion(*args, **kwargs): + logging.debug("Implementando funcion de redondeo") + try: + resultado = funcion(*args, **kwargs) + if resultado is None: + return None + elif isinstance(resultado, tuple): + return tuple(round(val, 2) for val in resultado) + else: + return round(resultado, 2) + except Exception as e: + logging.error(f"Error en la función '{funcion.__name__}': {e}") + return None + return nueva_funcion + + +class Suma(Calculadora): + logging.debug("Implementando funcion de Suma") + + def __init__(self, sumando1: float, sumando2: float) -> float: + self.sumando1 = sumando1 + self.sumando2 = sumando2 + + @redondeo + def operacion(self): + try: + suma_total = self.sumando1 + self.sumando2 + return suma_total + except Exception as e: + logging.error(f"Error al calcular la suma: {e}") + return None + + def impresion(self): + suma_total = self.operacion() + if suma_total is not None: + print(f"La suma de {self.sumando1} y { + self.sumando2} dio como resultado {suma_total}") + logging.debug("La implementación de suma fue exitosa") + else: + logging.warning("No se pudo realizar la suma.") + + +class Resta(Calculadora): + logging.debug("Implementando funcion de Resta") + + def __init__(self, minuendo: float, sustraendo: float) -> float: + self.minuendo = minuendo + self.sustraendo = sustraendo + + @redondeo + def operacion(self): + try: + resto = self.minuendo - self.sustraendo + return resto + except Exception as e: + logging.error(f"Error al calcular la resta: {e}") + return None + + def impresion(self): + resto = self.operacion() + if resto is not None: + print(f"La resta de {self.minuendo} y { + self.sustraendo} dio como resultado {resto}") + logging.debug("La implementación de resta fue exitosa") + else: + logging.warning("No se pudo realizar la resta.") + + +class Multiplicacion(Calculadora): + logging.debug("Implementando funcion de Multiplicación") + + def __init__(self, factor1: float, factor2: float) -> float: + self.factor1 = factor1 + self.factor2 = factor2 + + @redondeo + def operacion(self): + try: + producto = self.factor1 * self.factor2 + return producto + except Exception as e: + logging.error(f"Error al calcular la multiplicación: {e}") + return None + + def impresion(self): + producto = self.operacion() + if producto is not None: + print(f"La multiplicación de {self.factor1} y { + self.factor2} dio como resultado {producto}") + logging.debug("La implementación de multiplicació fue exitosa") + else: + logging.warning("No se pudo realizar la multiplicación.") + + +class Division(Calculadora): + logging.debug("Implementando funcion de división") + + def __init__(self, dividendo: float, divisor: float) -> float: + self.dividendo = dividendo + self.divisor = divisor + + @redondeo + def operacion(self): + try: + if self.divisor == 0: + raise ValueError("División por cero no está permitida.") + cociente = self.dividendo / self.divisor + resto = self.dividendo % self.divisor + return cociente, resto + except Exception as e: + logging.error(f"Error al calcular la división: {e}") + return None + + def impresion(self): + resultado = self.operacion() + if resultado is not None: + cociente, resto = resultado + print(f"La división de {self.dividendo} y { + self.divisor} dio como resultado cociente {cociente} y resto {resto}") + logging.debug("La implementación de división fue exitosa") + else: + logging.warning(f"No se puede realizar la división de { + self.dividendo} por {self.divisor}.") + + +class Potencia(Calculadora): + logging.debug("Implementando funcion de potencia") + + def __init__(self, base: int, exponente: float) -> float: + self.base = base + self.exponente = exponente + + @redondeo + def operacion(self): + try: + resultado = self.base ** self.exponente + return resultado + except Exception as e: + logging.error(f"Error al calcular la potencia: {e}") + return None + + def impresion(self): + resultado = self.operacion() + if resultado is not None: + print(f"La potenciación de {self.base} y { + self.exponente} dio como resultado {resultado}.") + logging.debug("La implementación de potencia fue exitosa") + else: + logging.warning("No se pudo realizar la potenciación.") + + +# Uso +s = Suma(4, 5) +s.impresion() + +r = Resta(9, 3) +r.impresion() + +m = Multiplicacion(7, 6) +m.impresion() + +d = Division(20, 0) +d.impresion() + +d = Division(20, 4) +d.impresion() + +p = Potencia(2, 2) +p.impresion() diff --git a/Roadmap/27 - SOLID OCP/python/hozlucas28.py b/Roadmap/27 - SOLID OCP/python/hozlucas28.py new file mode 100644 index 0000000000..28f6cd98bf --- /dev/null +++ b/Roadmap/27 - SOLID OCP/python/hozlucas28.py @@ -0,0 +1,346 @@ +# pylint: disable=pointless-string-statement,missing-class-docstring,missing-function-docstring,missing-module-docstring,too-few-public-methods,useless-parent-delegation + +from typing import Union, Literal, Self +from abc import abstractmethod, ABCMeta + +""" + Open-Close Principle (OCP)... +""" + +print("Open-Close Principle (OCP)...") + +print("\nBad implementation of Open-Close Principle (OCP)...") + + +class AbcProduct(metaclass=ABCMeta): + name: str + price: float + + def __init__(self, *, name: str, price: float) -> None: + self.name = name + self.price = price + + +class Product(AbcProduct): + def __init__(self, *, name: str, price: float) -> None: + super().__init__(name=name, price=price) + + +DiscountStrategy = Union[Literal["holyday"], Literal["seasonal"]] + + +class BadDiscountCalculator: + def __init__(self) -> None: + pass + + def get_discount( + self, *, product: AbcProduct, discount_strategy: DiscountStrategy + ) -> float: + if discount_strategy == "holyday": + return product.price * 0.15 + + if discount_strategy == "seasonal": + return product.price * 0.21 + + return -1 + + +print( + "\n```", + """class AbcProduct(metaclass=ABCMeta): + name: str + price: float + + def __init__(self, *, name: str, price: float) -> None: + self.name = name + self.price = price + + +class Product(AbcProduct): + def __init__(self, *, name: str, price: float) -> None: + super().__init__(name=name, price=price) + + +DiscountStrategy = Union[Literal["holyday"], Literal["seasonal"]] + + +class BadDiscountCalculator: + def __init__(self) -> None: + pass + + def get_discount( + self, *, product: AbcProduct, discount_strategy: DiscountStrategy + ) -> float: + if discount_strategy == "holyday": + return product.price * 0.15 + + if discount_strategy == "seasonal": + return product.price * 0.21 + + return -1""", + "```", + sep="\n", +) + +print( + "\nThis is a bad implementation of Open-Close Principle (OCP),", + "because the method 'get_discount' of class 'BadDiscountCalculator'", + "will must change if we have to add more discount strategies", + sep="\n", +) + +print("\nGood implementation of Open-Close Principle (OCP)...") + + +class AbcDiscountService(metaclass=ABCMeta): + product: AbcProduct + + def __init__(self, *, product: AbcProduct) -> None: + self.product = product + + @abstractmethod + def get_discount(self) -> float: + pass + + +class HolidayDiscountService(AbcDiscountService): + def __init__(self, *, product: AbcProduct) -> None: + super().__init__(product=product) + + def get_discount(self) -> float: + return self.product.price * 0.15 + + +class SeasonalDiscountService(AbcDiscountService): + def __init__(self, *, product: AbcProduct) -> None: + super().__init__(product=product) + + def get_discount(self) -> float: + return self.product.price * 0.21 + + +class AbcGoodDiscountCalculator(metaclass=ABCMeta): + @abstractmethod + def get_discount(self, *, discount_service: AbcDiscountService) -> float: + pass + + +class GoodDiscountCalculator(AbcGoodDiscountCalculator): + def __init__(self) -> None: + pass + + def get_discount(self, *, discount_service: AbcDiscountService) -> float: + return discount_service.get_discount() + + +print( + "\n```", + """class AbcDiscountService(metaclass=ABCMeta): + product: AbcProduct + + def __init__(self, *, product: AbcProduct) -> None: + self.product = product + + @abstractmethod + def get_discount(self) -> float: + pass + + +class HolidayDiscountService(AbcDiscountService): + def __init__(self, *, product: AbcProduct) -> None: + super().__init__(product=product) + + def get_discount(self) -> float: + return self.product.price * 0.15 + + +class SeasonalDiscountService(AbcDiscountService): + def __init__(self, *, product: AbcProduct) -> None: + super().__init__(product=product) + + def get_discount(self) -> float: + return self.product.price * 0.21 + + +class AbcGoodDiscountCalculator(metaclass=ABCMeta): + @abstractmethod + def get_discount(self, *, discount_service: AbcDiscountService) -> float: + pass + + +class GoodDiscountCalculator(AbcGoodDiscountCalculator): + def __init__(self) -> None: + pass + + def get_discount(self, *, discount_service: AbcDiscountService) -> float: + return discount_service.get_discount()""", + "```", + sep="\n", +) + +print( + "\nThis is a good implementation of Open-Close Principle (OCP),", + "because the method 'get_discount' of class 'GoodDiscountCalculator' will must", + "not change if we have to add more discount services. So, 'get_discount' is closed", + "to modification but it is open to extension throw any discount service which implements", + "'AbcDiscountService' abstract class.", + sep="\n", +) + +print( + "\n# ---------------------------------------------------------------------------------- #\n" +) + +""" + Additional challenge... +""" + +print("Additional challenge...") + + +class AbcMathOperation(metaclass=ABCMeta): + @abstractmethod + def execute(self, *, a: float, b: float) -> float: + pass + + +class AddOperation(AbcMathOperation): + def __init__(self) -> None: + pass + + def execute(self, *, a: float, b: float) -> float: + return a + b + + +class DivideOperation(AbcMathOperation): + def __init__(self) -> None: + pass + + def execute(self, *, a: float, b: float) -> float: + if b == 0: + raise ValueError("The second parameter must not be zero") + + return a / b + + +class MultiplyOperation(AbcMathOperation): + def __init__(self) -> None: + pass + + def execute(self, *, a: float, b: float) -> float: + return a * b + + +class SubtractOperation(AbcMathOperation): + def __init__(self) -> None: + pass + + def execute(self, *, a: float, b: float) -> float: + return a - b + + +class AbcCalculator(metaclass=ABCMeta): + operations: dict[str, AbcMathOperation] + + def __init__(self, *, operations) -> None: + self.operations = operations + + @abstractmethod + def add_operation(self, *, name: str, operation: AbcMathOperation) -> Self: + pass + + @abstractmethod + def execute_operation(self, *, name: str, a: float, b: float) -> float: + pass + + +class Calculator(AbcCalculator): + operations: dict[str, AbcMathOperation] + + def __init__( + self, *, operations: None | dict[str, AbcMathOperation] = None + ) -> None: + if operations is None: + operations = {} + + super().__init__(operations=operations) + + def add_operation(self, *, name: str, operation: AbcMathOperation) -> Self: + operation_exist: bool = self.operations.get(name) is not None + + if operation_exist: + raise ValueError(f"The operation with '{name}' name already exist") + + self.operations[name] = operation + return self + + def execute_operation(self, *, name: str, a: float, b: float) -> float: + operation_not_exist: bool = self.operations.get(name) is None + + if operation_not_exist: + raise ValueError(f"There is not operation with '${name}' name") + + return self.operations[name].execute(a=a, b=b) + + +print("\nTesting the OCP system without a pow operation...") + +CALCULATOR: Calculator = Calculator() + +CALCULATOR.add_operation(name="add", operation=AddOperation()) +CALCULATOR.add_operation(name="divide", operation=DivideOperation()) +CALCULATOR.add_operation(name="multiply", operation=MultiplyOperation()) +CALCULATOR.add_operation(name="subtract", operation=SubtractOperation()) + +A: float = 4.5 +B: float = 6.1 + +print( + f"\nAdd operation result ({A} + {B}):", + CALCULATOR.execute_operation(name="add", a=A, b=B), +) + +A = 5 +B = 2.2 + +print( + f"Divide operation result ({A} / {B}):", + CALCULATOR.execute_operation(name="divide", a=A, b=B), +) + +A = 3 +B = 1.75 + +print( + f"Multiply operation result ({A} * {B}):", + CALCULATOR.execute_operation(name="multiply", a=A, b=B), +) + +A = 10 +B = 8.7 + +print( + f"Subtract operation result ({A} - {B}):", + CALCULATOR.execute_operation(name="subtract", a=A, b=B), +) + +print("\nTesting the OCP system with a pow operation...") + + +class PowOperation(AbcMathOperation): + def __init__(self) -> None: + pass + + def execute(self, *, a: float, b: float) -> float: + return pow(base=a, exp=b) + + +CALCULATOR.add_operation(name="pow", operation=PowOperation()) + +A = 11 +B = 2 + +print( + f"\nPow operation result ({A}^{B}):", + CALCULATOR.execute_operation(name="pow", a=A, b=B), +) diff --git a/Roadmap/27 - SOLID OCP/python/neslarra.py b/Roadmap/27 - SOLID OCP/python/neslarra.py new file mode 100644 index 0000000000..742d398939 --- /dev/null +++ b/Roadmap/27 - SOLID OCP/python/neslarra.py @@ -0,0 +1,343 @@ +from functools import reduce +from typing import final + +""" + EJERCICIO: + Explora el "Principio SOLID Abierto-Cerrado (Open-Close Principle, OCP)" + y crea un ejemplo simple donde se muestre su funcionamiento + de forma correcta e incorrecta. + + DIFICULTAD EXTRA (opcional): + Desarrolla una calculadora que necesita realizar diversas operaciones matemáticas. + Requisitos: + - Debes diseñar un sistema que permita agregar nuevas operaciones utilizando el OCP. + Instrucciones: + 1. Implementa las operaciones de suma, resta, multiplicación y división. + 2. Comprueba que el sistema funciona. + 3. Agrega una quinta operación para calcular potencias. + 4. Comprueba que se cumple el OCP. +""" + +print(f"{'#' * 47}") +print(f"## Explicación {'#' * 30}") +print(f"{'#' * 47}") + +print(r""" +Para entender fácilmente los 5 ppios SOLID recomiendo leer: + + https://blog.damavis.com/los-principios-solid-ilustrados-en-ejemplos-sencillos-de-python/ + +en donde se explican de manera ordenada uno por uno, de manera sencilla y ejemplificada de manera progresiva (de hecho, de ahí +voy a tomar el ejemplo). + +El segundo de los ppios SOLID es "Open Close Principle" el cual establece que las clases deberían estar abiertas para su extensión +pero cerradas para su modificación. + +Retomando el caso anterior, tenemos la clase Calculate: + + class Calculate: + + def __init__(self, channel): + self.channel = channel + + def communicate(self, duck1 : Duck, duck2: Duck): + sentence1 = f"{duck1.name}: {duck1.do_sound()}, hello {duck2.name}" + sentence2 = f"{duck2.name}: {duck2.do_sound()}, hello {duck1.name}" + conversation = [sentence1, sentence2] + print(*conversation, + f"(via {self.channel})", + sep = '\n') + +En esta clase No se puede extender la funcionalidad de Calculate para añadir diferentes tipos de conversaciones sin modificar +el método communicate(). Para cumplir con el segundo principio, se crea una clase AbstractConversation que se encargará de definir +diferentes tipos de conversaciones en sus subclases con implementaciones de do_conversation(). De esta manera, el método communicate() +de Calculate solo se regirá a llevar a cabo la comunicación a través de una canal y nunca se requerirá de su modificación (es un método final). + +from typing import final + + + class Duck: + + def __init__(self, name): + self.name = name + + def fly(self): + print(f"{self.name} is flying not very high") + + def swim(self): + print(f"{self.name} swims in the lake and quacks") + + @staticmethod + def do_sound() -> str: + return "Quack" + + + class AbstractConversation: + + def do_conversation(self) -> list: + pass + + + class DuckConversation(AbstractConversation): + + def __init__(self, duck1: Duck, duck2: Duck): + self.duck1 = duck1 + self.duck2 = duck2 + + def do_conversation(self) -> list: + sentence1 = f"{self.duck1.name}: {self.duck1.do_sound()}, hello {self.duck2.name}" + sentence2 = f"{self.duck2.name}: {self.duck2.do_sound()}, hello {self.duck1.name}" + return [sentence1, sentence2] + + + class Calculate: + + def __init__(self, channel): + self.channel = channel + + @final + def communicate(self, conversation: AbstractConversation): + print(*conversation.do_conversation(), sep="\n") + + + lucas = Duck("Lucas") + donald = Duck("Donald") + comm = Calculate(DuckConversation(lucas, donald)) + + comm.communicate(comm.channel) + + scooby = Dog("Scooby") + pluto = Dog("Pluto") + comm = Calculate(DogConversation(scooby, pluto)) + + comm.communicate(comm.channel) + +lucas = Duck("Lucas") +donald = Duck("Donald") +comm = Calculate(DuckConversation(lucas, donald)) +comm.communicate(comm.channel) + + Lucas: Quack, hello Donald + Donald: Quack, hello Lucas + +Ahora, si necesito extender Comunicator para que puedan conversar dos perros, entonces solo tengo que agregar una clase DogConversation +(subclase de AbstractConversation) que implemente SU versión canina de "do_conversation" dejando "Calculate.communicate sin cambio. + + class Dog: + + def __init__(self, name): + self.name = name + + def jump(self): + print(f"{self.name} is jumpping not very high") + + def run(self): + print(f"{self.name} runs behind the cars") + + @staticmethod + def do_sound() -> str: + return "Guau" + + + class DogConversation(AbstractConversation): + + def __init__(self, dog1: Dog, dog2: Dog): + self.dog1 = dog1 + self.dog2 = dog2 + + def do_conversation(self) -> list: + sentence1 = f"{self.dog1.name}: {self.dog1.do_sound()}, hello {self.dog2.name}" + sentence2 = f"{self.dog2.name}: {self.dog2.do_sound()}, hello {self.dog1.name}" + return [sentence1, sentence2] + + +scooby = Dog("Scooby") +pluto = Dog("Pluto") +comm = Calculate(DogConversation(scooby, pluto)) +comm.communicate(comm.channel) + + Scooby: Guau, hello Pluto + Pluto: Guau, hello Scooby +""") + +print(f"{'#' * 52}") +print(f"## Dificultad Extra {'#' * 30}") +print(f"{'#' * 52}\n") + +print(f"\nNO OCP Way {'-' * 27}\n") + + +class OperationNoOCP: + + def __init__(self, name: str): + self.name = name + + def addition(self, *args): + return self.name + " addition = " + str(sum(args)) + + def substraction(self, *args): + return self.name + " substraction = " + str(args[0] + (-1 * sum(args[1:]))) + + def product(self, *args): + return self.name + " product = " + str(reduce(lambda a, b: a * b, args)) + + def division(self, dividend: float, divisor: float): + if divisor == 0: + return self.name + " Illegal operation: Divisor cannot be zero" + return self.name + " division = " + str(dividend / divisor) + + +operaciones = OperationNoOCP("NoOCP") +print(operaciones.addition(1, 2, 3, -4)) +print(operaciones.substraction(15, 2, 3, -4)) +print(operaciones.product(1, 2, 3, -4)) +print(operaciones.division(12, -4)) + +print(f"\nOCP Way {'-' * 30}\n") + + +class AbstractOperation: + + def calculate(self): + pass + + +class Addition: + + def __init__(self, *args): + self.name = "OCP addition" + self.args = args + + def calculate(self): + return self.name + " = " + str(sum(self.args)) + + +class Substraction: + + def __init__(self, *args): + self.name = "OCP substraction" + self.args = args + + def calculate(self): + return self.name + " = " + str(self.args[0] + (-1 * sum(self.args[1:]))) + + +class Product: + def __init__(self, *args): + self.name = "OCP product" + self.args = args + + def calculate(self): + return self.name + " = " + str(reduce(lambda a, b: a * b, self.args)) + + +class Division: + + def __init__(self, dividend: float, divisor: float): + self.name = "OCP division" + self.dividend = dividend + self.divisor = divisor + + def calculate(self): + if self.divisor == 0: + return self.name + " Illegal operation: Divisor cannot be zero" + return self.name + " = " + str(self.dividend / self.divisor) + + +class DoAddition(AbstractOperation): + + def __init__(self, operation: Addition): + self.operation = operation + + def calculate(self, *args): + return self.operation.calculate() + + +class DoSubstraction(AbstractOperation): + + def __init__(self, operation: Substraction): + self.operation = operation + + def calculate(self, *args): + return self.operation.calculate() + + +class DoProduct(AbstractOperation): + + def __init__(self, operation: Product): + self.operation = operation + + def calculate(self, *args): + return self.operation.calculate() + + +class DoDivision(AbstractOperation): + + def __init__(self, operation: Division): + self.operation = operation + + def calculate(self, *args): + return self.operation.calculate() + + +class Calculate: + + def __init__(self, channel): + self.channel = channel + + @final + def calculate(self, operation: AbstractOperation): + print(operation.calculate()) + + +suma = Addition(1, 2, 3, -4) +calc = Calculate(DoAddition(suma)) +calc.calculate(calc.channel) + +resta = Substraction(15, 2, 3, -4) +calc = Calculate(DoSubstraction(resta)) +calc.calculate(calc.channel) + +producto = Product(1, 2, 3, -4) +calc = Calculate(DoProduct(producto)) +calc.calculate(calc.channel) + +division = Division(12, -4) +calc = Calculate(DoDivision(division)) +calc.calculate(calc.channel) + +print(f""" +Está claro que si ahora quiero agregar la operación "potencia", para el caso NoOCP tendría que modificar +la clase OperationNoOCP agragando el nuevo método (con las posibles consecuencias de modificar una clase que está en +uso para las otras cuatro operaciones). + +En cambio, para el caso OCP, agregar la nueva operación es solo agregar el la clase y el método abstracto correspondiente +sin "tocar" las operaciones que ya están en uso. +""") + + +class Power: + def __init__(self, base: int, exponent: int): + self.name = "OCP power" + self.base = base + self.exponent = exponent + + def calculate(self): + return self.name + " = " + str(pow(self.base, self.exponent)) + + +class DoPower(AbstractOperation): + + def __init__(self, operation: Power): + self.operation = operation + + def calculate(self, *args): + return self.operation.calculate() + + +potencia = Power(4, 3) +calc = Calculate(DoPower(potencia)) +calc.calculate(calc.channel) + +print(f""" +¿Se ve..? ahora "Calculate" puede llamar a Power de la misma manera que llama a las otras operaciones PERO "Calcualte" NUNCA fue modificada.""") diff --git a/Roadmap/27 - SOLID OCP/swift/blackriper.swift b/Roadmap/27 - SOLID OCP/swift/blackriper.swift new file mode 100644 index 0000000000..ba69241574 --- /dev/null +++ b/Roadmap/27 - SOLID OCP/swift/blackriper.swift @@ -0,0 +1,163 @@ +import Foundation + +/* +Open Closed Principle + +Este principio establece que una entidad de software (clase, módulo, función, etc) +debe quedar abierta para su extensión, pero cerrada para su modificación. + +Con abierta para su extensión, nos quiere decir que una entidad de software debe tener la capacidad +de adaptarse a los cambios y nuevas necesidades de una aplicación, pero con la segunda parte de “cerrada +para su modificación” nos da a entender que la adaptabilidad de la entidad no debe darse +como resultado de la modificación del core de dicha entidad si no como resultado de un diseño +que facilite la extensión sin modificaciones. + +*/ + +// lo que no debe de hacerce +enum DriverDatabase { + case sqlite + case postgres + case mysql +} + +struct Product { + let id:UUID=UUID() + let name: String + let price: Double +} + + +final class DatabaseService{ + + func saveProduct(product: Product, database: DriverDatabase) { + switch database { + case .sqlite: + insertProductinSQLite(product: product) + case .postgres: + insertProductinPostgres(product: product) + case .mysql: + insertProductinMySQL(product: product) + + } + } + +} + +extension DatabaseService { + func insertProductinSQLite(product: Product) { + print("Insert \(product) in SQLite") + } + func insertProductinPostgres(product: Product) { + print("Insert \(product) in Postgres") + } + func insertProductinMySQL(product: Product) { + print("Insert \(product) in MySQL") + } +} + +/* las desventaja es que al querer hacer un cambio hay que modificar el core de la clase */ + +let db = DatabaseService() +db.saveProduct(product: Product(name: "macbook pro", price: 100.0), database: .sqlite) + + +// aplicando el principio open closed principle esto se puede usar aplicando protocolos, herencia , polimorfismo + +// usando herencia creamos nuestra clase padre +class DatabaseRepository{ + func saveProduct(product: Product) { + print("Insert \(product) in database") + } +} + +// creamos los diferentes drivers como subclases de la clase padre +class MongoDB: DatabaseRepository{ + override func saveProduct(product: Product) { + print("Insert product id \(product.id) in MongoDB") + } +} + +class RedisDB: DatabaseRepository{ + override func saveProduct(product: Product) { + print("Insert product id \(product.id) in Redis") + } +} + +class cockroachDB: DatabaseRepository{ + override func saveProduct(product: Product) { + print("Insert product id \(product.id) in cockroachDB") + } +} + +// creamos la clase que implementa la logica +class DatabaseServiceOPC { + func saveDataInDatabase(product: Product, database: DatabaseRepository) { + database.saveProduct(product: product) + } + } +// utlizaando el principio podemos extender la clase sin modificar su core +let dbOPC = DatabaseServiceOPC() +dbOPC.saveDataInDatabase(product: Product(name: "macbook pro", price: 100.0), database: MongoDB()) +dbOPC.saveDataInDatabase(product: Product(name: "iphone15", price: 400.00), database: RedisDB()) + +// ejercicio extra + +// implemementar operaciones y manteniendo el principio cerrado controlado mediante un protocolo +protocol Operation{ + func executeOperation(num1:Int, num2:Int)->Int +} + +struct Add:Operation{ + func executeOperation(num1: Int, num2: Int) -> Int { + return num1+num2 + } +} + +struct Sub:Operation{ + func executeOperation(num1: Int, num2: Int) -> Int { + return num1-num2 + } +} + +struct Mul:Operation{ + func executeOperation(num1: Int, num2: Int) -> Int { + return num1*num2 + } +} + +struct Div:Operation{ + func executeOperation(num1: Int, num2: Int) -> Int { + return num1/num2 + } +} + +class Calculator{ + func calculate(num1:Int, num2:Int, operation:Operation) -> Int { + return operation.executeOperation(num1: num1, num2: num2) + } +} + +// primera implementacion +let calc = Calculator() +let sum = Add() +let res = Sub() +let mul = Mul() +let div = Div() +print("Sum: \(calc.calculate(num1: 10, num2: 5, operation: sum))") +print("Res: \(calc.calculate(num1: 10, num2: 5, operation: res))") +print("Mult: \(calc.calculate(num1: 10, num2: 5, operation: mul))") +print("Div: \(calc.calculate(num1: 10, num2: 5, operation: div))") + +// agregando nueva operacion sin alterar el core de la clase por lo que el principio n se cumple + +struct MathPow:Operation{ + func executeOperation(num1: Int, num2: Int) -> Int { + return Int(pow(Double(num1), Double(num2))) + + } +} + +// segunda implementacion +let powM = MathPow() +print("Pow: \(calc.calculate(num1: 2, num2: 4, operation: powM))") diff --git a/Roadmap/stats.json b/Roadmap/stats.json index 99de9a04c5..b0c67055a7 100644 --- a/Roadmap/stats.json +++ b/Roadmap/stats.json @@ -1,7 +1,7 @@ { "challenges_total": 28, "languages_total": 47, - "files_total": 5325, + "files_total": 5330, "users_total": 925, "challenges_ranking": [ { @@ -22,7 +22,7 @@ { "order": 4, "name": "03 - ESTRUCTURAS DE DATOS", - "count": 330 + "count": 332 }, { "order": 5, @@ -132,7 +132,7 @@ { "order": 26, "name": "25 - LOGS", - "count": 43 + "count": 44 }, { "order": 27, @@ -142,21 +142,21 @@ { "order": 28, "name": "27 - SOLID OCP", - "count": 3 + "count": 5 } ], "languages_ranking": [ { "order": 1, "name": "python", - "count": 2281, - "percentage": 42.84 + "count": 2285, + "percentage": 42.87 }, { "order": 2, "name": "javascript", "count": 1063, - "percentage": 19.96 + "percentage": 19.94 }, { "order": 3, @@ -167,8 +167,8 @@ { "order": 4, "name": "typescript", - "count": 219, - "percentage": 4.11 + "count": 220, + "percentage": 4.13 }, { "order": 5, @@ -192,7 +192,7 @@ "order": 8, "name": "swift", "count": 151, - "percentage": 2.84 + "percentage": 2.83 }, { "order": 9, @@ -439,7 +439,7 @@ { "order": 2, "name": "kenysdev", - "count": 108, + "count": 109, "languages": 4 }, { @@ -450,13 +450,13 @@ }, { "order": 4, - "name": "hectorio23", - "count": 78, + "name": "hozlucas28", + "count": 79, "languages": 3 }, { "order": 5, - "name": "hozlucas28", + "name": "hectorio23", "count": 78, "languages": 3 }, @@ -678,43 +678,43 @@ }, { "order": 42, - "name": "pyramsd", + "name": "avcenal", "count": 26, "languages": 1 }, { "order": 43, - "name": "alanshakir", + "name": "pyramsd", "count": 26, "languages": 1 }, { "order": 44, - "name": "soohav", + "name": "alanshakir", "count": 26, "languages": 1 }, { "order": 45, - "name": "monicavaquerano", - "count": 25, - "languages": 2 + "name": "soohav", + "count": 26, + "languages": 1 }, { "order": 46, - "name": "allbertomd", + "name": "monicavaquerano", "count": 25, - "languages": 1 + "languages": 2 }, { "order": 47, - "name": "caverobrandon", + "name": "allbertomd", "count": 25, "languages": 1 }, { "order": 48, - "name": "avcenal", + "name": "caverobrandon", "count": 25, "languages": 1 }, @@ -912,43 +912,43 @@ }, { "order": 81, - "name": "mariovelascodev", + "name": "saicobys", "count": 16, "languages": 2 }, { "order": 82, - "name": "kontroldev", + "name": "mariovelascodev", "count": 16, - "languages": 1 + "languages": 2 }, { "order": 83, - "name": "artickun", + "name": "kontroldev", "count": 16, "languages": 1 }, { "order": 84, - "name": "oniricoh", + "name": "artickun", "count": 16, "languages": 1 }, { "order": 85, - "name": "hyromy", + "name": "oniricoh", "count": 16, "languages": 1 }, { "order": 86, - "name": "alvaro-neyra", - "count": 15, - "languages": 2 + "name": "hyromy", + "count": 16, + "languages": 1 }, { "order": 87, - "name": "saicobys", + "name": "alvaro-neyra", "count": 15, "languages": 2 }, @@ -2076,667 +2076,667 @@ }, { "order": 275, - "name": "dlgai12", + "name": "gordo-master", "count": 4, "languages": 1 }, { "order": 276, - "name": "dgrex", + "name": "dlgai12", "count": 4, "languages": 1 }, { "order": 277, - "name": "zerek247", + "name": "dgrex", "count": 4, "languages": 1 }, { "order": 278, - "name": "sarismejiasanchez", + "name": "zerek247", "count": 4, "languages": 1 }, { "order": 279, - "name": "franz-arzapalo", + "name": "sarismejiasanchez", "count": 4, "languages": 1 }, { "order": 280, - "name": "zakkdrte", + "name": "franz-arzapalo", "count": 4, "languages": 1 }, { "order": 281, - "name": "ramxv", + "name": "zakkdrte", "count": 4, "languages": 1 }, { "order": 282, - "name": "juanchernandezdev", + "name": "ramxv", "count": 4, "languages": 1 }, { "order": 283, - "name": "angell4s", + "name": "juanchernandezdev", "count": 4, "languages": 1 }, { "order": 284, - "name": "axelprz", + "name": "angell4s", "count": 4, "languages": 1 }, { "order": 285, - "name": "anvildestroyer", + "name": "axelprz", "count": 4, "languages": 1 }, { "order": 286, - "name": "sunjamer", + "name": "anvildestroyer", "count": 4, "languages": 1 }, { "order": 287, - "name": "buriticasara", + "name": "sunjamer", "count": 4, "languages": 1 }, { "order": 288, - "name": "guillermo-k", + "name": "buriticasara", "count": 4, "languages": 1 }, { "order": 289, - "name": "fborjalv", + "name": "guillermo-k", "count": 4, "languages": 1 }, { "order": 290, - "name": "tobibordino", + "name": "fborjalv", "count": 4, "languages": 1 }, { "order": 291, - "name": "txuky", + "name": "tobibordino", "count": 4, "languages": 1 }, { "order": 292, - "name": "inkhemi", + "name": "txuky", "count": 4, "languages": 1 }, { "order": 293, - "name": "mplatab", + "name": "inkhemi", "count": 4, "languages": 1 }, { "order": 294, - "name": "albertorevel", + "name": "mplatab", "count": 4, "languages": 1 }, { "order": 295, - "name": "javirub", + "name": "albertorevel", "count": 4, "languages": 1 }, { "order": 296, - "name": "luissssoto", + "name": "javirub", "count": 4, "languages": 1 }, { "order": 297, - "name": "abel-ade", + "name": "luissssoto", "count": 4, "languages": 1 }, { "order": 298, - "name": "cyberdidac", + "name": "abel-ade", "count": 4, "languages": 1 }, { "order": 299, - "name": "quejuan52", + "name": "cyberdidac", "count": 4, "languages": 1 }, { "order": 300, - "name": "deivitdev", + "name": "quejuan52", "count": 4, "languages": 1 }, { "order": 301, - "name": "abelsrzz", - "count": 3, - "languages": 3 + "name": "deivitdev", + "count": 4, + "languages": 1 }, { "order": 302, - "name": "oskarcali", + "name": "abelsrzz", "count": 3, "languages": 3 }, { "order": 303, - "name": "jehiselruth", + "name": "oskarcali", "count": 3, "languages": 3 }, { "order": 304, - "name": "akaisombra", + "name": "jehiselruth", "count": 3, "languages": 3 }, { "order": 305, - "name": "skala2301", + "name": "akaisombra", "count": 3, - "languages": 2 + "languages": 3 }, { "order": 306, - "name": "pablotaber", + "name": "skala2301", "count": 3, "languages": 2 }, { "order": 307, - "name": "cubandeveloper89", + "name": "pablotaber", "count": 3, "languages": 2 }, { "order": 308, - "name": "allanoscoding", + "name": "cubandeveloper89", "count": 3, "languages": 2 }, { "order": 309, - "name": "augustosdev", + "name": "allanoscoding", "count": 3, "languages": 2 }, { "order": 310, - "name": "robindev1812", + "name": "augustosdev", "count": 3, "languages": 2 }, { "order": 311, - "name": "arliumdev", + "name": "robindev1812", "count": 3, "languages": 2 }, { "order": 312, - "name": "diegopc-dev", + "name": "arliumdev", "count": 3, "languages": 2 }, { "order": 313, - "name": "eloitr", + "name": "diegopc-dev", "count": 3, "languages": 2 }, { "order": 314, - "name": "seba9906", + "name": "eloitr", "count": 3, "languages": 2 }, { "order": 315, - "name": "dimasb69", + "name": "seba9906", "count": 3, "languages": 2 }, { "order": 316, - "name": "n-skot", + "name": "dimasb69", "count": 3, "languages": 2 }, { "order": 317, - "name": "barbafebles", + "name": "n-skot", "count": 3, - "languages": 1 + "languages": 2 }, { "order": 318, - "name": "aggranadoss", + "name": "barbafebles", "count": 3, "languages": 1 }, { "order": 319, - "name": "heliercamejo", + "name": "aggranadoss", "count": 3, "languages": 1 }, { "order": 320, - "name": "crisvigas", + "name": "heliercamejo", "count": 3, "languages": 1 }, { "order": 321, - "name": "tomytsa", + "name": "crisvigas", "count": 3, "languages": 1 }, { "order": 322, - "name": "david-quinones", + "name": "tomytsa", "count": 3, "languages": 1 }, { "order": 323, - "name": "swifty0705", + "name": "david-quinones", "count": 3, "languages": 1 }, { "order": 324, - "name": "francofmv", + "name": "swifty0705", "count": 3, "languages": 1 }, { "order": 325, - "name": "freedainew", + "name": "francofmv", "count": 3, "languages": 1 }, { "order": 326, - "name": "oixild", + "name": "freedainew", "count": 3, "languages": 1 }, { "order": 327, - "name": "marce1084", + "name": "oixild", "count": 3, "languages": 1 }, { "order": 328, - "name": "kingsaul22", + "name": "marce1084", "count": 3, "languages": 1 }, { "order": 329, - "name": "kine-jdf", + "name": "kingsaul22", "count": 3, "languages": 1 }, { "order": 330, - "name": "fluna29", + "name": "kine-jdf", "count": 3, "languages": 1 }, { "order": 331, - "name": "xurxogz", + "name": "fluna29", "count": 3, "languages": 1 }, { "order": 332, - "name": "natalinacn", + "name": "xurxogz", "count": 3, "languages": 1 }, { "order": 333, - "name": "rocallejas", + "name": "natalinacn", "count": 3, "languages": 1 }, { "order": 334, - "name": "guido2288", + "name": "rocallejas", "count": 3, "languages": 1 }, { "order": 335, - "name": "githjuan", + "name": "guido2288", "count": 3, "languages": 1 }, { "order": 336, - "name": "jeyker-dev", + "name": "githjuan", "count": 3, "languages": 1 }, { "order": 337, - "name": "tebaah", + "name": "jeyker-dev", "count": 3, "languages": 1 }, { "order": 338, - "name": "matteozhao98", + "name": "tebaah", "count": 3, "languages": 1 }, { "order": 339, - "name": "atienzar", + "name": "matteozhao98", "count": 3, "languages": 1 }, { "order": 340, - "name": "javiearth", + "name": "atienzar", "count": 3, "languages": 1 }, { "order": 341, - "name": "coshiloco", + "name": "javiearth", "count": 3, "languages": 1 }, { "order": 342, - "name": "sitnestic", + "name": "coshiloco", "count": 3, "languages": 1 }, { "order": 343, - "name": "dannyvera1234", + "name": "sitnestic", "count": 3, "languages": 1 }, { "order": 344, - "name": "samuelarandia", + "name": "dannyvera1234", "count": 3, "languages": 1 }, { "order": 345, - "name": "jaimerocel96", + "name": "samuelarandia", "count": 3, "languages": 1 }, { "order": 346, - "name": "gitperalta", + "name": "jaimerocel96", "count": 3, "languages": 1 }, { "order": 347, - "name": "confley", + "name": "gitperalta", "count": 3, "languages": 1 }, { "order": 348, - "name": "blfuentes", + "name": "confley", "count": 3, "languages": 1 }, { "order": 349, - "name": "jelozanov", + "name": "blfuentes", "count": 3, "languages": 1 }, { "order": 350, - "name": "matrix-miguel", + "name": "jelozanov", "count": 3, "languages": 1 }, { "order": 351, - "name": "ahinar", + "name": "matrix-miguel", "count": 3, "languages": 1 }, { "order": 352, - "name": "gpinedaoviedo", + "name": "ahinar", "count": 3, "languages": 1 }, { "order": 353, - "name": "matiascba27", + "name": "gpinedaoviedo", "count": 3, "languages": 1 }, { "order": 354, - "name": "migueltfangche", + "name": "matiascba27", "count": 3, "languages": 1 }, { "order": 355, - "name": "agusbelp", + "name": "migueltfangche", "count": 3, "languages": 1 }, { "order": 356, - "name": "hatorob", + "name": "agusbelp", "count": 3, "languages": 1 }, { "order": 357, - "name": "marcoslombardo", + "name": "hatorob", "count": 3, "languages": 1 }, { "order": 358, - "name": "hectoriglesias", + "name": "marcoslombardo", "count": 3, "languages": 1 }, { "order": 359, - "name": "sebascmb", + "name": "hectoriglesias", "count": 3, "languages": 1 }, { "order": 360, - "name": "uyarra73", + "name": "sebascmb", "count": 3, "languages": 1 }, { "order": 361, - "name": "arbenisacosta", + "name": "uyarra73", "count": 3, "languages": 1 }, { "order": 362, - "name": "davidb313", + "name": "arbenisacosta", "count": 3, "languages": 1 }, { "order": 363, - "name": "douglasdiazr", + "name": "davidb313", "count": 3, "languages": 1 }, { "order": 364, - "name": "asaelz", + "name": "douglasdiazr", "count": 3, "languages": 1 }, { "order": 365, - "name": "fernandog25", + "name": "asaelz", "count": 3, "languages": 1 }, { "order": 366, - "name": "nathaliamf", + "name": "fernandog25", "count": 3, "languages": 1 }, { "order": 367, - "name": "jacarrillob", + "name": "nathaliamf", "count": 3, "languages": 1 }, { "order": 368, - "name": "r4kso", + "name": "jacarrillob", "count": 3, "languages": 1 }, { "order": 369, - "name": "dariorfm", + "name": "r4kso", "count": 3, "languages": 1 }, { "order": 370, - "name": "faga01", + "name": "dariorfm", "count": 3, "languages": 1 }, { "order": 371, - "name": "emaenriquez", + "name": "faga01", "count": 3, "languages": 1 }, { "order": 372, - "name": "ramon-almeida", + "name": "emaenriquez", "count": 3, "languages": 1 }, { "order": 373, - "name": "mizadlogcia", + "name": "ramon-almeida", "count": 3, "languages": 1 }, { "order": 374, - "name": "eliskopun", + "name": "mizadlogcia", "count": 3, "languages": 1 }, { "order": 375, - "name": "bertolini-victor", + "name": "eliskopun", "count": 3, "languages": 1 }, { "order": 376, - "name": "yeam-10", + "name": "bertolini-victor", "count": 3, "languages": 1 }, { "order": 377, - "name": "suescun845", + "name": "yeam-10", "count": 3, "languages": 1 }, { "order": 378, - "name": "jorgegarcia-dev", + "name": "suescun845", "count": 3, "languages": 1 }, { "order": 379, - "name": "elder202", + "name": "jorgegarcia-dev", "count": 3, "languages": 1 }, { "order": 380, - "name": "artdugarte", + "name": "elder202", "count": 3, "languages": 1 }, { "order": 381, - "name": "monikgbar", + "name": "artdugarte", "count": 3, "languages": 1 }, { "order": 382, - "name": "oscargeovannyrincon", + "name": "monikgbar", "count": 3, "languages": 1 }, { "order": 383, - "name": "danielperezrubio", + "name": "oscargeovannyrincon", "count": 3, "languages": 1 }, { "order": 384, - "name": "marcoh2325", + "name": "danielperezrubio", "count": 3, "languages": 1 }, { "order": 385, - "name": "gordo-master", + "name": "marcoh2325", "count": 3, "languages": 1 },