Introduzione

Una classe, analogamente ad una struttura, è un oggetto contraddistinto da degli attributi e, come estensione delle strutture, da dei metodi. Come un attributo rappresenta una variabile che “appartiene” alla classe ed è raggiungibile solo tramite di lei, un metodo è una funzione che la classe può espletare e viene dichiarato tramite la parola chiave method.

Per dichiarare una classe, si usa la parola chiave class, seguendo la seguente sintassi :

class /* nome della classe */ {
// attributi e metodi
}

Ad esempio, una classe potrebbe essere dichiarata come :

class Persona {
    nome;
    cognome;
    età;
 
    method Persona( nome, cognome, età ){
        me.nome    = nome;
        me.cognome = cognome;
        me.età     = età;
    }
 
    method presentati(){
        println( "Ciao, mi chiamo " + me.nome + " " + me.cognome + " e ho " + me.età + " anni." );
    }
}

La parola chiave me serve per accedere ad attributi e/o metodi dall'interno di un metodo della classe stessa.

In questo caso, sono stati dichiarati tre attributi e due metodi, anche se il primo metodo Persona, chiamandosi come la classe stessa, in realtà è il costruttore 1) della classe, ovvero il metodo che verrà lanciato per inizializzare la classe quando sarà instanziata tramite l'operatore new, ad esempio :

// istanzia la classe e lancia il costruttore
io = new Persona( "Mario", "Rossi", 41 );
 
io.presentati();

Questo script stamperà sullo schermo l'output :

Ciao, mi chiamo Mario Rossi e ho 41 anni.

Specificatori di accesso

Uno specificatore di accesso, è una parola chiave che determina chi può accedere ad un determinato attributo o usare un determinato metodo di una certa classe, che può assumere il valore di :

private Solo la classe stessa può accedere dal suo interno ad un membro dichiarato private.
protected Solo la classe stessa e le classi derivate 2) possono accedere ad un membro dichiarato protected.
public Il membro dichiarato public può essere utilizzato sia dalla classe, dalle classi derivate e dall'esterno della classe (nel resto dello script).

Se non viene dichiarato esplicitamente, il membro (attributo o metodo) verrà implicitamente considerato come public .

Un esempio per capirne la sintassi è :

class Persona {
    // questi attributi sono privati
    private nome, cognome, età;
 
    // un costruttore è sempre public
    method Persona( nome, cognome, età ){
        me.nome    = nome;
        me.cognome = cognome;
        me.età     = età;
    }
 
    // metodo pubblico
    public method presentati(){
        println( "Ciao, mi chiamo " + me.nome + " " + me.cognome + " e ho " + me.età + " anni." );
    }
}

In questo caso, gli attributi non sono richiamabili dall'esterno ma solo dall'interno della classe stessa, ad esempio, un codice del genere :

io = new Persona( "Mario", "Rossi", 41 );
 
println( io.nome );

Genererà l'errore :

[LINE 22] Syntax error : Private attribute 'nome' can be accessed only within 'Persona' class .

Metodi con lo stesso nome

E' possibile dichiarare più metodi in una classe che hanno lo stesso nome ma con numero di argomenti differente, ad esempio :

class Persona {
    private nome, cognome, età;
 
    method Persona( nome, cognome, età ){
        me.nome    = nome;
        me.cognome = cognome;
        me.età     = età;
    }
 
    public method presentati(){
        println( "Ciao, mi chiamo " + me.nome + " " + me.cognome + " e ho " + me.età + " anni." );
    }
 
    public method presentati( residenza ){
        println( "Ciao, mi chiamo " + me.nome + " " + me.cognome + " e ho " + me.età + " anni, vivo a " + residenza );
    }
}

In questo caso, in base al numero di argomenti che vengono passati al metodo “presentati”, verrà richiamata la prima versione (se non ci sono argomenti) o la seconda (nel caso di un argomento in input). La stessa cosa è valida per i costruttori, quindi è possibile dichiararne più di uno in base alle esigenze e poi, quando la classe verrà istanziata, verrà lanciato il costruttore appropriato in base al numero di argomenti.

Costruttori e Distruttori

Così come esiste il concetto di costruttore, ovvero quel metodo che, avendo lo stesso nome della classe, viene eseguito quando la classe viene istanziata, esiste anche il concetto di distruttore . Un distruttore è un metodo che, se dichiarato, verrà eseguito quando la classe va out of scope, viene dereferenziata o comunque non è più utile 3).

Un distruttore, viene dichiarato tramite la parola chiave

__expire

Ad esempio :

class Persona {
    private nome, cognome, età;
 
    method Persona( nome, cognome, età ){
        me.nome    = nome;
        me.cognome = cognome;
        me.età     = età;
    }
 
    method __expire(){
       println( "Addio a tutti!" );
    }
}

Quando la classe verrà deallocata dal garbage collector, distruttore della classe verrà eseguito automaticamente. Questo può tornare utile quando una classe, ad esempio, apre una connessione remota e vogliamo che quando viene deallocata venga chiusa tale connessione in automatico.

Membri Statici

Per membro statico, dichiarato con la keyword static si intende un attributo o un metodo che esiste a prescindere dall'istanza della classe, poichè risiede direttamente nel prototipo della classe stessa. Sono valori o metodi che in pratica possono essere utilizzati tramite il nome della classe e non necessariamente da un istanza di essa, ad esempio :

class Foo {
    static bar = "Hello World";
}
 
println( Foo.bar );

Come è possibile vedere, non è stata creata un istanza della classe per accedere all'attributo 'bar', ma direttamente il nome della classe (che in memoria mappa il prototipo stesso della classe.

Analogamente per un metodo statico :

class Foo {
    static bar = "Hello World";
 
    static method say_bar(){
        println( Foo.bar );
    }
}
 
Foo.say_bar();

Inoltre si può notare da quest'ultimo esempio, che dall'interno di un metodo statico non è possibile utilizzare l'istanza me, proprio perchè un metodo statico prescinde da qualsivoglia istanza della classe. Se un metodo statico deve accedere ad un dato attributo della classe, anche l'attributo deve essere dichiarato come statico. Viceversa, un istanza della classe può accedere liberamente ad un qualunque suo metodo statico anche tramite l'istanza me :

class Foo {
    static bar = "Hello World";
 
    public method say_bar(){
        println( me.bar );
    }
}
 
foo = new Foo();
foo.say_bar();

Nota 1

  Attributi e metodi dichiarati statici, automaticamente diventano anche pubblici.

Nota 2

  In tutto il processo esiste una sola istanza di un attributo statico, questo significa che un thread, un processo o una funzione che dovessero
  cambiare il valore di un attributo statico, farebbero in modo che la sola istanza di quell'attributo cambi valore, risultando di conseguenza
  diverso per ogni altro accesso successivo.

Nota 3

  Date le sue caratteristiche, un membro statico non viene gestito dal garbage collector fino al termine del programma, di conseguenza rimarrà
  residente nella memoria dell'interprete fino alla fine.

Ereditarietà

Una classe, tramite la direttiva extends, può ereditare metodi ed attributi di una o più classi, estendendone all'occorrenza le funzionalità e specializzandole.

Ad esempio :

class Impiegato extends Persona {
    private ufficio, salario;
 
    method Impiegato( nome, cognome, eta, ufficio, salario ){
        me.Persona( nome, cognome, eta );
        me.ufficio = ufficio;
        me.salario = salario;
    }
 
    public method presentati(){
        println( "Salve, mi chiamo " + me.nome + " " + me.cognome + ", ho " + me.età + " anni." );
        println( "Lavoro nell'ufficio numero " + me.ufficio + " per " + me.salario + " euro al mese." );
    }
}

Come possiamo vedere, la classe Impiegato è una specializzazione della classe Persona dalla quale eredita i metodi e gli attributi. Nel costruttore, la classe inizializza gli attributi ereditati dalla classe “Persona” richiamando esplicitamente il suo costruttore, per poi continuare ad inizializzare gli attributi che ha introdotto lei (ufficio e salario). Ciò nonostante, avrebbe potuto inizializzarli normalmente (accedendo a loro tramite l'istanza me) avendoli ereditati direttamente dalla classe base.

Inoltre vediamo che la classe rimpiazza il metodo presentati della classe base con uno dichiarato al suo interno che stamperà una output appropriato alla classe “Impiegato”, ad esempio :

io = new Impiegato( "Mario", "Rossi", 41, 112, 1200 );
 
io.presentati();

Stamperà l'output :

Salve, mi chiamo Mario Rossi, ho 41 anni.
Lavoro nell'ufficio numero 112 per 1200 euro al mese.

Per estendere più di una classe, basta elencarne i nomi dopo la direttiva extends separandoli da una virgola, ad esempio :

class Derivata extends Base1, Base2, Base3 {
 
}

Overloading degli Operatori

Una classe può eseguire l'overloading di uno o più operatori, al fine di istruire l'interprete su come trattare un operazione con quel determinato operatore se coinvolge una variabile che istanzia quella classe.

Gli operatori dei quali si può eseguire l'overloading tramite la parola chiave operator sono :

[], []=, []<, .., +, +=, -, -=, /, /=, *, *=, %, %=, ++, --, ^, ^=, ~, &, &=, |, |=, <<, <<=, >>, !, <, >, >=, <=, ==, !=, &&, ||, ~=

Ad esempio, possiamo vedere una classe Array che wrappa il medesimo tipo :

class Array {
	private __a;
 
	public method Array(){
		me.__a = [];	
	}
 
        // accesso ad un elemento in sola lettura
	operator [] ( index ){	
		return me.__a[index];
	}
        // accesso ad un elemento in scrittura
	operator []< ( index, object ){
		me.__a[index] = object;
	}
        // push di un elemento
	operator []= ( object ){
		me.__a[] = object;
	}
}

Potremmo istanziare ed usare tale classe nel seguente modo :

a = new Array();
 
// aggiungo degli elementi
a[] = "Hello";
a[] = "World";
a[] = "!";
 
// sostituisco un elemento
a[2] = "!!!";
 
// stampo il secondo elemento
println( a[1] );

Overloading dei Descrittori

Così come gli operatori, esistono dei descrittori standard dei quali una classe può eseguire l'overloading per istruire l'interprete su come trattare la classe in determinate situazioni, analiziamoli uno per uno.

Descrittore size

Questo descrittore serve per quantificare la grandezza di una determinata classe. Tale quantificazione, può servire ad esempio per funzioni come sizeof se usate sulla classe stessa.

Potremmo modificare l'esempio di prima così :

class Array {
	private __a;
 
	public method Array(){
		me.__a = [];	
	}
 
        // restituisce il numero di elementi che contiene l'array
        public method __size(){
	    return me.__a.size();
	}
 
	operator [] ( index ){	
		return me.__a[index];
	}
 
	operator []< ( index, object ){
		me.__a[index] = object;
	}
 
	operator []= ( object ){
		me.__a[] = object;
	}
}

In questo caso, il codice :

a = new Array();
 
a[] = "Hello";
a[] = "World";
a[] = "!";
 
println( sizeof(a) );

Stamperebbe ovviamente 3, cioè il numero di elementi che contiene .

Un altro utilizzo molto importante di questo descrittore è l'iterabilità di una classe. Per iterabilità di una classe, si intende la possibilità di utilizzare la classe nel ciclo foreach come se fosse una collezione. Per rendere una classe iterabile, è necessario eseguire l'overloading dell'operatore subscript in lettura e del descrittore size, di conseguenza, la classe Array che abbiamo visto nell'esempio, eseguendo l'overloading di entrambi, può essere usata nel seguente modo :

a = new Array();
 
a[] = "Hello";
a[] = "World";
a[] = "!\n";
 
foreach( elemento of a ){
    print( elemento );
}

Questo stamperà l'output :

Hello World!

Descrittore to_string

Questo descrittore restituisce una rappresentazione della classe in una variabile stringa, rappresentazione che può essere usata ad esempio per stampare la casse.

Sempre seguendo l'esempio di prima :

class Array {
	private __a;
 
	public method Array(){
		me.__a = [];	
	}
 
        public method __size(){
	    return me.__a.size();
	}
 
        // restituisce una stringa che contiene gli elementi dell'array
        public method __to_string(){
		return "( " + me.__a.join( ", " ) + " )";
	}
 
	operator [] ( index ){	
		return me.__a[index];
	}
 
	operator []< ( index, object ){
		me.__a[index] = object;
	}
 
	operator []= ( object ){
		me.__a[] = object;
	}
}

Potremmo quindi dichiarare :

a = new Array();
 
a[] = 1;
a[] = 2;
a[] = 3;
 
println( a );

La println, cercherà una rappresentazione sotto forma di stringa della classe Array ed il descrittore to_string verrà richiamato, di conseguenza questo script stamperà l'output :

( 1, 2, 3 )

Descrittore attribute

Questo descrittore, viene richiamato dall'engine nel momento in cui si tenta l'accesso ad un attributo che non è stato dichiarato nella classe. Presenta due forme, una per quando l'attributo viene considerato in sola lettura ed un altra per la scrittura.

Ad esempio :

class Prova {
    private un_attributo;
 
    method Prova(){
        me.un_attributo = "Qualcosa";
    }
 
    // descrittore in sola lettura
    method __attribute( name ){
        return "Non esiste '" + name + "' !!!";
    }
 
    // descrittore in scrittura
    method __attribute( name, value ){
        println( "Non esistendo '" + name + "', non puoi istanziarlo con il valore " + value + " !!!" );
    }
}

Potremmo poi scrivere :

p = new Prova();
 
println( p.foo );
 
p.foo = "bar";

Che stamperebbe l'output :

Non esiste 'foo' !!!
Non esistendo 'foo', non puoi istanziarlo con il valore bar !!!

Descrittore method

Similmente al descrittore attribute, il descrittore method, se dichiarato, viene richiamato dall'engine quando si tenta di utilizzare un metodo che non è stato dichiarato da una classe, ad esempio :

class Prova {
    private un_attributo;
 
    method Prova(){
        me.un_attributo = "Qualcosa";
    }
 
    method __method( name, argv ){
        println( "Hai tentato di usare il metodo " + name + " passandogli i seguenti argomenti : " + argv.join( ", " ) );
    }
}

Che potremmo sfruttare tramite lo script :

p = new Prova();
 
p.metodo_inesistente( "foo", "bar", 12345 );

Che genererebbe l'output :

Hai tentato di usare il metodo metodo_inesistente passandogli i seguenti argomenti : foo, bar, 12345
1) vedere paragrafo su costruttori e distruttori
2) vedere paragrafo “ereditarietà”
3) vedere sezione sulla gestione della memoria ed il garbage collector
Ultima modifica: 2010/05/15 05:38 da evilsocket