Questo post è scritto con l'intento di fornire una rapida carrellata di codice per l'implementazione della separazione tra interfacce ed implementazioni (in .NET). Come sempre lo stile sarà consono al nome del Blog ( xP Style ) quindi di progettazione c'è ne sarà ben poca!
Lo scopo finale sarà quello di separare il lavoro di definizione delle funzionalità dal lavoro di implementazione (un po' come fa' COM)
Alla fine del post vogliamo avere a disposizione un metodo per:
* Definire un file che conterrà solo la descrizione delle funzionalità
* Definire diverse implementazioni della stessa funzionalità!
* Caricare a runtime una delle implementazioni, a scelta, senza linkare direttamente le DLL al progetto.
L'immagine che segue dovrebbe dare l'idea del risultato che vogliamo ottenere. La HelloWorld multilingua si presta egregiamente all'esempio!
ATTENZIONE:
Il codice nel post non è completo. Per effettuare il download del progetto è possibile
cliccare qui.
1° PROGETTO: Tipo "Libreria di Classi" - Il contenitore delle interfacce
Dato che useremo (userò) VB2010 Express non possiamo caricare più progetti all'interno della soluzione. Procediamo un passo alla volta!
Il primo progetto, che chiamerò test_interface, conterrà la definizione delle interfacce (ovvero delle funzionalità che vogliamo fornire ad un ipotetico programmatore che userà le nostre dll). In questo progetto creiamo un unico file hello_world.vb con corpo:
interface hello_world
function SayHello() as String
end interface
Compiliamo il progetto e ci vien fuori la nostra bella DLL test_interface.dll
2° PROGETTO: Tipo "Libreria di Classi" - La prima implementazione (ITALIANO)
Il secondo progetto, che chiamerò test_impl_ita, conterrà l'implementazione dell'interfaccia hello_world per la lingua italiana. A questo progetto è necessario linkare la dll che contiene le definizioni (quindi quella compilata prima!).
Creiamo il file hello_world_ita.vb con corpo:
public class hello_world_ita
implements test_interface.hello_world
function SayHello() as String implements [...]
return "CIAO MONDO"
end function
end class
3° PROGETTO: Tipo "Libreria di Classi" - La seconda implementazione (INGLESE)
Il terzo progetto, che chiamerò test_impl_eng, conterrà l'implementazione dell'interfaccia hello_world per la lingua inglese. A questo progetto è necessario linkare la dll che contiene le definizioni (quindi quella compilata prima!).
Creiamo il file hello_world_eng.vb con corpo:
public class hello_world_eng
implements test_interface.hello_world
function SayHello() as String implements [...]
return "HELLO WORLD"
end function
end class
A questo punto abbiamo le nostre due implementazioni rispettivamente nei file test_impl_eng.dll e test_impl_ita.dll.
Passiamo ora a creare il progetto principale che utilizzerà le due implementazioni (di cui non conosce l'implementazione ma solo l'interfaccia - scusate la ripetizione-)
4° PROGETTO: Tipo "Eseguibile" -
Copiamo le due dll di implementazione nella cartella DEBUG del progetto eseguibile (in modo da poterle trovare facilmente tramite PATH) e linkiamo la DLL (questa va linkata!) contenente l'interfaccia. Fatto questo il progetto avrà a disposizione le firme dei metodi che potrà richiamare (l'interfaccia) ma non le implementazioni (ancora non caricate).
Per caricare, a runtime, le implementazioni faremo uso degli oggetti Assembly e Activator (manna dal cielo messa a disposizione da .NET!)
Creiamo una form e mettiamoci dentro quanto segue:
Imports System.Reflection
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'# DEFINIAMO I DUE OGGETTI COME INTERFACCE E NON COME CLASSI
Dim eng_imp As test_interface.hello_world
Dim ita_imp As test_interface.hello_world
'# CARICHIAMO L'IMPLEMENTAZIONE IN INGLESE
Dim LoaderImplementazioni As Assembly
LoaderImplementazioni = Assembly.LoadFile(Application.StartupPath & "\test_impl_eng.dll")
Dim Ptype() As Type = Nothing
'# CARICHIAMO I TIPI CONTENUTI NELL'IMPLEMENTAZIONE. A NOI INTERESSA test_impl_eng
'# CHE STA ALL'INDICE 10 (potete anche cercarvela a mano)
Ptype = LoaderImplementazioni.GetTypes
'# il tipo è all'indice 10 come prima. Activator ci crea l'istanza della classe contenuta nel file dll.
eng_imp = Activator.CreateInstance(Ptype(10))
'# CARICHIAMO L'IMPLEMENTAZIONE IN ITALIANO
LoaderImplementazioni = Assembly.LoadFile(Application.StartupPath & "\test_impl_ita.dll")
Ptype = LoaderImplementazioni.GetTypes
'# il tipo è all'indice 10 come prima. Activator ci crea l'istanza della classe contenuta nel file dll.
ita_imp = Activator.CreateInstance(Ptype(10))
'# USIAMO LE DUE IMPLEMENTAZIONI
MsgBox(eng_imp.SayHello)
MsgBox(ita_imp.SayHello)
End Sub
End Class
N.B:
Per essere totalmente slegati dalle implementazioni è necessario dare lo stesso nome alle due classi, in modo che in PType(10) il nome del tipo risulti sempre lo stesso. Tuttavia, l'idea di come usare Activator e Assembly dovrebbe essere comunque chiara. (Magari in un prossimo post vedremo perché è sempre necessaria l'Entry Point con un nome STANDARD nelle DLL di windows!)
ATTENZIONE:
Il codice nel post non è completo. Per effettuare il download del progetto è possibile
cliccare qui.