Nella programmazione orientata agli oggetti , la dichiarazione di una classe raggruppa membri, metodi e proprietà (attributi) comuni a un insieme di oggetti .
La classe dichiara, da un lato, attributi che rappresentano lo stato degli oggetti e, dall'altro, metodi che rappresentano il loro comportamento.
Una classe rappresenta quindi una categoria di oggetti. Si presenta anche come uno stampo o una fabbrica da cui è possibile creare oggetti; è in un certo senso una "cassetta degli attrezzi" che ti permette di creare un oggetto. Parliamo quindi di un oggetto come di un'istanza di una classe (creazione di un oggetto avente le proprietà della classe).
È possibile restringere l'insieme di oggetti rappresentati da una classe A grazie ad un meccanismo di ereditarietà . In questo caso creiamo una nuova classe B legata alla classe A e che aggiunge nuove proprietà. In questo caso, vengono utilizzati termini diversi:
Negli esempi seguenti definiamo in lingue diverse una classe Pointcon due attributi xe y. Questa classe contiene:
Il costruttore è una regola delle procedure di inizializzazione che verranno chiamate durante la creazione di una nuova istanza di una classe. Definisce un primo stato valido per lo stato interno dell'oggetto. Questa regola può essere sinteticamente tradotta dalla nozione di "dichiarazione di strutture, di variabili". Possono esserci diversi costruttori dichiarati. Se non è presente alcun costruttore, il compilatore ne genererà uno per impostazione predefinita.
Il metodo è una regola procedurale applicata agli attributi della classe. Questa regola può essere sinteticamente tradotta con la nozione di "funzione" o "routine". Possono esserci più metodi in una classe. I metodi sono gli elementi chiave della classe.
A partire dall'edizione 2015 ( ECMAScript 6 ), è stata aggiunta una sintassi di definizione di classe, semplificando l'uso del suo meccanismo di ereditarietà prototipale per lo sviluppo orientato agli oggetti:
class Point { constructor(x, y) { this._x = x; this._y = y; } getX() { return this._x; } getY() { return this._y; } isOrigin() { return this._x === 0 && this._y === 0; } translate(pt) { return new Point(this._x + pt._x, this._y + pt._y); } }Una classe si dice immutabile se non è possibile modificare un oggetto di questa classe dopo la sua creazione. Ad esempio, la classe Point, descritta sopra in vari linguaggi, è immutabile perché non espone alcun metodo per modificare il valore delle sue variabili membro. Il metodo translaterestituisce un nuovo oggetto invece di modificare l'oggetto stesso. La classe java.lang.Stringdell'ambiente Java è un altro esempio di classe immutabile, come la classe System.Stringdel Framework Microsoft .NET .
In alcune lingue, una classe può essere parzialmente definita. In particolare, alcuni metodi di questa classe non hanno corpo o implementazione. Questi metodi sono chiamati "astratti" (o virtuali in C ++ ).
Le classi con almeno un metodo astratto sono anche chiamate classi astratte (o virtuali) e non possono essere istanziate direttamente, tranne creando una sottoclasse non astratta.
Esempio Vogliamo modellare le relazioni oggettuali di un disegno vettoriale. Possiamo dire che un oggetto di disegno è un insieme di geometrie (la classe astratta) e ogni geometria può essere un punto, un poligono o una linea spezzata (queste tre classi ereditano dalla geometria). La classe astratta non è quindi essenziale di per sé, ma è essenziale per un modello pulito, generico e semplificato.Il mixin è un caso speciale di una classe astratta. Consente di aggiungere un servizio alle sottoclassi.
Una classe che ha solo metodi astratti è chiamata interfaccia o classe puramente virtuale (in C ++) o protocollo (in Objective C ).
La classe di una classe è una metaclasse . Le metaclassi consentono la riflessione strutturale.
Una classe, come definita in precedenza, è un insieme di membri (metodi e attributi) che si devono necessariamente gestire. Se p è un esempio di punto (a, b) in cui una e B sono di tipo int , accediamo i membri di p come questo:
La domanda che subito viene in mente è la seguente: perché definire un GetX () il metodo , quando siamo in grado di accedere direttamente al x ed y campi del Point di classe ?
Infatti, quando si devono gestire molte classi così come molte relazioni tra queste classi (cfr. Ereditarietà ), lo schema, i dati e le operazioni possono diventare molto complessi (soprattutto per un individuo che non ha progettato il codificato). Facciamo quindi ricorso a un meccanismo chiamato incapsulamento dei dati, che è responsabile di nascondere i campi della classe da alcune parti del programma per motivi di integrità. L'utente è quindi tenuto a manipolare solo metodi che sono stati approvati e che in teoria svolgono bene il loro ruolo.
Secondo il principio dell'incapsulamento, i metodi hanno accesso pubblico : ciò significa che qualsiasi elemento di un programma può utilizzare un metodo. Per quanto riguarda gli attributi dei componenti di stato, essi accedono a privato ( privato ) - Solo l'oggetto stesso (e quindi i metodi che contiene) ha accesso diretto ai suoi attributi. In questo caso, l'unico modo per accedere a questi attributi è utilizzare i metodi dell'oggetto. Gli attributi non possono essere utilizzati direttamente da un altro elemento del programma o anche da un altro oggetto, anche se questo oggetto è della stessa classe. Un altro punto: tutti gli attributi di un oggetto ereditati sono direttamente accessibili dagli altri membri di questo oggetto.
Per quanto riguarda le ultime considerazioni, c'è spesso confusione sulla semantica dell'accesso privato. Il principio di incapsulamento implica una protezione degli attributi che chiameremo verticali (solo l'oggetto stesso e gli oggetti di una sottoclasse hanno accesso ad esso). Troviamo questa semantica in linguaggi come Smalltalk , Oz o OCaml . Tuttavia, alcuni linguaggi, come C ++ , Pascal o Java , invocano la protezione degli attributi che chiameremo orizzontali (gli oggetti della stessa classe hanno accesso ad essi, ma non gli oggetti delle sottoclassi).
Alcuni linguaggi consentono, durante la dichiarazione della classe, di modificare l' ambito dei suoi membri e, di conseguenza, dei membri degli oggetti istanziati da questa classe; la modifica dell'ambito riduce la visibilità e quindi l'accessibilità ai membri.
Ad esempio, i linguaggi C ++ e Object Pascal offrono le seguenti modifiche all'ambito :
Scopo | Parola chiave | Visibile (accessibile) da | Nota | ||
---|---|---|---|---|---|
(visibilità) | in C ++ | un membro della classe | una classe derivata | un cliente dell'oggetto | |
pubblico | public | sì | sì | sì | I membri pubblici sono accessibili quanto l'oggetto istanziato di questa classe (i membri dichiarati in questo ambito sono visibili e utilizzabili dal client dell'oggetto). |
protetto | protected | sì | sì | no | È possibile accedere ai membri protetti solo dai membri dell'oggetto istanziato di questa classe e dalle classi derivate da esso. |
privato | private | sì | no | no | I membri privati sono accessibili solo dai membri dell'oggetto istanziato di questa classe (non sono accessibili da una classe figlia o dal client dell'oggetto). |
La semantica di questi cambiamenti di ambito e il loro numero variano a seconda del linguaggio di programmazione.
Ad esempio, Java offre una definizione leggermente diversa per i membri protetti; si estende a tutti gli oggetti della stessa classe, classi figlie e classi dello stesso pacchetto.
Ad esempio, Python offre la possibilità di modificare la visibilità dei membri di una classe anteponendo al nome del membro il carattere di sottolineatura ('_'):
Quando dobbiamo gestire dati dello stesso tipo (è ad esempio il caso di punti in un sistema di coordinate), potremmo voler applicare operazioni a questi oggetti (nel significato di OOP). Pertanto, la fusione di due zone (poligoni) dà una nuova zona, proprio come l'aggiunta di due punti dà un nuovo punto.
Continuiamo con la nostra classe Point. Il seguente esempio di Python è molto significativo:
class Point: (...) def __add__(self,point): # Surcharge de '+' return Point(self.x + point.x, self.y + point.y) p=Point(0,0) q=Point(2,3) r=p+qQuindi r.getX () restituirà 2.