Ir al contenido



Foto

Dudas tecnicas sobre POO


  • Por favor identifícate para responder
22 respuestas en este tema

#1 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 389 mensajes

Escrito 09 marzo 2017 - 03:42

Hola de vuelta. Poniendome a programar me saltan algunas dudas sobre la orientacion a objetos.

 

Mi sistema es uno de facturacion accediendo a servicios de AFIP. Pero igual sirve para aprender ja.

 

En un sistema orientado a objetos todo debe ser realizado por objetos? Es decir debe ser un metodo de una clase?

 

Porque para consulta al padron pense codificar una clase TsolicitudREST, teniendo como atributo un CUIT.


delphi
  1. type
  2. TsolicitudREST = class
  3. private
  4.  
  5. Fidentificador: string;
  6. procedure Setidentificador(const Value: string);
  7.  
  8.  
  9. public
  10. property identificador:string read Fidentificador write Setidentificador;
  11. constructor nuevaSolicitud(identificador:string);
  12. procedure consultaPadron();
  13. end;

Entonces este objeto tendria el metodo de consulta al padron y mas adelante el de descargar la constancia de inscripcion..

 

A su vez crearia un objeto cliente, dado que si el CUIT ingresado no esta en la bd (no tenemos al cliente en la lista) se lo debe registrar, de lo contrario sumarle a su cuenta,(ademas de ser el cliente asociado a la clase factura).


delphi
  1. procedure TsolicitudREST.consultaPadron;
  2. const
  3. recurso='persona';
  4. version='v2';
  5. sistema='sr-padron';
  6. var
  7. obj,objdata,objdomicilio:TJSONObject;
  8. objsuccess:TJSONValue;
  9. url,localidad,provincia,direccion,tipoDoc,nombre,impuestos,nroDoc:string;
  10. NCliente:TCliente;
  11. response:IHTTPResponse;
  12. NetHTTPRequestPADRON:TNetHTTPRequest;
  13. NetHTTPClientPADRON:TNetHTTPClient;
  14. begin
  15. //crear objetos a usar
  16. NetHTTPRequestPADRON:=TNetHTTPRequest.Create(nil);
  17. NetHTTPClientPADRON:=TNetHTTPClient.Create(nil);
  18. //armar url del servicio
  19. url:='https://soa.afip.gob.ar' + '/' + sistema + '/' + version + '/' + recurso + '/' + Fidentificador;
  20. //asignar valores a componentes
  21. NetHTTPRequestPADRON.URL:=url;
  22. NetHTTPRequestPADRON.Client:=NetHTTPClientPADRON;
  23. try
  24. response:=NetHTTPRequestPADRON.Execute();
  25. //tomo el objeto JSON
  26. obj:=TJSONObject.ParseJSONValue(response.ContentAsString()) as TJSONobject;
  27.  
  28. objdata :=obj.Values['data'] as TJSONObject;
  29.  
  30. nombre:=objdata.Values['nombre'].ToString;
  31. // tipoDoc:=objdata.Values['tipoDocumento'].ToString;
  32. objdomicilio:=objdata.Values['domicilioFiscal'] as TJSONObject;
  33. direccion:=objdomicilio.Values['direccion'].ToString;
  34. localidad:=objdomicilio.Values['localidad'].ToString;
  35. provincia:=objdomicilio.Values['idProvincia'].ToString;
  36. nroDoc:=Fidentificador;
  37. impuestos:=objdata.Values['impuestos'].ToString;
  38. ShowMessage(impuestos);
  39. //creo el objeto cliente
  40. NCliente:=TCliente.NuevoCliente(localidad,provincia,direccion,tipoDoc,nombre,nroDoc,impuestos);
  41. except
  42. on E:Exception do
  43. ShowMessage(E.ClassName + E.Message);
  44.  
  45.  
  46. end;
  47. end;

No se si estoy enfocando bien..por ahi me da miedo publicar je

 

Saludos

 


  • 0

#2 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 770 mensajes
  • LocationArgentina

Escrito 09 marzo 2017 - 08:16

La forma correcta de hacerlo bien es "empezando al reves". En lugar de codificar la clase que implementa un servicio (donde servicio es: tarea, calculo, computo, trabajo, acceso a bd/webservice, osea todo lo que sea "trabajo interesante") lo que deberias hacer es empezar por la clase "cliente" o por la clase que consume el servicio. De esta manera estarias pensando "como quiero que sea el codigo ideal para hacer X?". 

 

Si lo queres ver de otra manera, te estoy diciendo que hagas Test Driven Development. TDD no es necesariamente usar un framework de testing, derivar de una clase especial, escribir los casos de prueba, correrlos en el runner hasta que todo este bien. Yo cada vez me estoy convenciendo de que la idea de TDD es excelente, pero los nombres fallan y confunden.

 

Cuando yo empece a programar, por alla en el ingreso de la facultad en donde usabamos Pascal clasico, hacia TDD sin darme cuenta. La idea es, como dije arriba, escribir el codigo que "consume" y luego verificar la salida. Quien no hacia eso cuando empezo a programar? Uno de los programas que teniamos como ejercicio era convertir una cadena de caracteres de minuscula o mayuscula (no recuerdo exactamente) y la forma en que yo empece a programarlo fue algo asi:


delphi
  1. var
  2. Entrada, Sallida, NuevaSalida: string[25];
  3. begin
  4. Entrada := 'abcdef';
  5. Salida := 'ABCDEF';
  6. NuevaSalida := ConvertirMayusculas(Entrada);
  7. if NuevaSalida = Salida then
  8. Writeln('Exito')
  9. else
  10. Writeln('La conversion fallo, el resultado es: ' NuevaSalida);
  11. end.

Osea, cada test en realidad es un requerimiento del sistema. Los que hacen TDD bien, te dicen cosas como "no se como podria escribir codigo si no empiezo por el test". Los test deberian ser llamados en realidad algo asi como "minima especificacion cohesiva e independiente que se puede ejecutar". Es por eso que no tiene sentido escribir test despues (al menos test de unidad). Una vez algun colega me dijo que los test son la traduccion a codigo de las historias de usuario (minutas en una o dos oraciones de un requerimiento del sistema, ej "como usuario quiero consultar el padron de AFIP por numero de CUIT y obtener los datos de esa persona")

 

Lo ideal en tu caso seria definir alguna clase abstracta o interfaz e ir escribiendo el codigo, de esta manera te queda mas flexible y ademas te obliga a pensar en abstracciones y no en implementaciones. En algunos casos yo intentaba primero realizar la abstraccion y la implementacion al mismo tiempo, pero el problema es que eso puede dar lugar a leaky abstractions, es decir, por mas que tengas una abstraccion, esta "forzando" una implementacion

 

En tu caso ya tu clase dice "REST" en el nombre, pero en otro momento los datos podrian venir de cualquier otro lado, o de un webservice SOAP

 

Yo definiria algo mas sencillo, asi:


