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;
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.