Ver Mensaje Individual
  #8  
Antiguo 02-09-2005
Avatar de roman
roman roman is offline
Moderador
 
Registrado: may 2003
Ubicación: Ciudad de México
Posts: 20.269
Reputación: 10
roman Es un diamante en brutoroman Es un diamante en brutoroman Es un diamante en bruto
Cita:
Empezado por adlfv
Segun comentas, a la hora de crear el objeto, tengo que utilizar el constructor de la clase concreta, con lo cual si tengo 100 querys creados dinámicamente en el código, tendría que hacer 100 cambios, no?
No.

Es cierto que implica más trabajo pero no donde estás pensando. Te pongo una idea un poco más explícita.

Considera primero estas definiciones de clases:


Código Delphi [-]
type
  TDataClientClass = class of TDataClient;

  TDataClient = class
  public
    constructor Create; virtual;
    function NewConnection: TDataConnection; virtual; abstract;
  end;

  TDataConnection = class
  public
    property Host: String;
    property User: String;
    property Password: String;
    property Database: String;
    property Params: TStrings;

    function NewQuery(Sql: String): TDataQuery; virtual; abstract;
    procedure Open; virtual; abstract;
  end;

  TDataQuery = class
  public
    property Sql: String;

    procedure SetInteger(ParamName: String; Value: Integer); virtual; abstract;
    procedure SetDouble(ParamName: String; Value: Double); virtual; abstract;
    procedure SetString(ParamName: String; Value: String); virtual; abstract;
    procedure SetDate(ParamName: String; Value: TDate); virtual; abstract;
    procedure SetTime(ParamName: String; Value: TTime); virtual; abstract;

    function GetInteger(FieldName: String; Default: Integer): Integer; virtual; abstract;
    function GetDouble(FieldName: String; Default: Double): Double; virtual; abstract;
    function GetString(FieldName: String; Default: String): String; virtual; abstract;
    function GetDate(FieldName: String; Default: TDate): TDate; virtual; abstract;
    function GetTime(FieldName: String; Default: TTime): TTime; virtual; abstract;

    procedure Run; virtual; abstract;
    procedure First; virtual; abstract;
    procedure Next; virtual; abstract;
    function EoQ: Boolean; virtual; abstract;
  end;

TDataQuery representa un Query genérico, tal como lo describí antes. Aquí, los métodos SetXXX establecen valores de parámetros mientras que los métodos GetXXX devuelven valores de campos una vez ejecutada la consulta. Es decir, lo equivalente a las propiedades Params y Fields de un Query. Como antes, todos estos métodos junto con Run (que abre o ejecuta la consulta) y First, Next y EoQ (para navegación) son abstractos y cada descendiente debe implementarlos.

Ahora bien, los objetos Query los produce un objeto DataConnection con su método NewQuery. DataConnection representa una conexión a un servidor; de ahí sus propiedades User, Password y Database. La propiedad Params serviría para establecer otro tipo de parámetros según sea el caso.

TDataConnection también es una clase con métodos abstractos. Un objeto DataConnection lo produce un objeto DataClient con su método NewConnection. DataClient englobaría todo lo que sea común a conexiones a servidores del mismo tipo.

Para cada motor que pienses usar necesitarás crear descendientes adecuados, por ejemplo:


Código Delphi [-]
type
  TZeosDataClient = class(TDataClient)
  end;

  TZeosDataConnection = class(TDataConnection)
  end;

  TZeosDataQuery = class(TDataQuery)
  end;

TZeosDataClient implementará NewConnection para producir un objeto TZeosDataConnection.

TZeosDataConnection implementará NewQuery para producir un objeto TZeosDataQuery.

TZeosDataQuery implementará los métodos SetXXX, GetXXX, Run y de navegación (posiblemente tomándolos de un TZQuery).

Ahora bien; tú puedes, como dices, tener cien objetos DataQuery en tu aplicación; pero si todos los produces con TDataConnection.NewQuery, el día que quieras cambiar el motor que uses, sólo tienes que cambiar la creación del DataConnection específico, que normalmente sólo será uno a menos que ataques varios servidores a la vez.

Aún en este caso no importa, porque los objetos DataConnection son producidos por un objeto DataClient genérico, del cuál sólo habrá uno por motor, digamos ZeosDataClient, encargado de producir cualquier conexión vía Zeos.

En resumen, el curso de tu aplicación podría ser:


Código Delphi [-]
var
  GlobalDataClient: TDataClient;

...

GlobalDataClient := TZeosDataClient.Create;

...

var
  DataConnection: TDataConnection;

...

DataConnection.Host := 'xxx.yyy.zzz';
DataConnection := GlobalDataClient.NewConnection;
DataConnection.User := 'usuario';
DataConection.Password := 'password';

DataConnection.Open;

...

var
  Poductos: TDataQuery;

...

Productos := DataConnection.NewQuery;
Productos.Sql := 'select * from productos';
Productos.Run;

while not Productos.EoQ do
begin

  {
    Aquí usas Productos.GetXXX para llenar tus controles de edición
  }

  Productos.Next;
end;

Cuando cambies de motor, digamos a Firebird, únicamente debes cambiar la línea

GlobalDataClient := TZeosDataClient.Create;

por

GlobalDataClient := TFirebirdDataClient.Create;

Todos los objetos DataConnection que hayas creado en tu aplicación serán ahora creados por TFirebirdDataClient.Connection (por el polimorfismo) y todos los objetos DataQuery que uses serán ahora creados por TFirebirdDataConnection (otra vez por el polimorfismo).

Claro que para ello deberás implementar TFirebirdDataClient, TFirebirdDataConnection y TFirebirdDataQuery, pero esto lo haces una sóla vez y sin tocar la aplicación principal excepto por la línea susomentada ( )

---------------------

Todo esto son sólo ideas al aire y ciertamente no originales y susceptibles de cambios.

Por ejemplo, todos los objetos producidos por NewConnection y NewQuery tendrán que ser destruídos en algún momento. Por ello, en lugar de clases abstractas yo usaría interfaces para aprovechar su autodestrucción. En lugar de GetXXX podrías publicar una función abstracta en la clase base que devuelva un TDataSet que puedes conectar a tus controles DBAware. No me gusta mucho esto último pues TDataSet tiene muchos métodos y propiedades que de alguna manera te permiten actuar directamente con la base de datos con lo cual puedes perder un poco de control. En todo caso podría usarse un TClientDataSet llenado internamente por DataQuery.

Lo importante aquí es simplemente el hecho de poder cambiar de un motor a otro sin- en teoría -grandes cambios.

Yendo más lejos. Para cambiar de motor debes, como dije, cambiar la línea

GlobalDataClient := TZeosDataClient.Create;

pero puedes hacer uso de una fabrica de clientes:


Código Delphi [-]
  TDataClientFactory = class
  public
    procedure RegisterClient(ClientId: String; DataClientClass: TDataClientClass);
    procedure NewClient(ClientId: String): TDataClient;
  end;

  var
    DataClientFactory: TDataClientFactory;

Usando un TStringList, por ejemplo, puedes guadar una asociación entre un identificador y la clase de cliente correspondiente, por ejemplo:

'zeos' => TZeosDataClient

El método RegisterClient('zeos', TZeosDataClient) guardaría dicha asociación de manera que puedes obtener un objeto DataClient partiendo del identificador:

GlobalDataClient := DataClientFactory.NewClient('zeos');

El método NewClient construiría el objeto DataClient polimórficamente partiendo de la referencia de clase asociada a la cadena 'zeos':


Código Delphi [-]
var
  Index: Integer;
  DataClientClass: TDataClientClass;

begin
  Index := FClients.IndexOf(ClientId);
  DataClientClass := TDataClientClass(FClients.Objects[Index]);

  Result := DataClientClass.Create;
end;

Cada motor que implementes deberá hacer uso de DataClientFactory.RegisterClient para registrar su case de objeto DataClient; por ejemplo en la sección initialization de la unidad donde implementes las clases del motor.

Suponte ahora que todas las clases asociadas a un motor las colocas dentro de un paquete. Si usas paquetes dinámicos puedes cargar el paquete asociado a un motor en tiempo d ejecución partiendo sólo del identificador del cliente ('zeos' por ejemplo), de manera que puedes añadir motores sin necesidad siquiera de recompilar la aplicación principal.

// Saludos

Última edición por roman fecha: 02-09-2005 a las 06:46:02.
Responder Con Cita