Ya hemos revisado varios conceptos fundamentales del paradigma de la POO; ya sabemos lo que son los objetos, cómo hacerlos (abstraerlos), incluso podemos definir clases de objetos similares procurando hacerlo con una estructura atómica. Bien, pero hasta ahora solamente definimos los objetos, nos falta indagar sobre su interacción: la manera en que se comunican y relacionan.
La interfaz perfecta
La manera en que los objetos pueden comunicarse con otros es mediante el paso de mensajes. Sí, mensajes, pero no como los conocemos en la vida cotidiana, no son textos como en un chat o una carta enviada por correo; se trata de interfaces específicas que deben ser bien diseñadas a la hora de describir el comportamiento del objeto en la definición de la clase. Son interfaces perfectas porque sirven para lo necesario, ya sea recibir uno o más datos (un número, una letra, o una estructura de datos como un arreglo, etc.) y tal vez generar una respuesta, o simplemente hacer algo internamente por consecuencia de la indicación por parte de otro objeto.
Una interfaz se define como una conexión o una frontera común entre dos sistemas independientes. En esta definición los sistemas independientes son los objetos y la conexión o frontera serían algunos métodos, recordando que estos definen el comportamiento, entendemos, entonces, que los mensajes son llamadas a métodos.
Un objeto define los datos que requiere para la conexión, mientras que otro es capaz de entregarlos en el formato requerido para obtener una respuesta, o simplemente para comunicar algo sin esperar que le respondan (solo entregar los datos). Sin embargo, no siempre es necesario intercambiar datos, en algunos casos la interfaz consta de una simple llamada a un método sin argumentos “decirle al otro objeto que haga algo”.
Objetos colaborando
Haciendo uso de las interfaces es como los objetos interactúan, esta interacción puede propiciar una colaboración entre los objetos, pudiendo armar un panorama de organización que da lugar a complejas jerarquías que permiten la delegación de tareas, haciendo más fácil la resolución de problemas grandes.
Recordemos que los objetos son abstracciones que realizamos sobre entidades que queremos modelar para escribir programas. Nuestras abstracciones pueden ser tan complejas como queramos, sin embargo, mientras más complejidad deseemos, mayor será el esfuerzo de nuestro proceso de abstracción. Pero en lugar de abstraer una entidad muy grande, podemos abstraer varias entidades pequeñas que logren lo mismo. Sí lo hacemos de esta manera conseguiremos objetos que se encargan de tareas específicas (más fáciles de abstraer) y que juntos constituyen un objeto que resuelve el problema original, a este tipo de organización se le llama composición y es de lo más común que podemos encontrar en programas escritos usando la POO, incluso es de lo más común en la realidad, por ejemplo, en el cuerpo humano existe un sistema respiratorio compuesto por varios órganos (nariz, tráquea, pulmones, etc.) que se encargan de una función específica dentro del proceso de la respiración que es la función del sistema entero.
Parentesco entre objetos
Podemos encontrarnos en el dilema de que nuestros objetos necesitan unos ligeros ajustes para satisfacer nuestras necesidades después de que hemos definido su clase. Reescribir el código fuente o modificarlo parece la solución al problema, sin embargo, siempre queda la posibilidad de requerir los objetos como eran originalmente, o incluso la necesidad de aplicar cambios en un sentido diferente, lo que nos llevaría a tener varias versiones de las clases; algo no muy fiel a la idea de tener buenas abstracciones. Debemos tener muy bien definidos los objetos, sin dar lugar a dos definiciones que pretenden modelar al mismo objeto, en dado caso, mejor hay que definir dos objetos similares; tendríamos dos clases muy parecidas pero con suficientes diferencias para justificar su existencia. Como ejemplo podemos pensar en la clase Persona, ahora requerimos que las personas tengan características de mujeres, podemos modificar la clase y resolver el problema, pero después podríamos necesitar la clase Persona tal como estaba al principio, o que la clase tenga características de hombres. Podríamos generar tres clases diferentes de Persona, tres versiones, o podríamos mejor generar clases similares: Persona, Hombre y Mujer.
Clases similares, es la respuesta, pero ¿no es repetir mucho código? así es, definitivamente las clases tendrán entre ellas mucho código idéntico. Dejemos de pensar en el código y regresemos a las abstracciones, recordemos que la POO nos sirve para modelar mucho de lo que existe en la realidad: así como la naturaleza selecciona lo que ha prosperado y lo utiliza para dar lugar a nuevas especies que aprovechan esas características que antes funcionaron para asegurar la supervivencia, a través de la herencia genética, en la POO es posible seleccionar clases como base para crear nuevas, con las mismas características pero agregando los cambios necesarios y obtener clases especializadas. Dentro del ámbito de la organización de las clases, junto con la composición, entra la idea de la herencia o especialización de clases.
Una clase creada a partir de otra se denomina clase hija o derivada, y podrá heredar algunas propiedades (atributos) y parte del comportamiento (métodos) de su clase padre o base. Además podrá tener sus propias características y modificaciones a lo heredado, de esta manera, aprovechando las características del padre, puede especializarse. Un claro ejemplo podemos tomarlo de la realidad: un grupo de estudiantes que cursan juntos la educación básica, en algún momento tomarán caminos distintos y se especializarán en alguna de las áreas del conocimiento; pero tendrán la misma base, los mismos conocimientos básicos que les servirán para adquirir los nuevos y especializados. Así un ingeniero, un médico o un abogado serán derivados de la educación básica.
Construyendo mejores programas
Ya sea haciendo composiciones y creando objetos a partir de varios otros, o especializando clases usando la herencia, siempre es buena idea organizar nuestros objetos para tener programas altamente flexibles y escalables. Es importante también saber delegar las tareas a los objetos e intentar que colaboren equitativamente y sobre todo es imprescindible diseñar las mejores interfaces para que la comunicación entre ellos sea óptima.