Nuova funzione load, come realizzare in poche righe un sistema di plugin

In uno degli ultimi commit, sono state implementate le funzioni load ed eval.

Entrambe servono ad eseguire del codice a runtime (a differenza della direttiva include che viene eseguita in fase di pre parsing), la prima funzione accetta il nome di uno script Hybris che caricherà in memoria ed eseguirà, mentre la seconda serve per eseguire una stringa contenente del codice Hybris.

Molti linguaggi implementano queste due funzioni, ma sono pochi quelli che come Hybris, data la sua gestione dei tipi estremamente astratta e il sistema di ereditarietà delle classi, consentono di implementare tramite loro un sistema di plugin generalizzato con poche righe di codice.

Analiziamo un sistema di esempio, realizzato appunto tramite la funzione load.

La struttura delle directory è composta come nella figura

Dove la cartella "plugins" contiene i due plugin di esempio e la cartella principale contiene PluginBase. ovvero la classe dalla quale tutti i plugin devono ereditare i metodi per essere ammessi nel sistema, PluginManager, cioè la classe che si occupa del caricamento di tutti i plugin dalla cartella plugins, e main che è lo script che lancia il programma.

Partiamo dalla classe PluginBase

include std.Exception;
 
class PluginBase {
 
	public method onEvent( e ){
		throw new Exception( __FILE__, __LINE__, "Virtual function !" );
	}
 
	public method run( e ){
		me->onEvent( e );
	}
}

Come è possibile intuire, questa classe rappresenta l'interfaccia virtuale di tutti i plugin, la quale non può essere eseguita direttamente poichè serve solo come prototipo.

PluginManager

import  std.*;
include std.io.Directory;
 
class PluginManager extends Directory {
	private plugins;
 
	method PluginManager(){
		me->Directory("./plugins");
		me->plugins = array();
		me->loadPlugins();
	}
 
	private method loadPlugins(){
		if( sizeof(me) == 0 ){
			throw new Exception( __FILE__, __LINE__, "Plugin directory is empty!" );
		}
 
		foreach( file of me ){
			load( "./plugins/".file );
			me->plugins[] = __plugin_instance;
		}
	}
 
	method run( e ){
		foreach( plugin of me->plugins ){
			plugin->run(e);
		}
	}
}

Questa classe, che eredita da std.io.Directory, caricherà tutti i file dalla cartella plugins tramite la funzione load, aggiungendo ad un suo vettore interno la variabile "__plugin_instance" che ogni plugin sarà incaricato di impostare con un riferimento a se stesso.

Il metodo PluginManager::run richiamerà poi iterativamente tutti i metodi "run" dei plugin caricati.

I due plugin di esempio, Plugin0 e Plugin1 ereditano entrambi da PluginBase e hanno praticamente la stessa struttura :

class Plugin0 extends PluginBase {
	public method onEvent( e ){
		println( "Plugin0 : ".e )
	}
}
 
__plugin_instance = new Plugin0();

class Plugin1 extends PluginBase {
	public method onEvent( e ){
		println( "Plugin1 : ".e )
	}
}
 
__plugin_instance = new Plugin1();

L'utilizzo di questo sistema la vediamo nel main

include PluginBase;
include PluginManager;
include std.io.Console;
 
try{
	pm = new PluginManager();
 
	pm->run( "evento di prova" );
}
catch( e ){
	console << e << "\n";
}

Che, se eseguito, darà il seguente output :

Plugin1 : evento di prova
Plugin0 : evento di prova

Abbiamo visto in finale, come con pochissime righe di codice è possibile realizzare quello che in altri linguaggi richiederebbe una quantità di codice nettamente superiore e che implicherebbe meno leggibilità e un sistema meno mantenibile.


Launchpad logo