Lisp – oggetti – classi – 1

l19Finito il racconto sui metodi si parte con le classi.

Se le funzioni generiche sono i verbi del sistema degli oggetti le classi sono i nomi.
Ogni valore in Common Lisp è un’istanza a qualche classe e tutte le classi sono organizzate in una singola gerarchia la cui radice è la classe T.

La gerarchia delle classi consiste di due famiglie maggiori, built-in e deinite dall’utente. Le classi che rappresentano tipi di dati come quelli visti finora come integer, string e list sono tutte built-in. Vivono nella loro sezione della gerarchia, organizzate nelle loro sub- e superclassi appropriate e sono manipolate con le funzioni viste finora. Non è possibile subclassare queste classi ma, come visto nei post precedenti, si possono definire metodi per specializzarle, estendendone il comportamento.

Ma –riprendendo l’esercizio iniziale– quando si vogliono definire nuovi nomi, come le classi per l’esempio di bank-account dei post precedenti, si devono definire le proprie classi. Ecco oggi questo.

defclass

Si creano classi con la macro defclass. Siccome i comportamenti associati alla classe sono definiti con funzioni generiche e metodi specializzatori per la classe defclass deve solo definire la classe come tipo di dati.

I tre aspetti della classe come tipo di dati sono il nome, le sue relazioni con altre classi e i nomi degli slots che creano le istanze della classe. Gli slots sono quelli che altri chiamano fields, variabili membro o attributi.
la forma base è semplicemente:

(defclass name (direct-superclass-name*)
  (slot-specifier*))

Cosa sono le “Classi definite dall’utente”
Il termine user-defined class non è un termine tecnico, sono quelle classi che subclassano standard-object e la cui superclasse non è standard-class. Ma non ce ne dobbiamo preoccupare, dice Peter. Poi la cosa sarebbe ancora più incasinata, per esempio c’è defstruct, terribile, nato prima di CLOS e inserito dentro a martellate. Dai, questo paragrafo si può ignorare 😉

Come per funzioni e variabili si può usare qualunque simbolo per il nome di una nuova classe. I nomi delle classi sono un namespace separato sia dalle funzioni che dalle variabili così si può avere classe, funzione e variabile tutte con lo stesso nome. Si usa il nome della classe come argomento di make-instance, la funzione che crea nuove istanze per classi definite dall’utente.

Le direct-superclass-names specificano le classi che la nuova classe subclassa. Se nessuna superclasse è elencarla la nuova classe sarà una subclasse diretta di standard-object che a sua volta è una subclasse di T. Quindi tutte le colassi user-defined sono parti di una singola gerarchia di classi che contiene anche tutte le classi built-in.

Trascurando per un momento gli slots specificatori le forms defclass di qualcuna delle classi usate nell’esempio dei post passati sono simili a questo:

(defclass bank-account () ...)

(defclass checking-account (bank-account) ...)

(defclass savings-account (bank-account) ...)

Peter ci dirà in “Ereditarietà multipla” cosa vuol dire una lista di più di una diretta superclasse in direct-superclass-names.

Specificatori di slot

La maggior parte di defclass consiste della lista di specificatori di slot. Ogni specificatore di slot definisce uno slot che sarà parte di ogni istanza della classe. Ogni slot in un’istanza è il posto che può contenere un valore, che sarà accessibile usando la funzione slot-value. slot-value prende un oggetto e il nome di uno slot come argomenti e ritorna il valore dello slot nominato nell’oggetto dato. Si può usare setf per definire il valore di uno slot in un oggetto.

Una classe eredita anche gli specificatori di slot dalle sue superclassi, cosicché il set di slot realmente presenti in qualunque oggetto è l’unione di tutti gli slot specificati in una form defclass della classe e quelli specificati in tutte le sue superclassi.

Come minimo uno specificatore di slot nomina lo slot, nel qual caso lo specificatore può essere solo un nome. Per esempio si può definire la classe bank-account con due slots, customer-name e balance, così:

c17-0

Ogni istanza a questa classe conterrà due slots, con questa definizione i può creare un nuovo oggetto bank-account usando make-instance:

c17-1

La rappresentazione dell’oggetto ritornato è determinata dalla funzione generica print-object. In questo caso il metodo applicabile può essere uno di quelli previsti dall’implementazione, specializzato in nel metodo print di standard-object. Usa la sintassi #<> che induce il reader a segnalare un errore se deve leggerlo. In futuro vedremo come definire un metodo di print-object che rende le cose più umane.

Usando la definizione di bank-account data sopra nuovi oggetti possono essere creati con i loro slots scollegati (unbound). Ogni tentativo di accedere al valore di un unbound slot segnala un errore per cui occorre settare uno slot prima di poterlo leggere:

c17-2

Adesso è possibile leggere i valori degli slots:

c17-3

Inizializzazione degli oggetti

Siccome con gli slots unbound non si può fare molto sarebbe bello poter creare oggetti con gli slots già inizializzati. Ci sono tre modi per controllare i valori iniziali degli slots. I primi due implicano di aggiungere allo specificatore dello slot nella form defclass l’opzione :initarg specificando un nome che potrà essere usato come parametro keyword per make-instance e i cui argomenti saranno memorizzati nello slot. Una seconda opzione, :initform, permette di specificare un’espressione Lisp che sarà usata per calcolare il valore per lo slot se non si passa un argomento :initarg a make-instance. Infine, per un completo controllo dell’inizializzazione, si può definire un metodo nella funzione generica initialize-instance che è chiamato da make-instance. Trascuriamo per adesso un’altra cosa ancora: :default-initarg.

Uno specificatore di slot che include le opzioni :initarg o :initform è scritto come una lista iniziante con il nome dello slot e seguito dalle opzioni. Per esempio se si vuol modificare la definizione di bank-account per permettere alle chiamate a make-instance di passare il nome dell’utente e il saldo iniziale e provvederne uno iniziale di zero si può scrivere:

c17-4

Adesso è possibile creare un account e specificarne i valori degli slots allo stesso tempo:

c17-5

Se non si fornisce un argomento :balance a make-instance lo slot-value di balance verrà calcolato nella valutazione della form tramite l’opzione :initform. Ma se non si fornisce un argomento per :customer-name lo slot customer-name sarà unbound e un tentativo di leggerlo prima di assegnarlo segnalerà un errore:

c17-6

Se ci si vuole assicurare che il nome dell’utente sia fornito quando l’account è creato si può segnalare un errore nella initform visto che verrà valutata solo se un initarg non è fornito. Si possono anche usare initforms che generano un differente valore ogni volta che sono valutate –l’initform viene valutata per ogni nuovo oggetto. Per provare queste tecniche si può modificare lo specificatore dello slot customer-name e aggiungere un nuovo slot, account-number, che sarà inizializzato con un contatore con valore sempre crescente.

c17-7

Nella maggior parte dei casi  la combinazione delle opzioni :initarg e :initform risulterà sufficiente per inizializzare propriamente un oggetto. tuttavia, mentre un initform può essere qualsiasi espressione Lisp, essa non ha accesso all’oggetto che si sta inizializzando cosicché non si può inizializzare uno slot basato sul valore di un altro. Per far questo occorre definire un metodo nella funzione generica initialize-instance.

Il metodo primario di initialize-istance specializzato in standard-object si cura di inizializzare gli slots basati dalle opzioni :initarg e :initform. Siccome non si vuole disturbare questo il modo più comune di aggiungere codice di inizializzazioni personalizzate è di definire nella classe un metodo :after specializzato. Per esempio se si vuole aggiungere lo slot account-type che dev’essere settato a uno dei valori :gold, :silver o :bronze basato sulla balance iniziale si può cambiare la definizione della classe, aggiungendo lo slot account-type senza un opzione:

c17-8

Quindi si può definire un metodo :after per initialize-instance che setta lo slot account-type basato sul valore che è stato inserito nello slot balance.

Nota: Uno degli errori tipici è di definire un metodo initialize-instance senza qualificatore :after. In questo modo si crea un nuovo metodo primario che maschera quello di default. Si può rimuovere il metodo non voluto con remove-method e find-method:

(remove-method #'initialize-instance
  (find-method #'initialize-instance () 
    (list (find-class 'bank-account))))

Tornando al dunque:

c17-9

L’&key nella lista dei parametri è richiesta per premettere alla lista dei parametri del metodo di essere congruente con la funzione generica –la lista dei parametri specificata nella funzione generica initialize-instance contiene un &key per permettere ai metodi individuali di  fornire i loro parametri keyword ma non sono obbligatori. Quindi ogni metodo deve specificare un &key anche se non ha parametri di quel tipo.

D’altra parte se un metodo specializzato di initialize-instance in una particolare classe specifica un parametro &key questo parametro diventa un parametro legale per make-instance quando viene creata un’istanza a una classe. Per esempio se la banca paga a volte una percentuale della balance iniziale come bonus quando un account viene aperto si può implementare usando un metodo di initialize-instance che prende un argomento keyword che specifica la percentuale del bonus, così:

c17-10

Definendo questo metodo si crea :opening-bonus-percentage un argomento legale di make-instance quando si crea un oggetto bank-account:

c17-11

Pausa :mrgreen:

Posta un commento o usa questo indirizzo per il trackback.

Trackback

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.

%d blogger hanno fatto clic su Mi Piace per questo: