En el mundo de la programación hay diversas formas de resolver un problema. Los paradigmas de programación toman ideas de código y las traduce a un tipo de enfoque.

Hay que tomar en cuenta que cada paradigma tiene sus propias características y diferentes perspectivas para generar una solución, lo que lleva a plantearte a cómo quiero resolver este problema se resuelva de manera eficiente

Te quiero enseñar algunos de los paradigmas más comunes en la programación, sus diferentes formas de abarcar los problemas y con sus ejemplos de código.

Sin más que decir, ¿te has preguntado qué es un paradigma?

¿Qué es un paradigma?

Un paradigma es un conjunto de creencias, valores y técnicas que influyen en la forma en que abordamos un tema o problema particular.

En el contexto de la programación, un paradigma de programación es un enfoque o estilo particular de escribir código para resolver problemas.

two women looking at the code at laptop

Existen varios paradigmas de programación, cada uno con sus propias reglas y filosofías, ventajas y desventajas.

Se puede mencionar entre algunos, el paradigma imperativo, el paradigma orientado a objetos, el paradigma funcional, el paradigma lógico, etc.

Programación Orientada a Objetos

La programación orientada a objetos (POO) se centra en la creación de objetos. Estos objetos interactúan entre sí a través de métodos y mensajes, lo que permite una representación más cercana a la realidad de los sistemas.

La ventaja de POO es promoveer la reutilización de código, la modularidad y la organización estructurada. Se utiizan las clases para crear definir un tipo de objeto y dentro de esta contienen los atributos y metodos.

Este tipo de paradigma es muy común verlo en desarrollo web o de videojuegos.

Para aprender más, te invito a que revises este post, donde explico al detalle la programación orientada a objetos.

Ejemplo

Javascript

Vamos a mostrar un ejemplo en javascript aplicando POO, definiendo una clase persona:

class Persona {
  constructor(nombre, edad) {
    // Propiedades
    this.nombre = nombre;
    this.edad = edad;
  }

  // Método de la clase
  saludar() {
    console.log(`Hola, me llamo ${this.nombre} y tengo ${this.edad} años.`);
  }
}

Luego se crea un objeto de la clase Persona.

let p1 = new Persona("Juan", 25);

Para llamar al método saludar se utiliza el . seguido del nombre del método.

p1.saludar();

C++

// Definir una clase llamada Persona
class Persona {
  // Propiedades de la clase
  private:
    string nombre;
    int edad;

  // Métodos de la clase
  public:
    // Constructor de la clase
    Persona(string n, int e) {
      nombre = n;
      edad = e;
    }

    string getNombre() {
      return nombre;
    }

    int getEdad() {
      return edad;
    }

    void saludar() {
      cout << "Hola, me llamo " << nombre << " y tengo " << edad << " años." << endl;
    }
};

De esta manera, creas el objeto y llamas al método saludar:

#include <iostream>
using namespace std;

int main() {
  // Crear un objeto de la clase Persona
  Persona p1("Juan", 25);

  // Llamar al método saludar del objeto
  p1.saludar();

  return 0;
}

Programación Funcional

En la programación funcional se utiliza funciones puras, pasando a ser más declarativo que imperativo, y siempre se produce el mismo resultado para los mismos datos de entrada.

Al ser funciones puras, se evita el uso de variables mutables, bucles o asignaciones y se prefiere el uso de expresiones, funciones de orden superior o la recursividad.

Este paradigma se enfoca en la inmutabilidad de los datos y en la composición de funciones para resolver problemas, y promueve un código más declarativo y fácil de razonar.

Ejemplo

Javascript

Empecemos definiendo una función para sumar dos numeros.

const sumar = (a, b) => a + b;

Se crea una de orden superior que aplica una función a cada elemento de un array. Dentro, tendrá una función auxiliar recursiva encargada de acumular los resultados.

const map = (array, funcion) => {
  const mapRecursivo = (indice, resultado) => {
    // Caso base: si el índice es igual al tamaño del array, devolver el resultado
    if (indice === array.length) {
      return resultado;
    }
    // Caso recursivo: aplicar la función al elemento actual y añadirlo al resultado
    return mapRecursivo(indice + 1, resultado.concat([funcion(array[indice])])); 
  };
  // Llamar a la función auxiliar con el índice inicial y el resultado vacío
  return mapRecursivo(0, []);
};

Y ahora se procede a probar la funcion. Veamos el resultado:

let numeros = [1, 2, 3, 4, 5];

// Aplicar la función sumar a cada elemento del array, sumando 10
let numerosSumados = map(numeros, (n) => sumar(n, 10));

console.log(numerosSumados); // [11, 12, 13, 14, 15]

C++

/**
 * Definir una función pura que suma dos números
 */
int sumar(int a, int b) {
  return a + b;
}

/**
 * Definir una función de orden superior que aplica una función a cada elemento de un array
 */
template <typename T, typename F>
void map(T array[], int size, F funcion) {
  // Definir una función auxiliar recursiva que modifica el array
  void mapRecursivo(int indice) {
    // Caso base: si el índice es igual al tamaño del array, terminar la función
    if (indice == size) {
      return;
    }
    // Caso recursivo: aplicar la función al elemento actual y modificar el array
    array[indice] = funcion(array[indice]);
    // Llamar a la función auxiliar con el índice incrementado
    mapRecursivo(indice + 1);
  }
  // Llamar a la función auxiliar con el índice inicial
  mapRecursivo(0);
}

El cuerpo principal:

#include <iostream>
using namespace std;

int main() {
  // Crear un array de números
  int numeros[] = {1, 2, 3, 4, 5};
  // Obtener el tamaño del array
  int size = sizeof(numeros) / sizeof(numeros[0]);

  // Aplicar la función sumar a cada elemento del array, sumando 10
  map(numeros, size,  { return sumar(n, 10); });

  // Mostrar el resultado
  for (int i = 0; i < size; i++) {
    cout << numeros[i] << " ";
  }
  cout << endl; // 11 12 13 14 15

  return 0;
}

Programación Reactiva

La programación reactiva se centra en la gestión de flujos de datos asíncronos y eventos, utilizando observables y flujos de datos para propagar cambios y reaccionar a ellos de forma eficiente.

Es muy útil este paradigma en aplicaciones en tiempo real y en entornos donde la concurrencia es importante.

Ejemplo

Javascript

Se usará la librería RxJS para detectar los cambios cada vez que se hace click.

// Importar la librería RxJS
import { fromEvent, interval } from "rxjs";
import { map, filter, take } from "rxjs/operators";

// Crear un flujo de datos a partir de los clicks del ratón
const clicks$ = fromEvent(document, "click");

// Crear un flujo de datos a partir de un intervalo de tiempo
const interval$ = interval(1000);

// Combinar los dos flujos de datos y aplicar algunos operadores
const result$ = clicks$.pipe(
  map((event) => event.clientX),
  filter((x) => x % 2 === 0),
  // Tomar solo los primeros 10 valores
  take(10)
);

// Suscribirse al flujo de datos resultante y mostrar cada valor
result$.subscribe((x) => console.log(x));

C++

Para este caso se utilizará la librería RxCpp para aplicar la programación reactiva:

// Incluir la librería RxCpp
#include "rxcpp/rx.hpp"

// Usar los espacios de nombres de RxCpp
using namespace rxcpp;
using namespace rxcpp::sources;
using namespace rxcpp::operators;
using namespace rxcpp::util;

Se define una función para obtener los clicks del raton, donde se podrá simular los clicks y captar los valores aleatorios que genere.

/**
 * Definir una función para obtener los clicks del ratón
 */
observable<int> get_mouse_clicks() {
  // Crear un sujeto para emitir los eventos
  subjects::subject<int> subject;
  // Obtener el observable del sujeto
  auto observable = subject.get_observable();
  // Crear un hilo para simular los clicks del ratón
  auto thread = std::thread(&subject {
    // Generar valores aleatorios entre 0 y 1000
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, 1000);
    // Emitir 20 valores al sujeto con un intervalo de un segundo
    for (int i = 0; i < 20; i++) {
      std::this_thread::sleep_for(std::chrono::seconds(1));
      subject.get_subscriber().on_next(dis(gen));
    }
    // Finalizar el sujeto
    subject.get_subscriber().on_completed();
  });
  // Desacoplar el hilo
  thread.detach();
  // Devolver el observable
  return observable;
}

Se llama ahora al cuerpo principal:

int main() {
  // Crear un flujo de datos a partir de los clicks del ratón
  auto clicks = get_mouse_clicks();

  // Crear un flujo de datos a partir de un intervalo de tiempo
  auto interval = interval<>(std::chrono::seconds(1));

  // Combinar los dos flujos de datos y aplicar algunos operadores
  auto result = clicks |
                // Mapear cada click a la posición x del ratón
                map( { return x; }) |
                // Filtrar solo los valores pares
                filter( { return x % 2 == 0; }) |
                // Tomar solo los primeros 10 valores
                take(10);

  // Suscribirse al flujo de datos resultante y mostrar cada valor
  result.subscribe( { std::cout << x << std::endl; });

  // Esperar a que el flujo de datos termine
  result.as_blocking().subscribe();

  // Terminar el programa
  return 0;
}

Programación Modular

La programación modular es otro paradigma común de ver. Se basa en la división del código en unidades independientes y reutilizables llamadas módulos.

Cada módulo es un pequeño trozo manejable, con su propia funcionalidad específica y se puede reutilizar en diferentes partes del programa. Esto facilita el mantenimiento, la depuración y la escalabilidad del código.

Al tener un alto nivel de flexibilidad es una gran ventaja. Lo que permite a los módulos desarrollarlos, probarlos y combinarlos de diferentes maneras para construir aplicaciones más complejas.

Ejemplo

Javascript

Suponiendo que tenemos un archivo math.js que contiene una constante PI, con su respectivo valor, y una funcion potencia(base, exponente) que calcula la potencia.

// Importar el módulo de operaciones matemáticas
import * as math from "./math.js";

/*
 * Calcula el área de un círculo
 * @params {number} radio
 ~ @return el calculo de la radio
 */
function areaCirculo(radio) {
  let pi = math.PI;

  let radioCuadrado = math.potencia(radio, 2);
  let area = pi * radioCuadrado;

  return area;
}

/*
 * Calcula el volumen de una esfera
 * @params {number} radio
 ~ @return el calculo de la radio
 */
function volumenEsfera(radio) {
  let pi = math.PI;

  let radioCubo = math.potencia(radio, 3);
  let volumen = (pi * radioCubo * 4) / 3;

  return volumen;
}


let radio = 5;
console.log(`El área del círculo es ${areaCirculo(radio)}`);
console.log(`El volumen de la esfera es ${volumenEsfera(radio)}`);

C++

En el caso de C++, se utilizará la librería math.h y la función pow para calcular la potencia.

#include <iostream>

// Incluir el módulo de operaciones matemáticas
#include <math.h> 

using namespace std;

// Crear una función que calcula el área de un círculo
double areaCirculo(double radio) {
  double pi = 3.1416;
  
  double radioCuadrado = pow(radio, 2);
  double area = pi * radioCuadrado;

  return area;
}

// Crear una función que calcula el volumen de una esfera
double volumenEsfera(double radio) {
  double pi = 3.1416;
  
  double radioCubo = pow(radio, 3);
  double volumen = (pi * radioCubo * 4) / 3;

  return volumen;
}

int main() {
  double radio = 5;

  cout << "El área del círculo es " << areaCirculo(radio) << endl;
  cout << "El volumen de la esfera es " << volumenEsfera(radio) << endl;

  return 0;
}

Programación Concurrente

Imagina que tienes multiples tareas y necesitas activar varios procesos a la vez. Con la programación secuencial no puedes hacer eso.

En cambio, con el uso de múltiples hilos puedes realizar múltiples tareas a la vez y aprovechar al máximo los recursos del sistema. Y el resultado es una mejora en la eficiencia y la capacidad de respuesta de una aplicación.

Un detalle que debes tomar en cuenta es que requiere el uso de mecanismos de sincronización, comunicación y coordinación entre los hilos, para el manejo de posibles errores o excepciones.

Ejemplo

En esta ocasión mostraré el ejemplo en C++ por su facilidad de uso con la libreria para manejar hilos.

#include <iostream>

// Incluir la librería estándar de hilos
#include <thread>

using namespace std;

// Crear una función que calcula el factorial de un número
int factorial(int n) {
  if (n <= 1) {
    return 1;
  }
  return n * factorial(n - 1);
}

// Función que muestra el resultado de un cálculo
void mostrarResultado(string mensaje, int resultado) {
  cout << mensaje << resultado << endl;
}

Función principal del programa:

int main() {
  int resultado1, resultado2;

  // Crear variables para los argumentos de la función factorial
  int n1 = 10;
  int n2 = 5;

  // Crear un hilo que calcula el factorial de 10
  thread hilo1(factorial, n1);

  // Crear un hilo que calcula el factorial de 5
  thread hilo2(factorial, n2);

  // Esperar a que los hilos terminen y asignar los resultados a las variables resultado1 y resultado2
  hilo1.join();
  resultado1 = factorial(n1);
  hilo2.join();
  resultado2 = factorial(n2);

  mostrarResultado("El factorial de 10 es ", resultado1);
  mostrarResultado("El factorial de 5 es ", resultado2);

  return 0;
}

Conclusión

Cada paradigma tiene sus ventajas y conflictos. Elegir el correcto dependará de del contexto y los requisitos del proyecto.

En resumen, los paradigmas de programación son una herramienta fundamental para desarrollar software de calidad y eficiente.

Si quieres aprender más del tema, deja tu opinión en los comentarios. No olvides compartir este post si te ha ayudado y seguirme en mis redes sociales para más contenido.


Avatar de darkusphantom

Sigueme en mis redes sociales para más contenido


Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *