Orientación a objetos 101

Los lenguajes de programación pueden agruparse en paradigmas según un conjunto coherente de principios. Uno de ellos es el paradigma de la Programación Orientada a Objetos (POO). Pero, ¿por qué esta manera de pensar y programar se ha vuelto tan popular y sigue siendo utilizada hoy en día con tanto éxito? Este blog resume algunos de los aspectos y características que la programación orientada a objetos tiene, y cómo ellas, desde mi experiencia, han ayudado a los programadores a alcanzar soluciones para problemas de manera mucho más fácil e intuitiva.

¿Cuántos paradigmas existen?

En realidad, existen menos paradigmas que lenguajes de programación. Un lenguaje de programación implementa uno o más paradigmas y cada paradigma consiste en una serie de conceptos y principios. Pero aún así, hay muchos paradigmas en el mundo de la programación. Listando algunos:

  • Programación descriptiva declarativa
  • Programación funcional
  • Programación lógica determinista
  • Programación imperativa
  • Programación de eventos en bucle
  • Programación orientada a objetos

Cada problema de software puede resolverse aplicando uno o más paradigmas. Es importante entender que aunque la orientación a objetos sea ampliamente utilizada en la actualidad, existen muchas otras maneras de abordar un problema y en muchas ocasiones otros paradigmas se adaptan mejor a ciertos problemas que la POO.
La descripción de cada paradigma queda fuera del alcance de esta entrada, pero si quieres obtener información más detallada acerca de este tema, te recomiendo leer Programming Paradigms for Dummies: What Every Programmer Should Know.

Orientación a objetos

La orientación a objetos nació gracias al propio pensamiento humano. Nuestro mundo se compone de objetos: casas, edificios, oficinas, carros, alimentos, frutas, etc. El pensamiento humano es capaz de diferenciar y comprender todo lo que le rodea en forma de objetos. No es extraño entonces, que los lenguajes orientados a objetos sean más fáciles de comprender y adoptar. En los problemas que queremos solucionar pasamos de pensar en ¿qué es lo que mi programa debe hacer? (programación procedimental) hacia ¿cuáles son los actores principales de mi programa y cuál es su comportamiento? (POO). Así, hemos podido abstraer problemas gigantes hacia objetos que interactúan entre sí, y hemos cerrado un poco más la brecha que divide a los lenguajes de programación de las personas que los escriben.

El primer lenguaje orientado a objetos

Los lenguajes Simula creados por un proyecto noruego alrededor 1962 dan paso al nacimiento de la orientación a objetos. El lenguaje Algol 60 fue la base de estos nuevos lenguajes principalmente por su estructura de bloques y su buena seguridad de programación. El objetivo inicial de Simula era definir un lenguaje de propósito específico para aplicaciones de simulación.
Simula 67 fue el primero en introducir los conceptos de clases y objetos intentando así solventar dos deficiencias de Simula I:

  • Las entidades útiles en la simulación se creaban y destruían dinámicamente a lo largo de la ejecución del programa. El concepto de bloque derivado de Algol 60 era insuficiente para reflejar este dinamismo.
  • El código en las entidades era bastante similar, pero el lenguaje no proporcionaba un mecanismo para la reutilización de las partes comunes.

Así nacen entonces los conceptos de clase y objeto. Una clase que representa el texto del programador y los objetos que se derivan de ella y se crean y destruyen dinámicamente durante una ejecución concreta.
La asignación de memoria en la creación de un objeto necesitaba de dos innovaciones con respecto a los lenguajes de aquella época:

  • En la estructura de bloques derivada de Algol 60 un objeto existe solo dentro del procedimiento que lo crea. Ahora es necesario escapar de esta estructura para que el objeto sobreviva fuera del contexto del procedimiento. Muchos objetos de una misma clase deben poder coexistir en un mismo ámbito de la aplicación.
  • Necesidad del tipo de datos ref que no es más que un puntero a memoria, para designar objetos en distintos momentos.

La abstracción y la herencia de clases también llegaron con este lenguaje.
A pesar de que Simula introdujo muchos de los conceptos de la orientación a objetos, otras ideas como la noción del paso de mensajes fueron incluidas con un nuevo lenguaje llamado Smalltalk. Este fue el primer lenguaje orientado a objetos puro.
Si quieres conocer más acerca del nacimiento de la orientación a objetos y los lenguajes Simula te recomiendo leer The Birth of Object Orientation: the Simula Languages.