delphi
  1. type
  2. // tambien me parece buena idea tener un tipo para el Cuit, ya que en tu dominio es un elemento
  3. // clave. Mas adelante te vas a encontrar con que el Cuit en ciertos lugares lo tenes que
  4. // mandar como "XX-XXXXXXXXX-X", en otros como "XXXXXXXXXXXX" (sin los guiones), y tambien
  5. // te sirve para implementar la logica de "cuit valido", osea, si en tu codigo tenes una instancia
  6. // de TCuit, te podes asegurar de que es un cuit correcto. Esto lo podrias validar en el constructor
  7. // de esta forma, es imposible que en el sistema exista una instancia de cuit invalida
  8. TCuit = record/class/interface { segun tu gusto }
  9. end;
  10.  
  11. TTaxpayerData = record
  12. // propiedades con la info de un contribuyente
  13. end;
  14.  
  15. // Taxpayer = contribuyente en ingles
  16. ITaxpayerService = interface
  17. function Query(const Cuit: TCuit): TTaxpayerData;
  18. end;


  • 0

#3 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 389 mensajes

Escrito 10 marzo 2017 - 02:46

Osea definiste una interfaz con una función de consulta al servicio, la misma devuelve un registro con todos los datos. A partir de ese registro crearía el objeto cliente? 

 

Las interfaces deben ser implementadas para ser usadas, en este caso podria la clase factura implementar ITaxPayerService, para obtener el objeto cliente asociado?


  • 0

#4 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 770 mensajes
  • LocationArgentina

Escrito 10 marzo 2017 - 03:10

Osea definiste una interfaz con una función de consulta al servicio, la misma devuelve un registro con todos los datos. A partir de ese registro crearía el objeto cliente? 

 

Las interfaces deben ser implementadas para ser usadas, en este caso podria la clase factura implementar ITaxPayerService, para obtener el objeto cliente asociado?

 

No seria lo mejor, porque una factura primero que no es un servicio para obtener datos de un contribuyente; y ademas porque la factura seguramente tenga que hacer otra cosa, lo cual te deja con un objeto con mas de una responsabilidad y SOLID te dice que todas las clases tienen que hacer una sola cosa


  • 0

#5 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 389 mensajes

Escrito 10 marzo 2017 - 03:18

No seria lo mejor, porque una factura primero que no es un servicio para obtener datos de un contribuyente; y ademas porque la factura seguramente tenga que hacer otra cosa, lo cual te deja con un objeto con mas de una responsabilidad y SOLID te dice que todas las clases tienen que hacer una sola cosa

Y cual seria un ejemplo de implementacion de esa interfaz?. No tengo tanto entendimiento como vos je perdon.

La clase solicitud que yo cree deberia utilizarla?


  • 0

#6 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 770 mensajes
  • LocationArgentina

Escrito 10 marzo 2017 - 11:32

Una implementacion es la que usa los componentes REST de Delphi para bajar los datos del padron de AFIP  (y)


  • 1

#7 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 13.551 mensajes
  • LocationMéxico

Escrito 10 marzo 2017 - 01:00

Una implementacion es la que usa los componentes REST de Delphi para bajar los datos del padron de AFIP  (y)

 

Tengo un asunto pendiente con eso de las interfaces del cual usted es experto amigo Agustín. (y)

 

Saludos


  • 0

#8 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 770 mensajes
  • LocationArgentina

Escrito 10 marzo 2017 - 01:27

Tengo un asunto pendiente con eso de las interfaces del cual usted es experto amigo Agustín. (y)

 

Saludos

 

Cuando gustes. No soy experto, pero ultimamente estoy leyendo mucho, desde libros, blogs, videos, en varios lenguajes (e incluso articulos "lenguaje-agnostico") y te abre bastante la cabeza. 

 

Por citar algunos:

 

http://misko.hevery....eviewers-guide/

 

Mark Seemann (ya lo he recomendado en algun otro post)

 

Blog: http://blog.ploeh.dk/

Libro: https://www.manning....tion-in-dot-net

Este seminario esta muy interesante: 

Esta serie de videos de codificacion en tiempo real muestra una sesion de "TDD" incremental: https://www.youtube....DIBYJQadh08i-D6

 

Nick Hodges

 

Blog viejo: http://www.nickhodges.com/Tiene cierto material interesante, pero hay que tener cuidado porque algunas cosas estan mal (el propio Nick lo ha admitido); pero los articulos que estan "mal" se pueden tomar como una referencia de "que no hay que hacer"

 

Blog nuevo: http://www.codingindelphi.com/blog/--> Aunque esta mezclado con otra clase de articulos, recuerdo haber leido algunas cosas interesantes de diseño

 

Canal de Nick en Youtube: https://www.youtube....ckHodges/videos

 

Libros de Nick: Coding in Delphi, More Coding in Delphi, Dependency Injection in Delphi

 

Estos webinars tambien son importantes:

 

https://www.youtube.com/watch?v=qqKx8fQTTfI

 

Este blog tambien esta muy bueno: https://simpleprogrammer.com/

 

Ni hablar de Uncle Bob

 

http://butunclebob.com/

http://blog.cleancoder.com/

 

o de Martin Fowler: 

 

https://martinfowler.com/

 

Luego obviamente hay que leer codigo escrito por otros

 

Buenas referencias en Delphi:

 

https://github.com/V...es/Delphi-Mocks

https://github.com/V...nologies/DUnitX

https://bitbucket.org/sglienke/

https://bitbucket.org/NickHodges/

 

C#:

 

https://github.com/A...ure/AutoFixture

https://github.com/ploeh/Albedo

 

Otros libros que aun no he leido pero creo que son muy importantes (ya me pondre al dia)

 

Michael Feathers - Working Effectively with Legacy Code

Uncle Bob - Agile Software Development, Principles, Patterns, and Practices

Uncle Bob - Clean Code

Uncle Bob - The Clean Coder

Martin Fowler - Patterns of Enterprise Application Architecture

Martin Fowler - Refactoring Improving the Design of Existing Code

 

Esos son los obligatorios; la eleccion de los autores no es al azar, a mi particularmente me gusta leer a estos tipos, no solamente porque saben lo que dicen (hay muchos de estos y probablemente mejores) pero a mi me gusta como lo dicen, porque me hace mas agradable la lectura, en un lenguaje bastante informal. Es similar a leer a Ian Marteens


  • 2

#9 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 13.551 mensajes
  • LocationMéxico

Escrito 10 marzo 2017 - 02:32

Cuando gustes. No soy experto, pero ultimamente estoy leyendo mucho, desde libros, blogs, videos, en varios lenguajes (e incluso articulos "lenguaje-agnostico") y te abre bastante la cabeza. 

 

Por citar algunos:

 

 

Wow!!!, mucho material, Gracias amigo ya hay mucho por donde comenzar. (y)

 

Saludos 


  • 0

#10 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.929 mensajes
  • LocationArgentina

Escrito 10 marzo 2017 - 05:23

Además de las sugerencias que te ha aportado Agustín, deberías enfocar tu diseño considerando el uso de patrón capas (Layers).

Si tu sistema es de facturación y que incluye soporte hacia la facturación electrónica de la AFIP, ésta actúa como un subsistema. Como tal, se esperaría una Fachada que ofrezca una interfaz adecuada para interactuar con WebServices de la AFIP. Internamente este susbsistema puede (y es lo más probable de esperarse) que contenga sus clases con las que interactuará la Fachada y delegará parte de su trabajo.

Este susbsistema estaría en una capa de Soporte, o de Servicio Técnico. Ya que justamente esto es lo que ofrece: una funcionalidad que luego desde capas superiores serán solicitadas.

 

