PDA

View Full Version : [C#/Generics]Oggetti come parametri


RaouL_BennetH
29-04-2009, 13:02
Ciao a tutti :)

Ho un'interfaccia molto semplice così composta:



using System;
using Collection.Generics;

namespace UnderstandingGenerics
{

public interface IGenericTest<T>
{

void Add(T businessObject);
List<T> GetAll();
}
}


//una prima semplice implementazione:

public class Persona
{

private string nome;
private string cognome;

public Persona(string nome, string cognome)
{
this.nome = nome;
this.cognome = cognome;
}
}

public class Rubrica : IGenericTest<Persona>
{
public void Add(Persona persona)
{
//codice che aggiunge la persona alla rubrica
}

public List<Persona> GetAll()
{
//codice che ottiene tutte le persone presenti in rubrica
}
}



Ora il mio dubbio:

mi sono reso conto che così facendo sarò obbligato ad definire una classe che implementi i metodi dell'interfaccia per ogni tipo di entità che avrò.
Non so se è un bene o un male... cmq, mi chiedevo se invece avessi una sola classe che implementa l'interfaccia, come potrei passarle dinamicamente gli oggetti che mi interessano ?


Grazie mille :)

RaouL.

RaouL_BennetH
29-04-2009, 13:35
Forse ho capito:



public interface IProva
{

void Add(Object o);
}


public class Persona
{

private string nome;

public Persona(string nome)
{
this.nome = nome;
}
}

public class AzioniRubrica : IProva
{

public void Add(Object o)
{

//codice che aggiunge un oggetto alla rubrica
}
}


public class Rubrica
{

Persona persona;
Azienda azienda; //altra classe di prova

public void Test()
{

AzioniRubrica a = new AzioniRubrica();
persona = new Persona("raoul");
a.Add(persona);
a.Add(azienda);
}
}

gugoXX
29-04-2009, 14:29
object no, non usarlo. E' proprio controproducente e costa troppo in termini di errori runtime e anche velocita' di esecuzione.
I generics sono stati introdotti apposta.

Usare un'interfaccia qui, se non si hanno altri requisiti che ne vincolano l'esistenza, mi sembra un po' una stortura.
Userei una classe, eventualmente astratta, per questo specifico problema.


namespace UnderstandingGenerics
{

public abstract class MyBaseCollection<T>
{
protected List<T> myList;

public MyBaseCollection()
{
myList=new List<T>();
}

public void Add(T businessObject)
{
myList.Add(businessObject);
}

public List<T> GetAll()
{
return myList;
}

// Metodo banale aggiunto perche' altrimenti verrebbe voglia di usare il
// generics List direttamente senza dover costruire
// questa classe astratta
public List<T> GetReverse()
{
return myList.Reverse().ToList();
}
}
}


//una prima semplice implementazione:

public class Persona
{

private string nome;
private string cognome;

public Persona(string nome, string cognome)
{
this.nome = nome;
this.cognome = cognome;
}
}

public class Rubrica : MyBaseCollection<Persona>
{
public Rubrica():base()
{
}

// Metodo per giustificare l'esistenza della classe Rubrica
// Altrimenti si sarebbe usata direttamente la generic MyBaseCollection
// (ovviamente non piu' Abstract in questo caso)
public Persona GetFirstByName(string name)
{
Persona ret = myList.
Where(per => per.nome==name).
FirstOrDefault();

return ret;
}
}


Una classe astratta ha qui poco senso comunque, non essendoci metodi astratti che devono essere implementati dalle classi derivate.
L'unico effetto sara' quello di impedire la costruzione di un oggetto della classe specifica MyBaseCollection, effetto magari voluto dall'applicazione.

Un primo passo prima di costruire un nuovo Generics, e' quello di usarli. La maggior parte dei problemi si puo' risolvere usando Generics gia' fatti e classi normali. Per esigenze piu' complesse si puo' valutare la scrittura di una nuova classe Generic, cosa non proprio banale anche a causa della sintassi un po' forzata e poco leggibile.
Una volta fatta pero', almeno, l'uso di un Generic ben fatto risulta essere semplice.

RaouL_BennetH
29-04-2009, 15:28
Ciao gugoXX :)

In linea di massima ho capito la tua spiegazione, e, con le seguenti premesse:

1) Sto usando le interfacce perchè sono il mio attuale argomento di studio
2) non è un'applicazione reale, solo didattica

