Dart: reflection con oggetti Mirror

Di - 30 March 2013 - in
Post image for Dart: reflection con oggetti Mirror

Nell’articolo di oggi parleremo del concetto di riflessione, una caratteristica di Dart molto interessante e comune ad altri linguaggi fra i quali Scala, Python, Java e Haskell, che offre ad una applicazione la possibilità di analizzare la propria struttura tramite oggetti chiamati Mirrors. Per maggiori informazioni sulla riflessione si può consultare il documento Mirrors: Design Principles for Meta-level Facilities of Object-Oriented Programming Languages redatto da +Gilad Bracha e David Ungar.

Sebbene l’implementazione in Dart sia ancora parziale, nella libreria dart:mirrors sono presenti funzioni sufficienti per creare un oggetto Mirror che rifletta un altro oggetto e ne invochi i metodi. Dopo una veloce panoramica sulle funzioni principali di dart:mirrors vedremo come utilizzarle per ottenere in Dart un comportamento simile a quello del modulo pprint in python.

Innanzitutto importiamo dart:mirrors nel nostro progetto e creiamo una classe di prova:

import 'dart:mirrors';
import 'dart:async';

class Utente {

  final String nome;
  final String cognome;
  DateTime iscrizione;

  Utente(this.nome, this.cognome);

  void scriviMsg(String msg){
    print("$nome scrive: $msg");
  }

  void chiudiAccount() {
    // . . .
  }

}

(Trattandosi di una libreria ancora incompleta, DartEditor evidenzierà in giallo l’istruzione dell’importazione e nella scheda Problems sarà notificato: dart:mirrors is not fully implemented yet)

Per istanziare un nuovo oggetto InstanceMirror — sottotipo di Mirror che riflette un altro oggetto — utilizziamo la funzione reflect(Object oggettoDaRiflettere):

  InstanceMirror im = reflect(new Utente("Mario","Rossi"));

Per invocare un metodo d’istanza di Utente() dal mirror corrispondente utilizziamo il metodo invoke(), che ritorna un oggetto Future:

InstanceMirror.invoke(
    String nomeDelMembro,
    List<Object> argomentiPassatiPerPosizione,
    [Map<String,Object> argomentiPassatiPerNome]
    );`

Assumiamo quindi di dover invocare Utente.scriviMsg(String msg):

Future<InstanceMirror> future = im.invoke('scriviMsg',['ciao!']);

L’output sarà identico all’output di Utente.scriviMsg():

Mario scrive: ciao!

Per ottenere informazioni sulla struttura della classe Utente() dobbiamo innanzitutto istanziare un nuovo oggetto di tipo ClassMirror, ottenuto come oggetto di ritorno di InstanceMirror.type:

ClassMirror classe = im.type;

Ora creiamo una nuova funzione che prenda in ingresso una istanza di ClassMirror e restituisca, come mappa, la struttura della classe che riflette:

Map struttura(ClassMirror classe) {

  Map mappa = {};
  mappa["Nome"] = classe.simpleName;
  mappa["Costruttori"] = [];

  classe.constructors.forEach((k,v){
    mappa["Costruttori"].add(v.simpleName);
  });

  mappa["Membri"]={};
  mappa["Membri"]["Metodi"]=[];
  mappa["Membri"]["Variabili"]=[];

  classe.methods.forEach((k,v){
    mappa["Membri"]["Metodi"].add(v.simpleName);
  });

  classe.variables.forEach((k,v){
    mappa["Membri"]["Variabili"].add(v.simpleName);
  });

  return mappa;
}

I metodi classe.constructors, classe.methods e classe.variables — che restituiscono mappe — ci consentono di navigare fra gli attributi della classe, e nel caso specifico di questa funzione, ci permettono di aggiungerli ad una mappa. Il getter simpleName restituisce il nome completo dell’entità su cui è invocato.

Aggiungiamo a main() la funzione:

print(struttura(classe));

e il risultato ottenuto sarà:

{Nome: Utente, Costruttori: [Utente], Membri: {Metodi: [chiudiAccount, scriviMsg], Variabili: [nome, cognome, iscrizione]}}

Come sempre, il codice utilizzato in questo articolo è disponibile sull’account di Engeene su GitHub.

Leave a Reply

Claudio d'Angelis Articolo scritto da

Programmatore e studente di Informatica, appassionato di musica, web e sistemi UNIX. Collabora con Googlab dall'Ottobre 2012.

Contatta l'autore

Previous post:

Next post: