Written by xinyiman Novembre 28, 2011, 04:33:00 pm19072 ViewsRating: (1 Rates)Print
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:
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:
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
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.
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
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.
About the author
xinyiman registered at Italian community of Lazarus and Free Pascal on Ottobre 14, 2011, 10:56:28 pm and has posted 3249 posts in the boards since then. Last visit was Oggi alle 11:55:21 am.
Questo blog non rappresenta una testata giornalistica poiché viene
aggiornato senza alcuna periodicità. Non può pertanto considerarsi un
prodotto editoriale ai sensi della legge n. 62/2001.
Questo sito utilizza cookie, anche di terze parti, per offriti servizi in linea con le tue preferenze. Chiudendo questo banner, scorrendo questa pagina, cliccando su un link o proseguendo la navigazione in altra maniera, acconsenti all’uso dei cookie.