Alphasite

The programmers site

Languages

Inicio de sesión

Google AdSense

Encuesta

En línea

En este momento hay 0 usuarios y 17 invitados en línea.

Origenes de datos (abstractos)


Supongamos que tenemos un programa que necesita leer cierta información (por ejemplo un listado de clientes con sus datos personales y la lista de facturas asociadas a cada cliente (esto parece un problema de la uni eh!!)). El cliente no nos ha pedido ningún tipo de configuración especial para obtener los datos de forma que estamos barajando el obtener la información de un fichero o de una base de datos (aunque el cliente es un poco 'cliente' de forma que nunca se puede estar segudo de que no vaya a cambiar de opinión).

Una de las opciones que tenemos (para mi la peor) es meter el código que obtiene la información "a pelo" en nuestro código, por ejemplo, si suponemos que cuando se pulsa un botón se un form con un lista de clientes podríamos tener algo así (por simplicidad no voy a tener en cuenta el manejado de excepciones):

procedure frmListadoClientes.OnCreate(sender : TObject);
var
  Query : TQuery;
begin
  Query := TQuery.Create(nil);
  // Configurar la query para acceder a la base de datos
  ConfiguraBDQuery(Query);
  Query.SQL.ADD('SElECT * FROM Clientes');
  Query.Open;
  while not Query.Eof do
  begin
    // Rellenar los datos del form
    ListView1.Add(Query.FieldByName('nombre').AsString);
    Query.Next;
  end;
end;

De esta forma cada vez que se cree el form (también podría ir en el OnShow) se obtiene la información de la base de datos... sencillo, rápido ... y bastante chapucero. Mediante esta forma de hacer las cosas hay poca reutilización de código (siempre ponemos la misma query cuando queremos obtener los clientes) y un cambio en los nombres de las tablas o de los campos nos obliga a reescribir el código de todas las funciones en las que se haga referencia.

La solución más obvia y elegante a este problema es encapsular todas las funciones de acceso a base de datos en una clase estática o singleton y proporcionar las estructuras de datos necesarias para acceder de forma correcta a la información (no está todo el código, omito algunas partes que son más o menos obvias).

type
{ Forward Declaration (por que GetFacturas
  de la clase cliente necesita funciones de
  AccesoBD }

TAccesoBD = class;

TCliente = class
  private
    FNombre : string;
    FApellidos : string;
    FFacturas : TListaFacturas;
    function GetFacturas : TListaFacturas;
  public
    constructor Create(AId : integer);
 
    property Nombre : String read FNombre write FNombre;
    property Apellidos : String read FApellidos write FApellidos;
    property Facturas : TListaFacturas read GetFacturas;
end;

TListaClientes = class
  private
    FList : TList;
    function GetItem(Index : Integer) : TCliente;
  public
    constructor Create;

    property Items[Index : integer] : TCliente read GetItem; default;
    function AddCliente(cliente : TCliente) : TCliente;
    procedure DelCliente(index : integer)
end;

TAccesoBD = class
  public
    function ObtenerClientes : TListaClientes;
    function ObtenerFacturasCliente(AIdCliente : Integer) : TListaFacturas;
end;

implementation

function TAccesoBD.ObtenerClientes : TListaClientes;
var
  Qry : TQuery;
  Cliente : TCliente;
begin
  result := TListaClientes.Create;
  Qry := TQuery.Create;
  // Configurar la query para acceder a la base de datos
  ConfiguraBDQuery(Query);
  Qry.SQL.ADD('SElECT * FROM Clientes');
  Qry.Open;
  while not Qry.Eof do
  begin
    // Rellenar la lista
    Cliente := TCliente.Create(Qry.FieldByName('id').AsInteger);
    Cliente.Nombre := Qry.FieldByName('nombre').AsString;
    Cliente.Apellidos := Qry.FieldByName('apellidos').AsString;
    // ETC para el resto de propiedades ...
    result.AddCliente(Cliente);
    Query.Next;
  end;
end;

De esta forma (no he puesto el código correspondiente a que la clase sea un singleton o una clase estática de CSharp puesto que ya está explicado en este artículo como ya he mencionado) tenemos todo el acceso a la base de datos centralizado en un solo lugar de forma que, si algo cambia, nos será muchisimo más facil actualizarlo y, si el cambio solo afecta a como se rellenan las estructuras, el resto del código no habrá que tocarlo lo más minimo, además de hacer el añadido de campos mucho más sencillo.

Aún así todavía podemos darle una vuelta de tuerca más, abstraer los orígenes de datos. ¿Que significa esto?. Puesto que ya hemos definidos unas estructuras en código que van a representar los datos que nuestra aplicacion necesita (en este caso TCliente, TFactura, TListaClientes, etc...) a nosotros (como usuarios de esos datos) no nos importa quien nos los proporcione, mientras tengamos los datos quien nos los de nos da igual. Así que vamos a refinar un poquito, lo primero es abstraer la figura del origen de datos para que refine un modelo de comportamiento:

type TOrigenDatos = class
  public
    function ObtenerClientes : TListaClientes; virtual; abstract;
    function ObtenerFacturasCliente(AIdCliente : Integer) :
                                    TListaFacturas; virtual; abstract;
    function ObtenerListaDeCobros(AIdCliente : Integer) :
                                  TListaCobros; virtual; abstract;
    //.....
end;

type TAccesoBD = class(TOrigenDatos)
  public
    function ObtenerClientes : TListaClientes; override;
    function ObtenerFacturas(AIdCliente) : TListaFacturas; override;
    function ObtenerListaDeCobros(AIdCliente : Integer) : TListaCobros; override;
end;

type TAccesoFichero = class(TOrigenDatos)
  public
    function ObtenerClientes : TListaClientes; override;
    function ObtenerFacturas(AIdCliente) : TListaFacturas; override;
    function ObtenerListaDeCobros(AIdCliente : Integer) : TListaCobros; override;
end;

type TAccesoNetWork = class(TOrigenDatos)
  public
    function ObtenerClientes : TListaClientes; override;
    function ObtenerFacturas(AIdCliente) : TListaFacturas; override;
    function ObtenerListaDeCobros(AIdCliente : Integer) : TListaCobros; override;
end;

type TStub = class(TOrigenDatos)
  public
    function ObtenerClientes : TListaClientes; override;
    function ObtenerFacturas(AIdCliente) : TListaFacturas; override;
    function ObtenerListaDeCobros(AIdCliente : Integer) : TListaCobros; override;
end;

De esta forma podemos definir distintos tipos de origenes de datos sin tocar absolutamente nada de nuestra aplicación. Definimos un comportamiento, lo que entendemos por origen de datos, y definimos distintos tipos. Además, podemos definir e implementar primero un Stub que proporcione unos datos pequeños introducidos a mano (2 o 3 clientes con 2 o 3 facturas) para probar nuestra aplicación y, una vez que todo funcione, implementar origenes de datos más complejos (como un orígen de datos de red o un origen de fichero).

Por ejemplo, podemos tener los siguientes fragmentos de código (en la inicialización del programa y en el mostrado del form citado al principio)

program ProgramaPrincipal;
var
  // Variable global para el acceso a datos
  OrigenDatos : TOrigenDatos;
  origen : string;
begin
  origen := LeeFicheroIni('OrigenDatos');
  if origen = 'Red' then
    OrigenDatos := TAccesoNetWork.Create
  else if origen = 'BD' then
    OrigenDatos := TAccesoBD.Create
  else if origen = 'Fichero' then
    OrigenDatos := TAccesoFichero.Create
  else
    OrigenDatos := TStub.Create;
end;

{ En otra unidad, en que hay un form que necesita los datos }

procedure TForm1.OnShow(Sender : TObject);
var
  clientes : TListaClientes;
  i : integer;
begin
  clientes := OrigenDatos.ObtenerListaClientes;
  for i := 0 to clientes.Count - 1 do
  begin
    ListView1.Add(clientes[i].Nombre);
  end;
end;

De esta forma, el origen de los datos es independiente del código de la aplicación y sea cual sea, el form1 obtendrá dichos datos y los mostrará en su TreeView, ya vengan de la red, de un archivo o de donde sea, el nunca lo sabrá.

Por último, es interesante mencionar otra forma de programar los accesos a bases de datos. Es una técnica conocida como metaprogramación que consiste en "escribir código que escribe código". Gran parte de los accesos a base de datos que se realiza (y la mayor parte de las aplicaciones que se realizan hoy en dia se apoyan sobre una base de datos en algún momento) son básicamente del mismo tipo, obtención de listados con algun tipo de filtro y poco más. Por otro lado, las consultas SQL son relativamente sencillas y podrían generarse de forma automatizada de forma sencilla. Existen herramientas que permiten realizar esta generación de código de forma que, analizando la estructura de la base de datos, se genere automáticamente el código necesario para obtener los listados más comunes. AF-Gen es una de esas herramientas, forma parte de la Arquitectura AF y permite la generación de dicho código de forma automática.

Disclaimer: La gente de ASPL son colegas del nene pero eso no tiene nada que ver,(bueno, quizá solo un poco) la herramienta es muy buena y es GPL así que echarle un buen ojo.