Ensayo sobre la Importancia del Encapsulamiento


El encapsulamiento consiste en más que simplemente definir métodos de acceso y de mutación para una clase. Es un concepto mucho más amplio de programación (no exclusivamente vinculando con la orientación a objectos) que consiste en minimizar la interdependencia entre los módulos y típicamente se implementa mediante el ocultamiento de información. Para comprender el concepto de encapsulamiento es de primordial importancia darse cuenta de que tiene dos objetivos fundamentales: ocultar la complejidad y ocultar el origen de los cambios.

Sobre Ocultar la Complejidad

El encapsulamiento está inherentemente relacionado con los conceptos de modularidad y abstracción y es por eso que, en opinión de este escritor, es necesario primero comprender estos dos conceptos.

Consideremos, como ejemplo, el nivel de abstracción en el concepto de un automóvil. Un automóvil es complejo en su funcionamiento interno; posee varios subsistemas, como el sistema de transmisión, el sistema de frenos, el sistema de abastecimiento de combustible, etcétera.

No obstante hemos llegado a simplificar este concepto dentro de una simple abstracción,  e interactuamos con todos los automóviles en el mundo a través de lo que denominaremos su interfaz pública: por ejemplo, sabemos que todos los automóviles tienen un rueda de volante a través de la cual controlamos la dirección del movimiento del vehículo, tenemos varios pedales, uno de los cuales sirve para acelerar y controlar la velocidad y otro con el que controlamos el frenado, y tenemos una palanca de cambios con la que controlamos si deseamos ir hacia adelante o hacia a atrás. Todas estas características constituyen la interfaz pública de la abstracción de un automóvil. Una mañana podemos conducir un sedán y por la tarde conducir un automóvil deportivo como si fueran exactamente la misma cosa. Incluso si son de diferentes fabricantes.

demaged carSin embargo, pocos de nosotros conocemos los detalles del funcionamiento de todos los sistemas bajo el capó del vehículo. Esta simple analogía demuestra que los seres humanos lidiamos con la complejidad de los objetos del mundo real mediante definir abstracciones con interfaces públicas que usamos para interactuar con ellos y ocultar todos los detalles innecesarios bajo el capó de estas abstracciones. Y es importante enfatizar la palabra “innecesarios” en esta última oración, porque la belleza de las abstracción es no tener que comprender todos los detalles para ser capaz de interactuar con un objeto internamente complejo, sino que basta con entender un concepto abstracto general, una idea simple de cómo funciona y cómo utilizarlo para realizar una tarea.  Es por ese motivo que la mayoría de nosotros no sabemos, o no nos interesa saber a detalle, cómo un automóvil acelera o frena o cambia de velocidad, pero el desconocimiento de estos detalles no nos impide conducir uno.

De una forma similar el software a menudo se construye con decenas de otros componentes más pequeños y especializados. Estas piezas se agrupan para formar componentes cada vez más complejos y a menudo no tenemos necesidad de comprender el funcionamiento interno de todas las piezas para interactuar con ellas. Basta con comprender cuál es el estímulo o entrada que acepta un objeto y la respuesta esperada del mismo. Es decir, cada componente se convierte en una caja negra para su usuario.

En su libro Code Complete, Steve McConnell usa una analogía de un iceberg: solo una pequeña porción de un iceberg es visible en la superficie del océano, la mayor parte de su verdadero tamaño esta oculto bajo el agua. De manera similar, en nuestros diseños de software las partes visibles de nuestros clases y módulos son su interfaz pública, y ésta está expuesta al mundo exterior, el resto debería estar oculta a los ojos del usuario. En palabras del mismo McConell

“La interfaz de una clase debería revelar tan poco como sea posible sobre su funcionamiento interno”.

Claramente, si nos basamos en nuestra analogía del automóvil podemos ver que el encapsulamiento es algo bueno puesto que oculta detalles de complejidad innecesaria para los usuarios. Hace que los automóviles sean simples de utilizar y de entender.

Sobre Ocultar el Origen de los Cambios

Ahora bien,  continuando con la analogía, pensemos en aquellos días en la que los automóviles no tenían un sistema hidráulico de dirección. Un día los fabricantes de autos finalmente lo inventaron y decidieron que, a partir de ese día, los vehículos debería tenerlo. Aun así esto no cambio en nada la manera como los conductores debían interactuar con sus vehículos. A lo sumo los conductores llegaron a disfrutar una mejor experiencia de manejo. El punto es que un cambio como este fue posible porque la implementación interna del automóvil estaba encapsulada, es decir, oculta a los ojos de los usuarios. En otras palabras, se podían realizar cambios de forma segura sin afectar la interfaz pública de esta abstracción.

De forma similar, si alcanzamos niveles apropiados de encapsulamiento en nuestros diseños de software podemos fomentar de forma segura el cambio y la evolución de nuestas APIs sin afectar a los usuarios de las mismas. Al hacer esto minimizamos el impacto de los cambios y fomentamos la independencia de los módulos. Así las cosas, el encapsulamiento es un medio para conseguir implementar otros importante atributo de un buen diseño de software: el acoplamiento débil.

En su libro Effective Java, Joshua Bloch resalta el poder del ocultamiento de información y el acoplamiento débil cuando dice:

“El ocultamiento de la información es importante por muchas razones, la mayoría de las cuales se derivan del hecho que mediante ésta se logra el desacoplamiento de los módulos que comprometen el sistema, permitiendo que éstos puedan ser desarrollados, probados, optimizados, utilizados, comprendidos y modificados de manera independiente. Esto acelera el desarrollo de sistemas porque los módulos pueden ser desarrollados de forma paralela. Alivia el costo del mantenimiento porque los módulos pueden ser comprendidos más fácilmente y depurados sin el temor de dañar otros módulos […] facilita el afinamiento del desempeño [ya que] estos módulos pueden ser optimizados sin afectar la correctitud de otros módulos [e] incrementa la reutilización del software porque los módulos que no están fuertemente acoplados suelen resultar útiles en otros contextos además de aquellos para los cuales fueron desarrollados”

Así que una vez más podemos ver que el encapsulamiento es un atributo muy deseable que facilita la introducción de cambios y que fomentan la evolución del software. Siempre que respetemos las interfaz pública de nuestras abstracciones tenemos la libertad de cambiar lo que queramos de su funcionamiento encapsulado.

Cuando se Rompe la Interfaz Pública

Entonces ¿qué sucede cuando no alcanzamos los niveles apropiados de encapsulamiento en nuestros diseños?

Bueno, imaginemos que un día los fabricantes de autos deciden poner la tapilla del combustible debajo del automóvil en vez de en uno de sus costados. Digamos que compramos uno de estos nuevos automóviles modificados y cuando se nos termina el combustible vamos a la gasolinera más cercana y entonces nos damos cuenta de que no sabemos en donde está la tapilla del combustible. Tras buscar un rato finalmente descubrimos que está debajo del auto, pero la manguera del combustible no llega hasta ella y la boquilla de la manguera no se corresponde con la nueva interfaz de la tapilla. En ese momento el mundo entero se cae a pedazos, todo porque hemos desobedecido el contrato de la interfaz pública de nuestra abstracción. Un cambio como este costaría millones. Necesitaríamos cambiar todas las bombas de combustible del mundo, sin mencionar talleres mecánicos y repuestos.

Cuando no alcanzamos niveles apropiados de encapsulamiento terminamos rompiendo la interfaz pública de nuestras abstracciones y entonces hay que pagar un precio: el costo del cambio. Asimismo, esta última parte de nuestra analogía revela que el no definir abstracciones con niveles apropiados de encapsulamiento terminará causando dificultades cuando los cambios ocurran.

Es digno de mencionar que los cambios siempre ocurrirán, lo importante es poder minimizar el impacto de esos cambios. Cuando los cambios ocurren en la interfaz pública de una abstracción el cambio es sumamente caro, pues afecta a todos sus usuarios.