Además, este subsistema no tiene porqué saber ni meterse a crear elementos que hacen al dominio. Por ejemplo, en tu caso, ¿Porqué TSolicitudREST debería de crear una instancia de la clase TCliente? ¿Porqué un método llamado "ConsultarPadrón" debería crear un TCliente?

¿Porqué te digo esto? Simple: por una cuestión de reducir acomplamiento y ganar en independencia. Como elemento de soporte, te permitirá que además puedas reutilizarlo en otros proyectos.

 

TSolicitudREST debería hacer trabajos REST a un WebService, y devolver en todo caso elementos vinculados, relacionados, y/o dependientes de esta tecnología. Por ejemplo, los TJSONObject que luego tu usas para armar tu cliente.

La responsabilidad de "materializar" esta información en formato JSON al contexto de la clase TCliente ya no es responsabilidad de este subsistema, debiera de serlo de la/s clase/s que son clientes y "consumen" y van a interactuar con este subsistema.

El Subsistema, podría ser llamado AFIPWebService. Y que la Fachada se encargue de encapsular el trabajo "sucio" de lidiar con todo lo interno. Deja visible y al alcance lo necesario para interactuar, y ofrecer los ementos esperados a su contexto a devolver.

 

Yo puedo sugerirte que emples y complementes tu A+D con un Diseño Dirigido por Contratos.

¿Que propone este paradigma? En palabras simples es redactar "contratos" de operaciónes. Lo que se escriba en él, debe cumplirse. Si el diseño y la implementación violan lo que figura en el contrato debe ser revisado ya que no es lo esperado, o bien que el diseño ha sido llevado de forma débil y no se ha podido preveer nuevas situaciones. Si es lo 2do, lo que ameritaría es volver a examinar los contratos a fin de actualizarse a los cambios.

Un contrato no es más que una redacción de lo que se espera de alguna operación. Describe las pre-condiciones que deben cumplirse para hacer algo; las post-condiciones o escenario final una vez efectuada la operación y las operaciones que deben efectuarse, paso a paso, para alcanzar los post-condiciones.

 

El enfoque dirigido por contratos es útil cuando tenemos pensado un diagrama de clases elementales, pero no tenemos en claro las operaciones o algunas relaciones y aspectos internos de éstas, ni estamos seguros de los límites o alcances de cada una. Escribir contratos nos ayuda a marcar los "estados internos". Al pensar en el "estado" inicial y final, estamos determinando límites claros. Decimos "Esto se tiene que hacer así". "Esto tiene que quedar así".

 

Por ejemplo, ¿Que cosas debieran de haberse dado para que sea posible efectuar ConsultarPadrón? ¿Y que cosas deberían haber cambiado y de esperarse cuando finalice ConsultarPadrón? ¿Y luego, que? ¿Que tiene lugar cuando finalice este método? Esto nos da un enfoque más "ACID". Nos ayuda a verlo como si fueran transacciones, operaciones atómicas.

Los contratos fueron pensados para bajar a tierra, poner ideas claras y límites exactos a cada operación, y en última en poder establecer los métodos a implementar.

 

Si te sientes que tus implementaciones son vagas, difusas, o no las tienes claras. Utiliza los contratos basándote en tu diseño para forzarte a un diseño basado a pruebas.

En cierto modo, TDD y Contratos lo que te dicen es: "Programa pensando en las pruebas, en los resultados esperados". ¿Que esperas de tu ConsultarPadrón? ¿Te parece que algunas cosas puedan hacerse fuera de éste?

 

Saludos,


  • 1

#11 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.929 mensajes
  • LocationArgentina

Escrito 10 marzo 2017 - 06:19

Sobre el tema de los Contratos, puedo recomendarte la lectura del capítulo 13 del libro UML y Patrones (el nombre completo es muucho más largo) de Craig Larman 2da Edición (en español).

Cuando quieras pasar de una abstracción a una visión más de implementación y donde los diagramas de secuencia te resultan difíciles, los contratos te ayudarán a detectar los cambios de una clase y sus límites.

 

Ojo: tampoco es para abusar. Hacer demasiados contratos más que la cura es ya un síntoma de la misma enfermedad: no tener en claro las interacciones entre las clases y que el diseño sea débil. La escritura de contratos está justificada cuando hay mucha interacción, muchas clases que cambian sus atributos, se crean o destruyen muchas instancias, y/o se rompen muchas relaciones.

 

Los contratos son para ir desde lo abstracto a la implementación. Complementan a los diagramas de Secuencia y a los Casos de Uso. Lo que no es fácil de ver en un CU o en un DSS, en el contrato resalta, ya que nos está imponiendo como han de hacerse las cosas para que salgan bien... o en todo caso, que si hay algo mal o si aparece un error este aparecerá más temprano que tarde.... y si es que aparece, lo mejor sería que si hay algo mal, que luzca mal. Esto último es una traducción de unas líneas de un artículo de Joel Spolsky. Vale la pena su lectura (si están un poco vagos, pueden leer su traducción).

 

Saludos,


  • 1

#12 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 770 mensajes
  • LocationArgentina

Escrito 10 marzo 2017 - 07:33

Si el diseño lo mantenes simple, implementando solo lo justo y necesario y nada más, las fallas de diseño salen solas

Es difícil arrancar penando en todo el sistema, como se conecta todo, pensar acá meto una fachada, etc

Lo mejor es escribir el codigo de manera que estos malos olores sean muy explicitos.

Por ej uno de los beneficios de constructor injection es que hace muy evidente las violaciones al principio de responsabilidad única.


http://blog.ploeh.dk...regateServices/

Este blog es en realidad un extracto del libro de Seemann donde explica cómo un mal olor inducido por dependency injection te ayuda a descubrir partes de tú dominio que no pudiste ver en tú análisis

Otra cosa importante es tener muy en cuenta los conceptos de SOLID, patrones del GoF, ley de Demeter, principio Hollywood, anti patrones

Un sitio bastante amigable para los patrones del GoF y anti patrones, y que además tiene ejemplos en Delphi (aunque a mí no me gusten mucho las implementaciones) es este https://sourcemaking.com/
  • 0

#13 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 389 mensajes

Escrito 11 marzo 2017 - 03:17

Bueno amigos..muchas gracias..

Gracias Agustin por todo el material.tengo que dedicarle tiempo..Y gracias Delphius por tus consejos tambien.

Creo que debo ahondar un poco mas y buscar un diseño mas apropiado.

Saludos


  • 0

#14 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 770 mensajes
  • LocationArgentina

Escrito 11 marzo 2017 - 05:12

Yo creo que no es del todo sano solo diseñar. Es necesario que escribas codigo. Al fin y al cabo lo mas importante es la experiencia, veo muy necesario "sufrir" los problemas de un diseño poco adecuado. Un mal diseño siempre puede transformarse en uno bueno usando refactoring para acercarse a algo mejor encaminado. La lectura es importante, pero la practica tambien


  • 1

#15 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.929 mensajes
  • LocationArgentina

Escrito 11 marzo 2017 - 07:55

Si el diseño lo mantenes simple, implementando solo lo justo y necesario y nada más, las fallas de diseño salen solas

Es difícil arrancar penando en todo el sistema, como se conecta todo, pensar acá meto una fachada, etc

Lo mejor es escribir el codigo de manera que estos malos olores sean muy explicitos.

Por ej uno de los beneficios de constructor injection es que hace muy evidente las violaciones al principio de responsabilidad única.


http://blog.ploeh.dk...regateServices/

Este blog es en realidad un extracto del libro de Seemann donde explica cómo un mal olor inducido por dependency injection te ayuda a descubrir partes de tú dominio que no pudiste ver en tú análisis

Otra cosa importante es tener muy en cuenta los conceptos de SOLID, patrones del GoF, ley de Demeter, principio Hollywood, anti patrones

Un sitio bastante amigable para los patrones del GoF y anti patrones, y que además tiene ejemplos en Delphi (aunque a mí no me gusten mucho las implementaciones) es este https://sourcemaking.com/

 

Cuando uno ya tiene algo de "cancha", se acostumbra a "ver" donde hay fachadas, fábricas, controladores, etc.

Es cierto que no es fácil "entrenar" la cabeza a pensar así a la primera. Es algo que se toma años.

A mi al menos me parece muy apropiado empezar con un diseño básico, hacer un poco de implementación, y volver a revisar.

 

Justamente sugerí abordar el tema pensando en el patrón Capas para que pudiera detectar que clases son realmente exclusivas del Dominio y cuáles son las que le van a dar soporte. Este primer abordaje le ayudaría a apreciar aquellas clases que estén demasiados acopladas y con un bajo valor de cohesión al haber mezclado intereses de 2 o más contextos. Si hay una clase que hace más de lo que debería, va a saltar cuando se plantee en que lugar meterla ;)

 

Eso de que los malos olores se hagan explícitos es algo relativo. Cuando uno comienza cuesta reconocer los diferentes "olores", y en detectar cuando uno es un aroma, y cuando va a oler podrido. En los primeros momentos uno no se da cuenta sino es hasta que es tarde.

 

Voy a darle una lectura a ese artículo. Tengo que admitir que el concepto "constructor injection" me es ajeno... o lo he olvidado o lo estudié y conozco por otro nombre.

 

Para introducirse en el mundo de los patrones, lo mejor es seguir el orden

1)GRASP:

Experto o Experto en Información

Creador

Bajo Acoplamiento*

Alta Cohesión*

Controlador

Polimorfismo

Fabricación Pura

Indirección

Variaciones Protegidas*

 

(*) Si bien estos conceptos per se, no suelen ser considerados patrones por algunos, varios estudiosos si les da carácter de patrón y cada vez hay más aceptación de éstos.

2) GoF:

Adaptador

Singleton

Estrategia

Factoría o Fábrica

Composite

Fachada

Observador

Proxy

Factoría Abstracta o Fábrica Abstracta

3) Otros:

Capas (Layers, en inglés)

Hacerlo yo Mismo

Etc...

 

En cierto modo, el resto de los patrones se conciben como una combinación de los GRASP y/o GoF o bien son especializaciones.

La ley de Demeter, el principio de Hollywood (aunque yo lo conocí por otro nombre, o mejor por su frase "No nos llame, ya le llamaremos nosotros"), el principio abierto/cerrado, y otros más son los lineamientos básicos que dan forma a los patrones, y se los suele engloblar a estos puntos como el patrón Variaciones Protegidas que mencioné antes. Aunque hay quienes lo han propuesto como un patrón aparte a cada uno.

 

Los anti patrones son conceptos muchos más "elevados" y se los puede dejar como tema secundario. No es tan prioritario interiorizarse en éstos.

Al sitio SourceMaking yo también lo he consultado muchas veces. Explica bastante bien, aunque sus ejemplos (al menos los de Delphi) son algo sencillos y pobres. En ocasiones prefiero consultar el sitio OODesign aunque no tenga ejemplos en Delphi.

 

 

Bueno amigos..muchas gracias..

Gracias Agustin por todo el material.tengo que dedicarle tiempo..Y gracias Delphius por tus consejos tambien.

Creo que debo ahondar un poco mas y buscar un diseño mas apropiado.

Saludos

 

No te preocupes si tu primer diseño no resulta como esperabas. No es un fracaso, es algo muy frecuente, es normal. incluso es habitual tener un diseño básico elemental que luego se desecha.

Ya me estoy acordando a una parte del libro UML y Patrones en donde en forma graciosa da un mensaje: "¿Tu diseño no está del todo perfecto desde el comienzo? ¡Felicidades!"

El paradigma de A+D en OO es cíclico, repetitivo, incremental. Hay que volver sobre los pasos.

 

Lo importante es controlar cuanto, y cuando hacer Análisis, Diseño, y/o Codificación.

 

 

Yo creo que no es del todo sano solo diseñar. Es necesario que escribas codigo. Al fin y al cabo lo mas importante es la experiencia, veo muy necesario "sufrir" los problemas de un diseño poco adecuado. Un mal diseño siempre puede transformarse en uno bueno usando refactoring para acercarse a algo mejor encaminado. La lectura es importante, pero la practica tambien

 

Por supuesto. En algún momento se tiene que implementar.

No creo que exista, ni que haya existido, un desarrollador que no sufriera. Todos aprendemos a los golpes... ni en pocos días. Llevo un poco más de 10 años en esto, y me animo a decir: ¡Nunca se termina de aprender! Muchos dicen que los 10 primeros años son la práctica, en los 10 siguientes es donde se forma el carácter.

 

Saludos,


  • 1

#16 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 770 mensajes
  • LocationArgentina

Escrito 11 marzo 2017 - 10:13

Otra cosa que podes hacer es publicar lo que vas diseñando para que otros critiquen. A mi no me molesta, a menos que me pidas revisar un sistema entero
 

Tengo que admitir que el concepto "constructor injection" me es ajeno... o lo he olvidado o lo estudié y conozco por otro nombre.

 
Advierto que voy a escribir un "libro" mas que un "post"  :)

Constructor injection es el patron dominante y preferido dentro de dependecy injection. DI es una serie de tecnicas y patrones que te ayudan con el "programa a una interfaz y no a una implementacion". Te ayuda a construir aplicaciones reduciendo el acoplamiento, facilidad de testeo, y tambien adquieres "late-binding". Late binding vendria a ser que la implementacion que va a resolver las tareas se decide en tiempo de ejecucion, y no en tiempo de compilacion
 
Una de las primeras cosas que enseñan en cursos orientados a objetos para principiantes son polimorfismo y abstraccion. Muchas veces acompañan con un fragmento de codigo asi:
 


delphi
  1. type
  2. TService = class abstract
  3. public
  4. function Execute: string; virtual; abstract;
  5. end;
  6.  
  7. // representa el programa principal
  8. TMain = class
  9. private
  10. FService: TService;
  11. protected
  12. property Service: TService read FService;
  13. public
  14. procedure PrintServiceResponse;
  15. end;
  16.  
  17. procedure TMain.PrintServiceResponse;
  18. begin
  19. Writeln(Service.Execute);
  20. end;

 
