edutecnica

Oggetti in JavaScript

         

Le variabili di tipo oggetto sono collezioni arbitrarie di proprietà che possiamo aggiungere o eliminare a piacere.
Abbiamo già avuto modo di utilizzare degli oggetti. Sono oggetti, ad esempio, le stringhe e gli array.

Gli oggetti, come già visto per altri linguaggi di programmazione, oltre alle proprietà, sono dotati di metodi, che sono funzioni in grado di modificare lo stato dell’oggetto e di ritornare (eventualmente) un valore.

Nel caso degli array è stata messa in evidenza la proprietà length, che è una proprietà perchè contiene il numero di elementi dell’array stesso.
La funzione push() che permette di aggiungere un elemento all’array è invece da considerarsi un metodo, perchè si manifesta come una funzionalità.

In JavaScript, quasi tutto è un oggetto e quello che non lo è può diventarlo a breve giro; nel caso seguente definiamo un oggetto p (prodotto) dotato di due attributi ( le proprietà) stampando a video il valore di una di queste

var p={prd:'dado', prx:2};
console.log(p.prd);
//____>dado

Dunque, per creare un oggetto si può usare una notazione a parentesi graffe.
Dentro le parentesi graffe possiamo specificare elenchi di proprietà separati da virgole.
Ciascuna proprietà è rappresentata col suo nome, seguito da un segno di due punti e da un'espressione che le assegna un valore.
Si vede che per riferirsi ad una data proprietà di un oggetto basta rispettare la sintassi

nomeOggetto.nomeProprietà

Il frammento di codice precedente, evidenzia una certa differenza rispetto ad altri linguaggi di programmazione dove la creazione e la gestione degli oggetti impone dei rigorosi passaggi formali che non possono essere evitati.

Negli altri linguaggi, ci si basa sul paradigma classe-istanza dove la classe è una entità astratta che definisce le proprietà ed il comportamento di un dato oggetto, mentre l’istanza è l’implementazione della classe in un oggetto concreto.

Nell'esempio visto, per creare un oggetto abbiamo impiegato due righe; la stessa procedura eseguita in Java non prevede esattamente due righe:

class main {
public static void main (String[] args){
    P p=new P("dado",1);//istanziazione
    System.out.println(p.prd);
}//fine main
class P{
private String prd;
private int prx;
P(String pro,int pre){//costruttore
    prd=pro;
    prx=pre;
}//fine costruttore
}//fine classe P

L’oggetto istanziato dall’istruzione
P p=new P("dado",1);
si basa sulla classe P definita successivamente.

In JavaScript la nozione di classe non esiste; al suo posto è stato introdotto il concetto di prototipo.
Un prototipo è un altro oggetto che viene usato come "sorgente" di proprietà. Quando un oggetto riceve una richiesta per una proprietà che non ha, la proprietà viene cercata nel suo prototipo; se non la si trova nemmeno lì, la ricerca continua nel prototipo del prototipo e così via.
Tutte le funzioni derivano da Function.prototype e tutti gli array da Array.prototype; il prototipo di tutti i prototipi è per definizione Object.prototype il quale mantiene attributi e metodi generali e comuni a tutti gli oggetti come ad esempio il metodo toString() usato per rivelare lo stato di un oggetto.
In questo modo, si crea automaticamente una gerarchia di prototipi; così come accade in un linguaggio basato su classi, dove si crea una gerarchia tra le classi attraverso le definizioni delle classi stesse.
Questo approccio alla programmazione ad oggetti risulta essere molto flessibile tanto che una volta creato un oggetto risulta facile aggiungere attributi (proprietà).

var p={prd:'dado', prx:1};
p.qta=24;
console.log(p.qta);
//____>24

basta attenersi alla sintassi

nomeOggetto.nuovoAttributo=valore;

E' facile anche eliminare una proprietà da un oggetto; tramite l'operatore unario delete :

delete nomeOggetto.nomeAttributo;

var p={prd:'dado', prx:1};
p.qta=24;
console.log(p.qta);
delete p.qta;
console.log(p.qta);
//____>24
//____>undefined


Prototype

         

Non sarebbe corretto affermare che in JavaScript la classe sia stata completamente abolita: è rimasto soltanto il costruttore che, come in Java, coincide con una funzione

function pro(x,y,z) {
    this.prd = x;
    this.prx = y;
    this.qta = z;
    this.print = function(){return this.prd + ' ' + this.prx};
}//fine prototipo

var v=new pro('vite',3,50)
var d=new pro('dado',2,75)
console.log(p.print());
//________>vite 3

In questo caso, vediamo in azione un costruttore completo, costituito dai tre attributi prd (nome del prodotto) prx (prezzo unitario) qta (quantità in magazzino).
Si vede anche il metodo print() che restituisce il nome del prodotto con il suo prezzo.
Nel costruttore, la variabile this è legata al nuovo oggetto che si sta creando e che verrà restituito dalla chiamata se non è previsto esplicitamente un diverso oggetto come valore di restituzione.
In Javascript quando si invoca una funzione con la parola chiave new la si tratta come un costruttore.
I costruttori, come tutte le funzioni dispongono automaticamente di una proprietà, chiamata prototype che contiene un oggetto predefinito e vuoto ottenuto da Object.prototype.
Dunque per aggiungere un metodo valore() agli oggetti di tipo pro sarà sufficiente scrivere

pro.prototype.valore = function(){return this.qta*this.prx};
console.log(d.valore());
//________>vite 3
//________>150

il metodo valore restituisce il controvalore in magazzino di un determinato prodotto; applicato all'oggetto d (dado) deve restituire 150, perchè ci sono 75 dadi che costano 2€ ciascuno.

Quando ad un oggetto si aggiunge una proprietà, non importa se presente nel prototipo o meno, la proprietà viene aggiunta all'oggetto stesso. Se il prototipo dispone di una proprietà con lo stesso nome, questa non avrà effetto sull'oggetto ed il prototipo rimane invariato.


Incapsulamento

         

Le prerogative della filosofia dell'information hiding (incapsulamento) possono essere rispettate dichiarando all'interno di un costruttore l'attributo come
var nomeVar=nomeParametro;
invece che con
this.nomeVar=nomeParametro;

function pro(x,y,z) {
    this.prd = x;
    this.prx = y;
    var qta = z;
this.print = function(){return this.prd + ' ' + qta};
}//fine prototipo

var v=new pro('vite',3,50)
console.log(v.prx);
console.log(v.qta);
console.log(v.print());
//__>3
//__>undefined
//__>vite 50

Si vede, in questo caso, come l'attributo qta sia accessibile solo tramite un metodo print() definito all'interno del costruttore.
Possiamo riassumere dicendo che a differenza di altri linguaggi dove classi ed istanze sono entità separate, e l'istanziazione avviene rigorosamente con un metodo costruttore.


Ereditarietà

         

L'aspetto dell'ereditarietà, che caratterizza la programmazione ad oggetti tradizionale, può essere applicato anche JS.

La tecnica più tradizionale della OOP consiste nel creare una classe base, dove sono definiti attributi e metodi comuni a tipi di oggetti diversi, e poi definire classi derivate che, oltre a definire attributi e metodi specifici, ereditano gli attributi e metodi della classe base; in JavaScript è sufficiente disporre una funzione costruttore per creare una collezione di oggetti ed ereditare le proprietà seguendo la catena dei prototipi.

Supponiamo di dover descrivere una realtà lavorativa dove esistono operatori che possono avere parametri comuni ma anche diversificati come descritto nel seguente schema:

L'entità principale, denominata Soggetto è caratterizzata dai due attributi (nome,eta) comuni a tutte le successive istanze che saranno create. L'istanza Tecnico eredita questi due attributi e ne aggiunge un altro (paga).
L'istanza Operatore eredita i due attributi da Soggetto e ne aggiunge altri due più specifici (macchina, paga).
L'istanza Responsabile può essere ottenuta da una istanza Tecnico, da cui vengono ereditati in totale tre attributi, ma di un responsabile può interessare l'esperienza, per cui viene aggiunto un ulteriore attributo (servizio=anni di servizio).


Per garantire l'estensione di una classe verso l'altra si utilizza il metodo apply() che ha lo stesso compito del metodo Super() utilizzato in Java (invocazione alla superclasse genitrice). Nei costruttori derivati : Operatore, TecnicoResponsabile, si nota come il metodo info() debba essere riscritto ( o meglio sovrascritto=override) a causa dei parametri che si diversificano. Questa operazione può essere fatta nel modo indicato oppure esternamente alla funzione costruttrice ad esempio attraverso l'istruzione:

Responsabile.prototype.info = function(){
    return this.nome+' '+this.eta+' '+this.paga+'€ '+this.servizio;
};

Ovviamente se il costruttore base contiene già attributi necessari al metodo non è necessario riscrivere quest'ultimo nella funzione costruttrice derivata.


cioè, un oggetto può essere usato come oggetto del proprio tipo o come oggetto del suo tipo base; quest'ultima operazione viene solitamente chiamata upcasting.


Polimorfismo

         

Per polimorfismo si intende la possibilità di dare lo stesso nome a procedure che fanno cose diverse.
In ogni linguaggio esistono sorgenti di polimorfismo; ad esempio in JS quando scriviamo

3+2
//otteniamo_____>5

mentre quando scriviamo
'conca'+'tena'
//otteniamo_____>'concatena'

Allora, con lo stesso simbolo + indichiamo operazioni diverse: addizione se gli operandi sono numeri, concatenazione se gli operandi sono stringhe.
Questo particolare tipo di polimorfismo si chiama overloading (sovvracarico).
La decisione su quale tipo di operazione da intraprendere può essere presa in anticipo sulla base del tipo di operandi; ma questa non è una novità, gia sappiano che tutti i moderni linguaggi di programmazione permettono la scrittura di molteplici funzioni che abbiano lo stesso nume ma segnatura diversa (diverso numero di parametri o stesso numero di parametri ma di diverso tipo).
In JS questo meccanismo viene spinto oltre; in virtù del fatto che una funzione può accettare come parametro un'altra funzione, come si vede dall'esempio che segue.


L'insieme delle nozioni di incapsulazione, polimorfismo ed ereditarietà sono considerati delle componenti fondamentali della programmazione ad oggetti.
Mentre però i primi due concetti vengono generalmente considerati molto utili, l'ereditarietà è spesso soggetta a delle critiche.
Le ragioni principali sono che spesso viene confusa con il polimorfismo ed è comunque sopravalutata ed usata , talvolta, a sproposito.
Mentre incapsulazione e polimorfismo sono spesso usati per disgiungere tra loro parti di codice (che non devono potersi influenzare a vicenda) l'ereditarietà lega tra loro tipi diversi creando più confusione.
Questo per dire che l'ereditarietà dovrebbe essere vista solo come una tecnica per definire nuovi tipi di dato risparmiando righe di istruzioni e non come un principio fondamentale di organizzazione del codice.
Nell'ultimo esempio, abbiamo comunque visto, come si possa avere comunque, polimorfismo senza ereditarietà.


Array di oggetti

         

Come si può immaginare, è possibile usare un array per creare e gestire una collezione di oggetti, essendo l'array a sua volta un oggetto, non è nemmeno necessario dichiarare preventivamente un costruttore come si vede dal seguente esempio:


però, se si vuole proprio usare un costruttore, anche solo per un'ovvia ragione di ordine e leggibilità, è sempre possibile farlo basandosi, ad esempio, sul codice seguente.


Si riconosce una notevole analogia con gli array di oggetti del linguaggio Java. Si vede come sia possibile accedere direttamente agli attributi dell'oggetto oppure tramite metodi preposti (funzione print in questo caso) .