… continuazione dell’argomento (qui la parte precedente: https://blog.lazaruspascal.it/2023/09/25/interfacce-interface-parte-1/)
In questo post andremo a definire alcune peculiarità delle interfacce.
La definizione di una “interface” prevede che vengano definiti alcuni metodi “obbligatori” che sono i seguenti:
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
questi metodi necessitano per implementare il già citato meccanismo di reference counting.
Per facilitare ed uniformare l’uso delle “interface”, sia FPC che Delphi (come anche tutti gli altri linguaggi) mettono a disposizione una interfaccia di base (IInterface) e una classe base (TInterfacedObject) che
già implementa i metodi della “IInterface”.
Per facilità e convenzione (anche se non mancano ovviamente eccezioni) le classi che usano direttamente le interfacce vengono derivate da TInterfacedObject.
Ovviamente le classi derivate poi seguono tutti i principi dell’OOP, ossia ereditarietà, polimorfismo, etc …
Come funziona il concetto di reference counting ?
Il tutto nasce dall’idea di poter “distruggere” l’istanza dell’interfaccia (o il meglio il suo rifermento nella classe che ne implementa i metodi) in maniera automatica quando questa non viene più usata.
In pratica quando si crea la classe il “reference counting” assume il valore di 1 e poi ad ogni uso si incrementa di 1 e si decrementa di uno quando questa viene “liberata”. Vi ricordo che l’interfaccia si riferisce solo a metodi (anche se nascosti dietro proprietà che potrebbero apparentemente fare credere a riferimenti a variabili), quindi i richiamati “uso” e “liberata” non definiscono altro che una chiamata al metodo (uso) e al suo ritorno (liberata).
Quando una interfaccia viene posta a NIL, il reference counting và a zero e viene distrutta. La distruzione avviene immediatamente (in Pascal non esiste il garbage collector) e questo può creare non pochi problemi al codice e di conseguenza al programmatore se il codice non è ben studiato e realizzato.
LE INTERFACCE NON DEVONO MAI ESSERE DISTRUTTE CON IL FREE !!!!
Di base tutte le principali classi implementano delle interfacce: una delle più conosciute forse è la IEnumerator, l’interfaccia che consente di iterare in un elenco senza usare un indice esplicito e che è necessaria se si vuole usare la sintassi (esempio):
var Lista: TStringList;
var Elemento: string;
...
...
for Elemento in Lista do
begin
ShowMessage(Elemento);
end;
L’interfaccia IEnumerator normalmente viene così definita:
IEnumerator = interface(IInterface)
function GetCurrent: TObject;
function MoveNext: Boolean;
procedure Reset;
property Current: TObject read GetCurrent;
end;
IEnumerable = interface(IInterface)
function GetEnumerator: IEnumerator;
end;
Queste interfacce definiscono i metodi standard che tutte le classi che vogliano fornire la “enumerazione” devono implementare. ATTENZIONE TUTTI I METODI DEFINITI NELLE INTERFACCE DEVONO ESSERE IMPLEMENTATI NELLA CLASSE CHE LI DERIVA.
In Pascal (FPC e DELPHI) il controllo della presenza della IEnumerator viene eseguita a livello di compilazione, oppure già a livello di IDE (in Delphi tramite CodeInsight, LSP).
Da notare che simpaticamente (lo dico con ironia) l’elenco dei componenti e dei controlli ad esempio di una Form non implementa la IEnumerator e quindi non è possibile iterare senza un indice.
Nel prossimo appuntamento (parte 3) andremo a vedere finalmente un esempio concreto (qui il link: https://blog.lazaruspascal.it/2023/09/25/interfacce-interface-parte-3/)
Ciao