El mundo al revés

marzo 8, 2011 en Advertencia, Delphi, Enlace interesante, Entrada Diario, Nos deja la semana...

Durante el fin de semana, como casi siempre hago, dejé varios apuntes que me parecían interesantes señalados en la barra de favoritos. Muchos de esos apuntes, o enlaces, son páginas  cuyos blogs ya existen en los enlaces de Delphi Básico. Otros, son simplemente pág¡nas en las que cae uno de forma accidental tras revisar los twitters nuevos o los enlaces resaltados de cualquier otra aplicación social. De una forma u otra, la idea es  tenerlos a mano y compartirlos, bien en el blog, bien en facebook o twitter.

Tras escribir el comentario sobre el Hotfix que ya tenemos disponible, me quedaban resaltar algunos de esos apuntes de la semana que me parecían interesantes.

El primero a destacar podría ser la calidad e interés que descubren los últimos artículos de German (Neftalí y en general, todos los que se recopilan en su blog):

A me ha parecido particularmente util y didáctico el último, que aborda el tema de generar los puntos de ruta en Google Maps Generar ficheros KML de rutas; Tracks en Google Maps, pero igualmente podía haber destacado el anterior en el que muestra sus busquedas preferidas respecto a íconos (tema realmente importante para mejorar el aspecto de nuestras aplicaciones), la ultima actualización de las RX para XE o finalmente, por no alargar la lista mas, la descarga de imagenes a traves del componente TidHTTP (TidHTTP; Descargar imágenes de una Web). Germán está siguendo una linea donde destaca la regularidad y la constancia, asi como la claridad en la exposición de los temas.

El segundo punto a destacar, creo que podría ser el enorme esfuerzo de difusión de tips y consultas de foros que se está abordando por parte de los compañeros de la Comunidad de Delphiaccess, que se están volcando por hacer públicas muchas de esas respuestas que solucionaban problemas (también a través de distintas redes sociales). Enfoques nuevos para desafios nuevos… Me gusta la idea de alejarse de esa visión estática de los foros, tan tradicional, que han buscado principalmente en los foros una forma sencilla de incrementar las visitas al dominio y poco mas. Así que, punto arriba (+1) para DelphiAccess en ese intento de convertirse en referencia para nuestra Comunidad.

Para el tercer punto, veo anotado este enlace http://delphibugs.blogspot.com/, que visité durante el fin de semana. El blog se centra en describir algunos bugs que el autor ha destacado, siendo un blog muy reciente, ya que los artículos escritos son del año anterior.  Pero bueno, la fecha no es siempre indicativo de que sea realmente interesante o no lo sea, aunque tambien es verdad siendo realistas que muchos de estos blogs nacen y desaparecen con la misma rapidez, una vez se ha satisfecho el deseo irrefenable del comentario. La última entrada http://delphibugs.blogspot.com/2010/09/xe-new-bug-1-wild-memory-eating.html nos comenta de forma visual incluso, como se dispara el consumo de memoria al ejecutarse el Code Insight (si se encuentra activo ErrorInsight).

Bueno… yo lo he intentado reproducir siguiendo los mismos pasos que él, para ver si era cierto o no y en que condiciones se producía y comentarlo aquí, pero a pesar de todas las pruebas no he podido, no ha habido forma de comprobar la veracidad del comentario, por lo que para mi, de momento lo dejo como caso cerrado. Lo que si se le puede reprochar, es que este tipo de anotaciones o entradas, deberían ir acompañadas de algun dato que identifique la versión sobre la que se reproduce el error (yo no lo he encontrado por ninguna parte). Le supongo la buena fe de la publicación pero sirve de poco fuera del contexto del QC.

Seguimos nuestro camino con un blog (amor/odio a Delphi al 50%) http://delphihaters.blogspot.com/, dentro de una publicación que entra como un elefante en una cacharrería a despachar sobre todo lo humano y lo divino, especialmente beligerante en lo que afecta a la ruta de Embarcadero.  A mi particularmente me ha parecido interesante su lectura por el hecho de suponer un contrapunto con las voces que siempre escuchamos en nuestro entorno, lo cual no significa que yo asienta o disienta del contenido expresado en su blog. No obstante, Delphi Básico se ha caracterizado siempre por intentar ser respetuoso con otras opiniones, razón por la que pensé que debía resaltarlo. 

El cuarto punto, ya para finalizar la entrada, es el que hace referencia al post An ugly alternative to interface to object casting de Barry Kelly, ingeniero de software que trabaja para Embarcadero en el compilador de Delphi. Al inicio de la entrada, se puede leer su motivación al escribirla:

I was answering a question on Stack Overflow, but the user didn’t have the latest version of Delphi. My answer included converting an interface to an object instance, which is made possible with the as cast on interfaces in recent Delphi versions. But there is another way of doing it, exploiting the regularity Delphi interface vtable implementations

Es decir, que esta entrada venía a extender algunas respuesta en Stack Overflow que relataban como acceder al objeto que implementaba la interfaz, que se habían centrado básicamente en el uso del operador as.  He remarcado But there is another way of doing it, para que se vea claramente que lo expuesto en la entrada y que a mi, personalmente me pareció muy interesante, es que la via alternativa que analiza, se basa en un conocimiento muy cercano (íntimo) de la estructura de la vtable con los métodos del objeto e interfaces que implementa.

Ese tipo de lectura siempre me fascinó porque desmonta el entramado del escenario y te muestra los bastidores en contrapunto de los articulos que muestran solo la parte externa del “artilugio”  :-D    Así que por unos minutos me volvía sentir como Indiana Jones en busqueda del misterioso tesoro y látigo en mano fui recorriendo el código para comprenderlo. Para ampliar información y definitivamente comprender el articulo, si os sucede como a mi que dejó algunas inquietudes, os recomiendo la lectura de Delphi interfaces on binary level de ksTools, que fue respuesta al articulo de Barry Kelly.

Quizás el título de mi entrada vino inspirado por el artículo de Barry Kelly. Pero eso es menos interesante…  ;-)  

Anotad el enlace a Stack Overflow, los que no lo teníais, ya que hay muchas respuestas y preguntas dirigidas y respondidas por expertos y gurus de la Comunidad. Un enlace muy interesante recomendable 100%.

Y por esta semana, poco mas que contar.

Simplifica tu código Delphi…

marzo 30, 2010 en Código, Consejo, Delphi, Enlace interesante, Entrada Diario

Simplifica tu código, piensa en clases, abstrae y racionaliza, usa el sentido común, etc… son algunos de los lemas que hemos podido compartir durante muchos de los artículos anteriores, casi desde siempre, con mas o menos acierto. Valga la redundancia, casi diría que en realidad, es una preocupación cuasi universal que nos corroe, a medida que avanzamos y aprendemos y nos formamos. También de alguna forma, exteriorizamos esos pensamientos en muchos de los post que acabamos publicando.

Nuestro punto de parada hoy, es nuevamente el blog de Stefaan Lessage, y la parada es para compartir cuatro artículos que ha escrito durante el mes de marzo y que pienso que forman parte de esa idea general que siempre hemos intentado plasmar: pensar en clases y abstraer. Pienso que la lectura de las cuatro entradas de Stefaan es muy aconsejable  y si bien, puede resultarnos mas o menos incomodo que esté escrita en otro idioma (ese punto ya depende de cada uno), existe el suficiente código para que pueda entenderse el trasfondo y la enseñanza que aporta. Sobretodo, os la aconsejo si os estáis iniciando en el entorno y buscais patrones de razonamiento que os sirvan de referencia en vuestros desarrollos.

La serie se titula, como no, “Simplify your Delphi Code using some basic rules, OO techniques and some refactoring“. Mas o menos, “Simplifica tu codigo Delphi usando algunas reglas básicas, tecnicas orientadas a objetos y refactorización”.

Parte 1

En la primera parte de la serie, Stefaan Lessage presenta el problema o la cuestión. Es un capítulo introductorio y breve, en el que inicia su reflexión imaginando una necesidad que suele darse con frecuencia y que consiste en retomar código escrito años atrás para su revisión. Puede que nos toque enfrentarnos a código difícil de leer o entender y que requiera un nuevo enfoque para facilitar en un futuro su mantenimiento.

Al hilo de mostrarnos las ventajas, Stefaan se centra con un ejemplo que puede ser representativo de esa ganancia y que va a servir de eje sobre el que van a girar el resto de los artículos de la serie. Personalmente, creo que ha elegido un ejemplo que recoge una necesidad básica en cualquier aplicación: guardar y cargar datos de personalización. Y como imagináis, casi siempre van a estar implicados o bien archivos ini o el registro, ficheros xml, etc.

Parte 2

La segunda parte, una vez planteada esa introducción, se nos muestra algo de código. Código que podría representar a esas lineas que deberíamos revisar, bajo un nuevo enfoque al hilo de la programación orientada a objetos. Siguiendo con su ejemplo, muestra varios procedimientos que permitirían desde los eventos de creación y destrucción del formulario, cargar y guardar los valores de personalización. Lógicamente, junto con estas lineas de código se nos presentan las desventajas de estas decisiones y los problemas que pueden suponer en distintos escenarios.

Parte 3

Aquí ya entra Estefaan Lessage en el primer acercamiento a resolver el problema desde la perspectiva OOP. Para ello, crea una clase base que permita tratar distintos tipos de datos de forma unificada. Así pues, la clase TdvSetting realmente no hace nada mas que preparar el camino. De hecho, los metodos Get/Set se limitan unicamente a generar una excepción, puesto que ella no sabe realmente como debe responder a dichos métodos. Será sus descendientes los que sobrescriban los métodos adecuados al contexto de la aplicación, creando código especifico.

Parte 4

Y ya la parte cuarta, finaliza la serie. En este artículo se define la clase TdvStringSetting a modo de ejemplo, descendiente de TdvSetting, y se nos proponen nuevas clases descendientes (TdvInteggerSetting, TdvBooleanSetting, etc.) y una clase adicional TdvSettings, descendiente de TObjectList, al modo de contenedor. Fijaros en un detalle importante, esta lista de objetos, recibe como parámetros en su funciones y procedimientos la clase base, jugando de esa forma Steffaan Lessage con la herencia y el polimorfismo (si no recuerdo mal a este tipo de polimorfismo se denomina como Polimorfismo de subtipado o de inclusión).

Creo que vale la pena que perdáis unos minutos y reviséis el código ya que este tipo de razonamientos pueden aportar un valor añadido al código que escribimos. No es tan importante la cantidad de lineas que uno puede abarcar en una jornada frente a que éstas garanticen que pueda ser revisadas meses después al aire de un nuevo contexto o en respuesta de nuevos requerimientos. Calidad o cantidad… Siempre debemos “aspirar” a la calidad.

Comparto plenamente lo escrito por Stefaan en la introducción

Think before you start writing your first line of code…

Una verdad como un templo.

Experimentos, reflexiones y otros artefactos (y V)

junio 14, 2009 en Artículos, Código, Consejo, Delphi, Entrada Diario, Sintaxis

He preferido que esta quinta entrega cerrara el título “Experimentos, reflexiones y otros artefactos”,  y despedir estos mini artículos o mini serie, si es que se pueden llamar así, con un ejemplo un tanto más avanzado, pero en la linea de los anteriores. Así que me habeis tenido aperreado toda la semana, dándole vueltas a la cabeza sobre cómo iba a finalizar estas entradas. :-)

No. No creais que es sencillo elegir el “experimento” ya que debe cumplir a priori algunas condiciones en cuanto a la extensión, a su complejidad, al uso de componentes que puedan ser compatibles en varias versiones y casi siempre en cuanto a que sea verdaderamente didáctico. Por lo que no vale cualquier idea que te venga a la cabeza, sino que tienes que pelearte con ella y ver si realmente te vale. Y os confieso cuando uno llega a casa tras la jornada diaria quedan pocas ganas de pelearse con nada (os pasará tambien a vosotros casi con seguridad).  :-)

En fin… Yo creo que sí que vale la pena el esfuerzo, y creo muchos de vosotros lo agradecereis.

¿Teneis ganas de trabajar un rato?

Venga, vamos allá.

Este era el ejemplo que se me ocurrió:  Imaginaba que pudieramos tener la necesidad de calcular el modo más óptimo de recorrer una cantidad de puntos indefinido, eso sí, recorriendolos todos, desde un origen cualquiera hasta un destino cualquiera. Esa era la primera idea que me me vino a la mente. La idea además era vistosa, porque podíamos dibujar los puntos y las lineas sobre el formulario, lo cual no era demasiado difícil. Además, nos permitía seguir abordando el uso de clases muy básicas como las listas de punteros (TList), que muchos compañeros que dan sus primeros pasos pudieran no estar demasiado familiarizados.

El problema es que tras esa idea, existe una complejidad inherente que puede ser tan grande como uno quiera, por lo que decidí intentar abordarlo de una forma sencilla, aun a pesar de que pueda no ser optima. Y el criterio que se sigue para ordenar el camino que nos conduce desde el nodo (rojo) hasta el nodo (azul) sigue una pauta que se rige en calcular la menor distancia con respecto al nodo anterior, una vez ordenado el vector.

De hecho, si os fijais he puesto lineas mas arriba “¿Teneis ganas de trabajar un rato?” y lo he hecho porque os dejo para vosotros si quereis adaptarlo para que no considere todos los nodos sino el camino mínimo (fijado previamente una numero de puntos por los que haya que pasar)  y no considere en la ruta todos, sino esa cantidad.

Al crearse el formulario, recrea una cantidad de nodos (he puesto  10 por poner una cifra) en puntos aleaotorios de un recuadro del formulario. El punto rojo es la salida (es otro nodo pero no es del vector). El punto azul el destino(tambien otro nodo que no existe en el vector). Ambos puntos, se enlazan a cada uno de los nodos del vector simplemente para que se pudiera ver una idea que voy a comentar posteriormente (*).

Esta es un imagen del formulario:

Asi que, como comentaba en lineas anteriores, vamos a recorrer el vector de nodos y reordenarlo sucesivamente hasta que se cumpla la precondición, que es que la ruta quede ordenada en función de la distacia al nodo que nos sirve de pivote, que va recorriendo la ruta que parte desde el punto de origen. Iniciamos en el punto de salida y buscamos el nodo mas cercano.  Luego el más cercano a este. Y asi sucesivamente hasta llegar al punto de destino. En la imagen que figura tras el código se puede apreciar bastante bien.

Vamos a ver el módulo que hemos escrito para expresar esta idea:

unit UNodo;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, Math;

type
   TNodo = class;
   TLinea = class;

   TOrdenacion = procedure(Sender: TObject; ANodo:TNodo) of Object;

   TMalla = Class(TObject)
   private
    FPivote: TNodo;
    FLineas: TList;
    FNodos: TList;
    FZona: TForm;
    FSalida: TNodo;
    FDestino: TNodo;
    procedure SetSalida(const Value: TNodo);
    function GetLineas(Indice: Integer): TLinea;
    function GetNodos(Indice: Integer): TNodo;
    procedure InternalOrdenaNodos(Sender: TObject; ANodo: TNodo);
    procedure VaciarLineas;
    procedure VaciarNodos;
    procedure DibujaRuta;
    function CalculaMetricaRuta: Double;
   protected
    procedure DoOrdenaNodos(ANodo: TNodo; AOrdenacion: TOrdenacion); virtual;
    procedure DoDibujarMalla; virtual;
    procedure OrdenarNodos(ANodo: TNodo; ADestino: TNodo);overload; virtual;
   public
    FCambios: TStrings;
    Constructor Create(AForm: TForm); virtual;
    Destructor Destroy; override;
    procedure InicializarMalla;
    procedure OrdenarNodos; overload;
    Function CountLineas: Integer;
    Function CountNodos: Integer;
    procedure ActualizarMalla;
    function AddNodo: Integer;
    function AddLinea: Integer;
    function GenerarRuta: Double;
    property Salida : TNodo read FSalida;
    property Destino: TNodo read FDestino;
    property Lineas[Indice: Integer]: TLinea read GetLineas;
    property Nodos[Indice: Integer]: TNodo read GetNodos;

   End;

   TLinea = Class(TObject)
   private
    FMalla: TMalla;
    FAlfa: TNodo;
    FOmega: TNodo;
    FDistancia: Double;
    FIndice: Integer;
    procedure SetAlfa(const Value: TNodo);
    procedure SetOmega(const Value: TNodo);
    procedure SetIndice(const Value: Integer);
   protected
   public
    function GetDistancia: Double;
    Constructor Create(AMalla: TMalla); virtual;
    Destructor Destroy; override;
    procedure Enlaza(AAlfa,AOmega: TNodo); overload;
    procedure DibujarLinea;
    property Alfa: TNodo read FAlfa write SetAlfa;
    property Omega: TNodo read FOmega write SetOmega;
    property Indice: Integer  read FIndice write SetIndice;
   End;

  TNodo = Class(TObject)
  private
    FMalla: TMalla;
    FShape: TShape;
    FPoint: TPoint;
    FPeso: Integer;
    FTag: Integer;
    function GetX: Integer;
    procedure SetX(const Value: Integer);
    function GetY: Integer;
    procedure SetY(const Value: Integer);
    procedure SetPeso(const Value: Integer);
    procedure SetTag(const Value: Integer);
   protected
   public
    function GetDistancia: Double;
    function GetIndice: Integer;
    function AddLinea(ANodoTarget: TNodo): Integer;
    Constructor Create(AMalla: TMalla); virtual;
    Destructor Destroy; override;
    procedure DibujarNodo;
    property X: Integer read GetX write SetX;
    property Y: Integer read GetY write SetY;
    property Peso: Integer read FPeso write SetPeso;
    property Distancia: Double read GetDistancia;
    property Tag: Integer read FTag write SetTag;
  End;

implementation

function CalcularDistanciaNodos(AAlfa, AOmega: TNodo): Double;

        //Necesitamos dos puntos (x1,y1) (x2,y2)
        // [x1,y1] [x2,y2]
        // Formula Raiz de ( ((x2-x1) al cuadrado) + ((y2-y1) al cuadrado) )

      function CalcularDistancia(X1, Y1, X2, Y2: Integer): Double;
      begin
         Result:= SQrt(Power((X2-X1), 2) + Power((Y2-Y1), 2));
      end;
begin
  if (AAlfa = Nil) or (AOmega = Nil) then Result:= 0
  else Result:= CalcularDistancia(AAlfa.X, AAlfa.Y, AOmega.X, AOmega.Y);
end;

{ TMalla }

procedure TMalla.ActualizarMalla;
begin
  DoDibujarMalla;
end;

function TMalla.AddLinea: Integer;
begin
   Result:= FLineas.Add(TLinea.Create(Self));
   TLinea(FLineas[Result]).Indice:= Result;
end;

function TMalla.AddNodo: Integer;
begin
   Result:= FNodos.Add(TNodo.Create(Self));
end;

constructor TMalla.Create(AForm: TForm);
begin
  if AForm = Nil then
    Raise Exception.Create('Atención: La referencia no es valida');
  FZona:= AForm;

  FSalida:= TNodo.Create(Self);
  FDestino:= TNodo.Create(Self);

  FNodos:= TList.Create;
  FLineas:= TList.Create;
end;

destructor TMalla.Destroy;
begin
  //limpiamos la lista de nodos
  VaciarNodos;
  FreeAndNil(FNodos);
  //limpiamos la lista de lineas
  VaciarLineas;
  FreeAndNil(FLineas);
  //nos desvinculamos del objeto padre
  //sobre el que nos mostramos
  FZona:= Nil;

  FreeAndNil(FSalida);
  FreeAndNil(FDestino);

  inherited Destroy;
end;

procedure TMalla.DibujaRuta;
var
  i: Integer;
  FNodo: TNodo;
begin
  VaciarLineas;

  fNodo:= Nil;
  for i := 0 to CountNodos - 1 do begin
    if FNodo <> Nil then begin
       FNodo.AddLinea(Nodos[i]);
    end
    else FSalida.AddLinea(Nodos[i]);
    FNodo:= Nodos[i];
  end;
  FNodo.AddLinea(FDestino);
end;

procedure TMalla.DoDibujarMalla;
var
  i: Integer;
begin
  if Assigned(FZona) then begin
     for i := 0 to CountLineas - 1 do Lineas[i].DibujarLinea;
     for i := 0 to FNodos.Count - 1 do Nodos[i].DibujarNodo;
     Salida.DibujarNodo;
     Destino.DibujarNodo;
  end;
end;

procedure TMalla.DoOrdenaNodos(ANodo: TNodo; AOrdenacion: TOrdenacion);
begin
  AOrdenacion(Self, ANodo);
end;

function TMalla.GenerarRuta: Double;
begin
  if (CountNodos = 0) or (FSalida = Nil) or (FDestino = Nil) then
    Raise Exception.Create('Error: No existe suficente información en la ruta a generar');
  OrdenarNodos;
  DibujaRuta;
  Result:= CalculaMetricaRuta;
end;

function TMalla.GetLineas(Indice: Integer): TLinea;
begin
   if (Indice < 0) or (Indice > FLineas.Count-1) then
      Raise Exception.Create('Error en el valor del indice de la matriz');
  Result:= FLineas[Indice];
end;

function TMalla.GetNodos(Indice: Integer): TNodo;
begin
   if (Indice < 0) or (Indice > FNodos.Count-1) then
      Raise Exception.Create('Error en el valor del indice de la matriz');
   Result:= FNodos[Indice];
end;

procedure TMalla.InicializarMalla;
begin
   VaciarLineas;
   VaciarNodos;
   FPivote:= Nil;
end;

procedure TMalla.InternalOrdenaNodos(Sender: TObject; ANodo: TNodo);

   procedure OrdenaQuickSort(L,R: Integer);
   var
     i,j: Integer;
     piv, aux: TNodo;
   begin
     i:= L;
     j:= R;
     piv:= Nodos[(L+R) div 2]; //pivote
     Repeat
       while (Nodos[i].GetDistancia < piv.GetDistancia) do Inc(i);
       while (piv.GetDistancia < Nodos[j].GetDistancia) do Dec(j);
       if i<= j then begin
          Aux:= Nodos[i];
          FNodos.Exchange(i,j);
          FNodos[j]:= Aux;
          Inc(i);
          Dec(j);
       end;
       if L<j then OrdenaQuickSort(L,j);
       if i<R then OrdenaQuickSort(i,R);
     Until i>j;
   end;  

begin
   OrdenaQuickSort(ANodo.GetIndice, CountNodos-1);
end;

procedure TMalla.OrdenarNodos;
var
  i: Integer;
begin
  FPivote:= FSalida;
  for i := 0 to CountNodos - 1 do
     OrdenarNodos(Nodos[i], FDestino);
end;

procedure TMalla.OrdenarNodos(ANodo: TNodo; ADestino: TNodo);
var
  i: Integer;
begin
  //guardamos el indice del vector para que calcule la distancia
  //correctamente ya que el pivote debe avancar
  i:= ANodo.GetIndice;
  DoOrdenaNodos(ANodo, InternalOrdenaNodos);
  //en este punto el nodo puede haber cambiado
  FPivote:= Nodos[i];
end;

procedure TMalla.SetSalida(const Value: TNodo);
begin
  if FSalida = nil then
     FSalida := Value;
end;

procedure TMalla.VaciarLineas;
var
  i: Integer;
begin
  for i := 0 to CountLineas - 1 do begin
    TLinea(FLineas[i]).Free;
    FLineas[i]:= Nil;
  end;
  FLineas.Clear;
end;

procedure TMalla.VaciarNodos;
var
  i: Integer;
begin
  for i := 0 to CountNodos - 1 do begin
    TNodo(FNodos[i]).Free;
    FNodos[i]:= Nil;
  end;
  FNodos.Clear;
end;

function TMalla.CalculaMetricaRuta: Double;
var
  i: Integer;
begin
  Result:= 0;
  for i := 0 to CountLineas - 1 do begin
    Result:= Result + TLinea(FLineas[i]).GetDistancia;
  end;
end;

function TMalla.CountLineas: Integer;
begin
   Result:= FLineas.Count;
end;

function TMalla.CountNodos: Integer;
begin
  Result:= FNodos.Count;
end;

{TNodo}

procedure TNodo.DibujarNodo;
begin
  if not Assigned(Self) then Exit;

  if (Self = FMalla.Salida) or
     (Self = FMalla.FDestino) then begin
    if (Self = FMalla.Salida) then begin
      FShape.Brush.Color:= clRed;
      FShape.Pen.Color:= clRed;
    end
    else begin
      FShape.Brush.Color:= clBlue;
      FShape.Pen.Color:= clBlue;
    end;
  end
  else begin
    FShape.Brush.Color:= clLime;
    FShape.Pen.Color:= clGreen;
    FMalla.FZona.Canvas.Font.Size:= 7;
    FMalla.FZona.Canvas.TextOut(X-12,Y-12,'('+InTToStr(GetIndice)+')')
  end;
end;

function TNodo.AddLinea(ANodoTarget: TNodo): Integer;
var
  fLin: TLinea;
begin
   if ANodoTarget = Nil then
     Raise Exception.Create('Atención: La referencia al nodo no es valida');
   Result:= FMalla.AddLinea;
   if Result >= 0 then
      FMalla.Lineas[Result].Enlaza(Self, ANodoTarget);
end;

constructor TNodo.Create(AMalla: TMalla);
begin
  if AMalla = Nil then
     Raise Exception.Create('Atención: La referencia no es valida');
  FMalla:= AMalla;

  FShape:= TShape.Create(Nil);
  FShape.Parent:= AMalla.FZona;
  FShape.Shape:= stCircle;
  FShape.Height:= 8;
  FShape.Width:= 8;
  if Self = FMalla.Salida then begin
    FShape.Brush.Color:= clRed;
    FShape.Pen.Color:= clRed;
  end
  else begin
    FShape.Brush.Color:= clLime;
    FShape.Pen.Color:= clGreen;
  end;

  FPeso:= 0;

  x:= 0;
  y:= 0;

end;

destructor TNodo.Destroy;
begin
  FMalla:= Nil;
  FreeAndNil(FShape);
  inherited;
end;

function TNodo.GetDistancia: Double;
begin
  Result:= CalcularDistanciaNodos(FMalla.FPivote, Self);
end;

function TNodo.GetIndice: Integer;
var
  i: Integer;
begin
   i:= 0;
   while i < FMalla.CountNodos do begin
     if FMalla.Nodos[i] = Self then begin
       Result:= i;
       Exit;
     end;
     Inc(i);
   end;
   Result:= -1;
end;

function TNodo.GetX: Integer;
begin
  Result:= FPoint.X;
end;

procedure TNodo.SetTag(const Value: Integer);
begin
  FTag := Value;
end;

procedure TNodo.SetPeso(const Value: Integer);
begin
  FPeso := Value;
end;

procedure TNodo.SetX(const Value: Integer);
begin
  FPoint.X := Value;
  FShape.Left:= FPoint.X;
end;

function TNodo.GetY: Integer;
begin
  Result:= FPoint.Y;
end;

procedure TNodo.SetY(const Value: Integer);
begin
  FPoint.Y := Value;
  FShape.Top:= FPoint.Y;
end;

{ TLinea }

constructor TLinea.Create(AMalla: TMalla);
begin
  FMalla:= AMalla;
  FAlfa:= Nil;
  FOmega:= Nil;
end;

destructor TLinea.Destroy;
begin
  FMalla:= Nil;
  inherited;
end;

procedure TLinea.DibujarLinea;
begin
   if Assigned(FMalla) then begin
      FMalla.FZona.Canvas.MoveTo(FAlfa.X, FAlfa.Y);
      FMalla.FZona.Canvas.LineTo(FOmega.X, FOmega.Y);
   end;
end;

function TLinea.GetDistancia: Double;
begin
  Result:= FDistancia;
end;

procedure TLinea.Enlaza(AAlfa, AOmega: TNodo);
begin
   if AAlfa = AOmega then
      Raise Exception.Create('No se pueden enlazar dos nodos iguales');

   if AAlfa <> FAlfa then FAlfa:= AAlfa;
   if AOmega <> FOmega then FOmega:= AOmega;

   FDistancia:= CalcularDistanciaNodos(FAlfa, FOmega);
end;

