OOP e Classi

Con le classi si introduce anche il discorso di programmazione orientata agli oggetti (OOP), in quanto il Free Pascal (quindi di rimando anche Lazarus) è considerato tale.
Per capire cosa si intende per programmazione orientata agli oggetti ci viene in aiuto Wikipedia che ne da una definizione ottima:

La programmazione orientata agli oggetti (OOP, Object Oriented Programming) è un paradigma di programmazione, che prevede di raggruppare in un'unica entità (la classe) sia le strutture dati che le procedure che operano su di esse, creando per l'appunto un "oggetto" software dotato di proprietà (dati) e metodi (procedure) che operano sui dati dell'oggetto stesso. La programmazione orientata agli oggetti può essere vista come una modulazione di oggetti software sulla base degli oggetti del mondo reale.
La modularizzazione di un programma viene realizzata progettando e realizzando il codice sotto forma di classi che interagiscono tra di loro. Un programma ideale, realizzato applicando i criteri dell'OOP, sarebbe completamente costituito da oggetti software (istanze di classi) che interagiscono gli uni con gli altri.
La programmazione orientata agli oggetti è particolarmente adatta a realizzare interfacce grafiche.

Sulla base di ciò Wikipedia definisce le classi in questo modo:

Nella programmazione orientata agli oggetti una classe è un costrutto di un linguaggio di programmazione usato come modello per creare oggetti. Il modello comprende attributi e metodi che saranno condivisi da tutti gli oggetti creati.
Una classe può rappresentare una persona, un luogo oppure una cosa, ed è quindi l'astrazione di un concetto implementata in un programma per computer. Fondamentalmente essa definisce al proprio interno lo stato ed il comportamento dell'entità di cui è rappresentazione. I dati che descrivono lo stato sono memorizzati nelle variabili membro, mentre il comportamento è descritto da blocchi di codice riutilizzabile chiamati metodi.

Vediamo ora i vantaggi della programmazione ad oggetti
Incapsulamento
L'incapsulamento è la proprietà per cui i dati che definiscono lo stato interno di un oggetto sono accessibili ai metodi dell'oggetto stesso, mentre non sono visibili ai clients. Per alterare lo stato interno dell'oggetto, è necessario invocarne i metodi, ed è questo lo scopo principale dell'incapsulamento. Infatti, se gestito opportunamente, esso permette di vedere l'oggetto come una black-box, cioè una "scatola nera" di cui, attraverso l'interfaccia, è noto cosa fa, ma non come lo fa.

Ereditarietà
Il meccanismo dell'ereditarietà permette di derivare nuove classi a partire da quelle già definite. Una classe derivata attraverso l'ereditarietà, o sottoclasse, mantiene i metodi e gli attributi delle classi da cui deriva (classi base, o superclassi); inoltre, può definire i propri metodi o attributi, e può ridefinire il codice eseguibile di alcuni dei metodi ereditati tramite un meccanismo chiamato overriding.
Quando una classe eredita da una sola superclasse si parla di eredità singola; viceversa, si parla di eredità multipla.
L'ereditarietà può essere usata come meccanismo per ottenere l'estensibilità e il riuso del codice, e risulta particolarmente vantaggiosa quando viene usata per definire sottotipi, sfruttando le relazioni is-a esistenti nella realtà di cui la struttura delle classi è una modellizzazione. Oltre all'evidente riuso del codice della superclasse, l'ereditarietà permette la definizione di codice generico attraverso il meccanismo del polimorfismo.

Polimorfismo
Nella programmazione ad oggetti, con il nome di polimorfismo per inclusione, si indica il fatto che lo stesso codice eseguibile può essere utilizzato con istanze di classi diverse, aventi una superclasse comune.
Piccolo esempio che spiega l'implementazione del polimorfismo con free pascal: http://www.lazaruspascal.it/index.php?topic=2333.msg14593

Vediamo un po' di codice per capire meglio, come dichiarare una classe semplice ed usarla:
Codice: [Seleziona]

type
ClasseUtenti=class
private
NomeUtente: string;
public
costructor Create();
destructor Free();
                procedure setUtente(valore: string);
                function getUtente(): string;
end;
Constructor ClasseUtenti.Create();
begin
end;
Destructor ClasseUtenti.Free();
begin
end;
procedure ClasseUtenti.setUtente(valore: string);
begin
     NomeUtente:=valore;
end;
function ClasseUtenti.getUtente(): string;
begin
     getUtente:=NomeUtente;
end;