mi chiedevo però se:

anzichè avere un'entità Rubrica, io avessi un'entità CalendarioAttività, se dovessi aggiungere più oggetti diversi fra di loro a CalendarioAttività, sarei costretto ad implementare i metodi dell'interfaccia o della classe astratta per ogni tipo di oggetto che devo aggiungere?

cioè:



public interface IPlanning<T>
{
void Add(T oggetto);

}

public class Planning : IPlanning<Persona>
//già qui... mi blocco ...
//posso solo aggiungere un oggetto di tipo persona al mio planning,
//ma se dovessi aggiungere un appuntamento?



oppure, se considero invece le cose dal punto di vista di un'azienda credo che:

un'azienda ha dei dipendenti
un'azienda ha dei fornitori

quindi mi viene in mente un: azienda.Add(dipendente) e azienda.Add(fornitore)

e di conseguenza penso che potrei avere un'unico gestore di queste azioni.

Oppure è sbagliato proprio il mio approccio ?


Grazie di cuore :)

RaouL.

gugoXX
29-04-2009, 15:52
L'approccio non e' sbagliato, anche se non e' ortodosso.
Lo puo' diventare se devi giustificare le interfaccie per altri motivi.
Ovviamente per questo esempio non solo non userei le interfaccie, ma come detto non userei neppure un nuova classe Generics, quanto piu' userei direttamente le Collection offerte dai namespace Generics.

Comunque, per la tua implementazione ti e' sufficiente implementare l'interfaccia 2 volte...


public class Planning : IPlanning<Dipendente>, IPlanning<Fornitore>


Per cui dovrai implementare le 2 diverse Add specifiche.
Il controsenso sta qui, dato che molto probaiblmente il codice per le 2 add sara' identico, e quindi il tuo legittimo dubbio se l'uso delle interfaccie sia proprio questo.
Ebbene, ovviamente non lo e'. Raramente si inseriscono tecnicismi che forzano la riscrittura del codice, quanto piu' invece per ridurlo.

Ti faccio un esempio concreto in cui l'uso delle interfaccie puo' essere utile.
Immagina di avere un Database remoto a San Francisco, non da te manutenuto, che espone qualche Stored Procedure implementate su questo Database e che tu potrai chiamare.
Potrebbe venire voglia semplicemente di costruire una classe Helper, con tanti metodi quante sono le stored procedure.
Ti sono pero' arrivate delle direttive dall'alto, che ti consigliano fortemente di non legare l'implementazione pensando all'esistenza di un Database, perche' probabilmente in futuro l'azienda passera' ad usare WebService per l'accesso ai dati, che circonera' il Database che sara' di fatto reso invisibile, vietando quindi in futuro l'accesso diretto al database a qualsiasi utilizzatore aziendale.
Verra' creato quindi da qualuno un WebService che esporra' tanti metodi quante erano le stored procedure che tu prima avresti potuto utilizzare.
Pero' nel frattempo questo WebService non c'e'.
E tu vorresti evitare di riscrivere tanto codice.
Parti anche dal presupposto di avere una classe, come probabile che sia, che gia' sa come gestire connection string, aprire connessioni al database, eseguire query, lanciare stored procedure e ritirarne i risultati, etc.

Uno dei possibili modi e' questo:
- Crei l'interfaccia IRemoteSanFrancisco che contiene tanti metodi quante sono le stored procedure
- Crei una classe RemoteDBSanFrancisco: DBGeneric, IRemoteSanFrancisco che deriva dalla tua DBGeneric e che sa quindi come lavorare sui Database, e che implementa anche l'interfaccia.
- Ciascun metodo implementato dall'interfaccia sara' quindi la chiamata alla stored procedure relativa, ovviamente usando le facilities presenti nella classe base DBGeneric

- Crei quindi anche la classe RemoteWSSanFrancisco: IRemoteSanFrancisco, ovvero la classe che nell'implementazione di ciascun metodo remoto andra' a chiamare il WebMethod relativo al WebService, invece che accedere direttamente al DB.

Per concludere e mettere la ciliegina, doterai la tua soluzione di un SanFranciscoFactory, ovvero una classe con un Helper statico in grado di costruire un oggetto che implementa l'interfaccia IRemoteSanFrancisco

