Search code examples
classdelphimodeldelphi-2009

String to Class: Conversion Class or Base Conversion Method?


I'm working on a project where I builded a structure composed by a basic class "TFuncao", two descendant classes "TSolicitacao", "TResposta" and as many as needed descendant classes, all of them are class-like-models only with properties.

The first one is the focus of this question, it has a method "ParaTexto" that returns a string of all its properties in a specific way (basically converting them to string). That "TSolicitacao" class has many descendant and each one has the method "ParaTexto" calling its ancestor function and adding its own properties.

So, my question is:

Should I keep this method "ParaTexto"? Or remove it and create a class with a method that receives a "TSolicitacao" and accordingly to its type (a descendent class) returns a string?

I thought of this because a model class technically should have only getters and setters.

Code - the getters and setters where removed to save space:

"TFuncao" class file:

unit Funcao;

interface

uses
  Funcao.Tipo;

type
  TFuncao = class abstract
  private
    FFUNCAO: TFuncaoTipo;
    FSEQUENC: string;
    procedure SetFUNCAO(const Value: TFuncaoTipo);
    procedure SetSEQUENC(const Value: string);
  published
    property SEQUENC: string read FSEQUENC write SetSEQUENC;
    property FUNCAO: TFuncaoTipo read FFUNCAO write SetFUNCAO;
    constructor Create; virtual;
  end;

"TSolicitacao" class file:

unit Funcao.Solicitacao;

interface

uses
  Funcao;

type
  TSolicitacao = class abstract (TFuncao)
  published
    function ParaTexto: string; virtual;
  end;

implementation

uses
  Funcoes,
  SysUtils;

{ TSolicitacao }

function TSolicitacao.ParaTexto: string;
begin
  Result :=
    Copy(CompletarComEspacoAEsquerda(SEQUENC, 4), 1, 4) +
    Completar0AEsquerda(IntToStr(Integer(FUNCAO)), 2);
end;

end.

"TVendaSolicitacao" class file - a descendant class example of "TSolicitacao":

unit Venda.Solicitacao;

interface

uses
  Funcao.Solicitacao,
  Funcao.Tipo,
  Venda.Solicitacao.Produto;

type
  TVendaSolicitacao = class (TSolicitacao)
  private
    FNUMFISCAL: Integer;
    FNUMPDV: Integer;
    FAUTORIZ: string;
    FCONV_TIPO: Integer;
    FProdutos: TVendaSolicitacaoProdutoList;
    procedure SetAUTORIZ(const Value: string);
    procedure SetCONV_TIPO(const Value: Integer);
    procedure SetNUMFISCAL(const Value: Integer);
    procedure SetNUMPDV(const Value: Integer);
    procedure SetProdutos(const Value: TVendaSolicitacaoProdutoList);
  published
    property NUMPDV: Integer read FNUMPDV write SetNUMPDV;
    property NUMFISCAL: Integer read FNUMFISCAL write SetNUMFISCAL;
    property AUTORIZ: string read FAUTORIZ write SetAUTORIZ;
    property CONV_TIPO: Integer read FCONV_TIPO write SetCONV_TIPO;
    property Produtos: TVendaSolicitacaoProdutoList read FProdutos write SetProdutos;
    function ParaTexto: string; override;
    constructor Create; override;
  end;

implementation

uses
  SysUtils,
  Funcoes;

{ TVendaSolicitacao }

constructor TVendaSolicitacao.Create;
begin
  inherited;
  FUNCAO := ftVenda;
  FProdutos := TVendaSolicitacaoProdutoList.Create;
end;

function TVendaSolicitacao.ParaTexto: string;
begin
  Result :=
    inherited +
    Completar0AEsquerda(NUMPDV, 4) +
    Completar0AEsquerda(NUMFISCAL, 6) +
    Completar0AEsquerda(AUTORIZ, 12) +
    IntToStr(CONV_TIPO);

  if Produtos.Count > 0 then
    Result := Result + #13#10 + Produtos.ParaTexto;
end;

end.

"TConversao" class file - as I see it:

unit Conversao;

interface

uses
  Funcao;

type
  TConversao = class
  published
    function ParaTexto(Funcao: TFuncao): string;
  end;

implementation

uses
  Solicitacao,
  Solicitacao.Venda,
  Funcoes;

function ParaTexto(Funcao: TFuncao): string;
begin
  Result := '';

  if (Funcao is TSolicitacao) then
    Result :=
      Copy(CompletarComEspacoAEsquerda((Funcao as TSolicitacao).SEQUENC, 4), 1, 4) +
      Completar0AEsquerda(IntToStr(Integer((Funcao as TSolicitacao).FUNCAO)), 2);

  if (Funcao is TVendaSolicitacao) then
  begin
    Result :=
      inherited +
      Completar0AEsquerda((Funcao as TVendaSolicitacao).NUMPDV, 4) +
      Completar0AEsquerda((Funcao as TVendaSolicitacao).NUMFISCAL, 6) +
      Completar0AEsquerda((Funcao as TVendaSolicitacao).AUTORIZ, 12) +
      IntToStr((Funcao as TVendaSolicitacao).CONV_TIPO);

    if Produtos.Count > 0 then
      Result := Result + #13#10 + Produtos.ParaTexto;
  end;

Solution

  • Yes, you should keep this method, in this way code is much more readable and reusable. Say, you defined new descendant of TSolicitacao, you override ParaTexto and that's it. All changes are in one place - in your class definition.

    You could also try to use RTTI to iterate through all properties of class and make string automatically.

    UPDATE

    Some hints how you can use RTTI (working example). You define different types of integers:

      T7digitsInt = type Int64;
      T8digitsInt = type Int64;
    

    Also, function type for conversion from Int64 to string:

    TConvertCustomIntToStringProc = function (num: Int64): string;
    

    Some test class which have properties, something like your TSolicitacao:

      TEntry = class (TObject)
        private
          fPlainInt: Int64;
          f7digitInt: T7digitsInt;
          fWriteOnlyInt: T7digitsInt;
          f8digitInt: T8digitsInt;
        published
          property PlainInt: Int64 read fPlainInt write fPlainInt;
          property NotSoPlainInt: T7digitsInt read f7digitInt write f7digitInt;
          property AnotherInt: T7digitsInt write fWriteOnlyInt;
          property Biggest: T8digitsInt read f8digitInt write f8digitInt;
      end;
    

    Also, we declare global variable in our unit, this is list of different property types:

    var PropertyConverters: TDictionary<string, TConvertCustomIntToStringProc>;
    

    Now, in implementation section we define some functions which convert integer to string:

    function ShowPlainInt64(num: Int64): string;
    begin
        Result:=IntToStr(num);
    end;
    
    function Show7digitsInt(num: Int64): string;
    begin
        Result:=Format('%.7d',[num]);
    end;
    

    We 'register' this functions to handle Int64 and T7DigitsInt respectively, somewhere in initialization, for example in TForm1.create:

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      PropertyConverters:=TDictionary<string,  ConvertCustomIntToStringProc>.Create;
      PropertyConverters.Add('Int64',ShowPlainInt64);
      PropertyConverters.Add('T7digitsInt',Show7DigitsInt);
    end;
    

    In TForm1.FormShow we create instance of TEntry and populate it with values to test our convertor, and then try to access them, iterating through properties:

    procedure TForm1.FormShow(Sender: TObject);
    var entry: TEntry;
        ctx : TRttiContext;
        rt : TRttiType;
        prop : TRttiProperty;
        convertProc: TConvertCustomIntToStringProc;
    begin
      entry:=TEntry.Create;
      entry.PlainInt:=12;
      entry.NotSoPlainInt:=34;
    
      //iterating through all the properties with RTTI
      ctx := TRttiContext.Create();
        try
            rt := ctx.GetType(entry.ClassType);
    
            for prop in rt.GetProperties() do begin
              if prop.IsReadable then
                if PropertyConverters.TryGetValue(prop.PropertyType.Name,convertProc) then
                  Memo1.Lines.Add(prop.Name+'='+convertProc(prop.GetValue(entry).AsOrdinal))
                else
                  Memo1.lines.Add(prop.Name+':unregistered type')
              else
                Memo1.Lines.Add(prop.Name+':write-only');
    
    
    
            end;
        finally
            ctx.Free();
        end;
    
    end;
    

    That's what I got:

    PlainInt=12
    NotSoPlainInt=0000034
    AnotherInt:write-only
    Biggest:unregistered type
    

    As you see, we have several integers, they just have different type and we made them show differently because of that. If there are not so many types, than code like that could be very efficient.