Si alguien preguntara "que codigo se ejecuta cuando se invoca al metodo Execute en el metodo TMain.Print?" la respuesta podria ser "no se", o "necesito mas informacion" o "depende del tipo de la instancia privada" y eso es correcto porque TService es una abstraccion y gracias al polimorfimo y a los metodos virtuales, es en tiempo de ejecucion que se decide que codigo se ejecuta. Esto es casi el "ABC" de OOP. El dispatching de los mensajes es dinamico; el receptor "sabe" que clase es, y sabe que codigo ejecutar; los emisores solo saben que metodos disponibles existen
 
Pero ahora, si yo te mostrara esta aplicacion:
 


delphi
  1. type
  2. TMain = class
  3. private
  4. FService: TService;
  5. protected
  6. property Service: TService read FService;
  7. public
  8. constructor Create;
  9. destructor Destroy;
  10. procedure PrintServiceResponse;
  11. end;
  12.  
  13. constructor TMain.Create;
  14. begin
  15. inherited Create;
  16. FService := TServiceImpl.Create;
  17. end;
  18.  
  19. destructor TMain.Destroy;
  20. begin
  21. FService.Free;
  22. inherited Destroy;
  23. end;
  24.  
  25. var
  26. Main: TMain;
  27. begin
  28. Main := TMain.Create;
  29. Main.PrintServiceResponse;
  30. end.

Esto es algo que parece natural ya que desde el punto de vista POO las clases son simples, tienen una sola responsabilidad, y ademas, las dependencia de TMain (consumidor o cliente) a TService (servidor) es de bajo acoplamiento porque TService es una clase abstracta
 
Sin embargo, ahora yo si podria responder cual es el codigo que se ejecuta porque se decide en tiempo de compilacion. Se va a imprimir lo que diga el metodo Execute de la clase TServiceImpl. Este hecho no puede cambiar a menos que cambie la clase TMain, ya que ella es la que esta creando la instancia interna. La unica forma de cambiarlo es modificando y volviendo a compilar.
 
Esto en realidad muestra que estas violando principio abierto/cerrado y tambien la clase Main esta haciendo trabajo que no deberia hacer (tiene mas de una responsabilidad), esta creando una instancia de su dependencia en el constructor, y tambien se hace cargo del tiempo de vida de la instancia; y ademas implementa el metodo PrintServiceResponse.
 
Para poder compilar la clase TMain necesitas el paquete o unidad en donde esta definida la clase TServiceImpl. Una clase que crea sus propias dependencias es una instancia de un anti-patron llamado "control freak", porque la clase "controla" o "autogestiona" sus dependencias.
 
Si quisieras crear un test de unidad para la clase TMain, estas obligado a usar la clase TServiceImpl, y no un objeto falso.
 
Osea que podria decirse que si ves la foto completa, en realidad perdiste todo polimorfismo y dinamismo, ya que todo se decide estaticamente
 
Otro problemon:
 
1. La clase TServiceImpl cambia su constructor por algo como 
 


delphi
  1. type
  2. TServiceImpl = class(TService)
  3. public
  4. constructor Create(const ServiceURL: string);
  5. function Execute: override;
  6. end;

Esto provocaria un error de compilacion. La clase TMain debe cambiar porque la clase TServiceImpl cambio. Ironico no? Pero si "mis variables de instancia, mis dependencias son abstracciones!". 
 
2. Aparece una nueva implementacion de TService, con un constructor que tiene parametros
 
De donde saca la clase TMain los parametros que necesita para enviar a estos constructores? Recibir todo en el constructor no es la solucion, ya que de presentarse 1) o 2) nuevamente, nos trae a la misma situacion y estariamos en un circulo vicioso
 
Esto ocurre porque estamos cumpliendo con la mitad del camino del "programa a una interfaz y no a una implementacion", porque nuestras dependencias son abstracciones (bien), pero hay un problema con las interfaces o las clases abstractas: no pueden ser instanciadas. Las interfaces no tienen constructores: deben ser implementadas; las clases abstractas podrian tener un constructor, pero si lo ejecutaramos, obtendriamos un error en tiempo de ejecucion. Parece que se llega a una encrucijada. En realidad, se debe aprender que los constructores son una especie de "mal necesario" porque: es la unica manera de tener una referencia valida a una abstraccion, esto nos lleva a que debe ser un metodo publico, estatico (o metodo de clase) pero terminan siendo un detalle de implementacion. A mi me importa un rabano que parametros necesita una abstraccion para implementar el servicio que yo requiero. 
 
Imaginate el ejemplo que yo te mostre, en donde en una aplicacion, toda las clases del modelo hacen lo mismo. Terminas "solucionando" todo con globales, singletons, porque necesitas informacion de "contexto" muy en las entrañas de la aplicacion (referencia a conexion a la bd, referencia a la clase para escribir en en log, referencia a la clase que implementa SSL, etc)
 
El patron constructor injection te dice que apliques inversion de control y reescribas el codigo de la siguiente manera:
 


delphi
  1. type
  2. TMain = class
  3. private
  4. FService: TService;
  5. protected
  6. property Service: TService read FService;
  7. public
  8. constructor Create(Service: TService);
  9. procedure PrintServiceResponse;
  10. end;
  11.  
  12. constructor TMain.Create(Service: TService);
  13. begin
  14. if not Assigned(Service) then
  15. raise EArgumentException.Create('TService es nil');
  16.  
  17. inherited Create;
  18. FService := Service;
  19. end;

Parece una ganzada pero lo que se gana es mucho. Lo primero es que la clase es mas sencilla:
 
1. El constructor ya no crea una instancia sino que "valida" la entrada y luego la almacena para usarla mas adelante. Esta validacion es importante, ya que de otra manera la clase se pondria en un estado "invalido", algo que debe impedir a toda costa. Como ya hablamos de las pre y post condiciones, la clase verifica que el emisor cumpla las precondiciones y luego te promete cumplir con las postcondiciones
 
2. No hay necesidad del destructor. La clase no tiene porque hacerse cargo del tiempo de vida de la dependencia. Ella no lo creo. La misma dependencia podria ser usada por otras clases. Destruirla es trabajo de quien la creo
 
3. Facilidad de testeo, ya que cualquier clase que implemente el contrato que manda TService es valido
 
4. Ganamos late-binding, polimorfismo y dinamismo nuevamente
 
Obviamente que el punto 4 en algun momento se "rompe" sino tendriamos el problema del huevo y la gallina. Toda regla tiene su excepcion. Es un caso similar a otro de los conceptos que se enseñan a principiantes en POO: "todo objeto esta ejecutando un metodo porque otro objeto le envio un mensaje".
 
Existe un "punto" en las aplicaciones, que Seemann define como "composition root". Este punto o lugar varia de lenguaje a lenguaje, y dentro de cada lenguaje, de framework a framework. Este punto deberia estar lo mas cercano posible al inicio de la aplicacion. Los framework por lo general (y sino, hay que rebuscarselas o forzarlo) proveen algun hook para que nosotros podamos escribir codigo de "wiring" o de "cableado". Es el lugar en donde se decide cual es el grafo de objetos que va a intervenir en esta aplicacion, es decir, en donde comienza lo del "huevo y la gallina" en dependency injection. (Por ejemplo en Delphi, en una aplicacion de consola, este lugar es la primer linea de codigo, en una aplicacion VCL podria ser el metodo Create del form principal, etc)
 
La idea entonces es programar las clases del modelo "teniendo DI en mente", esto es, cada clase pide en su constructor lo que necesite, y que se haga cargo el que me use de darmelo. Si todas las clases "empujan" para atras estos requerimientos, llegas a la composition root. Osea que si queres instanciar una version de tu aplicacion, lo unico que tenes que cambiar, es la composition root. De esta manera eliminas los problemas al cambiar un constructor o cambiar una implementacion por otra. Obviamente que no evitas cambiar codigo. Pero el codigo que cambias esta en la composicion.
 
El compositor de la aplicacion cumple con el siguiente rol: es el que decide que grafo de objetos debe crearse, como y cuando debe crearse, y tambien muy importante maneja los tiempos de vida. Esto es fundamental. Por ejemplo, si tenes un objeto "pesado", podrias estar pensando en un Singleton del GoF. Yo hoy por hoy hablo del "Singleton" y del "singleton". El que tiene S mayuscula ya lo conocemos todos, es el del libro de patrones. Un "singleton" es una clase de la que solo hay una instancia en la aplicacion. El compositor es el que decide que hay una sola instancia, cuando crearla y cuando liberarla. Los que consumen el singleton no saben ni deberian asumir nada, simplemente consumen una clase.
 
El tener un lugar centralizado en donde se gestiona todo esto da lugar a realizar "cambios grandes" modificando poco codigo. Un buen uso que se le da a este lugar es para aplicar decoradores, proxies, interceptores y muchas cosas mas. Un ejemplo un poco mas "realista", supongamos una clase que maneja registros de productos de una BD:
 


delphi
  1. type
  2. Producto = record
  3. Codigo: string;
  4. Descripcion: string;
  5. end;
  6.  
  7. IProductRepository = interface
  8. procedure Save(const Codigo, Descripcion: string);
  9. function SelectAll: TArray<Producto>;
  10. end;
  11.  
  12. TFakeProductRepository = class(TInterfacedObject, IProductRepository)
  13. private
  14. FProductos: TList<Producto>;
  15. procedure Save(const Codigo, Descripcion: string);
  16. function SelectAll: TArray<Producto>;
  17. end;
  18.  
  19. TSQLiteProductRepository = class(TInterfacedObject, IProductRepository)
  20. private
  21. FConnection: TSQLiteConnection;
  22. procedure Save(const Codigo, Descripcion: string);
  23. function SelectAll: TArray<Producto>;
  24. public
  25. constructor Create(Connection: TSQLiteConnection);
  26. end;

No muestro las implementaciones porque creo que son obvias: TFakeProductRepository mantiene una variable tipo lista sobre la cual va guardando los productos y a partir de la cual crea el array para responder al metodo SelectAll; TSQLiteProductRepository hara lo propio usando una conexion a una BD SQLite
 
Si en algun momento decido usar una u otra implementacion, solamente tengo que cambiar la composicion. Una implementacion no tiene dependencias, la otra necesita de una conexion SQLite. No quiero cambiar todas las clases que consumen la interfaz IProductRepository cada vez que cambio de proveedor de BD o me paso a "modo pruebas"
 
Pero hay mas:
 
Supongamos que una regla de negocio es que el codigo del producto no este repetido y que para usar la misma logica, metemos una clase abstracta comun, con un metodo CheckCodeIsAvailable que se invoca en Save, y un metodo CodeIsAvailable virtual
 


delphi
  1. type
  2. TAbstractRepository = class abstract(TInterfacedObject)
  3. protected
  4. procedure CheckCodeIsAvailable(const CodigoProducto: string);
  5. function CodeIsAvailable(Code): Boolean; virtual; abstract;
  6. end;
  7.  
  8. procedure TAbstractRepository.CheckCodeIsAvailable(const CodigoProducto: string);
  9. begin
  10. if not CodeIsAvailable(Code) then
  11. raise EPrimaryKeyViolation.CreateFmt('Primary key violation on %s', [CodigoProducto]);
  12. end;

Esto resuelve el problema, pero ahora supongamos que en la aplicacion de pruebas esto funciona "bien", pero en produccion, no queremos una excepcion sin mas. Queremos "capturarla" y en lugar de abortar, crear un log con la traza y enviarla a nuestro servidor FTP con reportes de bugs, o queremos mostrar un cuadro de dialogo que permite al usuario reintentar, o simplemente mostrar un cuadro de dialogo con un mensaje mas "amigable" y no esa cosa fea en ingles
 
Supongamos que tenemos en la cabeza: usar ShowMessage y cambiar el mensaje de la excepcion por otro, indicar que el codigo esta en uso, y darle foco a ese TEdit en donde se ingresa el codigo del producto
 
Como TSQLiteProductRepository es la clase que usamos en produccion, modificamos el codigo para hacer esto, capturando la excepction en algun lugar del metodo Save. Peeero, problemas, y estos son los problemas "clasicos" de la herencia
 
Que pasa si tengo tambien un TOracleProductRepository? Tengo que duplicar el codigo... O poner una clase comun a ambos.. pero luego TOracleProductRepository la usamos para los clientes premium, a los cuales les brindamos el servicio de "enviar los bugs al servidor FTP".
 
Este problema lo resuelve bien el patron decorador, y nuevamente, lo unico que tenemos que cambiar es la composicion:
 
Instanciacion en una aplicacion de consola simple:


delphi
  1. program TestApplcation;
  2.  
  3. // la funcion CreateProductRepository es la composition root;
  4. function CreateProductRepository: IProductRepository;
  5. begin
  6. Result := TFakeProductRepository.Create;
  7. end;
  8.  
  9. var
  10. ProductRepository: IProductRepository;
  11. begin
  12. ProductRepository := TFakeProductRepository.Create;
  13. // utilizar la interfaz
  14. end;

Instanciacion clientes "normales"
 
 


delphi
  1. program ClientesNormales;
  2.  
  3. type
  4. TShowExceptionsOnConsoleRepository = class(TInterfacedObject, IProductRepository)
  5. private
  6. FRepository: IProductRepository;
  7.  
  8. procedure Save(const Producto: TProduct);
  9. function SelectAll: TArray<TProduct>;
  10. public
  11. constructor Create(const Repository: IProductRepository);
  12. end;
  13.  
  14. procedure TShowExceptionsOnConsoleRepository.Save(const Producto: TProduct);
  15. begin
  16. try
  17. FRepository.Save(Producto);
  18. except
  19. on E: EPrimaryKeyViolation do
  20. Writeln('El codigo ' + Producto.Codigo + ' ya existe');
  21. else
  22. raise E;
  23. end;
  24. end;
  25.  
  26. function TShowExceptionsOnConsoleRepository.SelectAll: TArray<TProduct>;
  27. begin
  28. // nada especial aca, delegar
  29. Result := FRepository.SelectAll;
  30. end;
  31.  
  32. // la funcion CreateProductRepository es la composition root;
  33. function CreateProductRepository: IProductRepository;
  34. var
  35. SQLiteConnection: TSQLiteConnection;
  36. begin
  37. SQLiteConnection := TSQLiteConnection.Create('localhost', 'admin', 'admin');
  38. Result := TShowExceptionsOnConsoleRepository(
  39. TSQliteProductRepository.Create(SQLiteConnection));
  40. end;
  41.  
  42. var
  43. ProductRepository: IProductRepository;
  44. begin
  45. ProductRepository := TFakeProductRepository.Create;
  46. // utilizar la interfaz
  47. end;

Es importante notar que el compositor debe "conocer el bosque", es decir, conoce todas las implementaciones posibles y selecciona la mas adecuada o las conecta de la manera mas adecuada para que el resto pueda consumir
 
Para clientes premium:
 


delphi
  1. type
  2. // similar anterior, pero este muestra con ShowMessage
  3. TShowExceptionDialogRepository= class(TInterfacedObject, IProductRepository)
  4. private
  5. FedCodigo: TEdit;
  6. FRepository: IProductRepository;
  7.  
  8. procedure Save(const Producto: TProduct);
  9. function SelectAll: TArray<Product>; // delegado a instancia FRepository
  10. public
  11. constructor Create(const Repository: IProductRepository; edCodigo: TEdit);
  12. end;
  13.  
  14. // podria ser una buena idea tambien inyectar en este decorador la implementacion que se encarga
  15. // de enviar el reporte al servidor FTP
  16. TSendBugReportRepository = class(TInterfacedObject, IProductRepository)
  17. private
  18. FIndy: TIndy { no me acuerdo como se llamaba el componente indy para mandar al ftp }
  19. FRepository: IProductRepository;
  20.  
  21. // capturar excepcion, mandar al FTP y luego propagarla nuevamente
  22. procedure Save(const Producto: TProduct);
  23. function SelectAll: TArray<Product>; // delegado a instancia FRepository
  24. public
  25. // el constructor inicializa el componente Indy
  26. constructor Create(const Repository: IProductRepository; const DireccionFTP, Password: string);
  27. // destruir componente Indy
  28. destructor Destroy; override;
  29. end;
  30.  
  31. TMainForm = class(TForm)
  32. edFoco: TEdit;
  33. procedure FormCreate(Sender: TObject);
  34. procedure FormDestroy(Sender: TObject);
  35. private
  36. FOracleConnection: TOracleConnection;
  37. FProductRepository: IProductRepository;
  38. end;
  39.  
  40. implementation
  41.  
  42. // en el framework VCL, una opcion es hacer al Main Form la composition root
  43. // el hook mas indicado es el evento OnCreate del form
  44. // tambien es posible crear la composicion en el archivo .dpr y luego pasarsela al form mediante
  45. // algun metodo setter o propiedad
  46. // Esto es asi porque la VCL si o si va a invocar al constructor por defecto en el .dpr
  47. // en su famosa linea Application.CreateForm(TMainForm, MainForm)
  48. procedure TMainForm.FormCreate(Sender: TObject);
  49. begin
  50. // ip base de datos user pass puerto
  51. FOracleConnection:= TOracleConnection.Create('192.168.123.456', 'root', 'root', 1235);
  52.  
  53. // esto es equivalente al ejemplo anterior, estoy "anidando" decoradores
  54. // pero creo que de esta manera la sintaxis es mas simple
  55. // primero creo el repositorio
  56. FProductRepository := TOracleProductRepository.Create(OracleConnection);
  57.  
  58. // decorar con envio de reporte de errores
  59. FProductRepository := TSendBugReportRepository.Create(FProductRepository, 'ftp:\\blabla.com', 'password');
  60.  
  61. // decorar con mostar mensaje en una excepcion
  62. FProductRepository:= TShowExceptionDialogRepository.Create(FProductRepository, edCodigo);
  63.  
  64. // podria seguir agregando mas decoradores que implementen mas caracteristicas, por ej
  65. // TEncryptDataRepository, TCompressDataRepository, etc
  66. // tambien sacarlos cuando quiera, el resto de la aplicacion ni se entera..
  67. end;
  68.  
  69. procedure TMainForm.FormDestroy(Sender: TObject);
  70. begin
  71. FOracleConnection.Free;
  72. end;
  73.  
  74. procedure TShowExceptionsOnConsoleRepository.Save(const Producto: TProduct);
  75. begin
  76. try
  77. FRepository.Save(Producto);
  78. except
  79. on E: EPrimaryKeyViolation do
  80. begin
  81. ShowMessageFmt('El codigo %s ya existe', [Producto.Codigo]);
  82. FedCodigo.Clear;
  83. FedCodigo.SetFocus;
  84. end
  85. else
  86. raise;
  87. end;
  88. end;
  89.  
  90. procedure TShowExceptionsOnConsoleRepository.Save(const Producto: TProduct);
  91. begin
  92. try
  93. FRepository.Save(Producto);
  94. except
  95. // registramos todas las excepciones, no solo las de violacion de clave primaria
  96. on E: Exception do
  97. Indy.Send(ArmarTraza(E)); // o como sea que se mande al FTP :)
  98.  
  99. raise;
  100. end;
  101. end;

Quien se haya quedado con ganas de mas, le advierto que esto es solo la punta del iceberg  (y) Hay muchos mas beneficios a todo esto, entre ellos, el codigo es mas sencillo de mantener, entender, depurar, corregir, extender, testear, (estamos cumpliendo con la maxima "programar a una interfaz, no una abstraccion", para lograr bajo acoplamiento, recuerda :D )

 

A veces existen ciertos objetos que no se puede "empujar tanto" porque se requiere informacion en tiempo de ejecucion para resolver su instanciacion, y aqui es donde las fabricas entran en juego (las factory se usan muchisimo en aplicaciones DI)

 

Existen frameworks para esto (los famosos DI Container) que agregan mas posibilidades, por ejemplo, leer un archivo de configuracion (tipicamente XML o JSON) e invocar a RTTI para crear las instancias; otros directamente utilizan RTTI, obtienen la informacion de los constructores y usan un algoritmo recursivo, siendo que se pide un objeto de "tipo T":

 

1. Obtener parametros constructor de T

2. Para cada parametro, ir al punto 1 con el tipo de ese parametro

3. Si es un constructor sin parametros, crear objeto

4. Con todos los parametros recolectados, instanciar el objeto

5. Retornar

 

Otros tienen una API para configurarlos mediante codigo, otros te permiten interceptar (terminas aplicando programacion orientada a aspectos de manera trivial, el decorador y el proxy tienen algunos problemas, pensar que pasa si quiero utilizar el TShowExceptionDialogRepository sobre una interfaz ICustomerRepository?). 

 

-- 

 

Si se aplica esta logica a toda la aplicacion de pronto surgen cosas como:

 

Esta clase tiene un constructor con muchas dependencias (y aqui es donde aparece lo del articulo de arriba, el codigo revela una clase que hace de mas), o bien el "no viste" un subdominio del problema y crear una fachada o agregacion de un par de clases es trivial, ya que si, adivinaste, lo unico que cambias es la composicion, la fachada implementa la misma interfaz que la clase que lo consume pero te simplifica el diseño

 

Tambien revela problemas de diseño del estilo "estoy pidiendo en mi constructor dependencias que luego tengo que pasarle a mis dependencias" --> esto es similar a la ley de demeter

 

Otros problemas que saltan a la vista enseguida es violaciones al principio de substitucion de liskov (tengo que hacer X si me inyectaron la dependencia Y, o tengo que usar QueryInterface o Supports sobre una dependencia para ver si Z)

 

Te olvidas del singleton y de todos los problemas que acarrea: Como el "contexto" lo vas inyectando desde el principio, hacer disponible informacion "global" es muy facil

 

Si de pronto cambias la aplicacion a multihilo, todo lo que sea pool de objetos, de hilos, de conexiones a BD, todo es manejado desde la composicion: por ejemplo, se podria tener un diccionario que almacene claves estilo: ThreadID, ObjetoConexionBD e implementar un "singleton-per-thread"

 

En fin, hay mucho mas pero.. creo por hoy escribi lo suficiente  :D  :D


  • 1

#17 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.929 mensajes
  • LocationArgentina

Escrito 11 marzo 2017 - 10:59

Plaff Plaff Agustín. :ap: :ap:

Me quedo sin palabras, ¡Brillante!

Estuve leyendo algo de material sobre CI y DI en la tarde, y si uso sus ideas y conceptos. Quizá no lo llevo a demasiada escala y a grandes niveles, pero si le doy su utilidad práctica. Lo que no recordaba era el nombre formal.

Como a muchas cosas que hago medio de forma "automática", les fuí perdiendo el toque académico. Si abusara de CI y DI seguramente me recordaría más su nombre.

 

A ciertos anti patrones les dí una leída muy superficial (no recordaba que CI fuera uno). Eso lo admito. Si tuviera que leerme todo sobre éstos más los tantos patrones que me faltan (y encima cada vez se proponen más... ¿en cuanto está la cifra ya? Andábamos en el orden de 200) necesitaría otra vida. Ya he sufrido de patronitis, no quisiera sufrir de antipatronitis.

En ocasiones esto de patrones y anti patrones termina nublando las cosas, como bien dejas escrito cerca del final... en algún punto hay que parar.

 

Había leído un artículo de uno de los grandes gurús en el mundo del OODesign el cual había lanzado serias advertencias de lo mucho que estamos sobre abusando de los patrones (en cierta forma lo comparto: que se cuenten por centenares ya es una evidencia de que estamos creando un monstruo) y nos complicamos demasiados. Surgieron así los anti patrones, pero de nuevo, ahora están creándose anti monstruos.

Son advertencias que hay que tener presente. Desde ambos lados de la vereda, nos podemos contagiar y terminar complicándonos innecesariamente. ;)

 

En un momento en que tengas tiempo y ganas puedes pasearte por el foro POO en Delphi y complementar los manuales que en su momento escribí con temas como éstos. Tienes buen toque para la escritura técnica.

 

Saludos,


  • 1

#18 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 770 mensajes
  • LocationArgentina

Escrito 11 marzo 2017 - 11:19

En un momento en que tengas tiempo y ganas puedes pasearte por el foro POO en Delphi y complementar los manuales que en su momento escribí con temas como éstos. Tienes buen toque para la escritura técnica.

 

 

 

En algun momento lo pense, ya veremos :)

 

Por cierto

 

A ciertos anti patrones les dí una leída muy superficial (no recordaba que CI fuera uno

 

 

Si con CI te refieres a constructor injection este es EL patron, es la pieza mas fundamental, recomendable y de hecho deberiamos esforzarnos en utilizar siempre este y no sus variantes (method injection, property injecton o field injection).

 

Lo que trate de decir es que aplicarlo sistematicamente te revela el mal olor "esta clase hace mucho, pide 7 parametros en su constructor!". Pero esto no lo convierte en un anti patron en ningun momento.

 

Por supuesto que todo puede ser abusado. A mi me gusta considerar a los anti patrones tan importantes como los patrones. Es decir, las buenas practicas, porque son buenas practicas, cuando usarlas, cuando no usarlas, cuando no valen la pena y cuales son sus consecuencias, experiencias, anecdotas, etc. Y lo mismo para las malas practicas.

 

Me gusta cuando las cosas estan relacionadas, fundamentadas, y hacer X se hace para cumplir el principio Y que te permite Z.

 

No me voy a cansar de recomendar el libro de Seemann. Se lee bastante rapido porque hay bastante codigo que hace que el libro "tenga mas paginas", de hecho, se construyen aplicaciones relativamente grandes para tratarse de ejemplos. DI no tiene casi sentido para aplicaciones simples porque termina siendo mas complejo que la aplicacion; pero para aplicaciones complejas, es muy util porque te ayuda a lidiar con esa complejidad. Esto llevo a ejemplos "sofisticados" en el libro y no en cosas como las que mostre yo arriba en los que es totalmente valido preguntarse: "todo muy lindo, pero porque demonios tengo que hacer todo eso?" 

 

Tambien el libro explora como encarar o como encontrar la composition root en los distintos tipos de aplicaciones que se pueden construir con C#, desde escritorio, a servicios windows, consola, powershell, y no recuerdo que mas. Esto se puede saltear, pero yo recomendaria leerlo, por lo menos los que tienen cierto "analogo" con los de Delphi

 

Luego otro buen pedazo del libro esta dedicado al uso y analisis de distintos DI Container para C#, y esa parte se puede leer o muy rapido o ni leerla 


  • 0

#19 santiago14

santiago14

    Advanced Member

  • Miembros
  • PipPipPip
  • 285 mensajes
  • LocationCerrillos - Salta - Argentina

Escrito 12 marzo 2017 - 08:58

Que bárbaro, tengo mucho para leer y probar.
Estamos implementando software cada vez mas exigente y me parece que esto puede ayudar muchísimo a la calidad de los productos.
Gracias.


Enviado desde mi iPhone utilizando Tapatalk
  • 0

#20 Delphius

Delphius

    Advanced Member

  • Administrador
  • 5.929 mensajes
  • LocationArgentina

Escrito 12 marzo 2017 - 01:50

Agustín algunas referencias y materiales de consulta que estuye leyendo clasifican a Constructor Injection como mecanismo de anti patrón, por eso mencioné dicha curiosidad ya que me resultaba extraño que lo fuera.

Este patrón junto con otros "Injection" forman al IoC, que es el última quien debería ser catalogado como el Patrón. Al menos es mi opinión personal.

 

¿Porqué digo eso?

Porque ya he visto ese mismo "esquema concepual" en otros principios y patrones. Bajo Acoplamiento y Variaciones Protegidas por mencionar otro ejemplo, tienen la misma "estructura conceptual": Bajo Acoplamiento es tanto un principio como un Patrón, y a su vez forma parte parte (junto con otros patrones y principios más) del Patrón Variaciones Protegidas.

Si bien no es una apreciación exacta, para que se hagan una aproximación y una idea, IoC y Variaciones Protegidas actúan como Patrones de Patrones... si se me permite el abuso literario: son Meta-Patrones (Aunque a mi ver no vendría mal emplear este concepto para que se los pueda diferenciar y entender. He leído alguna vez en algún lado que se ha propuesto este concepto de meta patrón)

 

Tendré que darle una lectura a ese libro que dices. Ya es hora que amplíe mi repertorio de material sobre patrones, lo vengo pateando desde hace un buen tiempo... me digo este mes me pongo al día pero ni hay caso. No logro sacarme el tiempo para buscarlo con tranquilidad... podría buscarlo en la biblioteca del Consejo Profesional, o en la pública. No se si lo lograré encontrar en alguna librería, pero primero, a ahorrar plata.

 

Saludos,


  • 0