Pascal - Classi
Hai visto che Pascal Objects esibisce alcune caratteristiche del paradigma orientato agli oggetti. Implementano l'incapsulamento, l'occultamento dei dati e l'ereditarietà, ma hanno anche dei limiti. Ad esempio, gli oggetti Pascal non prendono parte al polimorfismo. Quindi le classi sono ampiamente utilizzate per implementare un corretto comportamento orientato agli oggetti in un programma, in particolare il software basato su GUI.
Una classe è definita quasi allo stesso modo di un oggetto, ma è un puntatore a un oggetto piuttosto che all'oggetto stesso. Tecnicamente, questo significa che la Classe è allocata sull'Heap di un programma, mentre l'Oggetto è allocato sullo Stack. In altre parole, quando dichiari una variabile del tipo di oggetto, occuperà tanto spazio sullo stack quanto la dimensione dell'oggetto, ma quando dichiari una variabile del tipo di classe, prenderà sempre la dimensione di un puntatore sullo stack. I dati effettivi della classe saranno nell'heap.
Definizione di classi Pascal
Una classe viene dichiarata allo stesso modo di un oggetto, utilizzando la dichiarazione del tipo. La forma generale di una dichiarazione di classe è la seguente:
type class-identifier = class
private
field1 : field-type;
field2 : field-type;
...
public
constructor create();
procedure proc1;
function f1(): function-type;
end;
var classvar : class-identifier;
Vale la pena notare i seguenti punti importanti:
Le definizioni di classe dovrebbero rientrare solo nella parte relativa alla dichiarazione del tipo del programma.
Una classe viene definita utilizzando il class parola chiave.
I campi sono elementi di dati che esistono in ogni istanza della classe.
I metodi sono dichiarati all'interno della definizione di una classe.
C'è un costruttore predefinito chiamato Createnella classe Root. Ogni classe astratta e ogni classe concreta è un discendente di Root, quindi tutte le classi hanno almeno un costruttore.
C'è un distruttore predefinito chiamato Destroynella classe Root. Ogni classe astratta e ogni classe concreta è un discendente di Root, quindi tutte le classi hanno almeno un distruttore.
Definiamo una classe Rectangle che ha due membri di dati di tipo intero: lunghezza e larghezza e alcune funzioni membro per manipolare questi membri di dati e una procedura per disegnare il rettangolo.
type
Rectangle = class
private
length, width: integer;
public
constructor create(l, w: integer);
procedure setlength(l: integer);
function getlength(): integer;
procedure setwidth(w: integer);
function getwidth(): integer;
procedure draw;
end;
Scriviamo un programma completo che crei un'istanza di una classe rettangolo e disegna il rettangolo. Questo è lo stesso esempio che abbiamo usato discutendo di Pascal Objects. Scoprirai che entrambi i programmi sono quasi uguali, con le seguenti eccezioni:
Dovrai includere la direttiva {$ mode objfpc} per usare le classi.
Dovrai includere la direttiva {$ m +} per usare i costruttori.
L'istanziazione della classe è diversa dall'istanziazione dell'oggetto. Solo dichiarando la variabile non si crea spazio per l'istanza, si utilizzerà il costruttore create per allocare la memoria.
Ecco l'esempio completo:
{$mode objfpc} // directive to be used for defining classes
{$m+} // directive to be used for using constructor
program exClass;
type
Rectangle = class
private
length, width: integer;
public
constructor create(l, w: integer);
procedure setlength(l: integer);
function getlength(): integer;
procedure setwidth(w: integer);
function getwidth(): integer;
procedure draw;
end;
var
r1: Rectangle;
constructor Rectangle.create(l, w: integer);
begin
length := l;
width := w;
end;
procedure Rectangle.setlength(l: integer);
begin
length := l;
end;
procedure Rectangle.setwidth(w: integer);
begin
width :=w;
end;
function Rectangle.getlength(): integer;
begin
getlength := length;
end;
function Rectangle.getwidth(): integer;
begin
getwidth := width;
end;
procedure Rectangle.draw;
var
i, j: integer;
begin
for i:= 1 to length do
begin
for j:= 1 to width do
write(' * ');
writeln;
end;
end;
begin
r1:= Rectangle.create(3, 7);
writeln(' Draw Rectangle: ', r1.getlength(), ' by ' , r1.getwidth());
r1.draw;
r1.setlength(4);
r1.setwidth(6);
writeln(' Draw Rectangle: ', r1.getlength(), ' by ' , r1.getwidth());
r1.draw;
end.
Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:
Draw Rectangle: 3 by 7
* * * * * * *
* * * * * * *
* * * * * * *
Draw Rectangle: 4 by 6
* * * * * *
* * * * * *
* * * * * *
* * * * * *
Visibilità dei membri della classe
La visibilità indica l'accessibilità dei membri della classe. I membri della classe Pascal hanno cinque tipi di visibilità:
Suor n | Visibilità e accessibilità |
---|---|
1 | Public Questi membri sono sempre accessibili. |
2 | Private È possibile accedere a questi membri solo nel modulo o nell'unità che contiene la definizione della classe. È possibile accedervi dall'interno dei metodi della classe o dall'esterno. |
3 | Strict Private È possibile accedere a questi membri solo dai metodi della classe stessa. Altre classi o classi discendenti nella stessa unità non possono accedervi. |
4 | Protected È uguale a private, tranne per il fatto che questi membri sono accessibili ai tipi discendenti, anche se sono implementati in altri moduli. |
5 | Published È uguale a Public, ma il compilatore genera le informazioni sul tipo necessarie per lo streaming automatico di queste classi se il compilatore è nello stato {$ M +}. I campi definiti in una sezione pubblicata devono essere di tipo classe. |
Costruttori e distruttori per classi Pascal
I costruttori sono metodi speciali, che vengono chiamati automaticamente ogni volta che viene creato un oggetto. Quindi sfruttiamo appieno questo comportamento inizializzando molte cose tramite le funzioni di costruzione.
Pascal fornisce una funzione speciale chiamata create () per definire un costruttore. Puoi passare tutti gli argomenti che desideri nella funzione di costruzione.
L'esempio seguente creerà un costruttore per una classe denominata Books e inizializzerà prezzo e titolo per il libro al momento della creazione dell'oggetto.
program classExample;
{$MODE OBJFPC} //directive to be used for creating classes
{$M+} //directive that allows class constructors and destructors
type
Books = Class
private
title : String;
price: real;
public
constructor Create(t : String; p: real); //default constructor
procedure setTitle(t : String); //sets title for a book
function getTitle() : String; //retrieves title
procedure setPrice(p : real); //sets price for a book
function getPrice() : real; //retrieves price
procedure Display(); // display details of a book
end;
var
physics, chemistry, maths: Books;
//default constructor
constructor Books.Create(t : String; p: real);
begin
title := t;
price := p;
end;
procedure Books.setTitle(t : String); //sets title for a book
begin
title := t;
end;
function Books.getTitle() : String; //retrieves title
begin
getTitle := title;
end;
procedure Books.setPrice(p : real); //sets price for a book
begin
price := p;
end;
function Books.getPrice() : real; //retrieves price
begin
getPrice:= price;
end;
procedure Books.Display();
begin
writeln('Title: ', title);
writeln('Price: ', price:5:2);
end;
begin
physics := Books.Create('Physics for High School', 10);
chemistry := Books.Create('Advanced Chemistry', 15);
maths := Books.Create('Algebra', 7);
physics.Display;
chemistry.Display;
maths.Display;
end.
Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:
Title: Physics for High School
Price: 10
Title: Advanced Chemistry
Price: 15
Title: Algebra
Price: 7
Come il costruttore implicito denominato create, esiste anche un metodo distruttore implicito distruggere utilizzando il quale è possibile rilasciare tutte le risorse utilizzate nella classe.
Eredità
Le definizioni di classe Pascal possono opzionalmente ereditare da una definizione di classe padre. La sintassi è la seguente:
type
childClas-identifier = class(baseClass-identifier)
< members >
end;
L'esempio seguente fornisce una classe di romanzi, che eredita la classe Libri e aggiunge più funzionalità in base al requisito.
program inheritanceExample;
{$MODE OBJFPC} //directive to be used for creating classes
{$M+} //directive that allows class constructors and destructors
type
Books = Class
protected
title : String;
price: real;
public
constructor Create(t : String; p: real); //default constructor
procedure setTitle(t : String); //sets title for a book
function getTitle() : String; //retrieves title
procedure setPrice(p : real); //sets price for a book
function getPrice() : real; //retrieves price
procedure Display(); virtual; // display details of a book
end;
(* Creating a derived class *)
type
Novels = Class(Books)
private
author: String;
public
constructor Create(t: String); overload;
constructor Create(a: String; t: String; p: real); overload;
procedure setAuthor(a: String); // sets author for a book
function getAuthor(): String; // retrieves author name
procedure Display(); override;
end;
var
n1, n2: Novels;
//default constructor
constructor Books.Create(t : String; p: real);
begin
title := t;
price := p;
end;
procedure Books.setTitle(t : String); //sets title for a book
begin
title := t;
end;
function Books.getTitle() : String; //retrieves title
begin
getTitle := title;
end;
procedure Books.setPrice(p : real); //sets price for a book
begin
price := p;
end;
function Books.getPrice() : real; //retrieves price
begin
getPrice:= price;
end;
procedure Books.Display();
begin
writeln('Title: ', title);
writeln('Price: ', price);
end;
(* Now the derived class methods *)
constructor Novels.Create(t: String);
begin
inherited Create(t, 0.0);
author:= ' ';
end;
constructor Novels.Create(a: String; t: String; p: real);
begin
inherited Create(t, p);
author:= a;
end;
procedure Novels.setAuthor(a : String); //sets author for a book
begin
author := a;
end;
function Novels.getAuthor() : String; //retrieves author
begin
getAuthor := author;
end;
procedure Novels.Display();
begin
writeln('Title: ', title);
writeln('Price: ', price:5:2);
writeln('Author: ', author);
end;
begin
n1 := Novels.Create('Gone with the Wind');
n2 := Novels.Create('Ayn Rand','Atlas Shrugged', 467.75);
n1.setAuthor('Margaret Mitchell');
n1.setPrice(375.99);
n1.Display;
n2.Display;
end.
Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:
Title: Gone with the Wind
Price: 375.99
Author: Margaret Mitchell
Title: Atlas Shrugged
Price: 467.75
Author: Ayn Rand
Vale la pena notare i seguenti punti importanti:
I membri della classe Books hanno protected visibilità.
La classe Novels ha due costruttori, quindi il overload L'operatore viene utilizzato per il sovraccarico delle funzioni.
La procedura Books.Display è stata dichiarata virtual, in modo che lo stesso metodo della classe Novels possa farlo override esso.
Il costruttore Novels.Create chiama il costruttore della classe base usando il inherited parola chiave.
Interfacce
Le interfacce sono definite per fornire un nome di funzione comune agli implementatori. Diversi implementatori possono implementare queste interfacce in base alle loro esigenze. Puoi dire che le interfacce sono scheletri, che sono implementati dagli sviluppatori. Di seguito è riportato un esempio di interfaccia:
type
Mail = Interface
Procedure SendMail;
Procedure GetMail;
end;
Report = Class(TInterfacedObject, Mail)
Procedure SendMail;
Procedure GetMail;
end;
Si noti che, quando una classe implementa un'interfaccia, dovrebbe implementare tutti i metodi dell'interfaccia. Se un metodo di un'interfaccia non è implementato, il compilatore restituirà un errore.
Classi astratte
Una classe astratta è una classe che non può essere istanziata, ma solo ereditata. Una classe astratta viene specificata includendo la parola simbolo abstract nella definizione della classe, in questo modo:
type
Shape = ABSTRACT CLASS (Root)
Procedure draw; ABSTRACT;
...
end;
Quando si eredita da una classe astratta, tutti i metodi contrassegnati come astratti nella dichiarazione della classe del genitore devono essere definiti dal figlio; inoltre, questi metodi devono essere definiti con la stessa visibilità.
Parola chiave statica
La dichiarazione di membri o metodi della classe come statici li rende accessibili senza bisogno di un'istanza della classe. Non è possibile accedere a un membro dichiarato come statico con un oggetto di classe istanziato (sebbene possa farlo un metodo statico). L'esempio seguente illustra il concetto:
program StaticExample;
{$mode objfpc}
{$static on}
type
myclass=class
num : integer;static;
end;
var
n1, n2 : myclass;
begin
n1:= myclass.create;
n2:= myclass.create;
n1.num := 12;
writeln(n2.num);
n2.num := 31;
writeln(n1.num);
writeln(myclass.num);
myclass.num := myclass.num + 20;
writeln(n1.num);
writeln(n2.num);
end.
Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:
12
31
31
51
51
È necessario utilizzare la direttiva {$ static on} per utilizzare i membri statici.