Diseño de Modelos

En el post sobre tipos fantasmas usaba como ejemplo un método para calcular precios que dependía de los Ids de varias entidades y podía provocarnos problemas si confundíamos el orden de los parámetros:

decimal GetPrice(int productId, int customerId, int priceListId)

En los comentarios, Abel hacía una observación más que razonable: en lugar de utilizar los Ids como parámetros del método, utilizar instancias de las clases Customer, Product y PriceList, consiguiendo así la seguridad de tipos que buscamos sin tener que introducir ninguna nueva abstracción.

Obviando por completo si el uso del tipo fantasma Id es una buena o mala idea (al fin y al cabo era sólo un ejemplo), me parece interesante profundizar un poco sobre ello.

Algo sobre modelos

No hace tanto, casi todas las aplicaciones se diseñaban partiendo de un modelo relacional. Lo primero que se definía era un esquema de bases de datos y eso se convertía en la base de la aplicación.

Muchas de esas aplicaciones no estaban desarrolladas siguiendo realmente un paradigma de orientación a objetos. Aunque en algunas se usase un lenguaje orientado a objetos, realmente el modelo de objetos era poco más que una imagen directa de las tablas en la base de datos, sin apenas relaciones entre ellos.

Un ejemplo típico era tener algo así:

public class Category
{
   public int Id { get; set; }
}

public class Product
{
   public int Id { get; set; }
   public int CategoryId { get; set; }
}

He desarrollado unas cuantas aplicaciones como esas, en las que existían objetos que no eran más que sacos de propiedades y constituían lo que se suele llamar un modelo de dominio anémico, dejando la lógica en otra clases donde se implementaban los algoritmos.

Esto, estructuras de datos más algoritmos, es la base de la programación estructurada, con todas sus ventajas y limitaciones.

Después empezó a calar más profundamente el paradigma de orientación a objetos y, lo que antes era un modelo relacional, se convirtió en un modelo conceptual, o modelo de dominio, o cualquiera de los tantos otros nombres que se le han dado.

Lo importante es que este modelo estaba diseñado con clases y objetos en lugar de tablas y relaciones, lo que permitía crear asociaciones entre los objetos, aprovechar el polimorfismo y, en definitiva, sacar partido a todo lo que supone la programación orientada a objetos.

El ejemplo anterior se convertía en:

public class Category
{
   public int Id { get; set; }
   public IEnumerable Products { get; set; }
}

public class Product
{
   public int Id { get; set; }
   public Category Category { get; set; }
}

Ahora se trataba de encapsular el comportamiento junto con los datos en objetos y usar esto para modelar del dominio del problema.

Hasta dónde hay que llegar

Llevando al extremo el paradigma de orientación a objetos, hay veces que se intenta modelar la realidad a base de clases. Esto puede parece una buena idea, pero no hay que olvidar que estamos creando un modelo simplificado para resolver un problema, no pintando un cuadro hiperrealista.

Crear un modelo sumamente conectado implica que todas nuestras clases están relacionadas entre sí, y aunque eso pueda parecer una buena idea al principio porque se asemeje a la vida real, también implica que a nivel de software estamos acoplando unas partes del sistema con otras.

Al diseñar una clase, es importante tener en cuenta las dependencias que se introducen sobre otras partes del sistema. Podemos tener depedencias privadas que no se exponen hacia el exterior, o dependencias públicas que forman parte del interfaz de la clase, ya sea en una propiedad o como parámetro de entrada o salida de un método.

Cada vez que introducimos una dependencia pública en una clase, estamos forzando a todas aquellas clases que dependen de ella a asumir también esa dependencia. Esto no es necesariamente malo, pero sin duda es un factor a considerar.

Volviendo al ejemplo anterior de GetPrice, si el método sólo necesita conocer el Id del producto, ¿merece la pena que reciba el objecto Product completo?

Por una parte, parece lógico que si ya tengo un artefacto en el código (una clase) para representar un producto y encapsular esa entidad, la utilice en lugar de pasar sólo el Id.

Por otra, si el método GetPrice sólo necesita el Id y estamos pasándole el objeto completo, no deja de ser en cierto modo una violación del principio de segregación de interfaces. Además, estoy forzando a todos los usuarios del método GetPrice a ser capaces de obtener de alguna forma una instancia completa de la clase Product.

A la hora de decidir cómo diseñar este tipo de interfaces, es útil pensar en la relación lógica entre las clases que los van a utilizar. Normalmente es fácil encontrar un “paquete” de clases que interactúan entre sí y van a evolucionar unidas. En este caso en que existe una alta cohesión entre ellas no suele ser problemático introducir dependencias.

Sin embargo, si se trata de clases más genéricas o reutilizables en distintas partes de nuestra aplicación, o que pertenecen a subsistemas diferentes, utilizar tipos básicos (o al menos más básicos) y evitar introducir nuevas dependencias puede ser una estrategia mejor porque favorece la reutilización y la modularidad de la aplicación.

Conclusiones

Como siempre, esto no es una ciencia exacta y depende mucho de cada caso y de las preferencias personales. En un ejemplo como el de GetPrice, donde no hay ningún tipo de contexto, cualquier cosa podría ser válida (o desastrosa).

A lo largo de mi vida he diseñado aplicaciones siguiendo más o menos los pasos descritos en este post, empezando con modelos anémicos para luego pasar a modelos en los intentaba reproducir fielmente el mundo real, y llegar a un enfoque que (considero) un poco más pragmático.

Seguramente es cuestión de tiempo que cambie de opinión, pero de momento, creo que es fundamental no perder de vista que el paradigma de programación y el modelo que construyas con él nunca pueden ser un objetivo en si mismos.

El objetivo es resolver un problema y todo lo que hagas tiene ser visto desde esa perspectiva.

