Il problema
Diciamo che ho una classe chiamata DataSource
che fornisce un metodo ReadData
(e forse altri, ma manteniamo le cose semplici) per leggere i dati da un file .mdb
:
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Alcuni anni dopo, decido di voler supportare .xml
file oltre a .mdb
file come origini dati. L'implementazione per "lettura dei dati" è abbastanza diversa per .xml
e .mdb
file; quindi, se dovessi progettare il sistema da zero, lo definirei in questo modo:
abstract class DataSource {
abstract Data ReadData();
static DataSource OpenDataSource(string fileName) {
// return MdbDataSource or XmlDataSource, as appropriate
}
}
class MdbDataSource : DataSource {
override Data ReadData() { /* implementation 1 */ }
}
class XmlDataSource : DataSource {
override Data ReadData() { /* implementation 2 */ }
}
Grande, una perfetta implementazione del modello di metodo Factory. Purtroppo, DataSource
si trova in una libreria e il refactoring del codice come questo potrebbe interrompere tutte le chiamate esistenti di
var source = new DataSource("myFile.mdb");
nei vari client che utilizzano la libreria. Guai a me, perché non ho usato un metodo di fabbrica in primo luogo?
Soluzioni
Queste sono le soluzioni che ho potuto trovare:
-
Fai in modo che il costruttore DataSource restituisca un sottotipo (
MdbDataSource
oXmlDataSource
). Questo risolverebbe tutti i miei problemi. Sfortunatamente, C # non lo supporta. -
Usa nomi diversi:
abstract class DataSourceBase { ... } // corresponds to DataSource in the example above class DataSource : DataSourceBase { // corresponds to MdbDataSource in the example above [Obsolete("New code should use DataSourceBase.OpenDataSource instead")] DataSource(string fileName) { ... } ... } class XmlDataSource : DataSourceBase { ... }
Questo è quello che ho finito usando perché mantiene il codice compatibile all'indietro (ad esempio, le chiamate a
new DataSource("myFile.mdb")
funzionano ancora). Svantaggio: i nomi non sono così descrittivi come dovrebbero. -
Crea
DataSource
un "wrapper" per l'implementazione reale:class DataSource { private DataSourceImpl impl; DataSource(string fileName) { impl = ... ? new MdbDataSourceImpl(fileName) : new XmlDataSourceImpl(fileName); } Data ReadData() { return impl.ReadData(); } abstract private class DataSourceImpl { ... } private class MdbDataSourceImpl : DataSourceImpl { ... } private class XmlDataSourceImpl : DataSourceImpl { ... } }
Svantaggio: il ogni metodo di origine dati (come
ReadData
) deve essere instradato dal codice boilerplate. Non mi piace il codice standard. È ridondante e ingombra il codice.
C'è qualche soluzione elegante che mi è sfuggita?