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 (IV)

junio 7, 2009 en Advertencia, Artículos, Ayuda, Código, Delphi, Entrada Diario

Hace una tarde calurosa y bastante veraniega. En una tarde así, no se puede estar en casa escribiendo una entrada como esta… :-) El sol cae a plomo a las 6 de la tarde, un día de verano, y parece que todo invita a salir a disfrutar de un rato de deporte. Así que con con vuestro permiso :-) inicio la entrada ahora, hasta donde llegue, y la prosigo esta noche si quedara algo por decir.

¿Por donde empiezo? Ummmm…

Lo habíamos dejado en la parte en la que haciamos un pequeño boceto de las dos clases que ibamos a considerar. La clase TGenerador y la clase TAtributo. Eso nos permitía ver como se podían relacionar y a modo de prueba, imaginábamos un método para permitirnos alterar el orden de los mismos, en caso de ser necesario.

Durante esta semana he podido sacer algunos minutos para adelantarme y darle forma a esas dos ideas. Pero no teneis que perder de vista, por favor, que lo importante no es el código en si mismo, que no es mas que un ejemplo cualquiera y puede contener alguna que otra errata al analizarlo con profundidad, sino el hecho mismo de solicitar de cada uno, un ejercicio de análisis previo y reflexión, intentando razonar en términos de clases, en la medida que nos sea posible.

Una imagen vale mas que mil palabras. Este es el ejemplo que preparé para probar la implementación de ambas clases ya con una funcionalidad mayor.

exercicio2

La imagen muestra en la parte superior, 4 atributos que representan los tipos posibles que hemos adminitido. Un atributo combinable (Atributo 2), capaz de seleccionar multiples valores. Un atributo dependiente (Atributo 3), tambien combinable pero que va a permitirnos descartar alguna combinaciones en funcion de una relación imaginaria. Es decir, el caso de que un tipo de atributo solo pueda combinarse con determinados valores de otro atributo. Decirlo es mas complicado que razonarlo.  En el ejemplo, el color ’18′ solo puede ser combinado con los tipos ‘AA’ y ‘AB’.

Nos queda el atributo dependiente, representado en el Atributo 1, que toma siempre el mismo valor. Y el Atributo rellenable de, que toma valores respecto a otro atributo cualquiera combinable y no rellenable. Es el Atributo 4.

Así que al pulsar el boton “Solución”, nos presenta en los dos componentes TMemo, las combinaciones correctas y las descartadas. En este ejemplo, se aceptan 8 combinaciones y se descartan 16, del total de 24 combinaciones posibles. Las ventanas superiores unicamente existen con el motivo de permitirnos visualizar la información que contiene el generador y los atributos de cara a seguir mejor el ejemplo.

Ahora mismo, el desarrollo del ejemplo se va alejando progresivamente de los bocetos de las dos primeras entradas, representadas en el bucle anidado for, intimamente ligado al interfaz.

Podemos ver la declaración de tipos del módulo que contiene las clases protagonistas:


type

  TEstructura = Class(TObject)
    FIndice: Integer;
    FSiguiente: TObject;
  private
    procedure SetIndice(const Value: Integer);
    procedure SetSiguiente(const Value: TObject);
  published
  public
  constructor Create;
  destructor Destroy; override;
  function Situar(AIndice: Integer): TEstructura;
  property Indice: Integer read FIndice write SetIndice;
  property Siguiente: TObject read FSiguiente write SetSiguiente;
  end;

  TAtributo = class;

  TGenerador = Class(TObject)
  private
    FAtributos: TList;
    FCombinaciones: TStringList;
    function GetAtributos(Indice: Integer): TAtributo;
    function GetDescartado(Indice: Integer): Boolean;
    procedure SetDescartado(Indice: Integer; const Value: Boolean);
    function GetCombinaciones(Indice: Integer): String;
   protected
    procedure DoCombinar; virtual;
    procedure DoLimpiarCombinaciones; virtual;
    procedure AssignCombinaciones; virtual;
  public
    constructor Create;
    destructor Destroy; override;
    function Count: Integer;
    procedure SetUpPosition(AAtributo: TAtributo);
    procedure SetDownPosition(AAtributo: TAtributo);
    function GetPosicion(AAtributo: TAtributo): Integer;
    function GetMultiplicador(AAtributo: TAtributo): Integer;
    function GetTotalCombinaciones: Integer;
    function AddAtributo: TAtributo;
    procedure Combinar; virtual;
    procedure VerificarDependencias;
    property Atributos[Indice: Integer]: TAtributo read GetAtributos;
    property Descartado[Indice: Integer]: Boolean read GetDescartado write SetDescartado;
    property Combinaciones[Indice: Integer]: String read GetCombinaciones;
  end;

  TAtributo = class(TObject)
  private
    FGenerador: TGenerador;
    FListaValores: TStrings;
    FCombinaciones: TStrings;
    FCombinable: Boolean;
    FCaption: String;
    FRellenableDe: TAtributo;
    FDependienteDe: TAtributo;
    procedure SetCombinable(const Value: Boolean);
    procedure SetCaption(const Value: String);
    function GetValues(Indice: Integer): String;
    procedure SetValues(Indice: Integer; const Value: String);
    function GetCombinacion(Indice: Integer): String;
    procedure SetRellenableDe(const Value: TAtributo);
    procedure SetDependienteDe(const Value: TAtributo);
    function GetDependencia(Indice: Integer): TEstructura;
    function GetPosicion: Integer;
    { Private declarations }
  protected
    function DoGetMultiplicador: Integer; virtual;
  public
    { Public declarations }
    procedure LimpiarCombinaciones;
    procedure Combinar;
    function GetMultiplicador: Integer;
    function ValuesCount: Integer;
    procedure UpPosicion;
    procedure DownPosicion;
    constructor Create(AGenerador: TGenerador); virtual;
    destructor Destroy; override;
    function AddValue(const AValue: String): Integer; overload;
    function AddValue(const AValue, ADependencia: String): Integer; overload;
    procedure DeleteValue(const AValor: String);
    procedure VerificarDependencias;
    property Combinable: Boolean read FCombinable write SetCombinable;
    property Posicion: Integer read GetPosicion;
    property Caption: String read FCaption write SetCaption;
    property Values[Indice: Integer]: String read GetValues write SetValues;
    property Dependencia[Indice: Integer]: TEstructura read GetDependencia;
    property Combinacion[Indice: Integer]: String read GetCombinacion;
    property RellenableDe: TAtributo read FRellenableDe write SetRellenableDe;
    property DependienteDe: TAtributo read FDependienteDe write SetDependienteDe;
  end;

Descargar

No nos es posible en una entrada explicar todas las relaciones, por varios motivos razonables, entre los que considero el tiempo y el espacio. Así que lo mejor es que os descargueis el codigo fuente y le deis un vistazo.

Se me ocurren algunas ideas que pueden ser interesantes:

* A pesar de funcionar correctamente, o al menos eso creo, el hecho mismo de declarar las 3 clases en la parte pública del interfaz, hacen que queden disponibles para el uso, métodos que por ejemplo pertenen a TAtributo y que tan solo deberían estar disponibles para la instancia de TGenerador, puesto que va a ser esta quien quede intimamente ligada a ella, dado que conoce como deben ser ejecutados. De hecho, para ser formalmente correcto, el “usuario”, que en este caso es el formulario, debería batallar tanto con TGenerador como con la clase TAtributo, pero parece claro que nunca debería poder ejecutar por ejemplo el metodo VerificarDependencias en el atributo.  Y sin embargo, la disposición de las clases y la publicación de los métodos lo facilitan.

* En ocasiones, al pensar en clases aparecen puntos de vista que inicialmente no considerabamos. De hecho, lo que inicialmente era un bucle for anidado, ha sido sustituido por otro razonamiento. ¿Es posible que la clase Generador le pueda decir a cada atributo: Oye majo, que quiero que te combines? Ese es el motivo de que exista un metodo virtual DoCombinar, que se encarga de que cada atributo ejecute su propia expansión de combinaciones…