procedure TLinea.SetAlfa(const Value: TNodo);
begin
  if Value <> FOmega then FAlfa := Value;
  FDistancia:= CalcularDistanciaNodos(FAlfa, FOmega);
end;

procedure TLinea.SetIndice(const Value: Integer);
begin
  FIndice := Value;
end;

procedure TLinea.SetOmega(const Value: TNodo);
begin
  if Value <> FAlfa then FOmega := Value;
  FDistancia:= CalcularDistanciaNodos(FAlfa, FOmega);
end;

end.

Y esta es la imagen, una vez generada la ruta:

Descargar

El asterisco (*) venía a cuento cuando comentaba que podíais modificar facilmente el ejemplo para que calculara la ruta no en función de recorrer todos los nodos sino aquellos que pudieran hacer menor la distancia. Para ello, quizás bastaría modificar el calculo de la distancia, sobrescribiendo la función virtual

DoOrdenaNodos(ANodo: TNodo; AOrdenacion: TOrdenacion); virtual;

Y entregando como parámetro una nueva función que ademas, tuviera en cuenta no solo la distancia de los dos nodos sino también el calculo con respecto al punto de destino. Y una vez obtenido ese recalculo, compararlo en cada uno de los avances del vecto con el punto final. Podría ser una idea.

Como hicimos en la entrada anterior vamos a intentar destacar algunas ideas importantes que os pueden servir de guia:

* Este es un ejemplo y no deja de serlo aunque calcule un resultado. Os comento ésto porque este resultado puede ser correcto para mi, en el dominio de una aplicación concreta y no ser para otros requerimientos, puesto que no existe realmente una optimización al generar la ruta, ni existen caminos que precondicionen optar por una ruta o por  otra. Aqui hemos cogido puntos al azar y nos hemos imaginado la forma de recorrerlos todos.

* Otro punto que parece interesante y por eso lo he incluido así, es el paso de una función como parámetro, que nos permite reutilizar el metodo ante la necesidad de tener en cuenta nuevas condiciones. Para ello, nos basta sobrescribir el método, que es algo realmente sencillo. En este caso concreto, el método tiene un tipo declarado en el interfaz de la unidad UNodos.pas (TOrdenacion = procedure(Sender: TObject; ANodo:TNodo) of Object;)

* Y puede seros de curiosidad, por ejemplo el algoritmo QuickSort, que necesité modificar un tanto para que se reordenara en función de la distancia, como he explicado en lineas anteriores.

* El tratamiento de las listas de punteros (TList) y de las listas de cadenas y como podemos valernos de estas clases para almacenar y manipular referencias a objetos. Es un punto que se reitera continuamente en buena parte del código que podais encontrar, ya que son clases muy básicas.

* Y como siempre, teniendo en cuenta la reutilización. Aunque no lo he comentado, creo que incluso podríamos haber hecho uso del generador de códigos (visto en la entrada anterior), haciendo una ligera modificiación para que nos pudiera calcular distintas combinaciones de nodos y ofrecer a nuestro usuario, la posibilidad de elegir entre varias rutas alternativas.

Con un poco de imaginación quizás no hablariamos de distancia. Es una de las razones que me han hecho añadir la propiedad Peso en el Nodo, aunque luego no le he dado ninguna utilidad. ¿Una combinación de factores, distancia y peso y otros que pudieramos considerar? ¡Hay tantas posibilidades que en nuestro pequeño ejemplo nos hemos intentado quedar con la más didáctica y no enrevesar el código con mayor complejidad que no nos iba a aporatar realmente nada.

Yo creo que podemos dejarlo en este punto. La idea era compartir con vosotros las ventajas de razonar en términos de clases, de intentar en la medida que nos sea posible abstraernos y hacer que nuestro código sea lo mas reutilizable posible. Si bien es cierto que a corto plazo puede ser un inconveniente porque implica un mayor esfuerzo, a largo plazo es rentable, tanto a nivel de reusabilidad como de adaptabilidad a nuevos requerimientos.

Espero que os puedan ayudar estas lineas.


Experimentos, reflexiones y otros artefactos (III)

mayo 24, 2009 en Artículos, Código, Delphi, Entrada Diario, Taller práctico

Nuestro primer experimento...

Nuestro primer experimento...

Como reza esa primera imagen que abre el post, es un buen momento para intentar razonar en terminos de clases, y experimentar con el ejemplo que intentabamos abordar y que nos servía para reflexionar. En él, podríamos capturar la idea de atributo, como ya se ha podido extraer de las dos entradas anteriores, consistía en un rasgo diferenciador que combinado con otros, nos servía para identificar un artículo determinado. Podemos convenir aceptar la clase TAtributo  como una representación de ese rasgo.

Y siguiendo con ese razonamiento, también parece que se desprende de la misma existencia de ese atributo, la idea de que va a depender de otra entidad que sea capaz de gestionarlos, ya que será necesaria la existencia de uno o mas atributos y por lo tanto, necesitamos ”algo” que sea capaz de operar con ellos y relacionarlos. A esta clase, le podríamos llamar TGenerador, por buscar un nombre que resulte familiar a esa idea de “artefacto” que va a mostrar las distintas combinaciones de codigo posibles para formar nuestro artículo.

¿Qué os parece si empezamos a trabajar?

Vamos a resumir lo que tenemos hasta ahora. Partimos de esas dos ideas: TGenerador y TAtributo. Podemos empezar a perfilarlas, con dos operaciones básicas, como pueden ser:

* Añadir un atributo y

* Cambiar la posición de dos de ellos.

Vamos a escribir unas lineas de código y un pequeño ejemplo que las ponga en práctica, para cercionarnos de que  los razonamientos son correctos.  Para poder diferenciar los atributos en nuestro interfaz de prueba, hemos implementado la propiedad captión que nos permite visualizarlos de forma sencilla dentro de un listbox.

 

unit UPensarEnClases;

interface

uses
 SysUtils, Classes;
type

  TAtributo = class;

  TGenerador = class(TObject)
  private
    FAtributos: TList;
    function GetAtributos(Indice: Integer): TAtributo;
  protected
  public
    constructor Create;
    destructor Destroy; override;
    function AddAtributo: TAtributo;
    function Count: Integer;
    procedure SetUpPosition(AAtributo: TAtributo);
    procedure SetDownPosition(AAtributo: TAtributo);
    function GetPosicion(AAtributo: TAtributo): Integer;
    property Atributos[Indice: Integer]: TAtributo read GetAtributos;
  end;

  TAtributo = class(TObject)
  private
    FGenerador: TGenerador;
    FCaption: String;
    procedure SetCaption(const Value: String);
  protected
  public
    function GetPosicion: Integer;
    constructor Create(AGenerador: TGenerador); virtual;
    destructor Destroy; override;
    property Caption: String read FCaption write SetCaption;
  end;

implementation

{ TAtributo }

constructor TAtributo.Create(AGenerador: TGenerador);
begin
  if not Assigned(AGenerador)  then
     Raise Exception.Create('Error: Referencia al generador no valida');
   FGenerador:= AGenerador;
end;

destructor TAtributo.Destroy;
begin
  FGenerador:= Nil;
  inherited;
end;

function TAtributo.GetPosicion: Integer;
begin
  Result:= FGenerador.GetPosicion(Self);