Algunos conceptos en POO

Si has experimentado con lenguajes de programación que implementan POO como Java, C#, Python (entre muchos más), seguramente te habrás encontrado con términos como clases, objetos, métodos, abstracción, encapsulamiento, polimorfismo, herencia o generalización y otros.
Para programar con un verdadero pensamiento orientado a objetos es necesario entender estos conceptos desde su significado teórico hasta su actuación en los principios de diseño. Veamos algunos de ellos.

Clases y objetos

Una clase es el código que describe una entidad en nuestra aplicación. Está formada por un conjunto de características (atributos) y comportamiento (métodos) que detallan lo que la entidad es y hace en tu programa.
El objeto es la instancia particular de una entidad, creada con todas las características y comportamientos descritos por su clase.
A continuación podemos ver un ejemplo de una clase Perro y de la instanciación de un perro labrador en Java.

public class Perro {

//Características de un perro

//Comportamiento de un perro

}

...
Perro labrador = new Perro();
...

Encapsulamiento

Encapsular quiere decir separar, aislar, envolver. Cuando trabajamos con objetos no queremos dejar expuesta su información para que cualquiera pueda leerla o incluso modificarla. Pensemos en una entidad de nuestra aplicación que expone su propiedad de tamaño. Si cualquier persona puede acceder y modificar el tamaño de la entidad, podríamos llegar a tener una instancia de esa entidad ¡con un tamaño negativo!.
Este concepto nos permite manejar la información que queremos exponer y la manera en la que la queremos exponer, así es posible publicar fuera de un objeto solo lo que los demás necesitan conocer. ¿Por qué esto es importante?
El encapsulamiento es importante por que protege la información y protege tu derecho a modificar después tu implementación. Así, por ejemplo, evitamos tener objetos con características inapropiadas viajando por nuestra aplicación.
De igual forma, si queremos evitar que un cambio impacte sobre una aplicación, con el encapsulamiento minimizamos los lugares en donde se deben aplicar. Separando y protegiendo las características de un objeto, cuando una de estas cambie, o su validación sea distinta, solo la clase que define al objeto debería cambiar. Aislamos los puntos de cambio con la ayuda de un principio más de la POO.

Herencia

Una clase puede describir una entidad más específica (hija) o una más general (padre). Veamos esto con un ejemplo:

Ejemplo de herencia con animales mamíferos

Ejemplo de herencia: Animales mamíferos

Los animales mamíferos tienen una serie de características y comportamientos comunes que podemos agrupar para todos. A medida que bajamos en el árbol, vemos que distintos tipos de mamíferos presentan especificidades según cada grupo. Por ejemplo, los insectívoros se alimentan de insectos mientras que los carnívoros se alimentan de carne. Podemos seguir bajando niveles en el árbol, tantos cuantos se requieran según las necesidades de nuestra aplicación.
Enfoquemos ahora nuestra atención en el Oso Polar. Un Oso Polar pertenece a la familia de los osos, pero también es un carnívoro y un mamífero. Entonces el oso polar aprovechará todas las características y comportamientos de sí mismo, de los osos, los carnívoros y los mamíferos.
La programación orientada a objetos nos permite agrupar características y comportamientos en común de un grupo de entidades en una o más clases, para que las entidades más específicas puedan beneficiarse al heredarlos. Esta es la herencia en orientación a objetos y es un principio que es necesario comprender bien para que se convierta en un aliado y no en un enemigo a la hora de aplicarlo en nuestros programas.
Algunos de los beneficios de la herencia:

  • Minimiza las líneas de código en nuestra aplicación maximizando así las características de un código limpio como lo describe Robert C. Martin.
  • Uno de los beneficios de aplicar el concepto de herencia en el código es el polimorfismo. Este es uno de los principios más interesantes en mi opinión de la POO. Más adelante hablaremos un poco más sobre él.

Como ya mencioné, es necesario entender correctamente qué es y para qué nos sirve la herencia, ya que no siempre es la solución correcta para todos los problemas de duplicación de código y relacionamiento. Cuando un objeto a es un objeto b, entonces probablemente estamos hablando de herencia, y digo probablemente, por que la herencia tiene ciertas restricciones que limitan en gran parte su campo de aplicación.
El tema de la herencia es suficientemente extenso como para tratarlo en otra entrada. Sin embargo, si aún te quedan ciertas dudas sobre el concepto y su aplicación, puedes leer Head First Java, un libro de Kathy Sierra y Bert Bates que encuentro muy sencillo y didáctico o cualquier otro libro que introduzca estos conceptos.

Polimorfismo

El concepto de polimorfismo, al igual que el de herencia, es demasiado extenso como para tratarlo a profundidad en esta entrada. Existen distintas formas de comprender el polimorfismo y en esta ocasión voy a tomar la explicación de Fausto de la Torre, un conocido desarrollador y arquitecto de software en Ecuador, por ser lo suficientemente fuerte y a la vez sencilla para empezar.

El polimorfismo es una característica de la POO que permite responder al mismo tipo de mensaje de maneras diferentes.

Un ejemplo de polimorfismo desde el punto de vista de mensajes:

class Mamifero {

respirar() {}

desplazarse() {}

}
class Perro extends Mamifero {

respirar() {

//Respira como un perro

}

desplazarse() {

//Caminar

}

}
class Ballena extends Mamifero {

respirar() {

//Sale del agua para respirar

}

desplazarse() {

//Nadar

}

}
En el código Java del ejemplo tenemos tres clases:

  • Mamífero que es la clase más general y que describe dos comportamientos de los mamíferos: respirar y desplazarse.
  • Perro y Ballena que heredan de la clase mamífero mediante el uso de la palabra reservada extends.

Esto quiere decir, que tanto el perro como la ballena van a poseer los comportamientos de respirar y desplazarse de los mamíferos.
Ahora bien, podemos definir una clase Naturaleza y una clase principal del programa para efectos del ejemplo. En la clase principal instanciaremos a la naturaleza y agregaremos mamíferos en ella; estos mamíferos respirarán y se desplazarán al ser agregados.
class Naturaleza {

agregarMamifero(Mamifero mamifero) {

...

mamifero.respirar();

mamifero.desplazarse();

...

}

}
class Programa {

main() {

Naturaleza naturaleza = new Naturaleza();

naturaleza.agregarMamifero(new Perro());

naturaleza.agregarMamifero(new Ballena());

}

}

El poliformismo puede verse cuando la clase Perro y la clase Ballena responden de manera diferente a un mensaje sintácticamente igual. Los métodos respirar() y desplazarse() llamados en naturaleza son exactamente los mismos para el perro y la ballena, sin embargo, como cada uno responde, es diferente.

Aprovechar esta característica de la POO es clave para un buen diseño de los programas. La herencia es apenas una de las maneras con las que podemos aprovechar el polimorfismo; existen otras como el uso de Interfaces y la Abstracción de clases. Estos temas los trataré en una nueva entrada en donde hablaré más a detalle acerca del polimorfismo y los principios de diseño SOLID.

En conclusión y para cerrar este blog, el paradigma de la orientación a objetos encierra una basta gama de conceptos tanto útiles como interesantes. No se trata solamente de un pensamiento, sino de un conjunto de buenas prácticas y patrones que han sido ampliamente desarrollados desde que la POO tomó fuerza en el desarrollo de aplicaciones. El dinamismo que involucra este tipo de programación explica por qué ha sido utilizada para solucionar una gran cantidad de problemas de desarrollo. Aún así, existen nuevos pensamientos y paradigmas, con sus propios beneficios y ventajas. No todos los problemas pueden solucionarse con un martillo, por eso existen diferentes herramientas que se adecuan mejor a cada problema. Lo mismo aplica con los lenguajes de programación; tenemos diversos lenguajes de programación orientados a objetos, diferentes entre sí en algunas ocasiones, y es necesario escoger el más adecuado para cada tipo de problema.

2 comentarios

  1. luisaemme · marzo 14, 2016

    Reblogueó esto en luisaemme.

    Me gusta

  2. Pingback: Orientación a objetos 101 | Fausto De La Torre

Deja un comentario