A eso me refería al comentar que a veces surgen nuevos puntos de vista, mejores o peores, pero distintos. Se me ocurrío razonar al estilo de la cuenta de la vieja. Y generar una lista de las disposiciones individuales de cada uno de los atributos en las combinaciones.

Este es el razonamiento de la vieja.

Supongamos los siguentes pares (Atributos, valores)

Atributo1  > A, B          Atributo2 >  C, D           Atributo3 > E, F

Podemos seguir un patron para combinarlos (el mismo que seguiría el bucle for)

ACE, ACF, ADE, ADF, BCE, BCF, BDE, BDF

Nos permite individualmente obtener una lista de valores

Atributo1 > A,A,A,A,B,B,B,B     Atributo2 > C,C,D,D,C,C,D,D   Atributo3 > E,F,E,F,E,F,E,F

Por eso aparece por algun lado el multiplicador, que no es mas que las veces que tiene que repetirse el valor en la cadena. En este ejemplo en concreto, el Atributo1 tiene un multiplicador de 4, el Atributo2, un multiplicador de 2, y el atributo3 un multiplicador de 1. Y dichos valores, son factibles de ser implementados mediante una sencilla función.

function TGenerador.GetMultiplicador(AAtributo: TAtributo): Integer;
var
  i,j: Integer;
begin
   if (AAtributo.Combinable) and (not Assigned(AAtributo.RellenableDe)) then
   begin
      Result:= 1;
      i:= FAtributos.IndexOf(AAtributo);
      if  (i < 0) then Result:= -1
      else begin
         for j := i+1 to FAtributos.Count - 1 do
            if (TAtributo(FAtributos[j]).ValuesCount = 0) or
                            (not TAtributo(FAtributos[j]).Combinable)    then
               Result:= Result * 1
            else Result:= Result * TAtributo(FAtributos[j]).ValuesCount;
      end;
   end
   else begin
      Result:= 1;
   end;
end;

* La tercera reflexión que ya se desprende de todo lo comentado, es el tiempo que dedicamos a implementar lo mismo. Visto de la primera forma, como razonabamos en las dos primeras entradas, la ganancia de tiempo podría ser considerable (menor tiempo). Este módulo, el que vemos en la entrada actual, tiene mayor trabajo para hacer exactamente lo mismo y quizás fuera una buena razón para considerar que perdemos el tiempo. Eso que deciamos de: -acaba como sea pero acaba (El jefe dixit) suele ser pan para hoy y hambre para mañana. Así que de cuando en cuando, deberíais dar un buen puñetazo en la mesa :-) y poner una silla trabando la entrada de vuestro despacho de forma que sea imposible que nadie os distraiga. ;-)  Por paradojas de la vida, esa misma persona que hoy os pide que acabeis ya el desarrollo como sea, es la misma que mañana os atará de los pulgares en el mástil mayor, si dentro de 6 meses se complica el desarrollo.

Vale… es una exageración bastante malvada pero yo creo que se me entiende. Y el peor enemigo que puede tener vuestro trabajo es que seais vuestro propio jefe… :-)

* Otra cosa que puede extrañar en el codigo, es que aparezca un tercero en discordia (la clase TEstructura). Es algo bastante habitual y ya  lo referia en alguna entrada anterior al hablar de aquel componente Buscador, que dejabamos atras al hablar de los hilos de ejecución. En aquel caso, el tercero en discorida era una implementación del patron iterador, que permitía recorrer la estructura y avanzar. En este caso concreto, me encontré que para poder trabajar con la dependencias, siendo posible que existiera una relación múltiple entre los valores: un color podia pertenecer a varios tipos, pensé que me era necesaria una forma sencilla de recorrerla. En eso me ayuda la clase TEstructura, que apunta a otra Estructura con el valor de relación. De esa forma, puedo buscar si un color pertenece a un tipo y saber si la combinación debe ser descartada o no.

* Y la ultima reflexión que cierra la entrada: la reutilización de nuestro codigo. Ligarnos a nuestros interfaces nos puede permitir reutilizarlos en forma de herencia y en eso ya nos ayuda el IDE de Delphi, al habilitar nuestros formularios en el repositorio de objetos. Sin embargo, al hacerlo así nos obliga a arrastrar no solo las ideas sino las apariencias y eso no es demasiado bueno en todos los casos.

Creo que os lo puedo demostrar… Dadme unos minutos… Pongamos por caso que dos días despues, vamos a formar una peña con el gabinete de programadores para jugar a las quinielas y poder hacernos ricos y dejar de trabajar. :-) WOWWWWW…  Y como somos previsores recordamos que nuestro programa hacía algo similar… De haberme ligado al interfaz, me hubiera sido imposible en este caso concreto reutilizar esa idea de forma sencilla. Sin embargo, nuestro nuevo planteamiento sí me permite mediante controles distintos y buscando una finalidad que no tiene porque coincidir con la original, reutilizar el código escrito.

Esta es la imagen de nuestro nuevo interfaz de ejemplo:

Dadle un vistazo:

azar1x2

Este es el código que ha permitido ampliar la funcionalidad original (por si teneis curiosidad)

:-)

unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ButtonGroup, ExtCtrls, StdCtrls, CheckLst, uAtributos;
const

RestriccionFicticia = 100000;

type
  TForm1 = class(TForm)
    chbPartidos: TCheckListBox;
    chb1: TCheckListBox;
    chbx: TCheckListBox;
    chb2: TCheckListBox;
    Label1: TLabel;
    btnCombinar: TButton;
    chbCombinables: TCheckListBox;
    lbxResultados: TListBox;
    Label2: TLabel;
    labCombinaciones: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure chbPartidosClickCheck(Sender: TObject);
    procedure btnCombinarClick(Sender: TObject);
    procedure chb1ClickCheck(Sender: TObject);
    procedure chbCombinablesClickCheck(Sender: TObject);
  private
    { Private declarations }
    FGenerador: TGenerador;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnCombinarClick(Sender: TObject);
var
  i: Integer;
begin
  fGenerador.Combinar;
  lbxResultados.Clear;
  for i:= 0 to fGenerador.GetTotalCombinaciones-1 do begin
    if not fGenerador.Descartado[i] then
      lbxResultados.Items.Add(fGenerador.Combinaciones[i]);
  end;
  labCombinaciones.Caption:= ' Total: ' + IntToStr(lbxResultados.Items.Count);
end;

procedure TForm1.chb1ClickCheck(Sender: TObject);
begin

  case (Sender as TCheckListBox).Tag of
    1: begin
             if (Sender as TCheckListBox).Checked[(Sender as TCheckListBox).ItemIndex] then
                FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].AddValue('1')
             else FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('1');

          if not FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].Combinable then begin
             chbx.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
             chb2.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
          end;
       end;
    2: begin
          if (Sender as TCheckListBox).Checked[(Sender as TCheckListBox).ItemIndex] then
             FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].AddValue('X')
          else FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('X');

          if not FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].Combinable then begin
             chb1.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
             chb2.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
          end;
       end;
    3: begin
             if (Sender as TCheckListBox).Checked[(Sender as TCheckListBox).ItemIndex] then
                FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].AddValue('2')
             else FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('2');

          if not FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].Combinable then begin
             chb1.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
             chbX.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
          end;
       end;
  end;

  if fGenerador.GetTotalCombinaciones > RestriccionFicticia then begin
    (Sender as TCheckListBox).Checked[(Sender as TCheckListBox).ItemIndex]:= False;
    case (Sender as TCheckListBox).Tag of
       1: FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('1');
       2: FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('X');
       3: FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('2');
    end;

    Raise Exception.Create('No se puede exceder de '+
                           IntToStr(RestriccionFicticia)+' combinaciones...');
  end;

  labCombinaciones.Caption:= 'Total combinaciones ' +
                             IntToStr(fGenerador.GetTotalCombinaciones);

end;

procedure TForm1.chbCombinablesClickCheck(Sender: TObject);
begin
   if not chbCombinables.Checked[chbCombinables.ItemIndex] then begin
     if chb1.Checked[chbCombinables.ItemIndex] then begin
        chb1.Checked[chbCombinables.ItemIndex]:= False;
        FGenerador.Atributos[chbCombinables.ItemIndex].DeleteValue('1');
     end;

     if chbX.Checked[chbCombinables.ItemIndex] then begin
       chbX.Checked[chbCombinables.ItemIndex]:= False;
       FGenerador.Atributos[chbCombinables.ItemIndex].DeleteValue('X');
     end;

     if chb2.Checked[chbCombinables.ItemIndex] then begin
       chb2.Checked[chbCombinables.ItemIndex]:= False;
       FGenerador.Atributos[chbCombinables.ItemIndex].DeleteValue('2');
     end;
   end;

  FGenerador.Atributos[chbCombinables.ItemIndex].Combinable:=
                               chbCombinables.Checked[chbCombinables.ItemIndex];
  labCombinaciones.Caption:= 'Total combinaciones ' +
                              IntToStr(fGenerador.GetTotalCombinaciones);
end;

procedure TForm1.chbPartidosClickCheck(Sender: TObject);
begin
  chbPartidos.Checked[chbPartidos.ItemIndex]:= True;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
  fAtrib: TAtributo;
begin
  FGenerador:= TGenerador.Create;

  for i := 0 to chbPartidos.Count - 1 do begin
     chbPartidos.Checked[i]:= True;
     fAtrib:= FGenerador.AddAtributo;
     fAtrib.Caption:= IntToStr(i);
     fAtrib.Combinable:= True;
  end;

  for i := 0 to chbCombinables.Count - 1 do begin
     chbCombinables.Checked[i]:= True;
  end;

end;

end.

Descargar

Creo que por hoy ya esta bien.

Hace un día estupendo. Y también hay que sacar tiempo para el deporte. No todo puede ser trabajo, ¿no? Además, no se si alguna vez comenté que suelo correr con frecuencia, y a lo largo de la temporada intento participar en alguna que otra carrera popular, casi siempre en la distancia Media Maratón. Creo que es importante el equilibrio personal y que debemos buscarlo en nuestro interior.

Quedan más ideas en el tintero. Las compartiremos en próximas entradas.

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.

Experimentos, reflexiones y otros artefactos (I)

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

Vamos a iniciar una pequeña serie especialmente dedicada a los programadores que se inician en Delphi y me perdonareis que no tenga la menor idea de cuantos capítulos contiene ni de cuanto tiempo se pueda extender. ¿Uno, dos? ¿quizás tres? ¿veintiuno?. Realmente os confieso que no lo se. Habitualmente se cierran las series en funcion de varias premisas: que hayas dicho todo (resulta raro), que no tengas tiempo para continuarla (supone cerrar uno o varios capitulos rápidamente e intentando que la persona que te lee no note que tienes prisa por acabar) y finalmente, que hayas iniciado un tema y no tengas ni pajolera idea de como acabarlo, bien porque has llegado a un callejon sin salida, bien porque ya no sabes como sacarle mas miga al asunto .

En realidad, mientras escribo me vienen muchas ideas a la cabeza, fruto casi siempre del día a día, pero creerme que resultan dificiles de abstraer a un contexto que sea ejemplar y reducido, como lo puede ser el contenido de un blog. La idea principal, para que me vayais comprendiendo, es quizás concienciar a este programador que se inicia en el entorno, que sufrirá durante toda su vida la tentación de olvidarse de las clases y de la orientación a objetos, para convertirse en un mero “usador” indiscriminado de ellos. Es una paradoja.  Y también una de las maldades que vienen implícitas en el sistema, como el lado oscuro de la fuerza que acompañaba al mítico Jedi. Anteriormente ya he comentado cosas como esta, que siempre he recalcado, y es precisamente por esa razón por lo que el destinatario de estas reflexiones o experimentos, es el programador que da sus primeros pasos en el entorno… 

 ¡venga!, ¡vamos!. Si. Tú… Ehhhh… Que te digo a ti…¿no tenías otras cosas que hacer…? ¡No te hagas el remolón que Tú ya sabes mas que las ratas coloradas!. ;-)   Y esta serie es solo para los programadores que se inician…

Ahora que nos hemos quedado solos (vosotros y este mendrugo que os escribe) y que nos dejan trabajar con tranquilidad, podemos hablar de las cosas sencillas.

Vamos a empezar por un ejemplo muy básico que nos permitirá ir avanzando en estas reflexiones.

Imaginaros que tenemos una producto cualquiera en nuestra manos. Podría ser una bicicleta o un despertador. ¡Qué mas da!. Lo que querais imaginar nos puede servir.

Nos vamos a valer de ese imaginario producto para hacer un ejercicio de abstracción que nos permita asignarle un tipo y un color. Y si llegamos un poco mas lejos, incluso podemos concretar que ambos tipos se definen mediante una cadena de dos caracteres que pueden ser enlazados para formar un hipotético código.

Puestos en esa tesitura, podriamos escribir unas lineas como estas:

//al pulsar el boton Solucion
procedure TmainRelaciones.btnSolucion1Click(Sender: TObject);
var
 i,j: Integer;
begin
   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]);
end;

//al crearse el formulario
procedure TmainRelaciones.FormCreate(Sender: TObject);
begin
   lbxTipo.Items.Add('AA');
   lbxTipo.Items.Add('AB');
   lbxTipo.Items.Add('CC');

   lbxColor.Items.Add('10');
   lbxColor.Items.Add('12');
   lbxColor.Items.Add('15');
end;

lbxTipo, lbxColor y lbxResultados, son tres objetos de la clase TListBox que hemos dejado caer en nuestro formulario. El último, lbxResultados, nos permite concatenar una cadena de 4 caracteres y…

Sencillo. Vale. Hemos resuelto el problema y nuestro jefe estará orgulloso de nosotros porque somos la repera y le hacemos ganar dinero facilmente, PERO no hemos pensado en terminos de objetos ni de clases… Dos bucles for, anidados nos permitieron crear la estructura de un hipotetico código compuesto. Nos ha bastado encontrar un clase (contenedor) para resolver la permanencia y ¡voila!… ¿Os habeis dado cuenta como he remarcado la palabra PERO en la oración anterior?

Es una verdadera pena porque pensar en terminos de clases es bastante divertido y hasta saludable. Y no es que al razonar en  terminos de clases no pueda usarse un bucle for… ¡faltaría mas! ¡alguno habrá que todavía me lo recrimine en los post posteriores!

Como primera reflexión, esta bien ¿no?… 

Parece que no estáis demasiado conformes!. :-)  

Bueno. Vale. Extenderé el experimento y nuestro jefe, con ese aire transcendental que le caracteriza, nos comentará que en ocasiones es importante que se puedan alterar el orden de los atributos del artefacto.

Ummmmmmmmmmmmmmmmmmm….

-¡Vale! ¡Ya está!

Nuestro programador siempre encuentra soluciones y en este caso lo resolvió haciendo uso de otro componente, concretamente de la clase TRadioGroup. Éste y una estructura case, y su jefe vuelve a sonreir… :-)

procedure TmainRelaciones.btnSolucion1Click(Sender: TObject);
var
 i,j: Integer;
begin
   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;
end;

 

Para este programador el formulario es una gran cajon donde se deposita todo y lo mas peligroso de estos razonamientos no es cómo son sus comienzos sino cuales son sus finales.

En sucesivas entradas intentaremos complicarle la vida a nuestro programador y si os parece, de forma similar, le propondremos algunos razonamientos en función de la programación orientada a objetos.