end;

procedure TAtributo.SetCaption(const Value: String);
begin
  FCaption := Value;
end;

{ TGenerador }

function TGenerador.AddAtributo: TAtributo;
begin
   Result:= TAtributo.Create(Self);
   FAtributos.Add(Result);
end;

function TGenerador.Count: Integer;
begin
   Result:= FAtributos.Count;
end;

constructor TGenerador.Create;
begin
  FAtributos:= TList.Create;
end;

destructor TGenerador.Destroy;
var
 i: Integer;
 f: TAtributo;
begin
  for i:= 0 to Count - 1 do begin
    f:= TAtributo(FAtributos[i]);
    FreeAndNil(f);
  end;
  FAtributos.Clear;
  FreeAndNil(FAtributos);
  inherited;
end;

function TGenerador.GetAtributos(Indice: Integer): TAtributo;
begin
   if (Indice < 0) or (Indice > FAtributos.Count-1)then
     Raise Exception.Create('Error indice get fuera de rango');
   Result:= FAtributos[Indice];
end;

function TGenerador.GetPosicion(AAtributo: TAtributo): Integer;
begin
   Result:= FAtributos.IndexOf(AAtributo);
end;

procedure TGenerador.SetDownPosition(AAtributo: TAtributo);
var
  i: Integer;
begin
   i:= FAtributos.IndexOf(AAtributo);
   if (i < Count -1) then FAtributos.Exchange(i, i+1);
end;

procedure TGenerador.SetUpPosition(AAtributo: TAtributo);
var
  i: Integer;
begin
   i:= FAtributos.IndexOf(AAtributo);
   if (i > 0) then FAtributos.Exchange(i, i-1);
end;

end.

Finalmente, vamos a ver el pequeño formulario que nos permite verificar que nuestro interfaz e implementación hacen lo que le hemos solicitado.

Al ser creado el formulario, crearemos el generador y añadiremos los atributos (3)  y daremos la oportunidad a nuestro usuario de que los cambie de posición de los mismos.

Es muy sencillo… upsssss…  (aquí tenéis)

 

unit UMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, uPensarEnClases;

type
  TfrmPensarEnClases = class(TForm)
    lbxAtributos: TListBox;
    btnSubir: TButton;
    btnBajar: TButton;
    Label1: TLabel;
    procedure btnSubirClick(Sender: TObject);
    procedure btnBajarClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure ActualizarInterfaz;
  public
    { Public declarations }
    FGenerador: TGenerador;
  end;

var
  frmPensarEnClases: TfrmPensarEnClases;

implementation

{$R *.dfm}

procedure TfrmPensarEnClases.ActualizarInterfaz;
var
  i: Integer;
begin
   lbxAtributos.Clear;
   for i := 0 to FGenerador.Count - 1 do
      lbxAtributos.Items.Add(FGenerador.Atributos[i].Caption);
end;

procedure TfrmPensarEnClases.btnBajarClick(Sender: TObject);
var
  fAtrib: TAtributo;
  i: Integer;
begin
   i:= lbxAtributos.ItemIndex;
   fAtrib:= FGenerador.Atributos[i];
   if i >= 0 then begin
      with FGenerador do  SetDownPosition(fAtrib);
      ActualizarInterfaz;
      lbxAtributos.Itemindex:= fAtrib.GetPosicion;
   end;
end;

procedure TfrmPensarEnClases.btnSubirClick(Sender: TObject);
var
  fAtrib: TAtributo;
  i: Integer;
begin
   i:= lbxAtributos.ItemIndex;
   fAtrib:= FGenerador.Atributos[i];
   if i >= 0 then begin
      with FGenerador do  SetUpPosition(fAtrib);
      ActualizarInterfaz;
      lbxAtributos.Itemindex:= fAtrib.GetPosicion;
   end;
end;

procedure TfrmPensarEnClases.FormCreate(Sender: TObject);
begin
   FGenerador:= TGenerador.Create;
   with FGenerador do begin
      AddAtributo.Caption:= 'Caracteristica';
      AddAtributo.Caption:= 'Color';
      AddAtributo.Caption:= 'Tipo';
   end;
   ActualizarInterfaz;
   lbxAtributos.ItemIndex:= 0;
end;

procedure TfrmPensarEnClases.FormDestroy(Sender: TObject);
begin
   if Assigned(FGenerador) then FreeAndNil(FGenerador);
end;

end.

Algunas ideas nos pueden ayudar en esos primeros razonamientos:

* En este caso concreto, tanto la clase TGenerador como TAtributo, han descendido de TObject, que es el ascendente comun a todas las clases. Podríamos haber razonado que, por ejemplo, TAtributo, descendiera de un control visual, pudiendo ser insertado en nuestro formulario, lo cual no es ni mas ni menos correcto, ya que forma parte del abanico de decisiones de diseño que nuestra experiencia  puede valorar. Hacerlo de una forma o de otra, va a condicionar necesariamente las relaciones a las que voy a quedar ligado. Quizás por esa razón y por evitar ligarnos a ninguna representación es por lo que he optado por partir de TObject. Como hemos hecho que descienda de ésta, necesitábamos un procedimiento que sea capaz de recrear la instancia del Generador por lo cual, se ha implementado  ActualizarInterfaz. Tambien se ha hecho necesario destruir explicitamente la instacia del Generador y liberar la reserva de memoria que ha necesitado su creación. Lo hemos hecho en el evento OnDestroy de nuestro formulario.

* La clase TGenerador, necesita almacenar una lista de referencias a las distintas instacias de TAtributo, y para ello, una buena opción es hacer uso de la clase TList, que es especificada e implementada dentro del módulo Classes.pas. TList es una lista de punteros y un aliado extremadamente poderoso, ya que puede almacenar cualquier cosa. En contrapartida, la manipulación de cualquiera de las referencias a las que apuntamos desde nuestra lista de punteros, necesitaría inevitablemente de una conversión de tipos para acceder al objeto instanciado. 

Este módulo es bastante interesante pues contiene las definiciones de algunas clases muy básicas que en buena medida se correlacionan con algunos TDA´s clásicos de la programación, como la gestión de listas, pilas y colas. Estos dos ultimos (pilas y colas) se apoyan en la clase TList y se especifican e implementan en el modulo Constnrs.pas. En Classes.pas, además se incluyen los hilos de ejecución (TThreads), las interfaces, los enumeradores, la clase TStream y otras más, imprescindibles y recurrentes dentro del esquema de la VCL.

La miembros y metodos de TList, nos permiten de forma sencilla, generar por ejemplo el acceso a un atributo determinado, a través de una propiedad de tipo array que se gestiona con el índice de la lista. Tambien nos permite intercambiar de posición dos referencias. No tenemos que ir reinventando la rueda en aquellos puntos en los que nos podamos valer de clases que ya nos dan una funcionalidad. ¿no os parece?.

* Otra idea interesante, podría ser que el atributo conozca a la clase que va a gestionarlo, lo cual nos permitirá, invocar metodos que se ejecutan desde ésta, de ser necesario. Por ello, la creación del mismo atributo, parte del generador, que entrega una referencia a si mismo en el constructor de la clase TAtributo:   

constructor Create(AGenerador: TGenerador); virtual;

La invocacion de AddAtributo instancia un nuevo atributo y lo añade a la lista, devolviendo como valor de retorno una referencia al objeto creado.

function TGenerador.AddAtributo: TAtributo;
begin
   Result:= TAtributo.Create(Self);
   FAtributos.Add(Result);
end;

El constructor guarda esta referencia:
 constructor TAtributo.Create(AGenerador: TGenerador);
begin
  if not Assigned(AGenerador)  then
     Raise Exception.Create(‘Error: Referencia al generador no valida’);
   FGenerador:= AGenerador;
end;

Podemos ver un ejemplo en el código que hemos escrito. El método GetPosición de la clase TAtributo, nos devuelve la posición del mismo dentro de la lista. Esto podría obtenerse de varias formas. Guardando en un miembro privado el valor de esta posición cuando es añadido el atributo, siendo actualizado cada vez que es modificada la posición del mismo en la lista. Esa podría haber sido una opción. Sin embargo, finalmente, al guardar la referencia al generador, obtener ese indice es tan fácil como preguntarle a quien realmente lo sabe:  :-)

function TAtributo.GetPosicion: Integer;
begin
  Result:= FGenerador.GetPosicion(Self);
end;
  

Lo dejamos aquí y nuestro siguiente paso será ir añadiendo detalles a este escenario.

Experimentos, reflexiones y otros artefactos (II)

mayo 17, 2009 en Advertencia, Artículos, ¿Sabías que...?, ¿Sabías que...? [Delphi], Código, Delphi, Entrada Diario, Taller práctico

Seguimos avanzando en el artículo de la primera parte de la serie.

¿Por donde ibamos? Sí. Ya…

Nuestro formulario iba creciendo. De hecho habíamos incluído una pequeña modificación que invertía el código y el formulario, comenzaba a parecerse -con la prudencia que hace falta para comentar ésto ya que estas lineas son meramente experimentales- a una hipotética herramienta para generar combinaciones de códigos. Esto nos permite tomar una pequeña dosis de realidad en nuestro razonamiento. Una herramienta que iba a crecer pero ligada desgraciadamente a los componentes que iban formando su interfaz.

No. No creais que es algo irreal o fantástico. Aunque no es demasiado habitual, algunas empresas tratan sus artículos como una concreción de las características que contienen los patrones modelos y escogen los escenarios en los que se hace efectivo este paso de materialización. Por ejemplo al ser cursado un pedido por parte del cliente, momento en el que podrían las distintas selecciones de caracteristicas desembocar en la generación de un código único y desencadenar los proceso de alta en artículos e inserción en lineas de detalle de pedido. Y no confundais por favor este código necesariamente con la clave primaria de una tabla cualquiera, entre otras cosas porque no hay nada que impida que puedan coexistir una clave primaria incremental (o no incremental), con cualquier otra combinaciones de claves que acaban formado parte de un índice de clave única.

En este ejemplo, vamos descubriendo algunos detalles sobre la marcha. ¿Eso pasa tambien en los proyectos reales, no es así?. Nos añadiran un mes despues una caracteristica que tambien vamos a codificar con dos caracteres. Con la salvedad de que al parecer no es combinable, como sí ocurría con los dos atributos que ya conocíamos: el color y el tipo. Sera constante y variable para una combinación determinada de color/tipo, bien por selección de una entre una lista asociada a esta, bien por afinidad a uno de los atributos, por lo que hemos convenido en denominar ese comportamiento como ”rellenable de”. Esto que parece muy complejo lo vereis muy sencillo en el código. 

Pero lo mejor es que lo veais en una imagen del formulario, una vez que hemos hecho las modificaciones. Nuestro bucle “for” va creciendo y va anidadandose para generar las combinaciones. Para hacerlo un poco mas real el ejemplo, he añadido varios componentes TClientDataSet, asociados a cada uno de los atributos, y he almacenado localmente los datos en tres ficheros xml que cargo al principio y guardo al final del proceso. Esto es meramente anecdótico y para evitar conexiones reales que ya se pueden sobreentender. 

 

Formulario

Descargar
 

unit URelaciones;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, DB, DBClient, TConnect, Provider;

type
  TRelaciones = class(TForm)
    lbxTipo: TListBox;
    lbxColor: TListBox;
    lbxResultados: TListBox;
    rgpCodigo: TRadioGroup;
    bnSolucion: TButton;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    cdsTipo: TClientDataSet;
    cdsTipoTipo: TStringField;
    cdsColor: TClientDataSet;
    StringField2: TStringField;
    cdsTipoIDTipo: TIntegerField;
    cdsColorIDColor: TIntegerField;
    cbxCaracteristica: TComboBox;
    cdsCaracteristica: TClientDataSet;
    cdsCaracteristicaIdCaracteristica: TIntegerField;
    cdsCaracteristicaCaracteristica: TStringField;
    chbRellenar: TCheckBox;
    Bevel1: TBevel;
    procedure FormCreate(Sender: TObject);
    procedure bnSolucionClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure chbRellenarClick(Sender: TObject);
  private
    { Private declarations }
    procedure SimulaDatosEnBBDD;
    procedure SimulaPermanenciaDatos;
  public
    { Public declarations }
  end;

var
  Relaciones: TRelaciones;

implementation

{$R *.dfm}

procedure TRelaciones.bnSolucionClick(Sender: TObject);
var
 i,j, k: Integer;
 s: String;
begin
   lbxResultados.Clear;
   for i := 0 to lbxTipo.Count - 1 do
      for j := 0 to lbxColor.Count - 1 do begin
          case rgpCodigo.ItemIndex of
           0: s:= lbxTipo.Items[i]+ lbxColor.Items[j];
           1: s:= lbxColor.Items[j]+ lbxTipo.Items[i];
          end;
      case chbRellenar.Checked of
        True: lbxResultados.items.Add(lbxColor.Items[j]+s);
        False: lbxResultados.items.Add(cbxCaracteristica.Items[cbxCaracteristica.ItemIndex]+s);
      end;
   end;
end;

procedure TRelaciones.chbRellenarClick(Sender: TObject);
begin
   cbxCaracteristica.Enabled:= not chbRellenar.Checked;
end;

procedure TRelaciones.FormCreate(Sender: TObject);
begin
  SimulaDatosEnBBDD;

  if not cdsColor.Active then cdsColor.Open;
  cdsColor.First;
  while not cdsColor.Eof do begin
    lbxColor.Items.Add(cdsColor.FieldByName('Color').AsString);
    cdsColor.Next;
  end;

  if not cdsTipo.Active then cdsTipo.Open;
  cdsTipo.First;
  while not cdsTipo.Eof do begin
    lbxTipo.Items.Add(cdsTipo.FieldByName('Tipo').AsString);
    cdsTipo.Next;
  end;

  if not cdsCaracteristica.Active then cdsCaracteristica.Open;
  cdsCaracteristica.First;
  while not cdsCaracteristica.Eof do begin
    cbxCaracteristica.Items.Add(cdsCaracteristica.FieldByName('Caracteristica').AsString);
    cdsCaracteristica.Next;
  end;
  cbxCaracteristica.ItemIndex:= 0;
end;

procedure TRelaciones.FormDestroy(Sender: TObject);
begin
  //guardamos los datos antes de finalizar
  SimulaPermanenciaDatos;
end;

procedure TRelaciones.SimulaDatosEnBBDD;
begin
  cdsColor.FileName:= 'Color.xml';
  cdsColor.CreateDataSet;
  cdsColor.Active:= True;
  cdsColor.Insert;
  cdsColor.FieldByName('IDColor').AsInteger:= 1;
  cdsColor.FieldByName('Color').AsString:= '10';
  cdsColor.Insert;
  cdsColor.FieldByName('IDColor').AsInteger:= 2;
  cdsColor.FieldByName('Color').AsString:= '12';
  cdsColor.Insert;
  cdsColor.FieldByName('IDColor').AsInteger:= 3;
  cdsColor.FieldByName('Color').AsString:= '15';
  cdsColor.Post;

  cdsTipo.FileName:= 'Tipo.xml';
  cdsTipo.CreateDataSet;
  cdsTipo.Active:= True;
  cdsTipo.Insert;
  cdsTipo.FieldByName('IDTipo').AsInteger:= 1;
  cdsTipo.FieldByName('Tipo').AsString:= 'AA';
  cdsTipo.Insert;
  cdsTipo.FieldByName('IDTipo').AsInteger:= 2;
  cdsTipo.FieldByName('Tipo').AsString:= 'AB';
  cdsTipo.Insert;
  cdsTipo.FieldByName('IDTipo').AsInteger:= 3;
  cdsTipo.FieldByName('Tipo').AsString:= 'CC';
  cdsTipo.Post;

  cdsCaracteristica.FileName:= 'Caracteristica.xml';
  cdsCaracteristica.CreateDataSet;
  cdsCaracteristica.Active:= True;
  cdsCaracteristica.Insert;
  cdsCaracteristica.FieldByName('IDCaracteristica').AsInteger:= 1;
  cdsCaracteristica.FieldByName('Caracteristica').AsString:= '0A';
  cdsCaracteristica.Insert;
  cdsCaracteristica.FieldByName('IDCaracteristica').AsInteger:= 2;
  cdsCaracteristica.FieldByName('Caracteristica').AsString:= '0B';
  cdsCaracteristica.Insert;
  cdsCaracteristica.FieldByName('IDCaracteristica').AsInteger:= 3;
  cdsCaracteristica.FieldByName('Caracteristica').AsString:= '0C';
  cdsCaracteristica.Post;

end;

procedure TRelaciones.SimulaPermanenciaDatos;
begin
   cdsColor.SaveToFile('color.xml');
   cdsTipo.SaveToFile('tipo.xml');
   cdsCaracteristica.SaveToFile('Caracteristica.xml');
end;

end.

Todavía no hemos hecho un solo razonamiento que nos pueda indicar que nuestro chip “pensar en clases” está activado. :-)

Eso sí, hemos sido capaces de añadir una característica más y enrevesar nuestro código otro tanto. Y dado que ahora mismo tan solo contamos con un modelo que contiene tres atributos, me hace sospechar que al tener una cantidad mayor, aumentaria la complejidad de las relaciones, (representadas en esos bucles y las distintas estructuras que permtien bifurcar el fllujo de nuestro codigo), de forma peligrosa. Esa es quizás la moraleja de esta segunda parte y la que nos debería hacer pensar que estamos atando una soga a nuestro cuello y que no va a depender de nosotros que acabe ahogandonos.

Comparar las tres versiones del bucle:

   for i := 0 to lbxTipo.Count - 1 do
      for j := 0 to lbxColor.Count - 1 do
          lbxResultados.Items.Add(lbxTipo.Items[i]+ lbxColor.Items[j]);
   for i := 0 to lbxTipo.Count - 1 do
      for j := 0 to lbxColor.Count - 1 do
          case rgpCodigo.ItemIndex of
           0: lbxResultados.Items.Add(lbxTipo.Items[i]+ lbxColor.Items[j]);
           1: lbxResultados.Items.Add(lbxColor.Items[j]+ lbxTipo.Items[i]);
          end;
   for i := 0 to lbxTipo.Count - 1 do
      for j := 0 to lbxColor.Count - 1 do begin
          case rgpCodigo.ItemIndex of
           0: s:= lbxTipo.Items[i]+ lbxColor.Items[j];
           1: s:= lbxColor.Items[j]+ lbxTipo.Items[i];
          end;
      case chbRellenar.Checked of
        True: lbxResultados.items.Add(lbxColor.Items[j]+s);
        False: lbxResultados.items.Add(cbxCaracteristica.Items[cbxCaracteristica.ItemIndex]+s);
      end;
   end;

 

Podria darse el caso de que existieran relaciones de dependencia entre uno o varios atributos sin ir mas lejos. Ahora mismo no las estamos considerando. Por relaciones de dependencia entiendo por ejemplo, la existencia de restricciones que permitan combinar dos valores concretos de distintos atributos. Imaginad que para un determinado tipo no pudiera combinarse determinados colores (a efectos reales puede asimilarse a una relación maestro detalle entre los valores de dos atributos distintos, pero con la peculiaridad de que esos detalles son compartidos) .

Un ejemplo:

La bicicleta A contiene el chasis ’00′ y este puede venderse en los colores ’00′ (negro), ’01′ (rojo), ’02′ (azul)

La bicicleta B contiene el chasis ’01′ y este puede venderse en los colores  ’00′ (negro), ’03′ (beig), ’06′ (plata)

Como veis, ahora queda mas claro. Comparten el color ’00′ pero cada tipo solo se combina con determinados colores.

  ¿Que hariamos entonces? Tendriamos que actuar posiblemente en el interior de cualquiera de las estructura case antes de que se ejecutase los metodos añadir una linea a nuestros resultados de forma que se pudieran restringir las combinaciones y se pudiera desechar las que no son validas.

Es mas, la primera pregunta que podríais preguntaros es por qué tengo que suponer que sean tres, cuatro o x numero finito de elementos. Estoy simplemente generando una fotografia de los requerimientos actuales sin valorar que estos puedan cambiar y que puedan crecer el numero de ellos, por asumir que sea uno de los cambios que mas facilmente puedan producirse. Ese es uno de los errores en los que podemos facilmente caer en ocasiones. De haber contemplado esta posiblidad posiblemente hubiera sido facil generar un nuevo interfaz que asuma la nueva situación. Pensar en termino de clases no es la panacea de todos los males ni evita la complejidad intrinseca del elemento estudiado, pero si que nos permite minimizar los efectos de los cambios en los requerimientos. Un razonamiento correcto quizás nos hubiera descubierto una clase “Atributo”, capaz de manipular los distintos valores que puede manejar en funcion de un dominio. Podría establecer relaciones igualmente entre otros atributos y el mismo. Por poner algunos ejemplos que se me vienen así a “botepronto”. Asimismo, podríamos especificar una clase que permitiera gestionar añadir nuevos atributos, eliminarlos, que el orden de presentación fuera variable, etc.

Creo que todo esto encaja en la idea que mueve esta segunda parte ‘pensar en terminos de clases’… :-)

Lo dejamos aquí… y seguimos en la tercera parte. Espero y deseo que todos estos razonamientos os puedan ser de alguna utilidad.