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.
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.
Sigueme en mis redes sociales para más contenido