oop - programacion - modularidad poo
"Principio de muchas funciones que operan con pocas abstracciones" versus OOP (4)
El creador del lenguaje Clojure afirma que "un conjunto abierto y grande de funciones opera sobre un conjunto abierto, y pequeño, de abstracciones extensibles, es la clave para la reutilización algorítmica y la interoperabilidad de la biblioteca". Obviamente, contradice el enfoque OOP típico en el que se crean muchas abstracciones (clases) y un conjunto relativamente pequeño de funciones que operan en ellas. Sugiera un libro, un capítulo en un libro, un artículo o su experiencia personal que explique los temas:
- ejemplos motivadores de problemas que aparecen en OOP y cómo usar "muchas funciones con pocas abstracciones" los abordaría
- cómo hacer de manera efectiva el diseño de MFUFA *
- cómo refactorizar el código OOP hacia MFUFA
- cómo la sintaxis de los lenguajes OOP se interpone en el camino de MFUFA
* MFUFA: "muchas funciones con pocas abstracciones"
Creo que no entiendo que haya una diferencia entre las bibliotecas y los programas.
Las bibliotecas OO que funcionan bien generalmente generan una pequeña cantidad de abstracciones, que los programas utilizan para crear las abstracciones para su dominio. Las bibliotecas OO más grandes (y los programas) usan herencia para crear diferentes versiones de métodos e introducir nuevos métodos.
Entonces, sí, el mismo principio se aplica a las bibliotecas OO.
Una versión mucho más temprana de la cita:
"La estructura simple y la aplicabilidad natural de las listas se reflejan en funciones que son increíblemente noidiosincráticas. En Pascal, la plétora de estructuras de datos declarables induce una especialización dentro de funciones que inhibe y penaliza la cooperación casual. Es mejor tener 100 funciones operando en una estructura de datos que tener 10 funciones que operan en 10 estructuras de datos ".
... proviene del prólogo del famoso libro SICP . Creo que este libro tiene mucho material aplicable sobre este tema.
Cuando escribe un programa en un estilo orientado a objetos, hace énfasis en expresar el área de dominio en términos de tipos de datos . Y a primera vista, parece una buena idea: si trabajamos con usuarios, ¿por qué no tener un User
clase? Y si los usuarios venden y venden automóviles, ¿por qué no tener un Car
clase? De esta forma, podemos mantener fácilmente los datos y controlar el flujo; solo refleja el orden de los eventos en el mundo real. Si bien esto es bastante conveniente para objetos de dominio , para muchos objetos internos (es decir, objetos que no reflejan nada del mundo real, sino que ocurren solo en la lógica del programa) no es tan bueno. Tal vez el mejor ejemplo es una serie de tipos de colección en Java. En Java (y en muchos otros lenguajes OOP) hay dos matrices, List
s. En JDBC hay ResultSet
que también es una especie de colección, pero no implementa la interfaz de Collection
. Para la entrada, a menudo utilizará InputStream
que proporciona una interfaz para el acceso secuencial a los datos, ¡al igual que la lista vinculada! Sin embargo, no implementa ningún tipo de interfaz de colección. Por lo tanto, si su código funciona con la base de datos y usa ResultSet
, será más difícil refactorizarlo para archivos de texto y InputStream
.
El principio de MFUFA nos enseña a prestar menos atención a la definición de tipo y más a las abstracciones comunes . Por este motivo, Clojure introduce una sola abstracción para todos los tipos mencionados: secuencia. Cualquier iterable se coacciona automáticamente a la secuencia, las secuencias son simplemente listas vagas y el conjunto de resultados se puede transformar fácilmente en uno de los tipos anteriores.
Otro ejemplo es el uso de la interfaz PersistentMap
para estructuras y registros. Con tales interfaces comunes, es muy fácil crear subrutinas resusables y no dedicar mucho tiempo a la refactorización.
Para resumir y responder sus preguntas:
- Un ejemplo simple de un problema que aparece en OOP con frecuencia: leer datos de muchas fuentes diferentes (por ejemplo, DB, archivo, red, etc.) y procesarlo de la misma manera.
- Para hacer un buen diseño de MFUFA intente hacer las abstracciones lo más comunes posible y evite las implementaciones ad-hoc. Por ejemplo, evite los tipos a-la
UserList
-List<User>
es lo suficientemente bueno en la mayoría de los casos. - Siga las sugerencias del punto 2. Además, intente agregar tantas interfaces como sea posible a sus tipos de datos (clases). Por ejemplo, si realmente necesita tener
UserList
(por ejemplo, cuando debería tener mucha funcionalidad adicional), agregue las interfacesList
eIterable
a su definición. - OOP (al menos en Java y C #) no es muy adecuado para este principio, ya que intentan encapsular el comportamiento de todo el objeto durante el diseño inicial, por lo que se vuelve difícil agregarles más funciones. En la mayoría de los casos, puede extender la clase en cuestión y poner los métodos que necesita en un nuevo objeto, pero 1) si alguien más implementa su propia clase derivada, no será compatible con la suya; 2) a veces las clases son
final
o todos los campos se hacenprivate
, por lo que las clases derivadas no tienen acceso a ellos (por ejemplo, para agregar nuevas funciones a la claseString
StringUtils
debe implementar la clase adicionalStringUtils
). Sin embargo, las reglas que describí anteriormente hacen que sea mucho más fácil usar MFUFA en código OOP. Y el mejor ejemplo aquí es Clojure en sí, que se implementa con gracia en el estilo OO pero sigue el principio de MFUFA.
UPD. Recuerdo otra descripción de la diferencia entre estilos orientados a objetos y funcionales, que quizás resume mejor todo lo que dije anteriormente: diseñar un programa en OO es pensar en términos de tipos de datos (sustantivos), mientras que diseñar en estilo funcional es pensar en términos de operaciones ( verbos). Puede olvidar que algunos sustantivos son similares (por ejemplo, olvidarse de la herencia), pero siempre debe recordar que muchos verbos en la práctica hacen lo mismo (por ejemplo, tienen interfaces iguales o similares).
Hay dos nociones principales de "abstracción" en la programación:
- parametrización ("polimorfismo", genérico).
- encapsulación (ocultación de datos),
[Editar: Estos dos son duales. El primero es la abstracción del lado del cliente , la segunda abstracción del lado del implementador (y en caso de que se preocupe por estas cosas: en términos de lógica formal o teoría de tipos, corresponden a la cuantificación universal y existencial , respectivamente).]
En OO, la clase es la característica del fregadero de la cocina para lograr ambos tipos de abstracción.
Ad (1), para casi todos los "patrones" que necesita para definir una clase personalizada (o varias). En la programación funcional, por otro lado, a menudo tiene métodos más ligeros y directos para lograr los mismos objetivos, en particular, funciones y tuplas. A menudo se señala que la mayoría de los "patrones de diseño" del GoF son redundantes en FP, por ejemplo.
Ad (2), la encapsulación se necesita un poco menos a menudo si no tiene un estado mutable que persista en todas partes que necesita mantener bajo control. Aún construyes ADT en FP, pero tienden a ser más simples y genéricos, y por lo tanto necesitas menos de ellos.