Encontré esta cita en " La alegría de Clojure " en la pág. 32, pero alguien me dijo lo mismo durante la cena la semana pasada y también lo he escuchado en otros lugares:
[A] La desventaja de la programación orientada a objetos es el acoplamiento estrecho entre la función y los datos.
Entiendo por qué un acoplamiento innecesario es malo en una aplicación. También me siento cómodo diciendo que el estado mutable y la herencia deben evitarse, incluso en la programación orientada a objetos. Pero no veo por qué pegar funciones en las clases es inherentemente malo.
Quiero decir, agregar una función a una clase parece etiquetar un correo en Gmail o pegar un archivo en una carpeta. Es una técnica organizativa que te ayuda a encontrarla nuevamente. Eliges algunos criterios y luego juntas cosas similares. Antes de OOP, nuestros programas eran prácticamente bolsas de métodos en archivos. Quiero decir, tienes que poner funciones en alguna parte. ¿Por qué no organizarlos?
Si se trata de un ataque velado a los tipos, ¿por qué no dicen que restringir el tipo de entrada y salida a una función es incorrecto? No estoy seguro de si podría estar de acuerdo con eso, pero al menos estoy familiarizado con los argumentos pro y con seguridad de tipo. Esto me parece una preocupación mayoritariamente separada.
Claro, a veces las personas se equivocan y ponen la funcionalidad en la clase incorrecta. Pero en comparación con otros errores, esto parece un inconveniente muy menor.
Entonces, Clojure tiene espacios de nombres. ¿Cómo es diferente pegar una función en una clase en OOP de pegar una función en un espacio de nombres en Clojure y por qué es tan malo? Recuerde, las funciones en una clase no necesariamente operan solo en miembros de esa clase. Mire java.lang.StringBuilder: funciona en cualquier tipo de referencia, o mediante el auto-boxing, en cualquier tipo.
PD: Esta cita hace referencia a un libro que no he leído: Programación multiparadigm en Leda: Timothy Budd, 1995 .
Respuestas:
En teoría, el acoplamiento flojo de datos y funciones hace que sea más fácil agregar más funciones para trabajar en los mismos datos. La desventaja es que hace que sea más difícil cambiar la estructura de datos en sí, por lo que en la práctica, el código funcional bien diseñado y el código OOP bien diseñado tienen niveles de acoplamiento muy similares.
Tome un gráfico acíclico dirigido (DAG) como una estructura de datos de ejemplo. En la programación funcional, aún necesita un poco de abstracción para evitar repetirse, por lo que creará un módulo con funciones para agregar y eliminar nodos y bordes, buscar nodos accesibles desde un nodo determinado, crear una clasificación topológica, etc. Esas funciones están efectivamente acoplados a los datos, aunque el compilador no los imponga. Puede agregar un nodo de la manera difícil, pero ¿por qué querría hacerlo? La cohesión dentro de un módulo evita un acoplamiento apretado en todo el sistema.
Por el contrario, en el lado de OOP, cualquier función distinta de las operaciones básicas de DAG se realizará en clases separadas de "vista", con el objeto DAG pasado como parámetro. Es tan fácil agregar tantas vistas como desee que operen en los datos del DAG, creando el mismo nivel de desacoplamiento de datos de función que encontraría en el programa funcional. El compilador no le impedirá agrupar todo en una clase, pero sus colegas sí.
Cambiar los paradigmas de programación no cambia las mejores prácticas de abstracción, cohesión y acoplamiento, solo cambia las prácticas que el compilador le ayuda a aplicar. En la programación funcional, cuando se desea el acoplamiento de datos de función, se aplica mediante un acuerdo de caballeros en lugar del compilador. En OOP, la separación de la vista del modelo se impone por acuerdo de caballeros en lugar del compilador.
fuente
En caso de que no lo supiera, tome esta idea: los conceptos de objetos y cierres son dos caras de la misma moneda. Dicho esto, ¿qué es un cierre? Toma variables o datos del alcance circundante y se une a ellos dentro de la función, o desde una perspectiva OO, efectivamente hace lo mismo cuando, por ejemplo, pasa algo a un constructor para que luego pueda usarlo pieza de datos en una función miembro de esa instancia. Pero sacar las cosas del ámbito circundante no es algo agradable: cuanto mayor sea el ámbito circundante, más malvado es hacer esto (aunque pragmáticamente, a menudo es necesario algo de maldad para hacer el trabajo). El uso de variables globales está llevando esto al extremo, donde las funciones en un programa están usando variables en el alcance del programa, realmente muy malvado. Existenbuenas descripciones en otra parte sobre por qué las variables globales son malas.
Si sigues las técnicas de OO, básicamente ya aceptas que cada módulo de tu programa tendrá un cierto nivel mínimo de maldad. Si adopta un enfoque funcional para la programación, apunta a un ideal en el que ningún módulo en su programa contendrá el mal de cierre, aunque aún puede tener algunos, pero será mucho menos que OO.
Esa es la desventaja de OO: alienta este tipo de maldad, el acoplamiento de datos para funcionar a través del cierre estándar (una especie de teoría de programación de ventanas rotas ).
El único lado positivo es que, si sabía que iba a utilizar muchos cierres para comenzar, OO al menos le proporciona un marco ideal para ayudarlo a organizar ese enfoque para que el programador promedio pueda entenderlo. En particular, las variables que se están cerrando son explícitas en el constructor en lugar de simplemente tomarse implícitamente en el cierre de una función. Los programas funcionales que usan muchos cierres son a menudo más crípticos que el programa OO equivalente, aunque no necesariamente menos elegantes :)
fuente
Se trata de tipo de acoplamiento:
Una función integrada en un objeto para trabajar en ese objeto no se puede usar en otros tipos de objetos.
En Haskell, usted escribe funciones para trabajar contra clases de tipos , por lo que hay muchos tipos diferentes de objetos con los que cualquier función puede trabajar, siempre que sea un tipo de la clase en la que funciona esa función.
Las funciones independientes permiten tal desacoplamiento que no obtienes cuando te enfocas en escribir tus funciones para trabajar dentro del tipo A porque entonces no puedes usarlas si no tienes una instancia de tipo A, aunque la función podría de lo contrario, sea lo suficientemente general como para usarse en una instancia de tipo B o una instancia de tipo C.
fuente
En Java y encarnaciones similares de OOP, los métodos de instancia (a diferencia de las funciones libres o los métodos de extensión) no se pueden agregar desde otros módulos.
Esto se vuelve más una restricción cuando considera interfaces que solo pueden implementarse mediante los métodos de instancia. No puede definir una interfaz y una clase en diferentes módulos y luego usar el código de un tercer módulo para unirlos. Un enfoque más flexible, como las clases de tipos de Haskell, debería poder hacerlo.
fuente
La orientación a objetos es fundamentalmente sobre la abstracción de datos de procedimiento (o la abstracción de datos funcionales si elimina los efectos secundarios que son un problema ortogonal). En cierto sentido, el cálculo Lambda es el lenguaje orientado a objetos más antiguo y puro, ya que solo proporciona abstracción de datos funcionales (porque no tiene construcciones además de las funciones).
Solo las operaciones de un solo objeto pueden inspeccionar la representación de datos de ese objeto. Ni siquiera otros objetos del mismo tipo pueden hacer eso. (Esta es la principal diferencia entre la abstracción de datos orientada a objetos y los tipos de datos abstractos: con los ADT, los objetos del mismo tipo pueden inspeccionar la representación de datos de cada uno, solo se oculta la representación de objetos de otros tipos).
Lo que esto significa es que varios objetos del mismo tipo pueden tener diferentes representaciones de datos. Incluso el mismo objeto puede tener diferentes representaciones de datos en diferentes momentos. (Por ejemplo, en Scala,
Map
sysSet
cambian entre una matriz y un hash trie dependiendo del número de elementos porque para números muy pequeños la búsqueda lineal en una matriz es más rápida que la búsqueda logarítmica en un árbol de búsqueda debido a los factores constantes muy pequeños .)Desde el exterior de un objeto, no debería, no puede conocer su representación de datos. Eso es lo opuesto al acoplamiento apretado.
fuente
El acoplamiento apretado entre datos y funciones es malo porque desea poder cambiar cada uno independientemente del otro y el acoplamiento apretado lo hace difícil porque no puede cambiar uno sin conocimiento y posiblemente cambios en el otro.
Desea que se presenten diferentes datos a la función para que no requieran ningún cambio en la función y, de manera similar, desea poder realizar cambios en la función sin necesidad de ningún cambio en los datos en los que está operando para admitir esos cambios de función.
fuente