public static class SanFranciscoFactory
{
public static IRemoteSanFrancisco GetMyRemoteSanFrancisco()
{
// Qui si crea un'istanza della RemoteDBSanFrancisco
//oppure della RemoteWSSanFrancisco, a seconda di cosa c'e' scritto nei file di configurazione
// e se ne restituisce l'istanza
if (config qualcosa)
return new RemoteDBSanFrancisco();
else
return new RemoteWSSanFrancisco();
}
}

// L'utilizzatore quindi eseguira'

....
IRemoteSanFrancisco proxy = SanFranciscoFactory.GetMyRemoteSanFrancisco();
proxy.Metodo1(var1, var2, var3);
PippoClass inst = proxy.Metodo2(var4, var5);
...

senza sapere se Metodo1 e Metodo2 passeranno attraverso DB call oppure WebMethod call. In futuro sara' sufficinete cambiare l'istanza dell'interfaccia che dovra' restiture il Factory per agire mediante il primo o il nuovo metodo.
Tutto il codice che "usa" i vai Metodo1, Metodo2, etc. non sara' da cambiare.

PS: Questo discorso si puo' fare di nuovo con le abstract class invece che con le interfaccie. Farlo con le interfaccie pero' abilita' una metodologia avanzata di test che si chiama Mock, molto utile, direi indispensabile, se si usa il Test Driven Development.

RaouL_BennetH
29-04-2009, 16:31
>cut<


public class Planning : IPlanning<Dipendente>, IPlanning<Fornitore>


Per cui dovrai implementare le 2 diverse Add specifiche.
Il controsenso sta qui, dato che molto probaiblmente il codice per le 2 add sara' identico, e quindi il tuo legittimo dubbio se l'uso delle interfaccie sia proprio questo.
Ebbene, ovviamente non lo e'. Raramente si inseriscono tecnicismi che forzano la riscrittura del codice, quanto piu' invece per ridurlo.


ora mi è molto più chiaro!! :)


Ti faccio un esempio concreto...

....

Uno dei possibili modi e' questo:
- Crei l'interfaccia IRemoteSanFrancisco che contiene tanti metodi quante sono le stored procedure


mmm... quindi se non ci sarà più un metodo generico Add, ma ogni Add o metodo sarà relativo alla stored oppure, in assenza di esse, direttamente pensato per la specifica tabella? per es.: Add(Persona p), Add(Cliente c)

cioè:



public interface IAzienda
{
void CallProcedureAddCliente();
void CallProcedureAddPersona();
void CallProcedureAddFornitore();
......
}

public class DBManager : IAzienda
{

//metodi per le connessioni etc..

public void CallProcedureAddCliente()
{
blabla "insert into clienti..."
}
//etcetera..
}

//riducendo un pò l'ambito ad un umile winform:
//mi trovo sul form dei clienti
private buttonCliente_click(Object sender, EventArgs e)
{

DBManager db = new DBManager();
db.CallProcedureAddCliente();
}



