Monday, March 13, 2006
Product Trader and Generics
class Test1
{
[Factory( "product1" )]
public static SomeClass1 f()
{
return new SomeClass1() ;
}
}
class Test2
{
[Factory( "product2" )]
public static SomeClass2 f()
{
return new SomeClass2() ;
}
}
A method labeled as Factory doesn't have to be inside the class it creates (although it can). When you want to create a product by name, you just go through a product trader:
SomeClass1 p = (SomeClass1)ProductTrader.CreateProduct( "product1" ) ;
That's pretty much it. There is no need to register anything, no switch/case around, and stuff like that. The product trader will visit the type system the first time it's invoked, find out factories, build a product creators map, and then use the map upon subsequent calls.
Unfortunately, there are (at least) two flaws in this implementation. The first is that the product name is (by design) a string. In many cases a string is useful, but in others (e.g. when decoding messages) an integer would have been better. The other issue is that we need to cast the result of CreateProduct, as that function return type is object.
Obviously, with .NET 2 around, we might be tempted to use generics to solve the problem. However, this is not so trivial. The Factory attribute should be turned into a generic class, to that we could change the parameter from string to anything else. Unfortunately, this is not possible. From the C# 2 specification (20.5.8): "Attribute cannot be the base type of a generic class declaration"; just what we needed :-)). Still, even if we didn't have this problem, removing the cast in the CreateProduct calls would not be trivial at all, unless we settle for the idea that we have a product trader for each product family. This is not really a C# issue, but a more conceptual one, and I guess I'll leave it as the dreaded exercise for the reader :-)). While you're at it, you may want to ponder on the subtle design differences between using a logical product names (as I'm doing here) and labeled creational methods, or using physical product names (assemblyname.classname) and barebone reflection to build the products.
Comments:
<< Home
Per il primo problema mi viene in mente una pseudo soluzione, anche se non completa: al posto della stringa possiamo usare una classe con più costruttori in overload che rappresenti la Specifica di creazione, ed utilizzare quella. Certo si perde ancora di più sul controllo dei tipi :-))
Per la seconda passo, non conosco ancora la meccanica degli attributi. A tal proposito volevo un consiglio. So che è uscito "CLR via C#" di Richter, l'hai letto? credi sia un buon acquisto?
Poi una curiosità: Ho notato che nella piattaforma .NET il meccanismo delle asserzioni sia stato sostituito da controllo espicito tramite if + throw InvalidOperationException. Questo codice si porta appresso anche in release, soprattutto nella libreria, dove ad esempio la modifica di una connection string in una connessione ado.net è possibile solo se la connessione è chiusa. Non era meglio il vecchio meccanismo dove era possibile almeno decidere se tenerli o no? (so che la stessa cosa si può ricreare con poco, però se la mettevano nella libreria era meglio!)
Poi c'è il fatto che spesso le interfacce non sono ben disegnate, perchè magari progettando un po' meglio si poteva fare in modo di evitare che connectionstring si potesse modificare a connessione aperta (cioè magari eliminando la property, visto che difficilmente si riutilizzano nello stesso lifetime gli oggetti connessione per connessioni diverse!!! :-)...
Per la seconda passo, non conosco ancora la meccanica degli attributi. A tal proposito volevo un consiglio. So che è uscito "CLR via C#" di Richter, l'hai letto? credi sia un buon acquisto?
Poi una curiosità: Ho notato che nella piattaforma .NET il meccanismo delle asserzioni sia stato sostituito da controllo espicito tramite if + throw InvalidOperationException. Questo codice si porta appresso anche in release, soprattutto nella libreria, dove ad esempio la modifica di una connection string in una connessione ado.net è possibile solo se la connessione è chiusa. Non era meglio il vecchio meccanismo dove era possibile almeno decidere se tenerli o no? (so che la stessa cosa si può ricreare con poco, però se la mettevano nella libreria era meglio!)
Poi c'è il fatto che spesso le interfacce non sono ben disegnate, perchè magari progettando un po' meglio si poteva fare in modo di evitare che connectionstring si potesse modificare a connessione aperta (cioè magari eliminando la property, visto che difficilmente si riutilizzano nello stesso lifetime gli oggetti connessione per connessioni diverse!!! :-)...
Certo la versione in overloading e' orrorifica :-). In realta', considerando che nella vita reale la probabilita' di avere come nome prodotto qualcosa di diverso da una stringa, un carattere o un intero e' piuttosto piccola, potrebbe anche non essere male :-).
"CLR via C#" dalla descrizione mi sembra un clone aggiornato di "Applied .NET Framework Programming". In genere io ho apprezzato molto i libri di Richter, soprattutto quelli su Win32. "Applied .NET Framework" a mio avviso era un po' sotto il suo standard. Probabilmente prendero' anche questo suo ultimo lavoro... Comunque sugli attributi c'e' un intero libro ("Applied .NET Attributes"), che non e' affatto male (anzi, lo raccomando a chi vuole giocare con queste cose).
Asserzioni: in realta' per il nostro codice esiste System.Diagnostics.Debug.Assert che fa il suo onesto lavoro...
Sulle interfacce, premesso che in genere hanno fatto un lavoro migliore dei precedenti (e non solo precedenti Microsoft :-), va detto che qualche property in meno non mi sarebbe dispiaciuta (property set, poi...), ma qui si apre rapidamente una guerra con i fanatici delle property che sono tanti :-).
"CLR via C#" dalla descrizione mi sembra un clone aggiornato di "Applied .NET Framework Programming". In genere io ho apprezzato molto i libri di Richter, soprattutto quelli su Win32. "Applied .NET Framework" a mio avviso era un po' sotto il suo standard. Probabilmente prendero' anche questo suo ultimo lavoro... Comunque sugli attributi c'e' un intero libro ("Applied .NET Attributes"), che non e' affatto male (anzi, lo raccomando a chi vuole giocare con queste cose).
Asserzioni: in realta' per il nostro codice esiste System.Diagnostics.Debug.Assert che fa il suo onesto lavoro...
Sulle interfacce, premesso che in genere hanno fatto un lavoro migliore dei precedenti (e non solo precedenti Microsoft :-), va detto che qualche property in meno non mi sarebbe dispiaciuta (property set, poi...), ma qui si apre rapidamente una guerra con i fanatici delle property che sono tanti :-).
Lo so benissimo che la soluzione è orrorifica :-)) ma se è impossibile usare i generics le soluzioni sono poche, un'altra che mi viene in mente è avere un custom attribute per tipo di specifica, ci manteniamo il controllo sui tipi, abbiamo la possibilità di estendere ma credo sia più difficile scrivere la classe product trader (come ti ho detto ancora non conosco bene la meccanica degli attributi e non so quanto sia facile attraversare il typesystem ed accorgersi dell'insieme di attributi utili senza sapere a priori se c'è qualche estensione! Magari si marcano tutti in qualche modo, non so con un interfaccia marcatore per controllarne il tipo ).
Per quanto riguarda le asserzioni non ho esplorato il namespace diagnostic, più per mancanza di tempo che altro. Il fatto su cui volevo puntare l'attenzione è che spesso nella FCL vengono usate le eccezioni per controllare invarianti e precodizioni di interfaccia.
Sono daccordo che si stanno evolvendo sulle interfacce, ma si può fare molto di più senza introdurre rigidità. Io ad esempio sto costruendo un framework (molto lentamente) basato sulla tua "Record oriented architecture" in C++ e ho sto cercando di progettare le interfacce in modo da garantire le invarianti in maniera automatica (ad esempio non consento la modifica della stringa di connessione dell'oggetto connection, tanto se devo cambiare connessione non mi costa niente creare un nuovo oggetto :-).
Per non parlare del fatto che spesso i costruttori non vengono usati per portare gli oggetti in stati consistenti, ma bisogna settare a mano un certo numero di proprietà, e se ce ne dimentichiamo qualcuna salta un invalidoperationexception!
Just my 2 cents
Per quanto riguarda le asserzioni non ho esplorato il namespace diagnostic, più per mancanza di tempo che altro. Il fatto su cui volevo puntare l'attenzione è che spesso nella FCL vengono usate le eccezioni per controllare invarianti e precodizioni di interfaccia.
Sono daccordo che si stanno evolvendo sulle interfacce, ma si può fare molto di più senza introdurre rigidità. Io ad esempio sto costruendo un framework (molto lentamente) basato sulla tua "Record oriented architecture" in C++ e ho sto cercando di progettare le interfacce in modo da garantire le invarianti in maniera automatica (ad esempio non consento la modifica della stringa di connessione dell'oggetto connection, tanto se devo cambiare connessione non mi costa niente creare un nuovo oggetto :-).
Per non parlare del fatto che spesso i costruttori non vengono usati per portare gli oggetti in stati consistenti, ma bisogna settare a mano un certo numero di proprietà, e se ce ne dimentichiamo qualcuna salta un invalidoperationexception!
Just my 2 cents
Per quanto riguarda la questione eccezioni / asserzioni. Esiste comunque un concetto di design che guida nella scelta di una o dell'altra, legato al fatto di distribuire o meno una versione di debug della libreria. Se non lo si fa, si finisce per privilegiare comunque un approccio ad eccezioni, per ragioni piuttosto ovvie.
Per le property / costruttori, come dicevo, purtroppo ci sono [immotivati :-)] sostenitori della "comodita'" di questo approccio. Pazienza :-(. Con il tempo, si ravvedranno :-).
Post a Comment
Per le property / costruttori, come dicevo, purtroppo ci sono [immotivati :-)] sostenitori della "comodita'" di questo approccio. Pazienza :-(. Con il tempo, si ravvedranno :-).
<< Home





