Il concetto di oggetto informatico è assolutamente comprensibile se lo si paragona ad un oggetto del mondo reale. Non a caso, uno scopo degli oggetti della programmazione è proprio quello di simulare le proprietà e i comportamenti del mondo fisico. Un oggetto è dunque un’entità caratterizzata da una serie di caratteristiche specifiche (note come proprietà) e da un serie di possibili azioni (noti come metodi) che l’oggetto può realizzare eventualmente in risposta ad situazioni (note come eventi) che si verificano nel contesto attinente all’oggetto. Facciamo un banale esempio volendo rappresentare un oggetto per codificare i dati di una persona e vediamo come codificarlo in JavaScript.
<!DOCTYPE html>
<html>
<head>
<title>Gli oggetti di JavaScript</title>
</head>
<body>
<script>
var persona = { nome: “Carlo”, cognome: “Mazzone”, altezza: 180, capelli: “castani” };
document.write(“Io sono ” + persona.nome + ” ” + persona.cognome);
</script>
</body>
</html>
Per completezza ho riportato anche l’intero codice per una pagina HTML di esempio. Si intuisce, con grande semplicità, che la riga:
var persona = { nome: “Carlo”, cognome: “Mazzone”, altezza: 180, coloreOcchi: “castani” };
rappresenta una serie di coppie, separate da virgole, del tipo chiave:valore. ad esempio nome: “Carlo”. Quella presentata è la forma di creazione di un oggetto minimale e le coppie in questione sono le proprietà dell’oggetto. Nell’istruzione di stampa:
document.write(“Io sono ” + persona.nome + ” ” + persona.cognome);
si vede poi l’uso dell’operatore punto per accedere al valore delle varie proprietà. Faccio notare che la modalità di scrittura di un oggetto su di una singola linea è solo una possibilità e che, in generale, se ne preferisce una differente che vede ogni singola proprietà su di una specifica linea di codice come mostrato di seguito:
var persona = {
nome: “Carlo”,
cognome: “Mazzone”,
altezza: 180,
capelli: “castani”
};
Ovviamente, possiamo cambiare a piacimento il valore delle proprietà di un oggetto. Ad esempio, potremmo cambiare la proprietà nome scrivendo:
persona.nome=”Ludovica”;
Ancora, è possibile utilizzare la parola chiave new per la creazione, in un certo modo in differita dell’oggetto, scrivendo:
var persona = new Object();
persona.nome=”Carlo”;
persona.cognome=”Mazzone”;
persona.altezza=180;
persona.capelli=”castani”;
Tuttavia, in generale, si sconsiglia l’uso di questo secondo metodo, anche per una questione di velocità di esecuzione del codice, preferendo la modalità:
var persona = {
nome: “Carlo”,
cognome: “Mazzone”,
altezza: 180,
capelli: “castani”
};
Nella terminologia anglosassone questo tipo di oggetto viene definito object literal. Literal, infatti, è il modo con cui ci si riferisce ai valori assegnati alle costanti (in contrapposizione al termine variabile). In questo caso, infatti, quello che facciamo è proprio definire un oggetto con dei valori costanti preimpostati.
Sappiamo che è possibile assegnare un nuovo valore ad una proprietà scrivendo:
persona.nome=”Ludovica”;
Ebbene, sempre nell’ordine delle cose per cui è possibile realizzare le stesse operazioni in modo differente, vi segnalo che è possibile riferirsi al valore di una proprietà anche nel seguente modo:
persona[“nome”]=”Ludovica”;
Inoltre, è possibile riferirsi al nome di una proprietà di un dato oggetto passando ad esso una variabile contenente quel nome. Sembra complicato, ma si tratta di una banalità che vi mostro di seguito. Posso assegnare ad una variabile generica il nome del campo cognome in questo modo:
var temp=”cognome”;
e successivamente accedere, ad esempio per la stampa, alla corrispondente proprietà:
document.write(persona[temp]);
Faccio notare incidentalmente come sia possibile creare un oggetto vuoto attraverso la seguente sintassi:
var persona = {};
per poi andare ad inserire in esso delle proprietà, così come già visto in precedenza, scrivendo, ad esempio:
persona.nome=”Carlo”;
Inoltre, così come tali proprietà possono essere create sarà anche possibile eliminarle scrivendo, ad esempio:
delete persona.nome;
oppure:
delete persona[“nome”];
Ovviamente, una volta eliminata una specifica proprietà, andandone a stampare il valore, ad esempio con:
document.write(persona[“nome”]);
otterremmo in output un valore di tipo undefined segnalando che la proprietà in esame non è appunto definita.
Inutile dire che provare ad eliminare delle proprietà di oggetti predefiniti può essere un’idea quantomeno folle che può inevitabilmente portare a blocchi del sistema.
Dal dato all’operatore: il vero senso dell’oggetto
Finora abbiamo capito come sia possibile gestire un oggetto visto sostanzialmente come un contenitore di dati. Se ci pensiamo bene, infatti, gli oggetti appena studiati non sono altro che una sorta di array un po’ speciali in quanto possiamo inserire in essi dei valori di tipo differente (ad esempio, nell’oggetto persona abbiamo utilizzato dei valori stringa ed un numero). Tuttavia, ciò che rende un oggetto un “tipo” davvero speciale è la possibilità di gestire al proprio interno, oltre ai semplici dati (le proprietà), anche delle specifiche azioni realizzate attraverso delle funzioni che abbiamo definito con il nome di metodi. La creazione di specifici metodi all’interno di un oggetto rende quest’ultimo un’entità quasi autonoma capace di azioni proprie. Non dico che realizziamo un’entità capace di intendere e di volere ma la direzione è forse quella.
Vediamo allora come cominciare ad instillare un anelito vitale all’interno dell’oggetto inserendo in esso una funzione che gli dia la possibilità di “parlare”. Inizialmente l’oggetto si presenterà dicendo il proprio nome e cognome. Dunque, procediamo modificando l’oggetto persona come segue:
var persona = {
nome: “Carlo”,
cognome: “Mazzone”,
altezza: 180,
capelli: “castani”,
parla: function() {
document.write(“Io sono ” + persona.nome + ” ” + persona.cognome);
}
};
Per far parlare la nostra “persona” invocheremo il metodo parla scrivendo:
persona.parla();
Come è possibile osservare, quello che facciamo è di inserire, insieme alle proprietà separando normalmente i vari elementi con una virgola, una funzione che abbiamo chiamato banalmente parla. Notiamo come si debba scrivere prima il nome della funzione e poi, dopo i due punti, la parola chiave function. Si tratta di una sintassi che rende omogenea la dichiarazione delle proprietà e dei metodi. Ovviamente, bisogna fare attenzione al corretto uso delle virgole che servono per separare le varie dichiarazioni onde evitare errori durante l’esecuzione del codice.
Qualcuno potrebbe obiettare che tale modifica rispetto allo script precedente non introduce alcuna innovazione di comportamento. In effetti è così, almeno all’apparenza. Il senso è che dal punto di vista del risultato pratico i due pezzi di codice danno in output lo stesso risultato:
Io sono Carlo Mazzone
Tuttavia, essi lo realizzano in maniera, anche filosoficamente, del tutto diversa. Mentre nel primo caso l’istruzione di stampa:
document.write(“Io sono ” + persona.nome + ” ” + persona.cognome);
è esterna all’oggetto e viene quindi eseguita all’esterno dell’oggetto stesso, nel secondo caso, è l’oggetto stesso ad avere la “capacità di parola” e tutto quello che dobbiamo fare è di dirgli di usarla (si parla di “invio di messaggi all’oggetto”). Si tratta di una inversione di vedute, di un cambio assoluto di paradigma di programmazione, per cui è l’oggetto ad essere al centro del contesto di programmazione racchiudendo in esso, come in una scatola nera tutta la logica di funzionamento. Faccio notare incidentalmente che questo approccio che consiste nel nascondere all’interno di un oggetto i dettagli del codice per cui il programmatore non deve preoccuparsi del modo in cui inviare messaggi agli oggetti prende il nome, in questo paradigma, di information hiding (occultamento dell’informazione) attraverso una tecnica nota come incapsulamento.
Tutto ciò rappresenta la base per costruire moduli software che siano facilmente utilizzabili e che abbiano grande scalabilità, ovvero possano essere modificati per contenere nuove funzionalità in maniera non traumatica. Inoltre, questo approccio ad oggetti consente uno sviluppo a più mani (diversi programmatori sullo stesso progetto) in maniera più efficiente.
Per rafforzare il senso della vita interna di un oggetto vi invito ad osservare la seguente modifica all’oggetto persona:
var persona = {
nome: “Carlo”,
cognome: “Mazzone”,
altezza: 180,
capelli: “castani”,
parla: function() {
document.write(“Io sono ” + this.nome + ” ” + this.cognome);
}
};
in cui abbiamo utilizzato la parola chiave this. Tale riferimento, che banalmente ha il significato di “questo” si riferisce all’oggetto stesso e serve per generalizzare e semplificare la scrittura del codice.
Faccio ora notare come il metodo realizzato sia una procedura e non una vera e propria funzione. Infatti, esso provvede direttamente a stampare la stringa di presentazione e non restituisce nulla all’esterno. In realtà, per rendere il codice più professionale e manutenibile è necessario trasformare il metodo in una vera e propria funzione scrivendo:
var persona = {
nome: “Carlo”,
cognome: “Mazzone”,
altezza: 180,
capelli: “castani”,
parla: function() {
return(this.nome + ” ” + this.cognome);
}
};
document.write (“Io sono ” + persona.parla());
Giusto per fare una semplice considerazione rispetto alla bontà di questa soluzione di codifica alternativa possiamo pensare al fatto che in questo modo l’oggetto è maggiormente indipendente rispetto al resto del codice. In buona sostanza, abbiamo in un certo senso separato la parte di “business logic”, ovvero di funzionamento vero e proprio, rispetto alla parte di “interfaccia” realizzata con la chiamata document.write. In questo modo, giusto per intenderci, anche una possibile traduzione in altre lingue del nostro codice sarebbe più semplice in quanto realizziamo lo specifico output all’esterno dell’oggetto. Ad esempio, avremmo potuto scrivere:
document.write (“My name is ” + persona.parla());
Oggetti, costruttori e paradigmi di programmazione
L’oggetto persona visto in precedenza ha sicuramente grande utilità ma nasconde anche alcuni limiti. Innanzitutto, come già detto, esso è un sorta di costante. Infatti, lo abbiamo definito un object literal. Può essere utile a questo proposito spendere qualche parola in più relativamente allo sviluppo software in un senso più generale. Scrivere codice non è semplicemente l’azione consistente nel mettere una dopo l’altra una serie di righe di codice. Ogni progetto, anche il più piccolo, prevede uno specifico approccio alla sua realizzazione tramite codice. Si parla, in linea generale, di paradigmi di programmazione intendendo appunto la metodologia usata per creare uno specifico software. Infatti, indipendentemente dal fatto che si possano realizzare con un dato codice le medesime funzionalità, queste possono essere codificate in maniera sostanzialmente diversa. Seppure questo testo non sia un trattato di programmazione in senso stretto, trovo utile fornire qui alcune informazioni di base. Fondamentalmente e semplificando al massimo, un certo problema può essere risolto in due modalità principali. La prima, nota essenzialmente come approccio top-down (dall’alto verso il basso), prevede di scomporre il problema principale in tanti problemi più piccoli (noti come sottoproblemi) e per ognuno di questi scrivere una specifica funzione che risolva il particolare problema. Spesso, un dato sottoproblema è troppo complesso per essere risolto da una singola funzione e così esso viene ulteriormente scomposto in sottoproblemi e relative funzioni associate per risolverli. Per essere concreti, supponiamo di dover gestire in un nostro sito web la registrazione degli utenti tramite specifiche credenziali. Ebbene, il problema nella sua totalità può innanzitutto essere scomposto in un serie di sottoproblemi quali, ad esempio, la registrazione tramite i propri dati personali (logon) e l’accesso al sito attraverso i dati in proprio possesso (login). D’altra parte il logon prevede almeno due ulteriori sottoproblemi: il primo per la registrazione dei dati immessi dall’utente ed un secondo per la validazione, ad esempio tramite l’invio di una mail, della veridicità dei dati forniti dall’utente in questione. Come si vede, partiamo dall’alto (top) intendendo il problema nella sua totalità e andiamo verso il basso (down) scomponendo il tutto in sottoproblemi ai quali assoceremo specifiche funzioni. Questo approccio viene normalmente definito approccio procedurale: non a caso i sottoproblemi vengono gestiti attraverso specifiche procedure.
Tuttavia, oltre a questo approccio ne esiste un altro che per certi aspetti si muove in direzione opposta: si parte dal basso e si va verso l’alto. Si parla, infatti, di approccio bottom-up che si estrinseca in maniera pratica attraverso il paradigma ad oggetti. Cerchiamo allora di fare un po’ più di chiarezza. Dire che si parte dal basso (bottom) vuol dire iniziamo il nostro studio per la risoluzione del problema lavorando sui dati piuttosto che sulle procedure. Mi spiego meglio facendo riferimento all’esempio della gestione dei dati del nostro utente via web. Nell’approccio ad oggetti partiamo proprio definendo innanzitutto le caratteristiche dell’utente quali nome, cognome, password, email, ecc.. Altra caratteristica dell’utente sarà, inoltre, uno stato che definisce se l’utente è in un certo momento loggato al sistema oppure no. Ciò, infatti, condiziona le attività da realizzarsi in quanto se l’utente non risulta loggato dovremo eventualmente redirigere la sua navigazione verso la fase di login. Dunque, si definiscono le proprietà degli oggetti e anche le funzioni (i metodi) ad essi associati. Mettendo insieme le funzionalità dei vari oggetti “saliamo verso l’alto” rispettando il senso dell’approccio bottom-up.
È importante precisare che da un lato abbiamo il paradigma e dall’altro il linguaggio di programmazione. Il senso è che teoricamente con lo stesso linguaggio possiamo operare sfruttando l’uno o l’altro paradigma. Ovviamente ciò deve essere permesso dal linguaggio stesso: si parla in questo caso di linguaggi multi-paradigma. Per fare qualche esempio concreto, il linguaggio C supporta nativamente il solo paradigma procedurale mentre linguaggi come C++ o Java sono stati costruiti per essere utilizzato con lo sviluppo ad oggetti ma teoricamente possono essere usati anche con un approccio procedurale. Nel campo dello sviluppo web, sia JavaScript che PHP (che vedremo nel prosieguo del nostro viaggio) possono essere usati in entrambi i modi anche se, comunque, il suggerimento è di sfruttare le più moderne possibilità di sviluppo del paradigma ad oggetti.
Torniamo ora a parlare di oggetti da un punto di vista più tecnico. Affinché si possa parlare di programmazione orientata agli oggetti è importante che gli oggetti abbiamo alcune caratteristiche molto precise tra cui il fatto che essi possano essere creati a partire da uno specifico modello che indichi le caratteristiche comuni a tutti gli oggetti: la classe. Per classe si intende una sorta di stampo a partire dal quale creare oggetti tutti simili ma comunque singoli e unici. Può essere utile immaginare una classe come una formina che i bambini usano per giocare con la sabbia. La formina consente di creare elementi di sabbia tutti uguali ma ognuno è indipendente dagli altri avendo, ad esempio, una posizione sulla spiaggia diversa dagli altri. Di seguito un esempio di classe per l’oggetto persona visto in precedenza:
function Persona(nome, cognome, altezza, capelli) {
this.nome = nome;
this.cognome = cognome;
this.altezza = altezza;
this.capelli = capelli;
this.parla = function () {
return(this.nome + ” ” + this.cognome);
};
}
Vediamo dunque che cosa abbiamo realizzato. In buona sostanza abbiamo scritto una funzione, che viene chiamata costruttore in quanto costruisce l’oggetto. Vi faccio notare come abbia scelto come nome per la funzione il termine Persona scritto con l’iniziale in maiuscolo. Si tratta di una sorta di convenzione per indicare il fatto che abbiamo a che fare con una funzione costruttore. All’interno della funzione assegniamo i vari argomenti presi in input dalla funzione stessa (nome, cognome, altezza, capelli) a specifiche proprietà realizzate con la parola chiave this. Allo stesso modo definiamo il metodo parla, in maniera simile a quanto visto in precedenza con gli object literal. Tuttavia, una volta realizzato il costruttore dobbiamo invocare in maniera esplicita la creazione di un particolare oggetto. La creazione in questione avviene con la parola chiave new come vi mostro di seguito:
var Carlo = new Persona(“Carlo”, “Mazzone”, 180, “castani”);
dove abbiamo creato un nuovo oggetto, Carlo, con le proprietà elencate nello specifico passaggio di parametri. Ovviamente possiamo poi richiamare le specifiche proprietà:
document.write (Carlo.altezza);
e invocare i metodi di nostro interesse:
document.write (“Io sono ” + Carlo.parla());
Se ci riflettiamo un attimo, quello che abbiamo realizzato non è da poco. Siamo ora in grado di creare tutti gli oggetti di un certo tipo di cui necessitiamo: si parla di istanze della classe. Ad esempio, possiamo scrivere:
var Ciccio = new Persona(“Ciccio”, “Pasticcio”, 160, “bianchi”);
per creare un nuovo oggetto di tipo Persona che si chiama Ciccio. Ovviamente, possiamo creare un oggetto anche solo indicando alcuni parametri alla funzione costruttore:
var Topolino = new Persona(“Mickey”, “Mouse”);
Ovviamente, nel caso del nostro esempio, la stampa di una proprietà non inizializzata quale:
document.write (Topolino.altezza);
darà come risultato undefined. In ogni caso, addirittura, possiamo creare un oggetto privo di inizializzazione con:
var Topolino = new Persona();
e successivamente valorizzare i suoi membri:
Topolino.nome=”Mickey”;
Topolino.cognome=”Mouse”;
Un altro aspetto importante da sottolineare relativamente alla grande utilità di questo approccio è che, contrariamente a quanto succede con gli object literal, non siamo costretti a definire i vari metodi degli oggetti per ogni singola istanza. Infatti, definiremo le funzioni di interesse dell’oggetto una volta per tutte nel costruttore e potremo poi richiamarle dal singolo oggetto scrivendo, ad esempio:
document.write (“Io sono ” + Topolino.parla());
Prototipi: se questa è eredità
Gli oggetti JavaScript hanno in serbo ancora alcune sorprese. Tanto per dirne una, è possibile associare al volo una nuova proprietà ad un certo oggetto. Ad esempio, in relazione alla situazione seguente:
function Persona(nome, cognome, altezza, capelli) {
this.nome = nome;
this.cognome = cognome;
this.altezza = altezza;
this.capelli = capelli;
this.parla = function () {
return(this.nome + ” ” + this.cognome);
};
}
var Carlo = new Persona(“Carlo”, “Mazzone”, 180, “castani”);
var Ciccio = new Persona(“Ciccio”, “Pasticcio”, 160, “bianchi”);
possiamo scrivere:
Carlo.userName=”carlomazzone”;
dove abbiamo inserito la nuova proprietà userName al solo oggetto Carlo mentre l’oggetto Ciccio rimane immutato. In maniera similare, possiamo aggiungere anche un metodo ad un singolo oggetto (istanza di una classe) scrivendo, ad esempio:
Carlo.saluta = function () {
return(“Ciao, ciao da ” + this.nome);
};
Abbiamo così aggiunto la funzione saluta al solo oggetto Carlo. La cosa interessante da notare è che dentro la funzione in questione ho comunque potuto usare la parola chiave this per fare riferimento ad una proprietà interna all’oggetto.
Tuttavia, ribadisco che nuove proprietà e nuovi metodi possono essere aggiunti, con il semplice uso del carattere punto legato al nome dell’oggetto, solo in relazione alla specifica istanza e quindi al singolo oggetto. Infatti, se volessimo aggiungere determinate proprietà e metodi a tutti gli oggetti derivanti da una data classe dovremmo per forza di cose lavorare sul costruttore dell’oggetto. Vediamo allora come procedere in questo scenario. Per farlo è necessario ricorrere ad una nuova parola chiave: prototype. Usiamo dunque tale nuova keyword in un esempio concreto, sempre relativo alla classe Persona, scrivendo:
Persona.prototype.email=”info@example.com”;
Quello che abbiamo fatto è stato associare al costruttore di Persona una nuova proprietà che abbiamo chiamato email ed inizializzato al valore info@example.com. Ciò significa che tale proprietà con relativo valore è associata in automatico a tutti gli oggetti già istanziati a partire dalla classe Persona e che quindi, sempre in relazione all’esempio precedente, le seguenti istruzioni:
document.write (Carlo.email);
document.write (Ciccio.email);
restituiranno in output due volte la stringa info@example.com.
La tecnologia che si nasconde dietro ai prototipi è la base sulla quale di fonda la cosiddetta ereditarietà della programmazioni ad oggetti nel mondo di JavaScript. Tuttavia, a questo punto credo sia necessario spendere qualche ulteriore parola su questo argomento data la sua importanza in relazione allo sviluppo software che usa gli oggetti come principale paradigma di programmazione. L’ereditarietà consente di definire degli oggetti di base che possono poi essere utilizzati per costruire nuovi oggetti che dai primi prendono le proprietà e gli eventi già in essi presenti e ne aggiungono di nuovi per specializzare i propri comportamenti. In buona sostanza un modo per non riscrivere ogni volta tutto il codice ma sfruttare quanto già presente in altri oggetti. Questo meccanismo che consente di sfruttare caratteristiche di altri oggetti in una sorta di albero genealogico è appunto chiamato ereditarietà. Ogni linguaggio che usa il paradigma ad oggetti deve per forza di cosa implementare questo meccanismo ed ognuno, tuttavia, lo realizza in maniera leggermente diversa. Java, ad esempio, implementa in maniera leggermente diversa dal C++ l’ereditarietà e ancora differentemente lo fa JavaScript che, come detto, sfrutta il meccanismo dei prototipi.