Per la parte finale sto ancora cercando di capire il discorso relativo alla classe Factory :(

gugoXX
29-04-2009, 16:35
mmm... quindi se non ci sarà più un metodo generico Add, ma ogni Add o metodo sarà relativo alla stored oppure, in assenza di esse, direttamente pensato per la specifica tabella? per es.: Add(Persona p), Add(Cliente c)

cioè:



public interface IAzienda
{
void CallProcedureAddCliente();
void CallProcedureAddPersona();
void CallProcedureAddFornitore();
......
}

public class DBManager : IAzienda
{

//metodi per le connessioni etc..

public void CallProcedureAddCliente()
{
blabla "insert into clienti..."
}
//etcetera..
}

//riducendo un pò l'ambito ad un umile winform:
//mi trovo sul form dei clienti
private buttonCliente_click(Object sender, EventArgs e)
{

DBManager db = new DBManager();
db.CallProcedureAddCliente();
}



Per la parte finale sto ancora cercando di capire il discorso relativo alla classe Factory :(

Nono, non stavo piu' parlando di Generics, ma solo di Interfacce, dato che mi sembrava non le stessi usando in maniera produttiva.

Era quindi solo un esempio di uso delle interfaccie, senza generics di mezzo.
Proponevo un metodo per ciascuna stored procedure. Se la stored procedure si chiama
"UpdateUserState"
Avrai una entry
void UpdateUserState(int UserId, string UserState);

Che verra' implementata dalla classe del Database chiamando la stored procedure
e dalla classe del WebService chiamando il WebMethod.

tomminno
29-04-2009, 22:21
PS: Questo discorso si puo' fare di nuovo con le abstract class invece che con le interfaccie. Farlo con le interfaccie pero' abilita' una metodologia avanzata di test che si chiama Mock, molto utile, direi indispensabile, se si usa il Test Driven Development.

A proposito di test: usando TypeMock (per quanto riguarda .NET) puoi eseguire il mock di qualunque chiamata a metodo anche se non usi le interfacce, ad esempio puoi mockkare anche metodi del framework, veramente impressionante l'utilizzo che se ne può fare.

gugoXX
30-04-2009, 08:29
A proposito di test: usando TypeMock (per quanto riguarda .NET) puoi eseguire il mock di qualunque chiamata a metodo anche se non usi le interfacce, ad esempio puoi mockkare anche metodi del framework, veramente impressionante l'utilizzo che se ne può fare.

Bello, provo a dargli un'occhiata allora, potrebbe sempre essere utile.
Se poi (io) scrivessi interfacce invece che interfaccie sarebbe anche meglio.

RaouL_BennetH
30-04-2009, 14:38
Nono, non stavo piu' parlando di Generics, ma solo di Interfacce, dato che mi sembrava non le stessi usando in maniera produttiva.

Era quindi solo un esempio di uso delle interfaccie, senza generics di mezzo.
Proponevo un metodo per ciascuna stored procedure. Se la stored procedure si chiama
"UpdateUserState"
Avrai una entry
void UpdateUserState(int UserId, string UserState);

Che verra' implementata dalla classe del Database chiamando la stored procedure
e dalla classe del WebService chiamando il WebMethod.

Grazie gugoXX :)

Ma come sempre per ogni chiarimento nascono altri millemila dubbi :D

Se ho ben compreso quindi, in presenza dell'interfaccia, se il DB di San Francisco ha 200 stored procedure, avrò duecento metodi da definire nell'interfaccia e, di conseguenza, da riscrivere nella classe che implementerà l'interfaccia?

...cerco di spiegare meglio il mio dubbio:

Ogni volta che dovrò avere a che fare con il db, utilizzerò una sola classe che implementa l'interfaccia con tutti i suoi metodi ?

gugoXX
30-04-2009, 14:53
Grazie gugoXX :)

Ma come sempre per ogni chiarimento nascono altri millemila dubbi :D

Se ho ben compreso quindi, in presenza dell'interfaccia, se il DB di San Francisco ha 200 stored procedure, avrò duecento metodi da definire nell'interfaccia e, di conseguenza, da riscrivere nella classe che implementerà l'interfaccia?

Si', nell'esempio fatto quello sarebbe stato il panorama.
Diciamo che ci sono modi ulteriori per semplificare la scrittura di codice in quei casi, ma l'idea sarebbe stata comunque quella di dover implementare 200 metodi in ciascuna delle 2 classi.
Implementare come ora non impoterebbe piu' di tanto.


...cerco di spiegare meglio il mio dubbio:

Ogni volta che dovrò avere a che fare con il db, utilizzerò una sola classe che implementa l'interfaccia con tutti i suoi metodi ?
Ogni volta che avrai a che fare con questo "servizio", userai una classe sola, che implementa l'interfaccia con tutti i suoi metodi.
Che poi i metodi di questo servizio siano chiamate a database o chiamate a WebService, a chi questa classe la usa non importera' piu'.

RaouL_BennetH
04-05-2009, 14:59
....rieccomi.... :p

Altra piccolissima questione:

lo scenario da te descritto sarebbe lo stesso in assenza di stored procedures lato Db ?

per es.:



//codice interfaccia

void InsertCliente(Cliente c)
void InsertProdotti(Prodotto p)



?

Grazie mille.

RaouL.

gugoXX
04-05-2009, 21:56
....rieccomi.... :p

Altra piccolissima questione:

lo scenario da te descritto sarebbe lo stesso in assenza di stored procedures lato Db ?

per es.:



//codice interfaccia

void InsertCliente(Cliente c)
void InsertProdotti(Prodotto p)



?

Grazie mille.

RaouL.

Si', certo.
Stored procedure o query dirette oppure LINQ to Sql, oppure Entity framework, etc.
Tutti modi buoni per leggere dati dal database, e quindi per poter adottare la soluzione ad interfacce, ovvero se si prevede in futuro di utilizzare anche altri modi,oppure se si conta di testare con NMock che prevedono appunto l'uso di interfacce.