Share |

venerdì 19 marzo 2010

Javascript: La Programmazione Orientata agli Oggetti - OOP

Con l'avvento del Web2.0 e quindi con l'esponenziale arricchimento delle funzionalità delle interfacce grafiche e dei processi che le governano,
è divenuto essenziale per il programmatore riorganizzare il codice client in maniera che sia più facile da mantenere ed implementare.

Grazie ad alcuni frameworks ormai famosi, tipo Prototypejs e JQuery, è stata migliorata la produttività del programmatore, che finalmente può scrivere codice senza preoccuparsi (nella maggior parte dei casi) delle differenti piattaforme sul quale poi dovrà funzionare, concentrandosi maggiormente sull'organizzazione e sull'architettura dell'applicazione.

JavaScript permette un approccio alla programmazione sia strutturata, o procedurale, sia Object Oriented, o OOP.

Personalmente ritengo che organizzare in classi la gestione dello stato e dei dati applicativi sia alla lunga più conveniente: la possibilità di assemblare tutta la logica di funzionamento e comportamento all'interno di ogni singolo mattone che compone l'applicazione rende tutto il ciclo di vita dell'applicativo (analisi, realizzazione, test, rilascio e successive implementazioni) più fluido e più stabile perché logicamente organizzato in maniera migliore.

Certamente non è proprio corretto parlare di OOP in quanto JavaScript non ha i concetti di classe e di interfaccia, tanto meno di polimorfismo, conosce appena il senso di privato e pubblico e non è un linguaggio fortemente tipizzato.

Javascript pur non essendo dotato di tutte queste caratteristiche e funzionalità, permette comunque la creazione di oggetti: questo ed altre particolarità del linguaggio (tipo il metodo prototype) ci offrono la possibilità di colmare
le mancanze del linguaggio estendendone le funzionalità ed i concetti.

Definire ed Istanziare un Oggetto

Per istanziare un Oggetto si utilizza l'operatore new seguito dal costruttore della classe:

<script type="text/javascript">
<!--
//Esempio di dichiarazione di Classe.
/**
 * La classe Player ha 3 proprieta': 
 * Nome,Cognome e Sesso. 
 */
function Player(nm, cgn, ss){
  this.nome = nm;
  this.cognome = cgn;
  this.sesso = ss;
}

/**
 * Estendo le funzionalità di tutte 
 * le istanze della classe con il metodo 
 * toString().
 */
Player.prototype.toString() = function(){
  return this.nome + " " + this.cognome 
     + " (" + this.sesso + ")"; 
}

//Ora istanzio un oggetto di tipo Player.
var player = new Player('Mirko', 'Agrati', 'UOMO');

alert(player.toString());
-->  
</script>

Come si nota, creare una classe ed istanziare un oggetto è cosa da poco. Forse, meno intuitivo è l'utilizzo dell'operatore this.

this rappresenta un riferimento al contenitore padre(classe, metodo od oggetto che sia), che grazie al proprio reference si espone offrendo la possibilità di essere esteso in qualunque momento o stato applicativo.

La classe Player, per come è stata creata, offre un diretto controllo delle sue proprietà permettendo all'utente di modificarne i valori in qualunque momento.

Ma se invece volessimo progettare un oggetto che una volta istanziato proteggesse le sue proprietà da possibili modifiche?

Private & Public

Come detto in precedenza, JavaScript non offre i concetti di scope public o private però il concetto generale di scope lo conosce bene, infatti una variabile dichiarata all'interno di una funzione non viene vista al suo esterno.

Sfruttando questo meccanismo, basterà utilizzare delle variabili interne, per ottenere il mascheramento delle proprietà, e dei metodi pubblici per potervi accedere.

<script type="text/javascript">
<!--
function Player(nm, cgn, ss){
  nome = nm;
  cognome = cgn;
  sesso = ss;
 
  /**
   * Dichiaro i metodi pubblici per esporre 
   * i valori delle proprietà
   */
  this.getNome = function(){
    return nome;
  }
  this.getCognome = function(){
    return cognome;
  }
  this.getSesso = function(){
    return sesso;
  }
}

//Istanzio un oggetto di tipo Player.
var player = 
    new Player('Mirko', 'Agrati', 'UOMO');
alert(player.getNome());

/**
 * Per verificare che dall'esterno 
 * le proprietà sono invisibili
 * provo ad accedervi direttamente. 
 * Mi aspetto di ricevere un valore 
 * NULL o NaN.
 */
alert(player.nome);
-->  
</script>

Quindi è possibile gestire scope di membri come fossero variabili e perciò si può dire che il concetto base dell'incapsulamento, caratteristica fondamentale della OOP (insieme all'ereditarietà ed il polimorfismo) è supportato dal linguaggio.

Dopo l'ultima modifica, la classe Player si è parecchio irrobustita per quanto riguarda la sicurezza dei propri dati, ma ancora un punto debole esiste.

Il costruttore infatti accetta come terzo parametro il sesso, passato come Stringa: anche se esistono due soli sessi questi possono essere scritti in molteplici modi differenti, maiuscoli e minuscoli o in lingue differenti ecc....

Come reagirebbe la classe se anziché "UOMO" le passassi "OMMO" o "MAN"?
Per evitare questi problemi, in ambito OOP solitamente si utilizzano delle costanti static, ma JavaScript cosa offre?

Static

Bhe, l'utilizzo di costanti è da accantonare, mentre per quanto riguarda proprietà e membri static JavaScript offre una possibilità.

Cos'è un membro static?
Un membro static è un metodo, o proprietà, il cui scope, cioè la sua visibilità, è definito a livello di classe, cioè è utilizzabile senza dover creare alcuna istanza di classe.

Quindi vediamo come modificare il codice della classe Player introducendo la definizione di un membro static ed il suo utilizzo.

<script type="text/javascript">
<!--
//Static
Player.SEX_MALE = 1;
Player.SEX_FEMALE = 2;

//Esempio di dichiarazione di Classe.
function Player(nm, cgn, ss){
  if(ss != Player.SEX_MALE 
      && ss != Player.SEX_FEMALE ){
    alert('Impossibile istanziare un oggetto '
        + 'della classe Player');
    return null;
  }
 
  nome = nm;
  cognome = cgn;
  sesso = ss;
 
  this.getNome = function(){
    return nome;
  }
  this.getCognome = function(){
    return cognome;
  }
  this.getSesso = function(){
    return sesso;
  }
}

//Istanzio un oggetto di tipo Player.
var player = 
  new Player('Mirko', 'Agrati', Player.SEX_MALE);

alert(player.getNome());

/**
 * Per verificare che il meccanismo 
 * di controllo funzioni correttamente
 * provo a passare come sesso un valore 
 * non contemplato.
 * Mi aspetto un alert bloccante di avviso.
 */ 
player = new Player('Mirko', 'Agrati', 'UOMO');
-->  
</script>

E' bello sapere di poter contare su una caratteristica per me fondamentale della tecnica di programmazione come i membri static!!!

Ok, ora la classe Player è proprio barricata e non le manca nulla!
Darà sicuramente vita a buoni oggetti Player!

La OOP si fonda anche sull'ereditarietà oltre che sul polimorfismo e JavaScript non permette direttamente di ereditare da una classe. ;(

Però grazie ai membri prototype e call() possiamo indirettamente creare un meccanismo per colmare la mancanza dell'ereditarietà. Come?

Prototype

Ogni funzione Javascript possiede un attributo prototype che incapsula un oggetto di tipo Prototype.

All'oggetto prototype possiamo aggiungere attributi e metodi come potremmo fare con qualsiasi altro oggetto ma, differentemente da tutti gli altri oggetti, estendendo l'oggetto prototype in realtà si estendono tutte le istanze della
classe proprietaria dell'oggetto prototype su cui si sta agendo.

Questo perchè lo scopo esistenziale dell'oggetto prototype è fare da modello
(prototipo appunto) per la creazione di altri oggetti e modificarlo implica la creazione di un nuovo constructor di tale oggetto.

Ereditarietà

Ora mostrerò come poter derivare dalla classe base Player un nuovo tipo Calciatore.

Per fare questo utilizzerò il metodo call() e la proprietà constructor che ogni oggetto JavaScript mette a disposizione.

Il metodo call() permette di condividere un oggetto tra più funzioni, od oggetti, semplicemente chiamando da una funzione il costruttore di un'altra e passando this come parametro.

La proprietà constructor invece racchiude il costruttore della nuova classe: è necessario utilizzarla perché nel momento in cui si fa una chiamata con il metodo call() il membro prototype dell'oggetto chiamante viene sovrascritto con quello dell'oggetto chiamato.

<script type="text/javascript">
<!--
//Static
Player.SEX_MALE = 1;
Player.SEX_FEMALE = 2;

Calciatore.ATTACCANTE = "Attaccante";
Calciatore.DIFENSORE = "Difensore";
Calciatore.CENTROCAMPISTA = "CentroCampista";
Calciatore.PORTIERE = "Portiere";

function Player(nm, cgn, ss){
  if(ss != Player.SEX_MALE 
      && ss != Player.SEX_FEMALE ){
    alert('Immpossibile instanziare un oggetto '
        + ' della classe Player');
    return null;
  }
 
  nome = nm;
  cognome = cgn;
  sesso = ss;
 
  this.getNome = function(){ return nome;}
  this.getCognome = function(){ return cognome; }
  this.getSesso = function(){ return sesso; }
}

/**
 * La classe Calciatore ha 6 proprietà: 
 * Nome, Cognome, Sesso, Ruolo, Titolare e nMaglia.
 */
function Calciatore(n, c, s, r, t, m){
  Player.call(this,n,c,s);
  this.prototype = new Player(n,c,s);
  this.prototype.constructor = Calciatore;
  ruolo = r;
  titolare = t;
  nMaglia = m;
 
  this.getRuolo = function(){ return ruolo; }
  this.isTitolare = function(){ return titolare; }
  this.getNMaglia = function(){ return nMaglia; }
}

//Ora istanzio un oggetto di tipo Calciatore.
var mirko = 
    new Calciatore('Mirko', 'Agrati', Player.SEX_MALE
        , Calciatore.ATTACCANTE, true, 23);

  alert(mirko.getNMaglia() + ' - ' 
      + mirko.getNome() + ' ' 
      + mirko.getCognome() + ' - ' 
      + mirko.getRuolo());
-->  
</script>

Ecco fatto! Se si esegue il codice di esempio si potrà verificare che la classe Calciatore possiederà anche le proprietà ed i metodi della classe Player, da cui deriva.

Per quanto riguarda il polimorfismo si deve usare un processo più complicato ma efficace che consiste nell'estendere le proprietà baseConstructor e superClass.

Certamente rimane il limite delle 2 sole classi possibili da derivare, ma per essere un linguaggio di scripting JavaScript non si fa mancare niente.

Opinioni personali

E' possibile abbandonare la programmazione strutturata in favore della OOP anche per quanto riguarda la parte client delle applicazioni web, sfruttando anche le
potenzialità della sintassi JSON si possono ottenere risultati veramente soddisfacenti e sicuramente più facili da mantenere ed implementare.

Alla prossima,
MA.

0 commenti:

Posta un commento

Non ti è chiaro qualcosa?
No problem, posta il tuo dubbio ;)

..... e ricordati di firmarlo!