Interfacce vs Delegate

Spesso nella progettazione di applicazioni, per separare le competenze tra le classi e delegare esternamente alcune operazioni si ricorre alle interfacce.
Ad esempio di seguito ho riportato un frammento di "pseudo-codice C#" che simula il caricamento di una tabella clienti in un ArrayList utilizzando un DataAccessLayer esterno che si occupa fisicamente del database:

// interfaccia che viene implementata da chi vuole usare i dati provenienti dal db
public interface IManageData
{
 ArrayList UseReader(IDataReader dr);
}

// Classe che legge fisicamente dal DB
public class DataAccesLayer
{
 public ArrayList LoadData(String sqlQuery, IManageData md)
 {
  // Costruisce il command
  // ...
  
  IDataReader dr = cmd.ExecuteReader();
  return md.UseReader(dr);
 }
}

// Classe che sa come utilizzare i dati provenienti dal DataAccesLayer per costruire un Customer
public class ManageCustomer : IManageData
{
 public ArrayList UseReader(IDataReader dr)
 {
  ArrayList al = new ArrayList();
  Customer c = new Customer();
  c.Name = dr["Name"];
  al.Add(c);
  return al;
 }
}

public class Client
{
 private DataAccesLayer _dal;
 private ArrayList _customers;

 public void LoadCustomers()
 {
  ManageCustomer mc = new ManageCustomer();
  _customers = _dal.LoadData("SELECT * FROM Customers", md);
 }
}

L'idea è che la classe DataAccesLayer sa come caricare i dati ma non sa come usarli per cui si aspetta un oggetto che implementa l'interfaccia IManageData a cui passare il DataReader per estrarre i dati. La classe Client quindi nel metodo LoadCustomer passa al Dal la query da eseguire e un'instanza di una class (ManageCustomer) che implementa l'interfaccia IManageData e popola l'ArrayList usando il datareader.
La classe ManageCustomer tramite il metodo UseReader utilizza il DataReader per costruire una collezione di oggetti Customer

L'approccio usato in questo esempio fa uso delle interfacce. Esiste però un'altra strada che utilizza i delegate al posto delle interfacce.
Vediamo l'esempio:

public delegate ArrayList UseReaderDelegate(IDataReader dr);

// Come prima questa classe legge fisicamente dal DB.
class DataAccesLayer
{
 public ArrayList LoadData(String sqlQuery, UseReaderDelegate del)
 {
  // Costruisce il command
  IDataReader dr = cmd.ExecuteReader();
  return del(dr);
 }
}

public class Client
{
 private DataAccesLayer _dal;
 private ArrayList _customers;

 public void LoadCustomers()
 {
  UseReaderDelegate urs = new UseReaderDelegate(MyUseReader);
  _customers = _dal.LoadData("SELECT * FROM Customers", urs);
 }

 // Metodo con la stessa firma del delegate da usare per leggere i dati dal DataReader
 private ArrayList MyUseReader(IDataReader dr)
 {
  ArrayList al = new ArrayList();
  Customer c = new Customer();
  c.Name = dr["Name"];
  al.Add(c);
  return al;
 }
}

In questo caso il metodo LoadData si aspetta oltre alla query un delegate di tipo UseReaderDelegate al quale passerà il datareader per la gestione (come prima per la costruzione della collezione di Customer).
Nella classe client invece di creare un'istanza di una classe che implementa l'interfaccia IManageData istanzio un delegate (che implemento nella stessa classe ma che avrei potuto implementare in una classe separata) e chiamo il metodo LoadData.

Il risultato finale è il medesimo, il codice per ottenerlo è pressochè identico anche se nel caso dei delegate ne ho meno da scrivere.

Questo è un esempio di come utilizzare i delegate al di fuori del classico "Gestione degli eventi" in un contesto in cui un delegate rappresenta un'interfaccia che un metodo deve soddisfare per essere utilizzato.

Print | posted on venerdì 6 gennaio 2006 16:28