Skip to content

Blog su Lazarus e il Pascal

Scrivi una volta e compila dove vuoi…

Interfacce (Interface) – Parte 3

Posted on 25 Settembre 202325 Settembre 2023 By DragoRosso

Eccoci di nuovo per proseguire nel percorso delle interfacce (Parte 3) (link al post precedente: https://blog.lazaruspascal.it/2023/09/25/interfacce-interface-parte-2/)

In questo post proveremo ad usare le interfacce.

Spero che tutti abbiamo chiaro i concetti dell’OOP (programmazione orientata agli oggetti), nel caso serva un rinfresco possiamo rinfrescare la mente leggendo questo articolo: http://www.lazaruspascal.it/index.php?page=53

Oltre a questi concetti, nella programmazione esistono anche altri paradigmi, correnti di pensiero più o meno assodate, regole e buona prassi, etc …

Alcuni di questi concetti si sono sufficientemente affermati e seguirli vuole dire produrre un software veramente di ottima qualità.

In questo link potete leggere un articolo su una serie di regole (in acronimo SOLID) che forniscono delle linee guida e dei suggerimenti sulla OOP (è in Italiano):

https://www.freecodecamp.org/italian/news/i-solid-i-principi-della-programmazione-orientata-agli-oggetti-spiegati-in-parole-povere/

Consiglio vivamente a tutti (scafati o no) di leggere l’articolo: gli esempi non sono scritti in Pascal ma sono assolutamente comprensibili anche a chi non mastica alcun altro linguaggio.

Ho fatto questa premessa perchè le interfacce sono uno di quei strumenti (ma non sono i soli e da soli non sono sufficienti) che consentono di aderire a quei principi.

Conosciamo tutti il concetto di ereditarietà (inheritance), ossia che un oggetto può ereditare le caratteristiche (proprietà, funzioni, procedure, etc…) da un altro oggetto e “farle sue”.

Tutti i linguaggi di programmazione, almeno quelli che io conosco (ma magari li conosco male), non consentono l’ereditarietà multipla cioè ereditare da diversi oggetti che non siano degli “ancestor” dell’oggetto da cui stiamo derivando il modello.

Ossia per fare un esempio:

TQuadrato = class(TFigura)

La classe TQuadrato è derivata da TFigura e erediterà le sue caratteristiche (e quelle delle classi superiori).

Ma se io volessi che TQuadrato erediti si da TFigura ma anche da TPoligono, che non è un “ancestor” di TFigura, non sarebbe possibile. Posso estendere la classe con TPoligono ma non ha lo stesso significato ne la stessa funzionalità di una ereditarietà.

Ora, invece con ns. sorpresa (si, anche la mia la prima volta che l’ho visto fare), le interfacce ci vengono in aiuto e come vedremo ci consentono di ereditare (non è il termine corretto ma rende comunque l’idea) da più oggetti.

L’esempio che riporto è semplice e basilare ma vuole solo fare capire che l’uso delle interfacce consente di astrarre il codice mantenendo comunque alcune regole del buon programmatore.

L’esempio può essere anche svolto usando solo classi, ma ciò andrebbe probabilmente (anzi sicuramente) contro i concetti SOLID.

L’esempio è uno spunto tratto da una pubblicazione di Nick Hodges (More Coding in Delphi) che lavora presso Embarcadero.

Incominciamo a definire alcune interfacce che sfrutteremo poi nelle classi. I GUID (per esempio {CEE797D9-C71D-419F-ACB8-2F17B3D42382}) sono gli indentificativi delle interfacce e sono usati quando si ha a che fare con ActiveX e con COM oppure quando si devono usare le RTTI come “as” e “is”. Ogni interfaccia deve avere un GUID UNICO !!!

Se non avete tali necessità, le GUID non sono necessarie (si possono omettere).

Queste interfacce derivano da IInterface (che ricordo è l’interfaccia base sia in Delphi che in FPC) e definiscono delle procedure, e come abbiamo già riportato nei precedenti articoli queste procedure non sono implementate.

unit uMovimenti;

interface

type
  ICorre = interface(IInterface)
  ['{CEE797D9-C71D-419F-ACB8-2F17B3D42382}']
    procedure Corre;
  end;

  INuota = interface(IInterface)
  ['{E00BD402-36FE-4928-9940-C6EF69A191EC}']
    procedure Nuota;
  end;

  //Questa non la useremo
  ISalta = interface(IInterface)
  ['{3B8E4264-2D67-42D4-8FCD-FCABE4DF6DB1}']
    procedure Salta;
  end;

  IVola = interface(IInterface)
  ['{B2FCB64B-C3A9-4212-AD43-2C02E7F54408}']
    procedure Vola;
  end;

implementation

end.

Ora definiamo le classi che implementeranno tali interfacce.

unit uAnimaliClasses;

{$mode ObjFPC}{$H+}

interface

uses
  uMovimenti, uLogEvent;

type
  {$REGION 'Documentation'}
  ///	<summary>
  ///	  Classe base per tutti gli animali.
  ///	</summary>
  ///	<remarks>
  ///	  <para>
  ///	    Implementa <see cref="System|IInterface" /> come discendente e può
  ///	    implementare le interfacce da <see cref="uMovimenti" />.
  ///	  </para>
  ///	  <para>
  ///	    Discende da <see cref="System|TInterfacedObject" /> per
  ///	    la <see cref="System|IInterface" /> per funzionare.
  ///	  </para>
  ///	  <para>
  ///	    Implementa <see cref="OnLog" /> per poter effettuare un Log.
  ///	  </para>
  ///	</remarks>
  {$ENDREGION}
  TAnimali = class(TInterfacedObject, IInterface)
  strict private
    FOnLog: TLogEvent;
  strict protected
    procedure Log(const Line: string); virtual;
    procedure LogMethod(const MethodNam: string); virtual;
  public
    constructor Create(const LogEvent: TLogEvent = nil); virtual;
    property OnLog: TLogEvent read FOnLog write FOnLog;
  end;

  {$REGION 'Documentation'}
  ///	<summary>
  ///	  Per la creazione delle classi derivate da <see cref="TAnimal" />.
  ///	  Richiede un virtual <see cref="TAnimal.Create">Create</see> constructor.
  ///	</summary>
  {$ENDREGION}
  TAnimaliClass = class of TAnimali;

  {$REGION 'Documentation'}
  ///	<remarks>
  ///	  Non tutti gli uccelli volano, e non solo gli uccelli volano,
  ///     così creiamo delle sottoclassi comuni vuote o no
  ///	  <see cref="uMovimenti|IVola" />
  ///	</remarks>
  {$ENDREGION}
  TUccelli = class(TAnimali)
  end;

  TAnatra = class(TUccelli, IVola, INuota, ICorre)
  public
    procedure Vola;
    procedure Corre;
    procedure Nuota;
  end;

  TStruzzo = class(TUccelli, ICorre)
  public
    procedure Corre;
  end;

  TPinguino = class(TUccelli, INuota, ICorre)
  public
    procedure Nuota;
    procedure Corre;
  end;

  //Pesci, generalmente nuotano tutti quindi già deriviamo
  //l'interfaccia del nuoto
  TPesci = class(TAnimali, INuota)
  public
    procedure Nuota;
  end;

  TArringa = class(TPesci)
  end;

  TSalmone = class(TPesci)
  end;

  //Test per l'override di Nuota dichiarato sia in TPesci che in TSqualo
  TSqualo = class(TPesci, INuota)
    procedure Nuota;
  end;

  //Insetti, generalmente corrono tutti quindi deriviamo
  //l'interfaccia ICorre
  TInsetti = class(TAnimali, ICorre)
  public
    procedure Corre;
  end;

  TFormica = class(TInsetti)
  end;

  TFarfalla = class(TInsetti, IVola)
  public
    procedure Vola;
  end;

  //I mammiferi non hanno caratteristiche comuni,
  //qualcuno vola, qualcuno nuota, altri corrono
  //quindi la classe base è vuota
  TMammiferi = class(TAnimali)
  end;

  TPippistrello = class(TMammiferi, IVola)
  public
    procedure Vola;
  end;

  TGatto = class(TMammiferi, ICorre)
  public
    procedure Corre;
  end;

  TCane = class(TMammiferi, INuota, ICorre)
  public
    procedure Corre;
    procedure Nuota;
  end;

  TBalena = class(TMammiferi, INuota)
  public
    procedure Nuota;
  end;

implementation

uses
  SysUtils;

constructor TAnimali.Create(const LogEvent: TLogEvent = nil);
begin
  inherited Create();
  OnLog := LogEvent;
end;

procedure TAnimali.Log(const Line: string);
var
  LogEvent: TLogEvent;
begin
  LogEvent := OnLog; // Occhio che non siamo THREAD SAFE
  if Assigned(LogEvent) then
    LogEvent(Line);
end;

procedure TAnimali.LogMethod(const MethodNam: string);
var
  Line: string;
begin
  Line := Format('%s.%s', [ClassName, MethodNam]);
  Log(Line);
end;

procedure TAnatra.Vola;
begin
  LogMethod('Vola');
end;

procedure TAnatra.Corre;
begin
  LogMethod('Corre come una papera ...');
end;

procedure TAnatra.Nuota;
begin
  LogMethod('Nuota');
end;

procedure TStruzzo.Corre();
begin
  LogMethod('Corre');
end;

procedure TPinguino.Nuota();
begin
  LogMethod('Nuota');
end;

procedure TPinguino.Corre();
begin
  LogMethod('Corre');
end;

procedure TPesci.Nuota();
begin
  LogMethod('Nuota');
end;

procedure TSqualo.Nuota();
begin
  LogMethod('Nuota e .... mangia');
end;

procedure TInsetti.Corre();
begin
  LogMethod('Corre');
end;

procedure TFarfalla.Vola();
begin
  LogMethod('Vola quando non è una crisalide');
end;

procedure TPippistrello.Vola();
begin
  LogMethod('Vola');
end;

procedure TGatto.Corre();
begin
  LogMethod('Corre');
end;

procedure TCane.Corre();
begin
  LogMethod('Corre');
end;

procedure TCane.Nuota();
begin
  LogMethod('Nuota');
end;

procedure TBalena.Nuota();
begin
  LogMethod('Nuota');
end;

end.                       

L’unita di uLogEvent definisce un modo comune per visualizzare delle stringhe come un “log” dentro un memo.

unit uLogEvent;

{$mode ObjFPC}{$H+}

interface

uses
  Classes, SysUtils;

type
  TLogEvent = procedure(const Line: string) of object;
  //TLogEvent = reference to procedure(const Line: string);

implementation

end.

Ciò che invece volevo evidenziare, prendendo ad esempio TAnatra e TFarfalla e TSalmone e TSqualo è l’uso delle interfacce:

Come vedete, nella definizione di TAnatra abbiamo inserito tre interfacce derivando da TUccelli (dove non abbiamo inserito interfacce) e quindi nella implementazione TAnatra implementa tre metodi (Corre, Vola, Nuota).

In TFarfalla, invece abbiamo inserito solo una interfaccia (IVola) con Vola che viene implementato ma derivando da TInsetti che già definisce “CORRE” si trova quindi già definito anche il metodo Corre, per ereditarietà e quindi potranno essere usate sia la procedura Vola che Corre.

In TSalmone ad esempio, non abbiamo inserito interfacce in quanto la classe “ancestor” TPesci ha gia derivato la interfaccia INuota (con la procedura Nuota()) che quindi è già a disposizione di tutto le sottoclassi.

In TSqualo abbiamo inserito l’interfaccia (INuota), nonostante la classe “ancestor” TPesci già lo includa. In questo caso viene effettuato l’override dell’interfaccia e la procedura NUOTA che verrà eseguita sarà quello di TSqualo e non quella di TPesci.

Si poteva fare anche con le classi ? Si, probabilmente si ma ci saremmo sicuramente caricati di procedure che non potevano essere usate, con un dispendio di IF, CASE, etc… Inoltre se aggiungiamo ad esempio il “Salta”
le due classi basi non cambiano e possiamo inserire l’apposita interfaccia solo “all’animale” che può effettivamente saltare.

Altra cosa da notare: a parte il LOG… non ci sono variabili dichiarate, ne classi assegnate … (tipo var Squalo: TSqualo) … e vedremo che neanche ci saranno come si potrebbe pensare… questo è il bello.

Ciao, alla prossima (qui il link: https://blog.lazaruspascal.it/2023/09/25/interfacce-interface-parte-4/)

Funzioni avanzate Tags:Class, Classi, Interfacce, Interface

Navigazione articoli

Previous Post: Interfacce (Interface) – Parte 2
Next Post: Interfacce (Interface) – Parte 4

Archivi

  • Maggio 2025
  • Aprile 2025
  • Ottobre 2024
  • Maggio 2024
  • Marzo 2024
  • Dicembre 2023
  • Novembre 2023
  • Settembre 2023
  • Giugno 2023
  • Maggio 2023
  • Marzo 2023
  • Maggio 2022
  • Aprile 2022
  • Febbraio 2022
  • Gennaio 2022
  • Dicembre 2021

Categorie

  • Android
  • Funzioni avanzate
  • Installazione alternativa di Lazarus e Free Pascal Compiler (FPC)
  • Installazioni
  • Lazarus ed il Web
  • Lazarus, l'IDE ed i suoi strumenti
  • Sintassi e concetti base
  • Strutture dati
  • Tutorial
Vai al forum Italiano

Articoli recenti

  • Il framework Brook – tutorial 2 – un server CRUD
  • Il framework Brook – tutorial 1 – primi passi
  • Installazione di Lazarus in Linux – Risoluzione problemi
  • Autenticazione 2FA
  • Installazione di Free Pascal e Lazarus su Manjaro Linux

Commenti recenti

  1. DragoRosso su Installazione di Lazarus e Free Pascal – su Linux – tramite package manager a linea di comando

Copyright © 2025 Blog su Lazarus e il Pascal.

Powered by PressBook WordPress theme

Utilizziamo i cookie per essere sicuri che tu possa avere la migliore esperienza sul nostro sito. Se continui ad utilizzare questo sito noi assumiamo che tu ne sia felice.