22 comentarios en “Diseño de Modelos

  1. Creo que uno de los problemas de usar un unico modelo que pretende simular la realidad es que al final el programador y sus programas se ven atrapados en el mismo. Al final se intenta modelar absolutamente todo siguiendo un esquema prefijado y te olvidas o ignoras otras posibles formas de construir un programa. Me parece que el modelado intuitivo basado en objetos es especialmente peligroso en ese sentido, precisamente por lo de intuitivo, familiar y sencillo (al menos inicialmente) de su aplicacion. Al final confundimos el programa con la realidad e ignoramos las fricciones entre uno y otro.
    Por ello creo que hay que dar un paso (atras y arriba) y no solo ser pragmaticos con las herramientas para modelar sino con los modelos mismos y aprender y saber cuando usar uno u otro: teoria de conjuntos/relacional, matematico (en general)/funcional, basado en objetos…

  2. Uy que filosofico (en el mal sentido de la palabra) me ha quedado…
    En clojure me he encontrado con una situacion similar y el lenguaje me ha permitido una aproximacion intermedia:

    (ns user)
    (defrecord User [id name …])
    (defn find-by-id [{id :id :as user}] (merge user (db/find-by-id :user id)))

    (= (find-by-id (->User x y …)) (find-by-id {:id 1}))

    En este caso la asimilacion entre el record y el tipo de datos basico map permite no acoplar rigidamente la primera, aunque tiene sus propios problemas. ¿Seria algo similar a tener una «interface WithId { getId;setId}» en java o c#?

  3. uno que pasaba dijo:

    Creo que confundes objetos con estructuras de datos.

    El principio de segregación de interfaces trata sobre comportamiento, no sobre datos.

    Además si tu método depende del id de producto… está dependiendo del producto.
    A ver si me explico, si tú diseñas tu «Producto» como un objeto entonces tendrá que tener su datos ocultos y solo exponer comportamiento, es decir, no podrás acceder a su «id» pero tendrá expuesto un comportamiento que te permita hacer con ese «id» lo que fueras a hacer.
    Por otro lado si consideras tu «Producto» como una estructura de datos, entonces no tendrá comportamiento y tendrá expuesto su datos internos y habría que mirar que quizá pasar la estructura de datos «Producto» mejor que sólo su «id» daría menos problemas, porque al pasar sólo su id estas acoplando ya el método que usa el «id del producto» con la estructura interna de «Producto», es decir no sólo el método de que usa el id de producto dependería de producto sino que además dependeria de la implementación propia de la estructura de datos «producto» con lo que no podríamos por ejemplo cambiar el tipo de «id» de integer a long o a lo que sea. (¿no sé si me explico?)

    Meter comportamiento a las estructuras de datos (aunque tengan la forma de objetos) se llaman híbridos y es lo peor de los dos mundos.

  4. mmm pero es que esa «peligrosa» mezcla es intrinseca a la oop, no?
    «Los objetos son entidades que tienen un determinado estado, comportamiento (método) e identidad», cogido de la wikipedia por no buscar las fuentes originales (mas adelante equipara «estado» a «datos» que se sobrentiende son mutables).
    El ideal de encapsular los datos (mutables) y exponer solo los comportamientos de forma radical tiene su propia problematica, ya clasica en los lenguajes oop: meter (estaticamente o inyectado en ejecucion si estas en un lenguaje dinamico o con reflexion) en el objeto todos los comportamientos posibles actuales y futuros que tengan que ver con el estado (no solo con su id), que pueden ser trasversales a varios objetos.

    PD: Hay que señalar que en el ejemplo de clojure (que puede ser implementado en un lenguaje oop dinamico) se gana en modularidad y desacoplamiento a cambio de perder las restricciones de los tipos estaticos en tiempo de compilacion, que era uno de los objetivos de los tipos fantasmas de la entrada anterior.

  5. uno que pasaba dijo:

    jneira: no sé lo que quieres decir. Si haces el favor de explicármelo te lo agradecería

    De momento te comento un par de cosas.
    Un objeto puede ser mutable o inmutable; depende de si le metes mutators o no (métodos capaces de cambiar su estado interno) De hecho es preferible diseñar objetos inmutables.

    No todo en la oo es un objeto, en C# tienes los «struts» pero en java y otros lenguajes no, que una estructura de datos esté en la forma de un objeto no lo convierte en un objeto. El patrón Visitors de la colección GoF muestra una de las ventajas que tiene el no tener que tratar a todo como un objeto y romper las «reglas» de la OO, o es otra forma de decir que no todo tiene porque ser un objeto.

    Diseñar una clase con mutators, bien sea de manera estática en tiempo de compilación o de manera dinámica por reflexion, es problema del que lo programa así, todo depende de lo que quieras hacer. Y puede estar bien o no… si estás creando una api para ser usada por terceros… diseñar clases mutables es un problema.

  6. «jneira: no sé lo que quieres decir. Si haces el favor de explicármelo te lo agradecería»
    Mmm si me concretas un poco mas la duda lo intentare…

    «Un objeto puede ser mutable o inmutable; depende de si le metes mutators o no»
    Efectivamente usar objetos inmutables te libera de una serie de problemas, y hace que sea mucho menos peligroso exponer el estado para consulta (y no necesitar la encapsulacion). Pero si el lenguaje es por defecto mutable y provoca problemas de rendimiento facilmente el no usarlo terminas en muchos casos haciendolo. Sin embargo no soluciona el problema de las dependencias (aunque las hace menos peligrosas) que comentaba el autor.

  7. uno que pasaba dijo:

    «Efectivamente usar objetos inmutables te libera de una serie de problemas, y hace que sea mucho menos peligroso exponer el estado para consulta (y no necesitar la encapsulación)»

    Ésto es una contradicción. Si es un objeto no puedes consultar su estado interno; porque por definición un objeto es una encapsulación de unos datos con un comportamiento, si es una estructura de datos no tiene sentido hablar de encapsulación porque las estructuras de datos por definición tienen su estructura interna expuesta (los setters y getters no encapsulan nada, más bien exponen los detalles de la implementación interna del objeto, pero si es una estructura de datos no hay problema, el problema es cuando le das comportamiento creando un híbrido).

    «Pero si el lenguaje es por defecto mutable y provoca problemas de rendimiento fácilmente el no usarlo terminas en muchos casos haciéndolo»
    Los lenguajes no son ni mutables ni inmutables, una clase será inmutable o mutable si la has diseñado así. No le veo sentido a ésta frase.

    «Sin embargo no soluciona el problema de las dependencias (aunque las hace menos peligrosas) que comentaba el autor.»
    Ésto es lo que he intentado explicar en mi primer comentario.

  8. Muy buenos los dos puntos de vista y muy enriquecedor el «choque» entre orientación a objetos y otros paradigmas.

    Coincido plenamente en lo que comentaba jneria en su primer comentario del peligro del modelado orientado a objetos. En ese sentido, me gusta mucho la forma de explicarlo de Eric Evans en su libro Domain Driven Design en el que expone cómo el modelo debe estar dirigido por el problema a resolver, no por la realidad física que se representa.

    Un buen ejemplo de eso son las proyecciones que se usan en los mapas. Todas ellas son modelos de una misma realidad, pero cada está pensada para un uso concreto (calcular distancias, calcular áreas, calcular ángulos…) Ninguna es inherentemente mejor a las demás, pero algunas son más útiles que otras en determinados casos.

    En cuanto al encaspulado en OOP, siempre me ha parecido un «ideal» que perseguir pero que es inalcanzable desde un punto de vista práctico. Cuando trabajas en un problema real, casi siempre es necesario exponer de una forma u otra el estado del objeto, por ejemplo para serializarlo, guardarlo en una base de datos o presentarlo en un interfaz de usuario.

    Llevado al extremo, toda esta funcionalidad debería quedar encapsulada en la clase para no exponer su estado hacia el exterior, pero acabaría con clases enormes y con responsabilidades mezcladas, por lo que el concepto de «encapsular» empieza a perder sentido. Al final es como si encapsulases toda la aplicación en una única clase.

    Esto no quiere decir que no haya que intentar encapsular la información, pero a veces es necesario buscar un equilibrio.

    Por otra parte, en cuanto cuanto al tema de propiedades (especialmente getters), creo que no siempre se pueden considerar como que «exponen detalles de implementación». Si quiero modelar un coche que tiene un color, exponer el color a través de un getter puede ser perfectamente válido. El cómo represente dentro del coche ese color es un detalle de implementación, pero el hecho de que el coche tenga un color observable es una característica necesaria.

    Gracias nuevamente a los dos por esta interesante discusión.

  9. «Si es un objeto no puedes consultar su estado interno»
    Mmm, hombre poder si que puedes si es lo que llamas un hibrido. Lo que queria decir es que si es inmutable hacer visibles sus atributos es menos peligroso si necesitas (o te viene bien) hacerlo.

    «Los lenguajes no son ni mutables ni inmutables»
    Un lenguaje puede fomentar una u otra y puede hasta llegar a no dar soporte a una de las opciones . Por ejemplo si la definicion por defecto de una variable sin poner un modificador es mutable, fomenta la mutabilidad o viceversa. Si la diferencia de rendimiento entre modificar una estructura de datos y copiarla entera es grande (en favor de modificar) tambien esta fomentando la mutabilidad. Si el uso mas extendido del lenguaje (debido a cosas como la anteriores) es mutable tambien se fomenta por inercia.
    Hay lenguajes que ni siquiera tienen la operacion de asignacion con lo que la inmutabilidad se ve todavia mas reforzada.

    «Ésto es lo que he intentado explicar en mi primer comentario.»
    Mi referencia a la mutabilidad tambien era secundaria en cuanto al tema de las dependencias (las hace en mi forma de ver menos peligrosas aunque no las elimine)
    Mi comentario iba mas por como usar objetos «puros» con encapsulacion tiene sus propios problemas de diseño pero ya has comentado a tu vez que «no todo tiene porque ser un objeto», o sea que creo que coincidimos en eso.
    Suponiendo que producto es una estructura de datos y no un objeto para evitar esos problemas, aunque depender de Producto hace que no tengas que cambiar la firma de las funciones o metodos si cambia el tipo de la id (gran ventaja) seguramente si que tienes que cambiar o revisar el codigo de la implementacion (p.e. si cambia de int a String). Por otro lado si usas tipos estandar (int,String,List,Map) en un modulo no necesita siquiera importar o conocer la existencia del modulo donde este definido «Producto» (y/o «Usuario» o lo que sea) y por tanto eliminas esa dependencia.

  10. Hola Juanma,

    Como siempre, un buen post. Coincido contigo en el exceso de dependencias y relaciones que se pueden crear por querer modelar hasta el último detalle con un modelo conceptual. Apuesto por pasar tipos básicos donde sólo necesito tipos básicos… para mi esta decisión está clara.
    A propósito de leer tu post me ha surgido la duda de cómo evolucionar de un modelo anémico a uno no anémico. Parece obvio que el modelo anémico es un anti-patrón… pero es tan fácil hacer clases POCO (meros contenedores de propiedades) y pasar el comportamiento a Managers (meras operaciones)… ;-)
    ¿Realmente con un modelo no anémico no habría Managers? ¿Todas las operaciones deberían ser asignadas a las clases del modelo (según su responsablidad)? Cómo te decía en twitter, yo creo que hay un hueco importante para ese post, tienes lecturas garantizadas.
    Gracias por compartir!

  11. Fernando Serapio dijo:

    Lo que mencionas sobre objetos anemicos es cierto, de hecho en algunos casos se dice que es un antipatron, por si lo que quieres son modelos u objetos que sean simples transportadores de datos, mejor usa un DTO y no entidades de negocio, los cuales en DDD deben ser precisamente eso, objetos de negocio que contengan logica de negocio propia. Comunmente y me ha tocado ver que muchas veces muchos ponen (o ponemos) logica de negocios en servicios (o «helpers») o incluso, si usamos solo MVC, la ponemos en los controladores, siendo esto una mala implementacion de un patron como este

  12. Muy buen post, es un problema muchas veces obviado, pero muy importante.

    Lo malo de la OOP es que cuando no es gratis crear los objetos y acceder a su información (ej. ORM, webservice, etc…) el coste en rendimiento de disponer de un buen modelo es enorme y mal soportado por cualquier framework.

    En tales casos, una arquitectura estilo REST (caso de webservices) o más centrada en la base de datos (caso de uso intensivo de la capa de datos) me gusta mucho más que un bosque de interfaces y clases.

    Trabajas más (porque en lugar de solicitar Persona «a secas» solicitas en cada momento lo que te hace falta) pero tienes más control y el producto resultante es mucho más eficiente (y acoplado claro, no todo van a ser ventajas).

  13. «y acoplado claro, no todo van a ser ventajas»
    ¿en que sentido es acoplado? Porque no dependes de ese bosque de interfaces y clases que comentas. Supongo que te refiras a que esta acoplado implicitamente/por convencion ya que si, por ejemplo, solo ofreces buscar por la id como entero todas las llamadas deben usar un enteros (aunque puedes ofrecer varios metodos mas o menos genericos que respondan a diferentes tipos estandar)

  14. josejuan, personalmente, y creo que en el post está pareciendo lo contrario, me resulta más cómodo tener un «buen» modelo orientado a objetos, sólo creo que hay que saber dónde están los límites y cuando deja de ser práctico.

    Desde que deje de diseñar aplicaciones partiendo de la base de datos he sido mucho más feliz y creo que he conseguido una mayor productividad, pero en esto influye mucho la forma de trabajar de cada uno.

    El problema del rendimiento se puede resolver en gran medida cambiando el tipo de base de datos (p.ej, bases de datos documentales) o ajustando la forma de leer datos para cargar de forma optimizada lo que necesitas (algo más del estilo de CQRS, puedes echarle una vistazo a mi serie sobre repositorios https://blog.koalite.com/tag/repository/), pero tener objetos definidos también ayuda a acotar el tipo de consultas que se lanzan y facilita optimizar el modelo de datos para esas consultas.

  15. @jneira, el acoplamiento se debe a que los procesos concretos que debes resolver fuerzan a crear estructuras específicas (ej. un nuevo procedimiento almacenado, un nuevo REST, etc…) mientras que usando un modelo de objetos abstraes ciertos elementos que son reutilizados (de la otra forma también pero de forma menos clara y abstracta).

    @Juanma, totalmente en desacuerdo, el rendimiento que obtienes haciéndolo «a sabiendas» balancenado ahora bbdd ahora servidor de aplicaciones, etc… no lo vas a alcanzar ni de cerca (o haciendo lo mismo claro, pero entonces ¡estás haciendo lo mismo!).

    Por ejemplo, es habitual en reports «grandes» realizar una consulta optimizada para saltarse la iteración de objetos ORM, pero es que eso mismo te pasa en muchas partes de la aplicación, otra cosa es que como no tienes «la otra» app para comparar, pues te parece que va rápido.

    Pero yo no me refería a eso, sino al hecho de que en tu modelo OOP no puedes desacoplar (¡vaya! también tiene acople) fácilmente los elementos «pesados» (ej. algo como usar Persona pero sólo poblar Nombre para no consumir recursos en campos que no usas y cosas así).

    Al final, es más rápido desarrollar usando ORM, pero eso no quiere decir que sea mejor (como producto, como estrategia de desarrollo está claro que en general, sale a cuenta).

  16. @josejuan, el problema es que estás pensando en usar el mismo modelo de objetos todo el tiempo. Como decía antes, hay veces que la mejor solución es tener varios modelos dependiendo de los casos de uso.

    Una práctica habitual es tener un modelo diferente para leer y otro para escribir. Cuando lees, tiras consultas optimizadas para cargar los datos que necesitas para tu ViewModel/DTO/lo-que-sea (primer modelo), cuando escribes es cuando cargas tu entidad (segundo modelo) y operas con ellas (más info aquí: https://blog.koalite.com/2011/12/tipos-de-repositorio-separacion-de-responsabilidades/)

    Probablemente no llegues a ir tan rápido como tirando un select a pelo a la base de datos pero la diferencia tampoco es tan grande: https://blog.koalite.com/2012/06/carga-de-datos-con-nhibernate-vs-dapper/, https://blog.koalite.com/2012/07/orms-vs-microorms-vs-ado-net-cuando-usar-cada-uno/

  17. uno que pasaba dijo:

    Yo creo que estamos divagando.

    Si hablamos de diseño OO, entonces no tiene sentido hablar de rendimiento (que no digo que no se tenga en cuenta) ni de si la abuela fuma. Si quieres obtener los beneficios de la OO entonces tienes que acomodarte y seguir los principios, patrones y técnicas de la OO (no hay más vuelta de hoja… y no creo que sea una locura usar orientación a objetos en un lenguaje orientado a objetos). Si lo que buscas es rendimiento pues habría que ver el caso concreto y meterse en los DSL (domain specific lenguages), o programación funcional, o en ensamblador… (me da igual)

    El tema de los ORM es también otra guerra y el problema está en la «impedance mismatch», por cierto algunos mapeadores como iBatis (MyBatis) el SQL va en archivos xml que luego son llamados por el mapeador. Pero casos a parte más info en: http://martinfowler.com/bliki/OrmHate.html

    Para evitar acoplamientos o dicho de otra manera para ganar en flexibilidad ( no depender de implementaciones concretas…) la OO se basa en meter abstracciones (los principios SOLID tratan de esto). Pero más flexibilidad implica más complejidad… así que el truco es meter flexibilidad donde se necesite… en las uniones de modulos, por ejemplo, o de capas físicas de la aplicación, o donde quieras maximizar la tolerancia al cambio o reutilización…. que de eso se trata la OO, como ya he dicho…

    Y luego teniendo en cuenta el hilo principal del post, iba a poner un ejemplo de lo que quería decir en mi primer comentario pero al final he encontrado esto: https://sites.google.com/site/unclebobconsultingllc/active-record-vs-objects

  18. El tema del rendimiento es importante, porque desgraciadamente, por muy bonito que sea, un modelo que no es eficiente no es un modelo válido en la mayoría de los casos, pero realmente no era (o no pretendía ser) el punto principal del post.

    Es curioso que menciones el caso de uniones entre módulos, porque precisamente es en esas uniones donde la mayoría de aplicaciones dejan de ser orientadas a objetos para permitir evolucionar cada módulo por separado, manteniendo cada uno de ellos un modelo más apropiado a sus necesidades concretas.

    Es el caso típico de partes de aplicación que se comunican entre sí mediante DTOs o información serializada. En esas fronteras los objetos de un modelo se mapean a meros contendores de datos que pueden ser a su vez ser rehidratados en objetos (muchas veces distintos) en el otro extremo.

    No creo que utilizar exactamente las mismas abstracciones en todas las partes de la aplicación contribuya precisamente a desacoplarlas, más bien al contrario. Si necesito una clase Customer para el módulo de venta online y también para el módulo de facturación, puedo mantenerlas por separado (y unirlas a partir de un identificador) o tratar de usar la misma. Si uso la misma, acabará mezclando responsabilidades de ambos módulos y cada vez que queramos cambiarla porque varían los requisitos de un módulo será necesario tener en cuenta cómo afectan esos cambios a todos los demás módulos que la utilizan.

    No creo que separarla en dos clases sea violar los principios SOLID ni de la OOP, sino elegir el ámbito en que aplican estos principos (el Bounded Context en terminología DDD) para particionar un sistema en componentes más manejables.

    En ese sentido, me gusta bastante el estilo de clojure que ponía @jneira al principio, que es muy típico en lenguajes dinámicos, y que consiste en aprovechar abstracciones genéricas del lenguaje (maps, objetos literales en JS, etc.) y mixins para integrar componentes. Así cada componente puede construir su modelo sin depender de los demás pero pueden pasarse fácilmente la información que necesiten entre ellos.

  19. uno que pasaba dijo:

    El tema del rendimiento es importante… si es tan importante como para dejar de lado la OO entonces no tiene sentido usar un lenguaje OO, aunque sólo sea en una parte de la aplicación.

    Para que lo entienda. Si tu quieres adelgazar y vas al medico a que te ponga una dieta, vuelves a los dos meses y en lugar de adelgazar has engordado… el medico te pregunta «¿pero has seguido la dieta?» y tú responde «no»…. pues ¿cómo pretendes conseguir los beneficios que buscas si no sigues la dieta que te los proporciona?

    Con la OO buscamos unos beneficios en el desarrollo del software… pero esos beneficios sólo podrán llegar si sigues la dieta de la OO. Ahora bien si en tu aplicación buscas otros beneficios entonces ya estamos en otra conversación que no es ésta.

    Cuando yo me refiero a puntos de unión en la aplicación yo me refiero a dentro de la aplicación… no a los puntos de integración con otras aplicaciones. Por ejemplo: tú puedes tener tu aplicacion separada en capas físicas modelo, vista, acceso a datos. El mapeador objeto-realacional te proporciona unas abstracciones para que puedas integrar lel acceso a datos con tú modelo, o entre la vista y el modelo hay ciertas abstracciones para mapear el modelo con la vista… los típicos frameworks MVC , como struts, etc… Sin estas abstracciones tendrías que programarte todas esas «jars» o «dlls» que usas en tu proyecto…puedes reutilizar todo eso gracias a las abstracciones.

    Que tengas una api REST donde serialices los datos que sean en un json y se lo mandes a otra aplicación donde los interprete y los recomponga… eso es una integración entre sistemas … y es otra cosa de lo que estamos hablando.

    El ejemplo que pones es el de una estructura de datos que tiene un significado diferente en diferentes partes de la aplicación por eso aunque tengan el mismo nombre no significarían lo mismo.(creo que la separación entre objetos, donde la clave es el comportamiento, y las estructuras de datos está claro) Otra cosa seria si pusiera como ejemplo un objetos con un comportamiento concreto, porque entonces estarías replicando funcionalidad… el tema de la OO de encapsular unos datos y exponer un comportamiento es para no replicar ese comportamiento … sólo tener que ir a un sitio para mantener esa funcionalidad (DRY).. al no exponer los datos (su implementación interna) no se propagarían los cambios en todos los sitios donde se usaran….etc, etc.. por todos los demás principios.

  20. Por seguir divagando…

    Una implementacion concreta puede ser mas flexible que una abstraccion concreta. Para pasar a las abstracciones genericas no hay que tenerle miedo a las matematicas.

    El peor desfase de impedancia esta entre la realidad, nuestra cabeza y nuestro programa. Incluido esto.

    «Simplicity is a great virtue but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better.» Dijkstra

  21. «Con la OO buscamos unos beneficios en el desarrollo del software… pero esos beneficios sólo podrán llegar si sigues la dieta de la OO. Ahora bien si en tu aplicación buscas otros beneficios entonces ya estamos en otra conversación que no es ésta.»
    En programacion seguir dietas de medicos a rajatabla no siempre es una buena opcion, aunque se llamen doctor fowler, doctor bob o doctor djisktra. Ni plantear opciones binarias en plan o lo coges o lo dejas. Esa forma de verlo tiene sus ventajas: es mas sencillo mentalmente por la uniformidad que se consigue, si esa vision uniforme es compartida por todo el equipo.
    Sin embargo tiene sus desventajas: es mas rigida y a veces se renuncia a una opcion mejor, mas simple en un contexto determinado por no introducir combinaciones entre blanco y negro. Otras opciones ni siquiera se tienen en cuenta, aunque sean sin mezclar debido a que la mente se acostumbra o siquiera conoce una forma determinada (la dieta de turno). Y me da igual llamarlo OOP, SOLID, patrones de diseño, que TDD que DDD que programacion funcional.

    «Cuando yo me refiero a puntos de unión en la aplicación yo me refiero a dentro de la aplicación… no a los puntos de integración con otras aplicaciones.»
    ¿Por que no tambien dentro de la «aplicacion»? ¿Por que no dividir la «aplicacion» en «aplicaciones» mas pequeñas, autonomas en lugar de capas acopladas? Por ejemplo hay aplicaciones que han pasado de tener la logica de presentacion en MVC entera en el servidor a pasar la parte de la vista (e incluso del control) al navegador y han convertido el backend en un servidor de json. ¿Es la misma aplicacion? ¿Lo era antes? ¿Son dos? ¿Que es una aplicacion?

    «el tema de la OO de encapsular unos datos y exponer un comportamiento es para no replicar ese comportamiento»
    Todos estamos de acuerdo en las ventajas del DRY. Pero hay muchas formas de hacerlo, no solo a nivel de las abstracciones concretas de los objetos. Por ejemplo puedes abstraer mas alto y mas simple, a nivel de funciones y sus combinaciones y usar solo tipos estandar para comunicar a los modulos. Por otro lado aunque sea un principio que hay que tener muy arriba en las prioridades no es el unico ni hay que conseguirlo a toda costa, sobre todo no a costa de la simplicidad.

  22. «uno que pasaba»,

    Por seguir el ejemplo de la dieta, el objetivo es adelgazar, no hacer dieta. Si para adelgazar es mejor mezclar la dieta con algo de ejercicio, no creo que el médico se lo tomase mal y seguro que al paciente le venía bien.

    Al final todo son herramientas para conseguir un fin, algunas son mejores que otras en determinados momentos, pero no veo por qué hace falta tomárselo como un todo o nada.

    @jneria,

    «a nivel de funciones y sus combinaciones y usar solo tipos estandar para comunicar a los modulos»

    O como diría tu amigo Alan Perlis, «es mejor tener 100 funciones que operan con una estructura de datos, que 10 funciones que operan con 10 estruturas de datos».

    Es otra forma de verlo, y a veces ayuda a conseguir diseños muy interesantes (me gustó mucho la forma de trabajar con ring/compojure).

    «El peor desfase de impedancia esta entre la realidad, nuestra cabeza y nuestro programa.»

    Muy cierto. Son dos impedancias distintas (conocimiento vs lenguaje) que van muy ligadas, aunque eso sí que sería entrar en un terreno bastante filosófico.

Comentarios cerrados.