Con queste righe di codice abbiamo appena creato la nostra prima classe che contiene il nome di un ipotetico utente. Come si può notare la variabile che conterrà il nome dell'utente si chiama NomeUtente ed è una stringa. Questa variabile (proprietà) è stata dichiarata all'interno della sezione private, ovvero non è visibile all'esterno della classe, e può essere letta o settata solo attraverso funzioni e procedure che fanno parte della classe stessa. La funzione per leggere e la procedura (metodi) per settare  il nome dell'utente sono dichiarate nella sezione public, ovvero sono visibili all'esterno della classe. Un altra cosa che va notata è che all'interno del type...end; sono presenti solo le dichiarazioni dei metodi stessi, i metodi sono scritti per intero fuori dal type...end; e per identificare che appartengono ad una classe si mette il nome della classe prima del nome del metodo separati solo da un punto. Una nota particolare va posta sulle due diciture constructor e distructor che servono per far capire quale funzione crea (allocca in memoria) la classe e quale la distrugge (deallocca dalla memoria).

Usare la classe appena creata è davvero facile, diamo un occhiata a come fare, creiamo una nuova applicazione console con questo codice:
Codice: [Seleziona]

program project1;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes, SysUtils, CustApp,
  { you can add units after this };
{INIZIO MIO CODICE}
type
ClasseUtenti=class
private
NomeUtente: string;
public
constructor Create();
destructor Free();
                procedure setUtente(valore: string);
                function getUtente(): string;
end;
{FINE MIO CODICE}
type

  { TMyApplication }

  TMyApplication = class(TCustomApplication)
  protected
    procedure DoRun; override;
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
    procedure WriteHelp; virtual;
  end;

{ TMyApplication }

procedure TMyApplication.DoRun;
var
  ErrorMsg: String;
  app:ClasseUtenti; {VARIABILE DICHIARATA DA ME}
begin
  // quick check parameters
  ErrorMsg:=CheckOptions('h','help');
  if ErrorMsg'' then begin
    ShowException(Exception.Create(ErrorMsg));
    Terminate;
    Exit;
  end;

  // parse parameters
  if HasOption('h','help') then begin
    WriteHelp;
    Terminate;
    Exit;
  end;
  { add your program here }

  {
   dopo aver dichiarato una variabile di nome app di tipo ClasseUtenti
   ora devo far si che a questa variabile venga assegnato uno spazio in
   memoria per poterci scrivere dentro e quindi eseguo la riga successiva
   dove indico che ad app voglio assegnare NomeClasse.Costruttore
  }
  app:=ClasseUtenti.Create();
  app.setUtente('xinyiman');  {setto la variabile con il nome del mio utente, nel mio caso xinyiman}
  writeln('Utente: ',app.getUtente()); {ora vado a stampare a video il nome dell'utente inserito nella classe poco fa}
  app.Free(); {ho fatto tutto quello che dovevo con questa variabile app e quindi libero dalla memoria lo spazio che occupa richiamando il distruttore}
  // stop program loop
  Terminate;
end;

constructor TMyApplication.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  StopOnException:=True;
end;

destructor TMyApplication.Destroy;
begin
  inherited Destroy;
end;

procedure TMyApplication.WriteHelp;
begin
  { add your help code here }
  writeln('Usage: ',ExeName,' -h');
end;


{INIZIO MIO CODICE}
Constructor ClasseUtenti.Create();
begin
end;
Destructor ClasseUtenti.Free();
begin
end;
procedure ClasseUtenti.setUtente(valore: string);
begin
     NomeUtente:=valore;
end;
function ClasseUtenti.getUtente(): string;
begin
     getUtente:=NomeUtente;
end;
{FINE MIO CODICE}

var
  Application: TMyApplication;
begin
  Application:=TMyApplication.Create(nil);
  Application.Title:='My Application';
  Application.Run;
  Application.Free;
end.


Ed ecco un esempio che spiega come usare l'ereditarietà in Lazarus, ampliando l'esempio precedente.
Codice: [Seleziona]

program project1;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes, SysUtils, CustApp
  { you can add units after this };
{INIZIO MIO CODICE}
type
ClasseUtenti=class
private
NomeUtente: string;
public
     constructor Create(); virtual;
     destructor Free();
              procedure setUtente(valore: string);
              function getUtente(): string;
end;

type
        AdvClasseUtenti=class(ClasseUtenti)
        public
              constructor Create(); override; //il termine override significa "calpestare" o "passare sopra" e serve per dire che questo costruttore deve andare a sostituire il costruttore della superclasse
              function GetUtenteInverso(): string;
end;
{FINE MIO CODICE}
type

  { TMyApplication }

  TMyApplication = class(TCustomApplication)
  protected
    procedure DoRun; override;
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
    procedure WriteHelp; virtual;
  end;

{ TMyApplication }

procedure TMyApplication.DoRun;
var
  ErrorMsg: String;
  app:AdvClasseUtenti; {VARIABILE DICHIARATA DA ME}
begin
  // quick check parameters
  ErrorMsg:=CheckOptions('h','help');
  if ErrorMsg'' then begin
    ShowException(Exception.Create(ErrorMsg));
    Terminate;
    Exit;
  end;

  // parse parameters
  if HasOption('h','help') then begin
    WriteHelp;
    Terminate;
    Exit;
  end;
  { add your program here }

  {
   dopo aver dichiarato una variabile di nome app di tipo ClasseUtenti
   ora devo far si che a questa variabile venga assegnato uno spazio in
   memoria per poterci scrivere dentro e quindi eseguo la riga successiva
   dove indico che ad app voglio assegnare NomeClasse.Costruttore
  }
  app:=AdvClasseUtenti.Create();
  writeln('Utente: ',app.getUtente()); {ora vado a stampare a video il fatto che non è stato inserito ancora nessun utente}
  app.setUtente('xinyiman');  {setto la variabile con il nome del mio utente, nel mio caso xinyiman}
  writeln('Utente: ',app.getUtente()); {ora vado a stampare a video il nome dell'utente inserito nella classe poco fa}
  writeln('Utente: ',app.GetUtenteInverso()); {ora stampo al contrario il nome dell'utente, funzione che ho aggiunto alla classe figlia}
  app.Free(); {ho fatto tutto quello che dovevo con questa variabile app e quindi libero dalla memoria lo spazio che occupa richiamando il distruttore}
  // stop program loop
  Terminate;
end;

constructor TMyApplication.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  StopOnException:=True;
end;

destructor TMyApplication.Destroy;
begin
  inherited Destroy;
end;

procedure TMyApplication.WriteHelp;
begin
  { add your help code here }
  writeln('Usage: ',ExeName,' -h');
end;


{INIZIO MIO CODICE}
Constructor ClasseUtenti.Create();
begin
     Self.setUtente('');
end;
Destructor ClasseUtenti.Free();
begin
end;
procedure ClasseUtenti.setUtente(valore: string);
begin
     NomeUtente:=valore;
end;
function ClasseUtenti.getUtente(): string;
begin
     getUtente:=NomeUtente;
end;

constructor AdvClasseUtenti.Create();
begin
     inherited Create(); //inherited significa ereditato e vuol dire che vado ad eseguire prima il costruttore della super classe e poi il codice di questo costruttore
     {ovviamente potevamo escludere la riga sopra che inizia con inherited solo che non avrebbe eseguito il costruttore della superclasse, cosa non necessaria nel nostro esempio, ma fondamentale da sapere per livelli di programmazione un po' più avanzati di questo esempio}
     Self.setUtente('NESSUN UTENTE INSERITO'); //la procedura SetUtente l'ho ereditata dalla superclasse
end;

function AdvClasseUtenti.GetUtenteInverso(): string;
var
   i: integer;
   ret,appoggio: string;
begin
     ret:='';
     appoggio:=Self.getUtente();
     i:=Length(appoggio);
     while (i>=0) do
     begin
          ret:=ret + appoggio[i];
          Dec(i); //decremento la variabile i
     end;
     GetUtenteInverso:=ret;
end;
{FINE MIO CODICE}

var
  Application: TMyApplication;
begin
  Application:=TMyApplication.Create(nil);
  Application.Title:='My Application';
  Application.Run;
  Application.Free;
end.


Vediamo ora alcune parole riservate:
private - questo significa che gli elementi qui definiti, sono disponibili o visibili da altre classi procedure o funzioni definite all'interno della stessa unit di programma.
protected - questo significa che le voci definite qui sono solo disponibili o visibili da classi che discendono da quella classe antenata,ed eredita le sue proprietà o metodi
public - questo significa che le voci definite qui sono solo disponibili per qualunque unit che includa la unit corrente in essa con la clausula Uses
published - è simile alla sezione public, ma il compilatore genera anche il tipo di informazioni che è necessario per lo streaming automatico di queste classi. Spesso la lista delle voci appare nel Object Inspector di Lazarus; se non compare una lista published,tutti i campi public appaiono normalmente in the Object Inspector.

Metodi
Methods
Un metodo è esattamente come una procedura standard o una funzione, ma può avere qualche directives.
Alcuni dei metodi di definizione di cui sopra sono etichettati con la direttiva virtual; altri sono etichettati con la direttiva override.

virtual significa che il tipo o il grado effettivo di un metodo non è noto al momento della compilazione,ma è stato selezionato in run-time a seconda di ciò che effettivamente richiama il sotto-programma sul momento. Potrebbe essere considerato un segnaposto nella definizione della classe.
override significa che in fase di esecuzione la definizione data a livello locale può prendere il posto di una definizione ereditata da una classe antenata, soprattutto se fosse virtuale. Se si desidera utilizzare il metodo definito nella classe antenata, a volte è necessario chiamare in modo specifico con la clausola inherited come visto nell'esempio precedente.

Tutti i metodi virtual o override sono metodi dinamici, tutti gli altri vengono chiamati metodi statici.


SMF 2.0.8 | SMF © 2011, Simple Machines
Privacy Policy
SMFAds for Free Forums
TinyPortal © 2005-2012

Go back to article