Club Delphi  
    FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Principal > Varios
Registrarse FAQ Miembros Calendario Guía de estilo Temas de Hoy

Grupo de Teaming del ClubDelphi

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #21  
Antiguo 08-10-2015
Avatar de AgustinOrtu
[AgustinOrtu] AgustinOrtu is offline
Miembro Premium
NULL
 
Registrado: ago 2013
Ubicación: Argentina
Posts: 1.858
Poder: 15
AgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en bruto
[ Antes que nada quiero avisar que escribi un porron de post ]

Hola El Rayo

Vas encaminado, solamente me gustaria puntualizar unas cosas

Sigo pensando que el diseño no es lo mas flexible que podria ser. Yo no pensaria en un TCliente como una "clase que tiene los campos de las tablas con sus read y write". Vuelvo a remarcar lo que ya dije antes. Que representa para ti un cliente en tu negocio?. Recuerda lo que dijo Mario mas arriba: es como si fuera un simple y tonto record del Pascal antiguo. Simplemente contiene valores, y solo lo puede hacer en memoria. No sabe NADA de persistencia. A lo sumo podra hacer algun tipo de calculo sencillo pero nada mas.

El trabajo de persistencia es trabajo de otra clase. Porque? Sencillo, hay un "mantra" (principios SOLID) que dice que las clases deben tener solo una responsabilidad. Solo una mision. Nada mas. Si tienen que ocuparse de otra cosa, tenes en un 99% algo mal.

No hay que asustarse por tener un monton de clases (o mejor aun, clases implementando interfaces). Es un "error" comun en nosotros los desarrolladores mas nuevos; yo mismo a veces me pregunto a mi mismo: "tiene sentido crear la clase TPersona? si lo unico que tiene es una propiedad Nombre". La respuesta es SI, siempre. Hay que pensar en las clases como adolescentes vagos. Adolescentes tan o mas vagos como los de hoy en dia que no quieren ser responsables de nada, hacer lo minimo posible y lo mas facil y sencillo posible. Si tus clases son asi:

- El mantenimiento es mas sencillo. El impacto de cambiar algo es menor en todo tu sistema. Porque? Recuerda la simple clase TPersona de arriba que solo tiene un nombre, nada mas. Pues bien, las clases que usan a TPersona confian ciega y absolutamente que TPersona va a resolver un problema por ellos: darle el nombre de una persona!!!!!!!!. Y ese nombre puede venir de cualquier lado, puede haber un monton de codigo, complejos algoritmos de cifrado, etc detras de todo eso, pero no importa porque TPersona sabe hacerlo muy bien, verdad?

- Las clases son facilmente testeables. Imaginate tener que montar todo lo que necesita el framework solo para probar el funcionamiento "verdadero" del sistema. Ej, quiero probar una factura para ver como se imprime y resulta que: tengo que montar y configurar el gestor bd, montar la bd, cargar informacion de prueba. No seria mas sencillo poder hacer todo esto desde una app de consola? Si tu TCliente conoce una TABLA y los CAMPOS de esa tabla, como haces para separarlo de la tabla y poder probarlo? No hay manera

- Quiere decir que no solo podrias cambiar de motor de BD que estas usando; tu TCliente es tan poderoso que NO le importa de donde venga la data. Es algo muy sencillo que asusta. Tenes un servidor REST al que le pedis la data en formato JSON? A TCliente no le interesa. Eso es problema de una clase que se va a encargar de meter en TCliente la informacion correspondiente. Pero, una ves TCliente tenga su data, el va a saber calcular el Saldo o su Edad como lo hace siempre.

Otra cuestion interesante es el uso de interfaces. Hasta que uno no empieza a usarlas, no se da cuenta de su verdadero poder

La pregunta que todos nos hacemos en ese camino es: "pero cual es la diferencia??"

Código Delphi [-]
type
  TPersona = class
  private
    FNombre: string;
    procedure SetNombre(const Value: string);
  public
    property Nombre: string read FNombre write SetNombre;
  end;

Código Delphi [-]
type
  IPersona = interface
    function GetNombre: string;
    procedure SetNombre(const Value: string);
    property Nombre: string read GetNombre write SetNombre;
  end;

  TPersona = class(TInterfacedObject, IPersona)
  private
    FNombre: string;
    function GetNombre: string;
    procedure SetNombre(const Value: string);
  public
    property Nombre: string read GetNombre write SetNombre;

Repito, cual es la diferencia? El efecto logrado es el mismo, si. Bueno, al ahora descender de TInterfacedObject y no de TObject ganamos el reference counting y no tenemos que andar preocupandonos por manejar la memoria. Punto interesante.

El tema aca es el siguiente: hay, a grandes rasgos (no quiero hablar de patrones por ahora..) 3 formas basicas de crear un ecosistema de objetos

1. La clasica cadena de herencia. La herencia establece una relacion de "es un". Es decir, te dice de que tipo es el objeto

2. Composicion. Hay casos en los que usar herencia complica demasiado las cosas o no es deseable. Basicamente los casos en los que lo unico que cambia es "el tipo" pero en realidad estamos hablando de "lo mismo pero hecho con otras cosas". Un ejemplo muy clasico es el de la fabrica de pizzas: Imaginate tener, por cada combinacion de ingredientes, la clase de pizza que representa..

TPizzaBase, TPizzaQueso, TPizzaQuesoConSalchichas, TPizzaQuesoMantecosoConPapasFritas...

Entonces usamos composicion:

Código Delphi [-]
TPizza = class
private
  FListaIngredientes: TObjectList;
  FListaCondimentos: [...]

La composicion es como decir: este objeto esta hecho de estos otros objetos...es decir, agrupa a ciertos objetos que entre todos colaboran y hacen a la pizza

3. Interfaces. El caso de las interfaces, lo que cuesta tanto asimilar es, que demonios te dice una interface. El "problema" (mayor fortaleza) es que las interfaces es que no saben hacer nada. Solas no sirven para nada. Hasta que alguna clase las implementa, y puede ser una clase que cae dentro de los dos puntos anteriores. Entonces decimos: "vaya, aca tenemos un caso de herencia" porque vemos una implementacion PARTICULAR de esa interface. Lo que te dice una interface es que es lo que puede hacer una instancia.

No hay visibilidad (todo es publico). No hay "campos" (se pueden declarar propiedades pero nunca un atributo/variable). Cuando uno explora la declaracion de una interface lo primero que debe venirsele a la mente es: "Vaya, asi que esto es lo que puede hacer esta interface, que cool". No la nocion de su tipo, su herencia, etc.

De pronto, el ejempo anterior, en lugar de un IPerson deberiamos tener un INamed.. ya que esta interface sabe decirte tu nombre y nada mas. Que mas da que sea un TPerro, un TForm, un TColisionadorDeHadrones: Vos solamente vas a tener una referencia a INamed y solamente vas a poder llamar a su propiedad Nombre.

- Usa y abusa de Dependecy Injection. Que diablos es esto? Facil, nunca jamas dentro de una clase crees otra. Nunca. Jamas. En el 90 % es una muy mala idea. Si la clase que estas creando es una clase de tu dominio (es decir, no viene con Delphi) es en un 100% una mala idea. Porque? Porque eso quiere decir (dicho de la manera mas burda que se me ocurre) que si vas a la unit de la clase a la que creas, y borras la unit, o quitas la clase basicamente no podes compilar. Es decir, que la clase que crea a otra, tiene una dependencia FUERTE de otra clase. Esto es malo por varios motivos:

a) Como ya vimos antes, si alguien toca la clase en cuestion, puede afectar sobre tu clase
b) Que dijimos, que las clases solo tienen una responsabilidad? Crear una clase no es algo trivial. Y si el constructor falla? Y que es lo que sucede cuando se ejecuta el constructor? Se come un tocho de ram? Accede a disco? A un webservice? A una base de datos?
c) Complica el testing. Ya que siempre dependes de una implementacion (no de una abstraccion). Volvemos al ejemplo de montar el framework para probar

Dependecy Injection (DI de ahora en mas) es una sencillisima tecnica que soluciona los 3 problemas anteriores en un segundo: La clase en cuestion en vez de crear por si misma lo que necesita para funcionar (sus dependencias) lo pide de afuera. Muy facil de implementar. La mejor manera (y a mi gusto personal es la unica que le veo sentido, service locator me parece un anti patron en casi cualquier aplicacion y property/setter injection es incomodo) es mediante lo que se conoce como constructor injection. Es decir, tu clase pide en su constructor Create lo que necesita para trabajar. Ya que a ella solo le interesa unos pocos metodos de su dependencia. Tip: si lo que pides es una interface MEJOR.

Resumen DI:

a. No creas tus dependencias
b. Si necesitas algo PIDELO
c. Si pides una interface MEJOR
d. El constructor en el 90% de los casos debe ser simples asignaciones (lo que te enviaron para que uses mas adelante). Tambien es valido validar lo que te mandaron (si es nil lanzo una excepcion, etc)

Ejemplo practico

Código Delphi [-]
type
  IEncriptor = interface
    function Enctypt(const Value: string): string;
    function Decrypt(const Value: string): string;
  end;

  TXorEncriptor = class(TInterfacedObject, IEncriptor)
  public
    function Enctypt(const Value: string): string;
    function Decrypt(const Value: string): string;
  end;

  TGodModeEncriptor = class(TInterfacedObject, IEncriptor)
  public
    function Enctypt(const Value: string): string;
    function Decrypt(const Value: string): string;
  end;

Código Delphi [-]
  TPersona = class(TInterfacedObject, INamed)
  private
    FNombre: string;
    function GetNombre: string;
    procedure SetNombre(const Value: string);
  public
    constructor Create(const AName: string; AEncriptor: IEncriptor);
    property Nombre: string read GetNombre write SetNombre;

Bien, seguimos con la persona, y ahora vamos a encriptar su clave. Como lo hacemos: se lo pedmos a una interface. De esta forma en tiempo de ejecucion podemos incluso alterar facilmente el funcionamiento

Código Delphi [-]
var
  APerson: INamed;
begin
  if CheckBoxMaximumSecurity.Checked then
  begin
     APerson := TPerson.Create('Agustin', TGodModeEncriptor.Create);
  end
  else
  begin
    APerson := TPerson.Create('Agustin', TXorEncriptor.Create);
  end;

Imaginate como seria unit testing. Cuanto tiempo tardas en crear una clase TNotEncript que devuelva el mismo valor que se le manda (siempre y cuando en ese momento no te interese que el nombre este encriptado). A TPersona le importa? No, el va a llamar y confiar ciegamente en el metodo Encrypt

Con esto creo que si bien me fui mucho por las ramas te di un par de tips que te ayudan en algo (espero)

Saludos

Última edición por AgustinOrtu fecha: 08-10-2015 a las 03:46:46.
Responder Con Cita
  #22  
Antiguo 08-10-2015
elrayo76 elrayo76 is offline
Miembro
 
Registrado: ene 2004
Ubicación: En la tierra, por eso mis archivos en la tierra y no en la nuebe...
Posts: 291
Poder: 21
elrayo76 Va por buen camino
Cita:
Sigo pensando que el diseño no es lo mas flexible que podria ser. Yo no pensaria en un TCliente como una "clase que tiene los campos de las tablas con sus read y write". Vuelvo a remarcar lo que ya dije antes. Que representa para ti un cliente en tu negocio?. Recuerda lo que dijo Mario mas arriba: es como si fuera un simple y tonto record del Pascal antiguo. Simplemente contiene valores, y solo lo puede hacer en memoria. No sabe NADA de persistencia. A lo sumo podra hacer algun tipo de calculo sencillo pero nada mas.
La clase TCliente que mensioné no sabrá como se guarda en la base de datos. Como tu dijistes, tendrá solamete los datos que se le cargue a cada propiedad en memoria y será llamada para pasar esos datos a la base de datos o usar los datos que tenga. Esta clase en un futuro si es necesario se podrá ampliar con mas propiedades (estas propiedades representan los campos de la tabla)

Cita:
El trabajo de persistencia es trabajo de otra clase. Porque? Sencillo, hay un "mantra" (principios SOLID) que dice que las clases deben tener solo una responsabilidad. Solo una mision. Nada mas. Si tienen que ocuparse de otra cosa, tenes en un 99% algo mal.
Por eso mismo para la persistencia de datos es que he puesto una clase llamada TClienteServicio. Esta clase puede ser cualquier otra que me devuelva los datos de otro almacenamiento cualquiera (archivo, otro motor de BD) para llenar TCliente.

Cita:
Con esto creo que si bien me fui mucho por las ramas te di un par de tips que te ayudan en algo (espero)
Muchas gracias por todo. Algo similar es lo tenía en mente para desarrollar. Solo me queda ponerme a hacer algunas cosas y probar.

Saludos
__________________
Si tienes una función o procedimiento con diez parámetros, probablemente hayas olvidado uno
Responder Con Cita
Respuesta



Normas de Publicación
no Puedes crear nuevos temas
no Puedes responder a temas
no Puedes adjuntar archivos
no Puedes editar tus mensajes

El código vB está habilitado
Las caritas están habilitado
Código [IMG] está habilitado
Código HTML está deshabilitado
Saltar a Foro

Temas Similares
Tema Autor Foro Respuestas Último mensaje
Class Helpers sobre Genericos.. yapt OOP 1 24-04-2011 16:06:17
Procedures Genericos con Parametros de Controles.... Kenobi Varios 23 21-11-2007 00:42:41
Listbox con items genericos ANG4L Varios 2 11-05-2006 03:54:37
Parametros sql genericos AbcXxx Conexión con bases de datos 2 10-11-2005 00:31:59
... 100 tipos... Jure Humor 0 18-03-2004 14:24:30


La franja horaria es GMT +2. Ahora son las 17:36:31.


Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi
Copyright 1996-2007 Club Delphi