Paradigmas de programación. Principios generales (A3C34C2D01)

Paradigmas de Programación. Principios Generales

En la actualidad existen distintos estilos que definen cómo abordar el diseño y la escritura de un programa, lo que se conoce como paradigmas de programación. Cada uno de ellos posee características (reglas, técnicas y principios) específicas que lo diferencia de los demás, haciéndolo más o menos apropiado para resolver distintos tipos de problemas en función de sus necesidades. Los paradigmas de programación más utilizados son el imperativo, el orientado a objetos y el funcional. Es importante conocer las ventajas y los inconvenientes de cada uno de ellos con el fin de elegir el más adecuado para abordar un problema, ya que puede hacer que el código sea más legible, mantenible y escalable. En este sentido, los lenguajes de programación juegan también un papel significativo, ya que están diseñados para soportar uno o más paradigmas de programación. Esto significa que ciertos paradigmas pueden ser más fáciles de implementar o más efectivos en ciertos lenguajes de programación que en otros. Por ejemplo, la programación orientada a objetos es ampliamente utilizada en lenguajes como Java y C++, mientras que para la programación funcional se usan lenguajes como Haskell y Lisp. Además, algunos lenguajes de programación son específicos de un único paradigma, como los lenguajes Pascal y C, que se utilizan principalmente para la programación imperativa. Otros lenguajes son multiparadigma, lo que significa que permiten el uso de varios paradigmas diferentes en el mismo programa. Por ejemplo, Python es un lenguaje de programación multiparadigma que admite programación imperativa, orientada a objetos y funcional. La capacidad de un lenguaje de programación para soportar múltiples paradigmas puede brindar a los desarrolladores la flexibilidad y el poder para elegir el enfoque de programación más adecuado para su proyecto.

A continuación, se describen los tres paradigmas de programación más comunes, junto con el tipo de aplicaciones en el que son más indicados:

  • La programación imperativa se caracteriza por poner el foco en “cómo” se resuelve el problema, por lo que los programas consisten en la descripción precisa y detallada de la secuencia de pasos que hay que realizar para resolver un problema. La idea es hacer uso de las instrucciones para ir modificando el estado del programa. Para ello, se cuenta con las variables, que son los elementos que permiten almacenar los datos (tanto de entrada como de salida), y con los bucles y los condicionales, que controlan el orden de ejecución de las instrucciones. Además, los subprogramas (procedimientos o funciones) se usan para modularizar y reutilizar el código. La gran ventaja de este paradigma es que el modelo bajo el que se basa es muy intuitivo, ya que es muy parecido a un “manual de instrucciones paso a paso”. Por eso, es el que se suele utilizar para enseñar y aprender a programar. Sin embargo, a la hora de resolver problemas más complejos, el código puede hacerse demasiado largo, lo que puede dificultar su mantenimiento.
  • La programación orientada a objetos (POO) considera un programa como una colección de objetos que interactúan entre sí, de forma más parecida a como ocurre en la vida real. Un objeto constituye un ejemplar de una clase, que es la “plantilla” o modelo que define la representación, las propiedades y el comportamiento comunes a un conjunto de objetos. La representación del objeto se realiza por medio de variables que se denominan atributos. Por ejemplo, para definir las propiedades comunes de los y las estudiantes de la asignatura de programación, como su nombre, apellidos y calificación alfanumérica (“suspenso”, “aprobado”, “notable” y “sobresaliente”) se podría definir una clase, llamada Estudiante, con tres variables (atributos) denominados Nombre, Apellidos y Calificación, respectivamente.
Estudiante

Figura 1. Ejemplo de objeto de la clase Estudiante.

El comportamiento de los objetos lo definen los métodos, que son los procedimientos o funciones que implementan las operaciones para manipularlos y para interactuar con otros objetos. Por ejemplo, la clase Estudiante debería contener, al menos, métodos para conocer el nombre, los apellidos y la calificación de un o una estudiante. Existe un método particular, llamado constructor, que es el que se invoca para crear un objeto. Así, para crear el objeto correspondiente a la alumna Ana Sánchez Megía podría invocarse al constructor con el nombre y los apellidos como parámetros. El resultado implica la creación de una “variable” similar a un registro con tres campos como ilustra la Figura 1.

