Esta publicación se
centra en el capítulo de Objetos y estructuras de datos de Clean
Code (Robert
C. Martin).
Abstracción de datos
La abstracción de datos tiene como finalidad manipular la esencia de los datos de un sistema sin conocer los detalles de implementación del mismo.
Si el sistema
presenta un dato que es un punto en un plano, su representación se
podría hacer mediante una clase que exponga su implementación o con
una abstracción que esconda completamente su implementación.
Clase que expone su
implementación.
Clase que esconde su
implementación.
La clase «Point»
expone la implementación. Los atributos de la instancia se tienen
que modificar directa e independientemente. Hacer las variables
privadas y manipularlas a través de métodos públicos no oculta la
implementación, esta sigue ahí.
La «interface»
punto sí que oculta la implementación. Esta representa más que una
estructura de datos formada por dos atributos. El método
«setCoordinates» está forzando una política de acceso
donde la asignación de las dos coordenadas se hace de manera
atómica.
La abstracción de
datos es más compleja que ocultar la implementación detrás de
métodos «get» o «set». Esta se basa en representar
los componentes del sistema y su funcionamiento sin detenerse en cómo
se va a implementar.
Antisimetría de datos y objetos
Mientras que las estructuras de datos exponen sus datos y no tienen funciones significativas, los objetos ocultan los datos mediante abstracciones. Por definición son justo lo opuesto.
En el siguiente
ejemplo se observa un estilo de estructuras de datos para unas
figuras geométricas. Se puede apreciar que las estructuras de datos
no tienen ningún comportamiento y sólo tienen los datos. Todo el
comportamiento aparece en la clase «Geometry».
Si se añade un
nuevo comportamiento a la clase «Geometry», ninguna de las
clases que son estructuras de datos cambia. Por otra parte, si se
añade una nueva figura geométrica, se debe cambiar el
comportamiento de la clase «Geometry».
Si se utilizan
objetos para la implementación del ejemplo anterior, el
comportamiento estaría incluido en cada clase con el método «area».
Si se añade una nueva figura geométrica, esto no afectaría a las
demás pero, si se añade un nuevo comportamiento, entonces todas las
clases tienen que incluir esta modificación. Esto se puede solventar
con un «Visitor».
Con estos ejemplos
se hace patente la naturaleza de los dos estilos.
Las estructuras
de datos facilitan la adición de nuevas funciones sin cambiar las
estructuras de datos existentes mientras que los objetos
posibilitan la incorporación de nuevas clases sin cambiar las
funciones existentes.
Hay situaciones en
las cuales es estilo con estructuras de datos será más apropiado y
otros en los que los objetos serán la mejor solución.
La ley de Demeter
La ley de Demeter establece que un módulo no debería saber sobre la implementación de los objetos que manipula.
Un método «f» de
una clase «C» debería llamar únicamente a métodos de:
-
«C».
-
Un objeto creado por «f».
-
Un objeto pasado como argumento a «f».
-
Un objeto en una variable de instancia de «C».
El método «f» no
debería invocar métodos de objetos que se devuelven por cualquiera
de las funciones permitidas. En otras palabras, debería limitarse a
hablar con amigos y no con extraños.
En algunos lenguajes
de programación se usa el punto como identificador y la ley se puede
simplificar como «usar sólo un punto». El código a.b.method()
vulnera dicha ley mientras que a.method() no lo hace.
Choque de trenes
Este tipo de código se ve como un encadenamiento de funciones y/o variables. Este tipo de cadenas se considera un estilo descuidado y debería evitarse.
Usualmente es mejor
no encadenar y almacenar en variables con un nombre semántico.
En principio parece que la función sabe cómo navegar a través de distintos objetos. Si esto incumple o no la ley de Demeter va a depender de si estos objetos son estructuras de datos sin comportamiento, lo cual no vulnera la ley, o si son objetos con un compartimiento lo cual sí incumpliría la ley.
En principio parece que la función sabe cómo navegar a través de distintos objetos. Si esto incumple o no la ley de Demeter va a depender de si estos objetos son estructuras de datos sin comportamiento, lo cual no vulnera la ley, o si son objetos con un compartimiento lo cual sí incumpliría la ley.
Si el código
estuviera escrito como acceso a distintos atributos no cabría duda
de que se trata de una estructura de datos y la ley de Demeter no
aplicaría aquí.
Híbridos
Hay veces que la confusión viene dada por estructuras híbridas que son en parte objetos y en parte estructuras de datos. Tienen funciones que realizan tareas significativas (comportamientos) y también tienen variables públicas.
Estos Híbridos
tienen lo peor de los dos aspectos, por lo que se debe tratar de
evitarlos.
Ocultar la estructura
Como no podemos encadenar llamadas a través de los objetos para no romper la Ley de Demeter podríamos crear un método en «$app» que obtenga el resultado:
Esta opción provoca
una explosión de método en el objeto «$app». Además, desvela el
funcionamiento interno del mismo.
Otra posibilidad
sería:
En este caso se está
asumiendo que el resultado de «getPathsFromConfig()» es una
estructura de datos.
Ninguna de las
soluciones parece buena ya que se está preguntando sobre su
estructura interna. Al ser un objeto, este debería tener un
comportamiento, una abstracción del negocio, que es el que se
tendría que utilizar.
Una estrategia es
analizar el porqué de obtener esa ruta, ¿dónde se va a utilizar y
para qué? Si un lector analizara el código y encontrase lo
siguiente:
Se puede inferir que la intención de obtener la ruta de las imágenes es mover una imagen que ha sido previamente subida.
Así que se podría
abstraer este comportamiento en el objeto «$app» de la
siguiente manera:
Objetos de transferencia de datos
Los objetos de transferencia de datos también conocidos como DTO (Data Transfer Object) o Bean (en Java) son básicamente estructuras de datos que en su forma más pura sólo tienen los atributos. Se pueden añadir «getter» y «setter» para los más puristas aunque no añade beneficios sustanciales.
Si utilizamos la
clase «Rectangle» del ejemplo anterior, su DTO sería el siguiente:
Registros activos o Active Record
Los registros activos o active records, en inglés, son una forma sofisticada de DTO. Son estructuras de datos formadas por atributos públicos con funcionalidades adicionales como «save», «delete», «find», etc. Estos sirven para la manipulación directa sobre una base de datos u otra fuente de datos.
Un problema de estas
estructuras de datos con dichas funcionalidades es que los
desarrolladores las tratan como si fueran objetos añadiendo más
funcionalidades de las reglas de negocio. Esto al final genera un
híbrido entre una estructura de datos y un objeto.
La solución es
tratar a los registros activos o active records como una
estructura de datos y crear abstracciones mediante objetos para las
reglas de negocio.
Conclusiones
Los objetos exponen comportamientos y ocultan los datos (implementación). Añadir nuevos tipos de objetos sin cambiar el comportamiento es fácil ya que sólo hay que crear una nueva clase y añadir los comportamientos sin tener que modificar los existentes. Por contra, añadir un nuevo comportamiento es más difícil, ya que hay modificar todas las clases existentes para incluir ese comportamiento.
Las estructuras de
datos exponen sus datos pero no tienen un comportamiento
significativo. Esto facilita añadir nuevos comportamientos en las
clases que implementan dichos comportamientos ya que sólo hay que
modificar esa clase pero hace que resulte más complejo añadir
nuevas estructuras de datos, ya que hay que crear la nueva estructura
de datos y modificar todos los comportamientos para que incluyan esa
estructura de datos.
No hay comentarios:
Publicar un comentario