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

👉 Únete a mi Canal de WhatsApp para más recursos

👉 Sigueme en mis redes sociales para más contenido de programación y productividad


Deja una respuesta

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