lunes, 8 de enero de 2018

Datos en capas desacopladas (1 de 2)

Siguiente nivel

Vamos a modificar el proyecto que usamos a modo de ejemplo en esta entrada para desacoplar las distintas capas y así llevarlo a un nivel superior en lo que a arquitectura e implementación se refiere.

Interfaces

Primero vamos a declarar las interfaces, cuatro en total, una para la conexión con la base de datos, otra para el acceso a datos, otra para el proveedor de datos y otra para la lógica de datos.

  IDataConnection = interface(IInterface)
  ['{7C9D5EDD-EDF8-4587-8276-9504C0F1EF4E}']
    procedure DoConfigureDataSet(aDataSet: TDataSet);
  end;

  IDataAccess = interface(IInterface)
  ['{CF319B42-93E2-48CA-AA04-1C8B8FFB78EA}']
    function GetDataSet: TDataSet;
  end;

  IDataProvider = interface(IInterface)
  ['{56670237-2508-4561-8B34-009C3C447197}']
    function GetProvider: TComponent;
  end;

  IDataLogic = interface(IInterface)
  ['{85F47C24-1AC9-4431-A9DA-43EE9C3A2D92}']
    function GetDataSet(const aName: string): TDataSet;
    procedure DoAbrir;
    procedure DoGuardarCambios;
  end;


Estas cuatro interfaces las vamos a declarar en su propia unidad llamada UInterfaces.

Clases

Lo siguiente es modificar las clases para que implementen su respectiva interface. Por ejemplo, el DataModule de lógica de datos debe implementar la interface IDataLogic.

  TdtmdlDLCountry = class(TDataModule, IDataLogic)

  { IDataLogic }
  protected
    function GetDataSet(const aName: string): TDataSet;
    procedure DoAbrir;
    procedure DoGuardarCambios;
  { IDataLogic }


Cuando una clase declara que implementa una interface tiene la obligación de implementar todos los métodos de la interface tal y como están declarados en la propia interface.

Registrar

Para poder desacoplar las clases que implementan las interfaces de las clases que las usan vamos a usar un mecanismo de registración muy simple. En la unidad UClases vamos a declarar variables para asignar las clases que implementan las interfaces.

var
  oDataConnectionClass: TComponentClass = nil;
  oDataAccessClass: TComponentClass = nil;
  oDataProviderClass: TComponentClass = nil;
  oDataLogicClass: TComponentClass = nil;


Y en la unidad URegistrar asignamos las clases correspondientes.

procedure DoRegistrar;
begin
  UClases.oDataConnectionClass := DMCon_IBX.TdtmdlCon_IBX;
  UClases.oDataAccessClass := DMDACountry_IBX.TdtmdlDACountry_IBX;
  UClases.oDataProviderClass := DMDSPCountry.TdtmdlDSPCountry;
  UClases.oDataLogicClass := DMDLCountry.TdtmdlDLCountry;
end;


Las bondades de Delphi nos permiten hacer esta maravilla. Primero declaramos una variable de tipo TComponentClass y luego le asignamos una clase descendiente de TComponentClass.

Instancias

En las clases que las usan vamos a crear las instancias de dichas clases cuando sea necesario. Por ejemplo, en el DataModule de lógica de datos sería así:

  { new introduced members }
  private
    FDataProvider: IDataProvider;
  protected
    procedure DoDataProvider;
  { new introduced members }


procedure TdtmdlDLCountry.DoDataProvider;
begin
  if not Assigned(FDataProvider) then
  begin
    if not Assigned(oDataProviderClass) then
      raise Exception.Create('Clase proveedora de datos no asignada');
    FDataProvider := oDataProviderClass.Create(nil) as IDataProvider;
  end;
  clntdtstCountry.SetProvider(FDataProvider.GetProvider);
end;


El DataModule de lógica de datos necesita una instancia del DataModule proveedor de datos. Pero no necesita saber nada sobre la clase en sí siempre y cuando dicha clase implemente la interface IDataProvider. Y aunque la variable oDataProviderClass sea de tipo TComponentClass, al crear una instancia se ejecutará el contructur de la clase asignada (en este caso, el constructor de TdtmdlDSPCountry)

Descargar proyecto Delphi 2010