Una vez definida una clase, se pueden crear tantos objetos o instancias como se necesiten.

La POO se fundamenta en los tres pilares siguientes:

- Encapsulación se usa para ocultar la representación concreta de los objetos, lo que los protege de usos indebidos. Por ejemplo, que no se pueda asignar una calificación inexistente o que dos calificaciones no se puedan sumar.

- Herencia se utiliza para crear subclases, es decir, clases derivadas de otra, que heredan sus propiedades y métodos, pero que pueden tener otros adicionales. Por ejemplo, se podría crear la clase EstudianteRepetidor, como subclase de Estudiante, para representar los estudiantes que ya cursaron otro año la asignatura, con el atributo específico AñoRealización, además de los que hereda (Nombre, Apellidos, Calificación).

- Polimorfismo, que permite que los objetos de diferentes clases se comporten de manera similar en función del contexto. Por ejemplo, los objetos de la clase EstudianteRepetidor también son objetos de la clase Estudiante, por lo que pueden comportarse de ambas formas. Así, pueden formar parte de la lista de todos los estudiantes de la asignatura, formada por objetos de la clase Estudiante. El polimorfismo dota de gran flexibilidad a los programas, lo que constituye una ventaja de este paradigma; la herencia facilita la reutilización de código, y el encapsulamiento ayuda a prevenir errores. Además, el diseño de clases permite abordar el programa de forma modular, facilitando, además, la inserción de nuevos objetos y la modificación de los existentes. Por tanto, es un paradigma idóneo para el desarrollo de grandes aplicaciones en equipo. Sin embargo, hay que tener en cuenta que la ejecución de los programas es más lenta y que el diseño de clases puede ser demasiado complicado.

  • La programación funcional se encuadra en el paradigma de programación declarativa, mediante el que los programas especifican qué quieren conseguir sin describir cómo hacerlo. En el paradigma funcional, los elementos con los que se escriben los programas son las funciones puras, es decir, aquellas que para una misma entrada siempre producen el mismo resultado. Al contrario que en el paradigma imperativo o en el orientado a objetos, no interesa modificar el estado del programa, por lo que los datos permanecen inmutables: durante la ejecución del programa los datos no se modifican, sino que se van creando otros nuevos. Para ello, es imprescindible que las funciones estén correctamente parametrizadas y que la invocación a las mismas se haga con los valores adecuados. Hay que tener en cuenta que las funciones pueden ser usadas también como parámetros de otra función. Además, las funciones no producen efectos colaterales indeseados. Por otro lado, en lugar de bucles se usa la recursión. La recursión ocurre cuando una función se llama a sí misma, por lo que se crea una repetición en el que los valores antiguos permanecen inalterables. Un ejemplo de función recursiva puede ser la que calcula la suma desde 1 hasta n, siendo n su argumento, y que se podría definir así:

    Suma(1)=1;
    Suma(n)=Suma(n-1)+n, n>1

    De esta forma, Suma(3)=Suma(2)+3; pero Suma(2)=Suma(1)+2. Como Suma(1)=1, ahora se van “deshaciendo” las llamadas, es decir, ahora ya se puede calcular Suma(2)=1+2=3; y después, Suma(3)=3+3=6. El valor obtenido es el resultado de 1+2+3.

Las ventajas de la programación funcional son varias: el código suele ser más conciso y expresivo, ya que las funciones suelen ser pequeñas e independientes entre sí, lo que facilita el mantenimiento de los programas. Además, la inmutabilidad de los datos evita la existencia de efectos colaterales, por lo que es más fácil detectar errores y facilita la concurrencia. Teniendo en cuenta que la complejidad de las aplicaciones es cada vez mayor, especialmente por la cantidad de datos que procesan, se hace necesario desarrollar programas que puedan ser ejecutados en varias máquinas a la vez, soportando concurrencia y paralelismo. Por eso, este paradigma está resurgiendo con fuerza en el mundo empresarial e industrial. No obstante, la programación funcional no es sencilla, ya que la recursividad es una técnica compleja, que puede dar a errores graves si no se domina. Por otra parte, el mantenimiento de los programas es complicado y el código es difícilmente reutilizable.