Entonces el objetivo del encapsulamiento es reducir la complejidad de las abstracciones por medio de proveer un mecanismo para ocultar detalles de implementación. También ayuda a minimizar la interdependencia y facilitar el cambio. Maximizamos el encapsulamiento por medio de minimizar la exposición de los detalles de implementación.

Sin embargo, el encapsulamiento no servirá de nada si no definimos abstracciones apropiadas. Puesto de manera sencilla, no hay forma de cambiar la interfaz pública de una abstracción sin afectar y probablemente interrumpir a los usuarios. Así, el diseño de abstracciones apropiadas es de primordial importancia para facilitar la evolución del software y el encapsulamiento es solo una de muchas herramientas que contribuyen a la creación de dichas abstracciones. No obstante, ningún nivel de encapsulamiento va a hacer que una mala abstracción funcione bien.

Encapsulamiento con Java

Una de esas cosas que siempre queremos tener encpsuladas es el estado de una clase. El estado de una clase solo debería ser accesible a través de su interfaz pública.

En un lenguaje orientado a objetos, como Java, conseguimos esto por medio de los modificadores de accesibilidad (public, protected, private, etc). Con estos niveles de accesibilidad controlamos el nivel de encapsulamiento. Entre menos restrictivo el nivel, más caro será el cambio cuando este ocurra y más acoplada está la clase con otras clases dependientes (clases de usuario, subclases, etc).

En los lenguajes que soportan la herencia existen dos interfaces públicas: la interfaz pública compartida con los usuarios de la clase, y la interfaz protegida compartida con sus subclases, es decir, las subclases son un tipo especial de usuario.  De ahí que cuando se introduce la herencia en el diseño de software entonces se vuelve de mayor relevancia que diseñemos niveles apropiados de encapsulamiento para las interfaces públicas y protegidas a fin de facilitar los cambios futuros y la evolución de las APIs.

¿Por qué Getters y Setters?

Muchas gente se pregunta por qué necesitamos métodos de acceso y de mutación en Java (popularmente conocidos como getters y setters). Muchos dicen, ¿por qué no podemos simplemente acceder a los datos directamente?

El propósito del encapsulamiento aquí no es ocultar los datos mismos, sino los detalles de implementación de éstos y la manera en cómo son manipulados para presentarlos al usuario. Así que el propósito de los getters y setters es ofrecer una interfaz pública a través de la cual podemos obtener acceso a la información encapsulada en un objeto. Más tarde podemos alterar la representación interna de los datos sin comprometer la interfaz pública de la clase. Por el contrario, si se exponen los datos directamente comprometemos el encapsulamiento y, por lo tanto, la capacidad de cambiar la forma de manipularlos en el futuro sin afectar a sus usuarios. Estaríamos creando una dependencia con los datos directamente y no con la interfaz pública de la clase. Es un coctél perfecto de problemas y nos lo tendremos que beber cuando el cambio finalmente no encuentre.

Hay varias razones de peso por las cuales queremos encapsular acceso a nuestros campos. El mejor compendio de razones que he encontrado fue formulado, una vez más, por Joshua Bloch en Effective Java.

  • Se pueden limitar los valores almacenados en un campo(p.ej. género debe ser F o M).
  • Se pueden tomar acciones cuando el campo es modificado (desencadenar un evento, validar, etc).
  • Se puede proveer seguridad de hilos por medio de sincronizar los métodos.
  • Se puede cambiar la representación interna de los datos (p.ej. campos calculados, tipos de datos diferentes).
  • Se puede hacer que un campo sea de solo lectura.

Sin embargo, una vez más, queremos apuntar a que el encapsulamiento es más que solo ocultar campos. En Java, como ejemplo, podemos ocultar clases completas, y en el futuro inclusive módulos completos.

Lectura Adicionales

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

A %d blogueros les gusta esto: