Eccoci all’ultimo capitolo sull’argomento (qui il link al precedente articolo: https://blog.lazaruspascal.it/2023/09/25/interfacce-interface-parte-3/)
In questo post costruiremo un programma che sfrutti le unità che abbiamo costruito negli scorsi capitoli.
In sintesi abbiamo implementato varie classi di animali dando a ciascuno delle caratteristiche sui movimenti tipici della specie (es. l’anatra corre, vola e nuota).
Tutto ciò è stato effettuato usando le interfacce per descrivere i movimenti e abbinarle alle singole classi o alle famiglie (i cui derivati ereditano i metodi appunto per ereditarietà).
Come prima cosa andiamo a generare una applicazione nuova (Form1, Unit1) e poi ci aggiungiamo le unità viste in precedenza.
Dopo di che andiamo a immettere nella Form due controlli (RadioGroup e Memo) che chiameremo RG_ElencoTipiAnimali e LogMemo, inoltre aggiungeremo due pulsanti che ci consentiranno di effettuare le operazioni volute.
In particolare in LogMemo verranno visualizzate tutte le messaggistiche.
Il progetto completo si trova in allegato.
Ciò che andremo a documentare è come andare a trovare per la tipologia di animale selezionata (o le famiglie) le proprietà di movimento ed eseguire i metodi delle varie interfacce attribuite alla classe.
Come vedremo, la cosa interessante è che andremo a costruire di volta in volta un riferimento alla singola interfaccia (tramite la classe e non si dovrà però eseguire alcun processo di “free” o distruzione. In effetti potete osservare che in tutto il programma non c’è alcuna esecuzione del “Free”.
Questo perchè le interfacce godono della proprietà di essere automaticamente distrutte nel momento in cui vanno a NIL o con il conteggio dei riferimenti a ZERO.
C’è ovviamente una contropartita a ciò: per mantenere il conteggio dei riferimenti, si aggiungono automaticamente delle chiamate a delle funzioni (Query, Add e Release di cui avevamo accennato nei precedenti Post) e ciò ovviamente ha un impatto sulle prestazioni. L’impatto in un normale programma non è apprezzabile, ma se sono richieste prestazioni importanti questo fatto può incidere in maniera più o meno significativa.
Come dicevo, viene chiamato il costruttore della classe ma come riferimento ad una interfaccia (Animali: IInterface) e non alla classe.
var Animali: IInterface;
Animali := AnimaliClass.Create(...)
Questa cosa fa si l’interfaccia così riferita venga liberata in maniera “automatica” secondo quanto già detto in precedenza.
Ci sono tre blocchi funzionali che consento di effettuare quanto detto come scopo:
1) Andiamo per praticità a costruire un elenco delle classi degli animali (e delle famiglie) implementati.
function TForm1.GetAnimaliClasses(): TArray<TAnimaliClass>;
begin
Result := TArray<TAnimaliClass>.Create(
TAnimali,
TUccelli, TAnatra, TStruzzo, TPinguino,
TPesci, TArringa, TSalmone, TSqualo,
TInsetti, TFormica, TFarfalla,
TMammiferi, TPippistrello, TGatto, TCane, TBalena
);
end;
2) Cicliamo nell’elenco e tramite le RTTI andiamo a riempire una griglia di “RadioButton” associando a ciascun selettore il nome della classe.
procedure TForm1.FillAnimalTypesButtonClick(Sender: TObject);
var
AnimaliClass: TAnimaliClass;
RttiContext: TRttiContext;
RttiTypes: TArray<TRttiType>;
RttiType: TRttiType;
begin
RG_ElencoTipiAnimali.Items.Clear();
for AnimaliClass in GetAnimaliClasses do
begin
Log(AnimaliClass);
RG_ElencoTipiAnimali.Items.Add(' ' + AnimaliClass.ClassName);
end;
RG_ElencoTipiAnimali.ItemIndex := 0;
end;
3) Ora che abbiamo l’elenco, possiamo selezione una classe e sempre tramite le RTTI e le RTL possiamo visualizzare i movimenti possibili (anche i derivati dall’interfaccia di famiglia) ed eseguirli.
procedure TForm1.ShowAnimalLocomotionsButtonClick(Sender: TObject);
var
AnimaliClassName: string;
AnimaliClass: TAnimaliClass;
Animali: IInterface;
Volare: IVola;
Saltare: ISalta;
Correre: ICorre;
Nuotare: INuota;
begin
Log('');
AnimaliClassName := Trim(RG_ElencoTipiAnimali.Items[RG_ElencoTipiAnimali.ItemIndex]);
Log(Format('Ricerca per il match della classe %s', [AnimaliClassName]));
for AnimaliClass in GetAnimaliClasses() do
begin
if AnimaliClass.ClassNameIs(AnimaliClassName) then
begin
Log(Format('Creazione della istanza della classe %s', [AnimaliClassName]));
//Qui costruiamo il riferimento all'interfaccia della classe !!!
Animali := AnimaliClass.Create(Self.Log);
Log(Format('Elenca le interfacce supportate da %s', [AnimaliClassName]));
if SupportsAndLog(Animali, ISalta, Saltare) then //, TypeInfo(ISalta)) then
begin
Log('Esecuzione metodo');
Saltare.Salta();
end;
if SupportsAndLog(Animali, IVola, Volare) then //, TypeInfo(IVola)) then
begin
Log('Esecuzione metodo');
Volare.Vola();
end;
if SupportsAndLog(Animali, ICorre, Correre) then //, TypeInfo(ICorre)) then
begin
Log('Esecuzione metodo');
Correre.Corre();
end;
if SupportsAndLog(Animali, INuota, Nuotare) then //, TypeInfo(INuota)) then
begin
Log('Esecuzione metodo');
Nuotare.Nuota();
end;
end;
end;
end;
function TForm1.SupportsAndLog(const Instance: IInterface; const IID:TGUID; out Intf; const InterfaceTypeInfo: Pointer = nil): Boolean;
begin
Result := SupportsAndLog_Works(Instance, Intf, IID, InterfaceTypeInfo);
end;
function TForm1.SupportsAndLog_Works(const Instance: IInterface; out Intf;
const IID: TGUID; const InterfaceTypeInfo: Pointer = nil): Boolean;
begin
Result := Supports(Instance, IID, Intf);
end;
Altra ulteriore caratteristica è che non abbiamo usato variabili di alcun tipo nelle classi (ad esempio per distinguere “gli animali”) e anche nel programma abbiamo usato lo stretto indispensabile per definire le istanze.
Qui potete scaricare il progetto completo per Lazarus : https://cloud.dyn-o-saur.com/Test_Interfacce.zip
Se avete suggerimenti o volete fare qualche domanda riguardo questo articolo, potete usufruire del forum italiano su Lazarus e Free Pascal: